Складська прога на WCF-сервісах зі сканером.
У цьому році я так не напружуюсь, як у минулому, коли лише в одному мойому проєкті було третина мільйону стрічок коду. Я намагаюся робити невеличкі проги, які дають кінцевий результат і гроші якомога швидше і по добре відомим мені шляхом. Одну з таких прог я частково опишу на цієї сторінці.
По задуму власника цього бізнесу є сайт, у адмінці якого працює менеджер, який також має публічний інтерфейс для клієнтів і також є кладовщик, який працює со сканером - він оприбутковує та видає товар зі складу. Така типова невеличка прога була зроблена мною за пару днів, нище ви можете побачити окремі компоненти цієї проги:
По перше - самий найзвичайний компонент - це сторінка адмінки на ASP.NET MVC - у який працює менеджер (сторінку кліента я описувати не буду).
На цієї сторінці менеджер ставить дозвіл на видачу деталі. Тобто кожна деталь проходить у процесі обробці декілька фаз, які відображаються у статусі деталі - у цьому моменті нам буде цікаво, що спочатку деталь набуває статус "Відправлено до Омська", а лише потім кладовщик має можливість оприбутковувати деталь на склад, потім менеджер ставить дозвіл на видачу детали, а лише потім кладовщик видає деталь клієнту. Тобто прога працює тільки зі статусами 15-23-24-25 у таблиці нище. Це важливо, бо статути деталі не можуть змінюватися будь-як, а тільки поступово - алгоритм поступових переходів статуту деталі реалізується у коді, який ви зможете побачити нище.
Але спочатку подивимося на сторінку, де менеджер ставить дозвіл на видачу деталі. Ця досить звичайна сторінка, у якої немає будь-яких особливостей - ви можете побачите її на ціх чотирьох скрінах:
Get-Post контроллер тут також досить звичайний, нема навіть що про нього розповісти:
Більш-менш тут цікавий лише фільтр для отбору лише потрібних записів з бази, але на ньому ми не будемо загострюватися, а краще подивимося детальніше на web-сервіси, з якими працює windows-forms прога. Такі веб-сервіси на технології WCF я роблю дуже часто, майже у кожному своєму проєкті, наприклад:
- B2B-Сервисы с криптографическим залогиниванием (для клиентской и серверной интеграции).
- Конфиги WCF-сервисов, обеспечивающие совместимость с JAVA, PHP, FLEX.
- SOAP/WSDL vs XML data exchange.
Найбільш цікаве питання, як зробити аутентификацию звернення до сервісу. Я роблю аутентифікацію по добре відпрацьованому шляху, який у мене описан тут:
На цієї сторінці ви побачите той же самий шлях, але не для Flex, а для NET. Тут усе буде дуже просто, тому що MD5-хеш спочатку убудований у NET, а також ніяких персональних даних по мережі не передається, тобто шифрувати усе тіло сервісу - це марнотратство свого часу, достатньо закриптувати лише пароль який підтвердить права клієнтської прогі на звернення до web-сервісу.
Тут також можно зробити трошки важче і універсальніше - на Reflection, а можно зробити якомога простіше. Прога комерційна, потрібен швидкий результат, ніхто у код не дивиться, на вулиці - солнце і море - не мае сенсу марно витрачати свій час, тренуватися з Рефлецтіон без гострої необхідності мені було цікаво років десять тому - і тому я вибрав найбільш простий із можливих шляхів - для кожного із трьох методів заздалегідь прошити імена параметрів у коді.
У межах таких обмежень (не треба криптувати тіло звернення і заздалегідь прошиті імена параметрів )- це буде найпростіший відомий мені механізм аутентифікації:
Але спочатку подивимося на дата-контракт:
1: <ServiceContract()>
2: Public Interface IService1
3:
4: <OperationContract()>
5: Function Test(ByVal value As Integer) As String
6:
7: <OperationContract()>
8: Function TestAU(ByVal value As Integer, AU As String) As String
9:
10: <OperationContract()>
11: Function GetDetail(ByVal GetParm As GetParmType, AU As String) As System.Collections.Generic.List(Of RetType)
12:
13: <OperationContract()>
14: Function SetState(ByVal SetParm As SetParmType, AU As String) As Integer
15:
16: End Interface
17:
18: ' Use a data contract as illustrated in the sample below to add composite types to service operations.
19:
20: <DataContract()>
21: Public Class SetParmType
22:
23: <DataMember()>
24: Public Property NewState() As Integer
25:
26: <DataMember()>
27: Public Property BacketID() As Guid
28:
29: End Class
30:
31:
32: <DataContract()>
33: Public Class GetParmType
34:
35: <DataMember()>
36: Public Property PartNumber() As String
37:
38: End Class
39:
40: <DataContract()>
41: Public Class RetType
42:
43: <DataMember()>
44: Public Property BacketID() As Guid
45:
46: <DataMember()>
47: Public Property Logo() As String
48:
49: <DataMember()>
50: Public Property EmNumber() As Integer
51:
52: <DataMember()>
53: Public Property DetailName() As String
54:
55: <DataMember()>
56: Public Property State() As Integer
57:
58: <DataMember()>
59: Public Property CrDate() As DateTime
60:
61: <DataMember()>
62: Public Property UserID() As Guid
63:
64: <DataMember()>
65: Public Property UserLogin() As String
66:
67: <DataMember()>
68: Public Property UserName1() As String
69:
70: <DataMember()>
71: Public Property UserName2() As String
72:
73: <DataMember()>
74: Public Property Kol() As Integer
75:
76: <DataMember()>
77: Public Property RealKol() As Integer
78:
79: <DataMember()>
80: Public Property InSklad() As Integer
81:
82: <DataMember()>
83: Public Property OutSklad() As Integer
84:
85: End Class
Як ви бачите, тут є два метода GetDetail і SetState, обидва з них мають параметр звернення (комбіноване поле) і поле аутентіфакції, що не дозволить кому завгодно отримувати інформацію через сервіс з бази.
IsAU - це той самий криптографічний MD5-підпис, що побудована на базі таємного ключа-пароля. Який неможливо ніяк відновити з односторонній функції MD5. Якщо злодій якось дізнається цей пароль - він зможе звертатися до бази і змінювати статут товару.
Цю функцію краще було би відкомпілювати у окрему бібліотеку і лінкувати її до сервісу та клієнтської проги, але тут такий маленький проєкт, що я просто скопипастів код із сервіса до клієнта.
1: <Activation.AspNetCompatibilityRequirements(RequirementsMode:=Activation.AspNetCompatibilityRequirementsMode.Allowed)> _
2: Public Class Service1
3: Implements IService1
4:
5:
6: Public Sub New()
7: End Sub
8:
9: Public Function Test(ByVal value As Integer) As String Implements IService1.Test
10: Return String.Format("You entered: {0}", value)
11: End Function
12:
13:
14: Public Function TestAU(value As Integer, AU As String) As String Implements IService1.TestAU
15: If IsAU(value, AU) Then Return String.Format("You entered: {0}", value)
16: End Function
17:
18: Public Function GetDetail(GetParm As GetParmType, AU As String) As System.Collections.Generic.List(Of RetType) Implements IService1.GetDetail
19: If IsAU(GetParm, AU) Then
20: Dim Db1 As New OmskDataContext
21: Dim Rows = (Db1.GetEmexPrice(GetParm.PartNumber)).ToList
22: If Rows.Count > 0 Then
23: Dim X As New System.Collections.Generic.List(Of RetType)
24: For i As Integer = 0 To Rows.Count - 1
25: X.Add(New RetType With {.BacketID = Rows(i).BacketID, .Logo = Rows(i).Logo, .EmNumber = Rows(i).EmexNum, .DetailName = Rows(i).DetailName, .State = Rows(i).State, .CrDate = Rows(i).CrDate, .UserID = Rows(i).ToUser, .UserLogin = Rows(i).Login, .UserName1 = Rows(i).Name1, .UserName2 = Rows(i).Name2, .Kol = Rows(i).Kol})
26: If Rows(i).RealKol Is Nothing Then
27: X.Last.RealKol = 0
28: Else
29: X.Last.RealKol = Rows(i).RealKol
30: End If
31: If Rows(i).InSklad Is Nothing Then
32: X.Last.InSklad = 0
33: Else
34: X.Last.InSklad = Rows(i).InSklad
35: End If
36: If Rows(i).OutSklad Is Nothing Then
37: X.Last.OutSklad = 0
38: Else
39: X.Last.OutSklad = Rows(i).OutSklad
40: End If
41: Next
42: Return X
43: End If
44: End If
45: End Function
46:
47: Public Function SetState(SetParm As SetParmType, AU As String) As Integer Implements IService1.SetState
48: If IsAU(SetParm, AU) Then
49: Dim Db1 As New OmskDataContext
50: If SetParm.NewState = 23 Then
51: 'Поступило ОМСК - кладовщик делает приход
52: Db1.SetEmexInSclad(SetParm.BacketID)
53: ElseIf SetParm.NewState = 25 Then
54: 'Выдано - кладовщик делает выдачу
55: Db1.SetEmexOutSclad(SetParm.BacketID)
56: End If
57: End If
58: End Function
59:
60:
61: Function IsAU(Prm As Object, AU As String) As Boolean
62: If AU Is Nothing Then Return False
63: If AU = "" Then Return False
64: If AU.Length <> 32 Then Return False
65: IsAU = False
66: Dim Str0 As String
67: If Prm.GetType.Name = GetType(System.Int32).Name Then
68: Str0 = Prm.ToString
69: ElseIf Prm.GetType.Name = GetType(GetParmType).Name Then
70: Str0 = (Prm.PartNumber).ToString
71: ElseIf Prm.GetType.Name = GetType(SetParmType).Name Then
72: Str0 = (Prm.BacketID).ToString & "~" & (Prm.NewState).ToString
73: Else
74: Return False
75: End If
76: Dim Str1 As String = Str0 & System.Configuration.ConfigurationManager.AppSettings("AuKey")
77: Dim MD5 = System.Security.Cryptography.MD5.Create()
78: Dim Buf1 As Byte() = (New System.Text.UnicodeEncoding).GetBytes(Str1)
79: Dim MD5Hash As Byte() = MD5.ComputeHash(Buf1)
80: Dim StrOut As New System.Text.StringBuilder
81: For j As Integer = 0 To MD5Hash.Length - 1
82: StrOut.AppendFormat("{0:X2}", MD5Hash(j))
83: Next
84: If StrOut.ToString.Trim.ToLower = AU.Trim.ToLower Then
85: Return True
86: End If
87:
88: End Function
89: End Class
Мабуть, отой найпростіший механізм аутентифікації (та ще й з простим копіпастом) - який я обрав для цього проєкту - це і є родзинка усього цього проєкту. Бо таких простих проєктів у мене майже не зустрічається.
Але підемо далі - як можна побачити на цьому скрині - проєкт має усього три звернення до SQL-процедур:
Ці процедури маніпулюють лише декількома останніми полями у таблиці Basket, що описує товар на складі:
Процедури ці теж майже вироджені, майже пусті, майже прості Селекти та Апдейти і виглядають ось так:
1: ALTER procedure [dbo].[GetEmexPrice]
2: @SearchString as nvarchar(250)
3: as
4: With BasketRows as
5: (
6: SELECT B.id as BacketID,
7: LEFT(B.Keys, CHARINDEX('~',B.Keys)-1) AS Logo,
8: (select top 1 Replace(s,'~','') from dbo.SplitString((select top 1 Keys from [Basket] as B
9: where B.Archive = 0 and B.AdmArchive=0 and upper(B.Keys) like '%'+'~|~'+upper(Replace(@SearchString,'-',''))+'~|~'+'%'), '|')
10: where zeroBasedOccurance=2) as DetailName,
11: B.EmexNum,
12: B.[State],
13: B.CrDate,
14: B.ToUser,
15: AspnetUsers.Login,
16: AspnetUsers.Name1,
17: AspnetUsers.Name2,
18: Kol,
19: RealKol,
20: InSklad,
21: OutSklad
22: --,B.Keys
23: FROM [Omsk].[dbo].[Basket] as B
24: join AspnetUsers on B.ToUser=AspnetUsers.id
25: where B.AdmArchive=0 and (B.Keys) like '%'+'~|~'+upper(Replace(@SearchString,'-',''))+'~|~'+'%'
26: and (State=15 or State=23 or State=24 or State=25)
27: )
28: Select * from BasketRows
1: ALTER procedure [dbo].[SetEmexInSclad]
2: @BaskedID as uniqueidentifier
3: as
4:
5:
6: update dbo.Basket
7: set InSklad=ISNULL(InSklad,0) +1, State=23, SetManualState=1
8: where id=@BaskedID
9:
10: Declare @First datetime
11:
12: select @First=InDateStart from dbo.Basket
13: where id=@BaskedID
14:
15: If (@First is NULL)
16:
17: Update dbo.Basket
18: Set InDateStart=GETDATE(), InDateEnd=GETDATE()
19: where id=@BaskedID
20:
21: ELSE
22:
23: Update dbo.Basket
24: Set InDateEnd=GETDATE()
25: where id=@BaskedID
1: ALTER procedure [dbo].[SetEmexOutSclad]
2: @BaskedID as uniqueidentifier
3: as
4:
5:
6: update dbo.Basket
7: set OutSklad=ISNULL(OutSklad,0) +1, State=25, SetManualState=1
8: where id=@BaskedID
9:
10: Declare @First datetime
11:
12: select @First=OutDateStart from dbo.Basket
13: where id=@BaskedID
14:
15: If (@First is NULL)
16:
17: Update dbo.Basket
18: Set OutDateStart=GETDATE(), OutDateEnd=GETDATE()
19: where id=@BaskedID
20:
21: ELSE
22:
23: Update dbo.Basket
24: Set OutDateEnd=GETDATE()
25: where id=@BaskedID
Щоб завершити опис цього B2B-Сервіса, залишаеться показати тільки його дійсній конфіг:
1: <?xml version="1.0"?>
2: <configuration>
3: <connectionStrings>
4: <add name="OmskConnectionString" connectionString="Data Source=xxx.xxx.xxx.xxx;Initial Catalog=Omsk;User ID=Omsk;Password=yyyyyyyyy"
5: providerName="System.Data.SqlClient" />
6: </connectionStrings>
7: <appSettings>
8: <add key="AUKey" value="MyMainPassword"/>
9: </appSettings>
10: <system.web>
11: <customErrors mode="Off"/>
12: <compilation debug="true" strict="false" explicit="true" targetFramework="4.0" />
13: </system.web>
14: <system.serviceModel>
15: <services>
16: <service behaviorConfiguration="debug" name="ShelService1.Service1">
17: <endpoint address="http://scaner.zzzzzzzzzz.ru/Service1.svc" binding="basicHttpBinding"
18: bindingConfiguration="NewBinding0" contract="ShelService1.IService1" />
19: </service>
20: </services>
21: <bindings>
22: <basicHttpBinding>
23: <binding name="NewBinding0">
24: <security>
25: <transport clientCredentialType="None" />
26: </security>
27: </binding>
28: </basicHttpBinding>
29: </bindings>
30: <behaviors>
31: <serviceBehaviors>
32: <behavior name="debug">
33: <!-- To avoid disclosing metadata information, set the value below to false and remove the metadata endpoint above before deployment -->
34: <serviceMetadata httpGetEnabled="true"/>
35: <!-- To receive exception details in faults for debugging purposes, set the value below to true. Set to false before deployment to avoid disclosing exception information -->
36: <serviceDebug includeExceptionDetailInFaults="true"/>
37: </behavior>
38: </serviceBehaviors>
39: </behaviors>
40: <serviceHostingEnvironment multipleSiteBindingsEnabled="false" aspNetCompatibilityEnabled="true" />
41: </system.serviceModel>
42: <system.webServer>
43: <modules runAllManagedModulesForAllRequests="true"/>
44: </system.webServer>
45:
46: </configuration>
На цьому з цим простеньким сервісом усе. Це найпростіший B2B-сервіс, що я зробив за останні роки, тому мені так приємно його описувати.
Тепер подивимося на клієнтську прогу, яка працює у кладовщика. Це теж одна з найпростіших прог, яку я зробив за останні роки. Мабуть, меня нічого не вдалося зробити найменшого розміру. Тому так приємно описувати таку просту речь.
Спочатку подивимося на форму. У верхнє поле сканер зчитує штріх-код. Тут мені знову пощастило, сканер у кладовщика виявився PROTON IMS-3190 USB - а він просто працює як клавіатура, тобто нічого робити не треба, проініціалізував його об штрих-код англійської мови, поставив мишку на поле - і він зчитує штрих-код і записує його у потрібне місце.
Ну назву полей форми я приводити не буду, бо назви зрозуміли з контексту:
Модуль АU містить той самий код, що і в сервісі, з крихітною різницею звернення до конфігу з паролем у web-forms порівняно з windows-forms.
У класс MyGrid я зібрав обробку заповнення мережі з даними на головній формі. Цей класс приймає посилання на результат праці сервісу ScanerService.RetType і посилання на Windows-forms мережу DataGridView і записує дані, отримані від сервіса у необхідні клітини мережі, при чому - головне - робить це не напрямки, а через специфічній метод цієї мережі.
Grid.Invoke(New MethodInvoker(AddressOf DataFiller))
що дозволяє зробити цю операцію у іншому потоці, ніж поток, що працює зі вводом з клавіатури.
1: Public Class MyGrid
2:
3: Property _Grid As DataGridView
4: Property _Data As ScanerService.RetType()
5: Property _RowCount As Integer
6: Property _Msg As String
7: Property _PartNumber As String
8:
9: Public Sub New(Grid As DataGridView)
10: _Grid = Grid
11: End Sub
12:
13: Public Sub Fill(Data As ScanerService.RetType(), PartNumber As String)
14: SyncLock Me
15: _Data = Data
16: _PartNumber = PartNumber
17: 'маршалинг из потока BackgroundWorker в поток формы
18: _Grid.Invoke(New MethodInvoker(AddressOf DataFiller))
19: End SyncLock
20: End Sub
21:
22: Sub AddRows(Count As Integer)
23: SyncLock Me
24: _RowCount = Count
25: _Grid.Invoke(New MethodInvoker(AddressOf RowAdder))
26: End SyncLock
27: End Sub
28:
29: Sub WriteError(Msg As String)
30: SyncLock Me
31: _Msg = Msg
32: _Grid.Invoke(New MethodInvoker(AddressOf MsgWriter))
33: End SyncLock
34: End Sub
35:
36: Sub ClearGrid()
37: SyncLock Me
38: _Grid.Invoke(New MethodInvoker(AddressOf Clearer))
39: End SyncLock
40: End Sub
41:
42: Sub DataFiller()
43: For i As Integer = 0 To _Data.Count - 1
44: _Grid.Rows.Add()
45: _Grid.Rows(_Grid.Rows.Count - 1).Visible = True
46: _Grid.Rows(_Grid.Rows.Count - 1).Cells(0).Value = _Data(i).Logo
47: _Grid.Rows(_Grid.Rows.Count - 1).Cells(1).Value = _Data(i).DetailName
48: _Grid.Rows(_Grid.Rows.Count - 1).Cells(2).Value = _Data(i).CrDate
49: _Grid.Rows(_Grid.Rows.Count - 1).Cells(3).Value = _Data(i).EmNumber
50: _Grid.Rows(_Grid.Rows.Count - 1).Cells(4).Value = _Data(i).BacketID
51: '
52: _Grid.Rows.Add()
53: _Grid.Rows(_Grid.Rows.Count - 1).Visible = True
54: _Grid.Rows(_Grid.Rows.Count - 1).Cells(0).Value = _PartNumber
55: _Grid.Rows(_Grid.Rows.Count - 1).Cells(1).Value = _Data(i).UserName1()
56: _Grid.Rows(_Grid.Rows.Count - 1).Cells(2).Value = _Data(i).UserName2()
57: _Grid.Rows(_Grid.Rows.Count - 1).Cells(3).Value = _Data(i).UserLogin()
58: _Grid.Rows(_Grid.Rows.Count - 1).Cells(4).Value = _Data(i).UserID()
59: '
60: _Grid.Rows.Add()
61: _Grid.Rows(_Grid.Rows.Count - 1).Visible = True
62: Dim St1 As New State1
63: _Grid.Rows(_Grid.Rows.Count - 1).Cells(0).Value = St1.GetState(_Data(i).State)
64: _Grid.Rows(_Grid.Rows.Count - 1).Cells(1).Value = "Заказывали: " & _Data(i).Kol
65: _Grid.Rows(_Grid.Rows.Count - 1).Cells(2).Value = "Обещали: " & _Data(i).RealKol
66: _Grid.Rows(_Grid.Rows.Count - 1).Cells(3).Value = "На складе: " & _Data(i).InSklad
67: _Grid.Rows(_Grid.Rows.Count - 1).Cells(4).Value = "Выдано: " & _Data(i).OutSklad
68:
69: Next
70: End Sub
71:
72:
73: Sub Clearer()
74: _Grid.Rows.Clear()
75: End Sub
76:
77: Sub RowAdder()
78: _Grid.Rows.Add(_RowCount)
79: End Sub
80:
81: Sub MsgWriter()
82: _Grid.Rows.Add()
83: _Grid.Rows(_Grid.Rows.Count - 1).Visible = True
84: _Grid.Rows(_Grid.Rows.Count - 1).Cells(1).Value = _Msg
85: End Sub
86:
87: End Class
Крихітний допоміжний класс State я скопипастив з сайту, якщо б проєкт був хоч трошки більше, краще було б його, як і класс AU откомпілити у окрему DLL.
1: Public Class State1
2: Dim StateList = New System.Collections.Generic.List(Of Object)
3:
4: Public Sub New()
5: StateList.Add(New With {.Selected = False, .Value = "15", .Text = "Отправлено ОМСК"})
6: StateList.Add(New With {.Selected = False, .Value = "23", .Text = "Поступило ОМСК"})
7: StateList.Add(New With {.Selected = False, .Value = "24", .Text = "Готово к выдаче"})
8: StateList.Add(New With {.Selected = False, .Value = "25", .Text = "Выдано со склада"})
9: End Sub
10:
11: Public Function GetState(Code As Integer) As String
12: For j As Integer = 0 To StateList.Count - 1
13: If StateList(j).Value = Code Then
14: Return StateList(j).Text
15: Exit Function
16: End If
17: Next
18: End Function
19: End Class
І ось нарешті головний код форми, який я навмисно добре закоментував. Як ви бачите, я зробив звернення до сервісу у іншому потоці, ніж поток форми.
1: Public Class Form1
2:
3: #Region "Инициализация проги кладовщика и связь с сайтом, на котором работает менежжер"
4:
5: Private Sub Form1_Load(sender As Object, e As System.EventArgs) Handles Me.Load
6: System.Windows.Forms.Application.DoEvents()
7: BackgroundWorker1.WorkerSupportsCancellation = True
8: BackgroundWorker1.WorkerReportsProgress = True
9: End Sub
10:
11: Private Sub PictureBox1_Click(sender As System.Object, e As System.EventArgs) Handles PictureBox1.Click
12: Dim sInfo As ProcessStartInfo = New ProcessStartInfo("http://XXXXXXX.ru/")
13: Process.Start(sInfo)
14: End Sub
15:
16: #End Region
17:
18: #Region "Особый фон формы"
19:
20: Private Sub Form1_Resize_Activate(sender As Object, e As System.EventArgs) Handles Me.Resize, Me.Activated
21: Me.Invalidate()
22: End Sub
23:
24: Protected Overrides Sub OnPaintBackground(e As System.Windows.Forms.PaintEventArgs)
25: Try
26: If Me.ClientRectangle.Height <> 0 And Me.ClientRectangle.Width <> 0 Then
27: Using Brush As Drawing2D.LinearGradientBrush = New Drawing2D.LinearGradientBrush(Me.ClientRectangle, _
28: Color.LightGray, _
29: Color.Gray, _
30: 90.0F)
31: e.Graphics.FillRectangle(Brush, Me.ClientRectangle)
32: End Using
33: End If
34:
35: Catch ex As Exception
36: 'MsgBox(ex.Message)
37: End Try
38: End Sub
39:
40: #End Region
41:
42:
43: #Region "Обращение к BackgroundWorker, в потоке которого происходит обращение к web-сервису"
44:
45: Private Sub TextBoxScanerCode_TextChanged(sender As Object, e As System.EventArgs) Handles TextBoxScanerCode.TextChanged
46: If TextBoxScanerCode.Text = "" Then
47: ButtonRequest.Enabled = False
48: Else
49: ButtonRequest.Enabled = True
50: End If
51: End Sub
52:
53: Private Sub ButtonRequest_Click(sender As System.Object, e As System.EventArgs) Handles ButtonRequest.Click
54: If Not BackgroundWorker1.IsBusy = True Then
55: ButtonIN.Enabled = False
56: ButtonOUT.Enabled = False
57: TextBoxIN.Enabled = False
58: TextBoxOUT.Enabled = False
59: BackgroundWorker1.RunWorkerAsync()
60: End If
61: End Sub
62:
63: #End Region
64:
65: #Region "Обращение к web-сервису"
66:
67: Dim Ret As ScanerService.RetType()
68: Private Sub BackgroundWorker1_DoWork(sender As Object, e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork
69: 'тут все уже в потоке BackgroundWorker
70: Dim Gr1 As New MyGrid(DataGridView1)
71: If CheckBox1.Checked Then
72: Gr1.ClearGrid()
73: End If
74: Dim X As New ScanerService.Service1Client
75: Dim Prm As New ScanerService.GetParmType
76: Prm.PartNumber = TextBoxScanerCode.Text
77: Ret = X.GetDetail(Prm, AU.CreateAU(Prm))
78: '
79: If BackgroundWorker1.CancellationPending Then
80: e.Cancel = True
81: End If
82: End Sub
83:
84:
85: Private Sub BackgroundWorker1_RunWorkerCompleted(sender As Object, e As System.ComponentModel.RunWorkerCompletedEventArgs) Handles BackgroundWorker1.RunWorkerCompleted
86: 'тут все уже в потоке BackgroundWorker
87: Dim Grid1 As New MyGrid(DataGridView1)
88: If e.Cancelled = True Then
89: Grid1.WriteError("Canceled!")
90: ElseIf e.Error IsNot Nothing Then
91: DataGridView1.Rows.Add(1)
92: Grid1.WriteError(e.Error.Message)
93: Else
94: If Ret IsNot Nothing Then
95: If Ret.Count > 0 Then
96: Grid1.Fill(Ret, TextBoxScanerCode.Text)
97: ButtonIN.Enabled = True
98: ButtonOUT.Enabled = True
99: TextBoxIN.Enabled = True
100: TextBoxOUT.Enabled = True
101: End If
102: End If
103: End If
104: End Sub
105:
106: #End Region
107:
108: #Region "Логика приема на склад товара - отгрузки со склада"
109:
110: Private Sub DataGridView1_SelectionChanged(sender As Object, e As System.EventArgs) Handles DataGridView1.SelectionChanged
111: 'выделить можно только титульную строчку описания товара (один товар описан в трех строчках)
112: If DataGridView1.SelectedRows.Count > 0 Then
113: If DataGridView1.SelectedRows(0).Index = 0 Or DataGridView1.SelectedRows(0).Index Mod 3 = 0 Then
114: Else
115: Dim CurrentSelectedRow As Integer = DataGridView1.SelectedRows(0).Index
116: DataGridView1.SelectedRows(0).Selected = False
117: If CurrentSelectedRow Mod 3 = 1 Then
118: DataGridView1.Rows(CurrentSelectedRow - 1).Selected = True
119: ElseIf CurrentSelectedRow Mod 3 = 2 Then
120: DataGridView1.Rows(CurrentSelectedRow - 2).Selected = True
121: End If
122: End If
123: End If
124: End Sub
125:
126:
127: Private Sub ButtonIN_Click(sender As Object, e As System.EventArgs) Handles ButtonIN.Click
128: Dim CurrentSelectedRow As Integer = DataGridView1.SelectedRows(0).Index
129: 'сервис выдает список товаров только в нужном состоянии
130: Dim BasketID As Guid = DataGridView1.Rows(CurrentSelectedRow).Cells(4).Value
131: Dim X As New ScanerService.Service1Client
132: Dim Prm As New ScanerService.SetParmType
133: If IsNumeric(TextBoxIN.Text) Then
134: ' нельзя оприходовать на склад больше товаров, чем отправлено
135: If CInt(TextBoxIN.Text) + CInt(DataGridView1.Rows(CurrentSelectedRow + 2).Cells(3).Value.ToString.Replace("На складе: ", "")) <= CInt(DataGridView1.Rows(CurrentSelectedRow + 2).Cells(2).Value.ToString.Replace("Обещали: ", "")) Then
136: For i As Integer = 0 To CInt(TextBoxIN.Text) - 1
137: Prm.BacketID = BasketID
138: Prm.NewState = 23 'ставится состояние ПРИШЛО В ОМСК
139: X.SetState(Prm, AU.CreateAU(Prm))
140: Next
141: ButtonRequest_Click(Nothing, Nothing)
142: Else
143: Beep()
144: MsgBox("Проверь количество", MsgBoxStyle.OkOnly Or MsgBoxStyle.Critical)
145: End If
146: End If
147: End Sub
148:
149: 'после прихода товара, менеджер с сайта ставит РАЗРЕШЕНИЕ НА ВЫДАЧУ
150: 'после выдачи всего товара менеджер с сайта удаляет обработанный товар в архив
151:
152: Private Sub TextBoxOUT_Click(sender As Object, e As System.EventArgs) Handles ButtonOUT.Click
153: Dim CurrentSelectedRow As Integer = DataGridView1.SelectedRows(0).Index
154: 'возможна выдача только товаров в состоянии "Готово к выдаче" и "Выдано со склада" (уже началась выдача)
155: If DataGridView1.Rows(CurrentSelectedRow + 2).Cells(0).Value = "Готово к выдаче" Or DataGridView1.Rows(CurrentSelectedRow + 2).Cells(0).Value = "Выдано со склада" Then
156: Dim BasketID As Guid = DataGridView1.Rows(CurrentSelectedRow).Cells(4).Value
157: Dim X As New ScanerService.Service1Client
158: Dim Prm As New ScanerService.SetParmType
159: If IsNumeric(TextBoxOUT.Text) Then
160: 'нельзя выдать больше чем есть
161: If CInt(TextBoxOUT.Text) + CInt(DataGridView1.Rows(CurrentSelectedRow + 2).Cells(4).Value.ToString.Replace("Выдано: ", "")) <= CInt(DataGridView1.Rows(CurrentSelectedRow + 2).Cells(3).Value.ToString.Replace("На складе: ", "")) Then
162: For i As Integer = 0 To CInt(TextBoxOUT.Text) - 1
163: Prm.BacketID = BasketID
164: Prm.NewState = 25 'после отгрузки первой единицы товара товар ставится в состояние ВЫДАНО
165: X.SetState(Prm, AU.CreateAU(Prm))
166: Next
167: ButtonRequest_Click(Nothing, Nothing)
168: Else
169: Beep()
170: MsgBox("Проверь количество", MsgBoxStyle.OkOnly Or MsgBoxStyle.Critical)
171: End If
172: End If
173: Else
174: Beep()
175: MsgBox("Нет разрешения на выдачу", MsgBoxStyle.OkOnly Or MsgBoxStyle.Critical)
176: End If
177: End Sub
178:
179: #End Region
180:
181: End Class
Посилання на B2B web-cервіс я побудував за допомогою студії автоматично - WCF як раз і зроблений для того, щоб величезний класс Reference.vb (на 500 стрічок) був побудований автоматично і абсолютно точно по WSDL за допомогою утиліти SVCUTIL, що на задньому плані відпрацьовує за формами Студії.
Ну ось таку (можливо щонайменшу із можливих за багато років) програм мені довелося зробити у цьому році. Може декому буде цікаво щось подивитися у моєму коді.
Ще почитати про будівництво web-сервісів ви можете тут:
- Типовий SOAP/WSDL сервіс.
- B2B-Сервисы с криптографическим залогиниванием (для клиентской и серверной интеграции).
- Конфиги WCF-сервисов, обеспечивающие совместимость с JAVA, PHP, FLEX.
- SOAP/WSDL vs XML data exchange.
- Как сделать SOAP/WSDL-вебсервис на ASP.NET/MONO для вызова его из FLEX/a>
|