twinfieldrb 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (78) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/codeql-analysis.yml +70 -0
  3. data/.github/workflows/rspec.yml +33 -0
  4. data/.gitignore +6 -0
  5. data/.rspec +1 -0
  6. data/CHANGELOG.md +15 -0
  7. data/Gemfile +4 -0
  8. data/README.md +120 -0
  9. data/Rakefile +1 -0
  10. data/lib/twinfield/abstract_model.rb +7 -0
  11. data/lib/twinfield/api/base_api.rb +50 -0
  12. data/lib/twinfield/api/finder.rb +45 -0
  13. data/lib/twinfield/api/o_auth_session.rb +58 -0
  14. data/lib/twinfield/api/process.rb +44 -0
  15. data/lib/twinfield/api/session.rb +170 -0
  16. data/lib/twinfield/browse/transaction/cost_center.rb +145 -0
  17. data/lib/twinfield/browse/transaction/customer.rb +413 -0
  18. data/lib/twinfield/browse/transaction/general_ledger.rb +144 -0
  19. data/lib/twinfield/configuration.rb +49 -0
  20. data/lib/twinfield/create/cost_center.rb +39 -0
  21. data/lib/twinfield/create/creditor.rb +88 -0
  22. data/lib/twinfield/create/debtor.rb +88 -0
  23. data/lib/twinfield/create/error.rb +30 -0
  24. data/lib/twinfield/create/general_ledger.rb +39 -0
  25. data/lib/twinfield/create/transaction.rb +97 -0
  26. data/lib/twinfield/customer.rb +612 -0
  27. data/lib/twinfield/helpers/parsers.rb +23 -0
  28. data/lib/twinfield/helpers/transaction_match.rb +40 -0
  29. data/lib/twinfield/request/find.rb +149 -0
  30. data/lib/twinfield/request/list.rb +66 -0
  31. data/lib/twinfield/request/read.rb +111 -0
  32. data/lib/twinfield/sales_invoice.rb +409 -0
  33. data/lib/twinfield/transaction.rb +112 -0
  34. data/lib/twinfield/version.rb +5 -0
  35. data/lib/twinfield.rb +89 -0
  36. data/script/boot.rb +58 -0
  37. data/script/console +2 -0
  38. data/spec/fixtures/cluster/finder/ivt.xml +1 -0
  39. data/spec/fixtures/cluster/processxml/columns/sales_transactions.xml +312 -0
  40. data/spec/fixtures/cluster/processxml/customer/create_success.xml +100 -0
  41. data/spec/fixtures/cluster/processxml/customer/read_success.xml +93 -0
  42. data/spec/fixtures/cluster/processxml/customer/update_success.xml +1 -0
  43. data/spec/fixtures/cluster/processxml/invoice/create_error.xml +8 -0
  44. data/spec/fixtures/cluster/processxml/invoice/create_final_error.xml +67 -0
  45. data/spec/fixtures/cluster/processxml/invoice/create_success.xml +64 -0
  46. data/spec/fixtures/cluster/processxml/invoice/read_not_found.xml +1 -0
  47. data/spec/fixtures/cluster/processxml/invoice/read_success.xml +106 -0
  48. data/spec/fixtures/cluster/processxml/read/deb.xml +12 -0
  49. data/spec/fixtures/cluster/processxml/response.xml +8 -0
  50. data/spec/fixtures/login/session/wsdl.xml +210 -0
  51. data/spec/spec_helper.rb +17 -0
  52. data/spec/stubs/finder_stubs.rb +19 -0
  53. data/spec/stubs/processxml_stubs.rb +41 -0
  54. data/spec/stubs/session_stubs.rb +28 -0
  55. data/spec/twinfield/api/oauth_session_spec.rb +37 -0
  56. data/spec/twinfield/api/process_spec.rb +7 -0
  57. data/spec/twinfield/browse/transaction/cost_center_spec.rb +60 -0
  58. data/spec/twinfield/browse/transaction/general_ledger_spec.rb +60 -0
  59. data/spec/twinfield/browse/transaction/transaction_spec.rb +72 -0
  60. data/spec/twinfield/config_spec.rb +60 -0
  61. data/spec/twinfield/customer_spec.rb +326 -0
  62. data/spec/twinfield/request/find_spec.rb +24 -0
  63. data/spec/twinfield/request/list_spec.rb +58 -0
  64. data/spec/twinfield/request/read_spec.rb +26 -0
  65. data/spec/twinfield/sales_invoice_spec.rb +253 -0
  66. data/spec/twinfield/session_spec.rb +77 -0
  67. data/spec/twinfield/transaction_spec.rb +149 -0
  68. data/twinfieldrb.gemspec +24 -0
  69. data/wsdls/accounting/finder.wsdl +157 -0
  70. data/wsdls/accounting/process.wsdl +199 -0
  71. data/wsdls/accounting/session.wsdl +452 -0
  72. data/wsdls/accounting2/finder.wsdl +157 -0
  73. data/wsdls/accounting2/process.wsdl +199 -0
  74. data/wsdls/api.accounting/finder.wsdl +157 -0
  75. data/wsdls/api.accounting/process.wsdl +199 -0
  76. data/wsdls/session.wsdl +210 -0
  77. data/wsdls/update +10 -0
  78. metadata +196 -0
@@ -0,0 +1,612 @@
1
+ module Twinfield
2
+ class Customer < Twinfield::AbstractModel
3
+ extend Twinfield::Helpers::Parsers
4
+
5
+ class CollectMandate
6
+ extend Twinfield::Helpers::Parsers
7
+
8
+ attr_accessor :signaturedate, :id
9
+
10
+ def initialize(signaturedate: nil, id: nil)
11
+ @signaturedate = signaturedate
12
+ @id = id
13
+ end
14
+
15
+ def to_h
16
+ {signaturedate: signaturedate, id: id}
17
+ end
18
+ alias_method :to_hash, :to_h
19
+
20
+ def to_xml
21
+ Nokogiri::XML::Builder.new do |xml|
22
+ xml.collectmandate do
23
+ xml.id id
24
+ xml.signaturedate signaturedate&.strftime("%Y%m%d")
25
+ end
26
+ end.doc.root.to_xml
27
+ end
28
+
29
+ def self.from_xml(nokogiri)
30
+ obj = new
31
+ obj.signaturedate = parse_date(nokogiri.css("signaturedate").text)
32
+ obj.id = nokogiri.css("id").text
33
+ obj
34
+ end
35
+ end
36
+
37
+ class RemittanceAdvice
38
+ attr_accessor :sendtype, :sendmail
39
+
40
+ def initialize(sendtype: nil, sendmail: nil)
41
+ @sendtype = sendtype
42
+ @sendmail = sendmail
43
+ end
44
+
45
+ def to_h
46
+ {sendtype: sendtype, sendmail: sendmail}
47
+ end
48
+ alias_method :to_hash, :to_h
49
+
50
+ def to_xml
51
+ Nokogiri::XML::Builder.new do |xml|
52
+ xml.remittanceadvice do
53
+ xml.sendtype sendtype
54
+ xml.sendmail sendmail
55
+ end
56
+ end.doc.root.to_xml
57
+ end
58
+
59
+ def self.from_xml(nokogiri)
60
+ obj = new
61
+ obj.sendtype = nokogiri.css("sendtype").text
62
+ obj.sendmail = nokogiri.css("sendmail").text
63
+ obj
64
+ end
65
+ end
66
+
67
+ class Bank
68
+ attr_accessor :ascription, :accountnumber, :address, :bankname, :biccode, :city, :country, :iban, :natbiccode, :postcode, :state, :id, :default
69
+
70
+ def initialize(ascription: nil, accountnumber: nil, address: nil, bankname: nil, biccode: nil, city: nil, country: nil, iban: nil, natbiccode: nil, postcode: nil, state: nil, id: nil, default: nil)
71
+ @ascription = ascription
72
+ @accountnumber = accountnumber
73
+ @address = (address.is_a?(Hash) && address != {}) ? Address.new(**address) : address
74
+ @bankname = bankname
75
+ @biccode = biccode
76
+ @city = city
77
+ @country = country
78
+ @iban = iban
79
+ @natbiccode = natbiccode
80
+ @postcode = postcode
81
+ @state = state
82
+ @default = ["true", true, 1, "1"].include?(default)
83
+ @id = id
84
+ end
85
+
86
+ def to_h
87
+ {
88
+ address: address.to_h,
89
+ ascription: ascription,
90
+ accountnumber: accountnumber,
91
+ bankname: bankname,
92
+ biccode: biccode,
93
+ city: city,
94
+ country: country,
95
+ iban: iban,
96
+ natbiccode: natbiccode,
97
+ postcode: postcode,
98
+ state: state,
99
+ id: id,
100
+ default: default
101
+ }
102
+ end
103
+ alias_method :to_hash, :to_h
104
+
105
+ def to_xml
106
+ Nokogiri::XML::Builder.new do |xml|
107
+ attributes = {default: default}
108
+ attributes[:id] = id if id
109
+
110
+ xml.bank(attributes) do
111
+ xml.ascription ascription
112
+ xml.accountnumber accountnumber
113
+ xml.address address
114
+ xml.bankname bankname
115
+ xml.biccode biccode
116
+ xml.city city
117
+ xml.country country
118
+ xml.iban iban
119
+ xml.natbiccode natbiccode
120
+ xml.postcode postcode
121
+ xml.state state
122
+ end
123
+ end.doc.root.to_xml
124
+ end
125
+
126
+ def self.from_xml(nokogiri_xml)
127
+ obj = new(id: nokogiri_xml.attributes["id"].text, default: nokogiri_xml.attributes["default"].text)
128
+ obj.ascription = nokogiri_xml.css("ascription").text
129
+ obj.accountnumber = nokogiri_xml.css("accountnumber").text
130
+ obj.address = Address.from_xml(nokogiri_xml.css("address")[0]) if nokogiri_xml.css("address")[0] && nokogiri_xml.css("address")[0].children.count > 0
131
+ obj.bankname = nokogiri_xml.css("bankname").text
132
+ obj.biccode = nokogiri_xml.css("biccode").text
133
+ obj.city = nokogiri_xml.css("city").text
134
+ obj.country = nokogiri_xml.css("country").text
135
+ obj.iban = nokogiri_xml.css("iban").text
136
+ obj.natbiccode = nokogiri_xml.css("natbiccode").text
137
+ obj.postcode = nokogiri_xml.css("postcode").text
138
+ obj.state = nokogiri_xml.css("state").text
139
+ obj
140
+ end
141
+ end
142
+
143
+ class Financials
144
+ extend Twinfield::Helpers::Parsers
145
+
146
+ attr_writer :meansofpayment
147
+ attr_reader :payavailable
148
+ attr_accessor :matchtype, :accounttype, :subanalyse, :duedays, :level, :paycode, :ebilling, :ebillmail, :substitutewith, :substitutionlevel, :relationsreference, :vattype, :vatcode, :vatobligatory, :performancetype, :collectmandate, :collectionschema, :childvalidations
149
+
150
+ def initialize(matchtype: nil, accounttype: nil, subanalyse: nil, duedays: nil, level: nil, payavailable: nil, meansofpayment: nil, paycode: nil, ebilling: false, ebillmail: nil, substitutewith: nil, substitutionlevel: nil, relationsreference: nil, vattype: nil, vatcode: nil, vatobligatory: nil, performancetype: nil, collectmandate: nil, collectionschema: nil, childvalidations: nil)
151
+ @matchtype = matchtype
152
+ @accounttype = accounttype
153
+ @subanalyse = subanalyse
154
+ @duedays = duedays
155
+ @level = level
156
+ @payavailable = payavailable # Direct Debit / Automatische incasso
157
+ @meansofpayment = meansofpayment
158
+ @paycode = paycode
159
+ @ebilling = ebilling
160
+ @ebillmail = ebillmail
161
+ @substitutewith = substitutewith
162
+ @substitutionlevel = substitutionlevel
163
+ @relationsreference = relationsreference # not in use
164
+ @vattype = vattype # not in use
165
+ @vatcode = vatcode
166
+ @vatobligatory = vatobligatory
167
+ @performancetype = performancetype
168
+ @collectmandate = collectmandate.is_a?(Hash) ? Twinfield::Customer::CollectMandate.new(**collectmandate) : collectmandate
169
+ @collectionschema = collectionschema
170
+ @childvalidations = childvalidations
171
+ end
172
+
173
+ def meansofpayment
174
+ @meansofpayment || (payavailable ? "paymentfile" : "none")
175
+ end
176
+
177
+ def payavailable= value
178
+ if [true, "true"].include? value
179
+ @meansofpayment = nil
180
+ @payavailable = true
181
+ end
182
+ end
183
+
184
+ def to_h
185
+ {
186
+ matchtype: matchtype,
187
+ accounttype: accounttype,
188
+ subanalyse: subanalyse,
189
+ duedays: duedays,
190
+ level: level,
191
+ payavailable: payavailable,
192
+ meansofpayment: meansofpayment,
193
+ paycode: paycode,
194
+ ebilling: ebilling,
195
+ ebillmail: ebillmail,
196
+ substitutewith: substitutewith,
197
+ substitutionlevel: substitutionlevel,
198
+ relationsreference: relationsreference,
199
+ vattype: vattype,
200
+ vatcode: vatcode,
201
+ vatobligatory: vatobligatory,
202
+ performancetype: performancetype,
203
+ collectmandate: collectmandate&.to_h,
204
+ collectionschema: collectionschema,
205
+ childvalidations: childvalidations
206
+ }
207
+ end
208
+ alias_method :to_hash, :to_h
209
+
210
+ def to_xml
211
+ Nokogiri::XML::Builder.new do |xml|
212
+ xml.financials do
213
+ xml.matchtype matchtype if matchtype
214
+ xml.accounttype accounttype if accounttype
215
+ xml.subanalyse subanalyse if subanalyse
216
+ xml.duedays duedays if duedays
217
+ xml.level level if level
218
+ xml.payavailable payavailable if payavailable
219
+ xml.meansofpayment meansofpayment if meansofpayment
220
+ xml.paycode paycode if paycode
221
+ xml.ebilling ebilling if ebilling
222
+ xml.ebillmail ebillmail if ebillmail
223
+ xml.substitutewith substitutewith if substitutewith
224
+ xml.substitutionlevel substitutionlevel if substitutionlevel
225
+ xml.relationsreference relationsreference if relationsreference
226
+ xml.vattype vattype if vattype
227
+ xml.vatcode vatcode if vatcode
228
+ xml.vatobligatory vatobligatory if vatobligatory
229
+ xml.performancetype performancetype if performancetype
230
+ xml << collectmandate.to_xml if collectmandate
231
+ xml.collectionschema collectionschema if collectionschema
232
+ end
233
+ end.doc.root.to_xml
234
+ end
235
+
236
+ def self.from_xml(nokogiri)
237
+ obj = new
238
+ obj.matchtype = nokogiri.css("matchtype").text
239
+ obj.accounttype = nokogiri.css("accounttype").text
240
+ obj.subanalyse = nokogiri.css("subanalyse").text
241
+ obj.duedays = nokogiri.css("duedays").text
242
+ obj.level = nokogiri.css("level").text
243
+ obj.payavailable = nokogiri.css("payavailable").text
244
+ obj.meansofpayment = nokogiri.css("meansofpayment").text
245
+ obj.paycode = nokogiri.css("paycode").text
246
+ obj.ebilling = nokogiri.css("ebilling").text == "true"
247
+ obj.ebillmail = nokogiri.css("ebillmail").text
248
+ obj.substitutewith = nokogiri.css("substitutewith").text
249
+ obj.substitutionlevel = nokogiri.css("substitutionlevel").text
250
+ obj.relationsreference = nokogiri.css("relationsreference").text
251
+ obj.vattype = nokogiri.css("vattype").text
252
+ obj.vatcode = nokogiri.css("vatcode").text
253
+ obj.vatobligatory = nokogiri.css("vatobligatory").text
254
+ obj.performancetype = nokogiri.css("performancetype").text
255
+ obj.collectmandate = CollectMandate.from_xml(nokogiri.css("collectmandate"))
256
+ obj.collectionschema = nokogiri.css("collectionschema").text
257
+ obj.childvalidations = nokogiri.css("childvalidations").text&.strip
258
+ obj
259
+ end
260
+ end
261
+
262
+ class CreditManagement
263
+ attr_accessor :responsibleuser, :basecreditlimit, :sendreminder, :reminderemail, :blocked, :freetext1, :freetext2, :freetext3, :comment
264
+
265
+ def to_h
266
+ {
267
+ responsibleuser: responsibleuser,
268
+ basecreditlimit: basecreditlimit,
269
+ sendreminder: sendreminder,
270
+ reminderemail: reminderemail,
271
+ blocked: blocked,
272
+ freetext1: freetext1,
273
+ freetext2: freetext2,
274
+ freetext3: freetext3,
275
+ comment: comment
276
+ }
277
+ end
278
+ alias_method :to_hash, :to_h
279
+
280
+ def present?
281
+ "#{responsibleuser}#{basecreditlimit}#{sendreminder}#{reminderemail}#{blocked}#{freetext3}#{freetext1}#{freetext2}#{comment}".strip != ""
282
+ end
283
+
284
+ def initialize(responsibleuser: nil, basecreditlimit: nil, sendreminder: nil, reminderemail: nil, blocked: nil, freetext1: nil, freetext2: nil, freetext3: nil, comment: nil)
285
+ @responsibleuser = responsibleuser
286
+ @basecreditlimit = basecreditlimit
287
+ @sendreminder = sendreminder
288
+ @reminderemail = reminderemail
289
+ @blocked = blocked
290
+ @freetext1 = freetext1
291
+ @freetext2 = freetext2
292
+ @freetext3 = freetext3
293
+ @comment = comment
294
+ end
295
+
296
+ def to_xml
297
+ Nokogiri::XML::Builder.new do |xml|
298
+ xml.creditmanagement do
299
+ xml.responsibleuser responsibleuser if responsibleuser
300
+ xml.basecreditlimit basecreditlimit if basecreditlimit
301
+ xml.sendreminder sendreminder if sendreminder
302
+ xml.reminderemail reminderemail if reminderemail
303
+ xml.blocked blocked
304
+ xml.freetext1 freetext1
305
+ xml.freetext2 freetext2
306
+ xml.freetext3 freetext3
307
+ xml.comment comment
308
+ end
309
+ end.doc.root.to_xml
310
+ end
311
+
312
+ def self.from_xml(nokogiri)
313
+ obj = new
314
+ obj.responsibleuser = nokogiri.css("responsibleuser").text
315
+ obj.basecreditlimit = nokogiri.css("basecreditlimit").text
316
+ obj.sendreminder = nokogiri.css("sendreminder").text
317
+ obj.reminderemail = nokogiri.css("reminderemail").text
318
+ obj.blocked = nokogiri.css("blocked").text
319
+ obj.freetext1 = nokogiri.css("freetext1").text
320
+ obj.freetext2 = nokogiri.css("freetext2").text
321
+ obj.freetext3 = nokogiri.css("freetext3").text
322
+ obj.comment = nokogiri.css("comment").text
323
+ obj
324
+ end
325
+ end
326
+
327
+ class Address
328
+ TYPES = [:invoice, :postal, :contact]
329
+
330
+ attr_accessor :name, :country, :ictcountrycode, :city, :postcode, :telephone, :telefax, :email, :contact, :field1, :field2, :field3, :field4, :field5, :field6, :type, :default, :id
331
+
332
+ def initialize(name:, country: nil, ictcountrycode: nil, city: nil, postcode: nil, telephone: nil, telefax: nil, email: nil, contact: nil, field1: nil, field2: nil, field3: nil, field4: nil, field5: nil, field6: nil, id: nil, type: nil, default: nil)
333
+ @name = name
334
+ @country = country
335
+ @ictcountrycode = ictcountrycode
336
+ @city = city
337
+ @postcode = postcode
338
+ @telephone = telephone
339
+ @telefax = telefax
340
+ @email = email
341
+ @contact = contact
342
+ @field1 = field1
343
+ @field2 = field2
344
+ @field3 = field3
345
+ @field4 = field4
346
+ @field5 = field5
347
+ @field6 = field6
348
+ @id = id
349
+ @type = type
350
+ @default = ["true", true, 1, "1"].include?(default)
351
+ end
352
+
353
+ def to_h
354
+ {
355
+ name: name,
356
+ country: country,
357
+ ictcountrycode: ictcountrycode,
358
+ city: city,
359
+ postcode: postcode,
360
+ telephone: telephone,
361
+ telefax: telefax,
362
+ email: email,
363
+ contact: contact,
364
+ field1: field1,
365
+ field2: field2,
366
+ field3: field3,
367
+ field4: field4,
368
+ field5: field5,
369
+ field6: field6,
370
+ type: type,
371
+ id: id,
372
+ default: default
373
+ }
374
+ end
375
+ alias_method :to_hash, :to_h
376
+
377
+ def to_s
378
+ [name, field1, field2, [country, postcode, city].join(" ")].select { |a| !(a.nil? || a.empty?) }.join("\n")
379
+ end
380
+
381
+ def to_xml
382
+ Nokogiri::XML::Builder.new do |xml|
383
+ xml.address(id: @id, type: @type, default: @default) do
384
+ xml.name name
385
+ xml.country country
386
+ xml.ictcountrycode ictcountrycode
387
+ xml.city city
388
+ xml.postcode postcode
389
+ xml.telephone telephone
390
+ xml.telefax telefax
391
+ xml.email email
392
+ xml.contact contact
393
+ xml.field1 field1
394
+ xml.field2 field2
395
+ xml.field3 field3
396
+ xml.field4 field4
397
+ xml.field5 field5
398
+ xml.field6 field6
399
+ end
400
+ end.doc.root.to_xml
401
+ end
402
+
403
+ def self.from_xml nokogiri
404
+ obj = new(id: nokogiri.attributes["id"]&.text, name: nokogiri.css("name")&.text, default: nokogiri.attributes["default"]&.text)
405
+ obj.country = nokogiri.css("country").text
406
+ obj.type = nokogiri.attributes["type"]&.text
407
+ obj.ictcountrycode = nokogiri.css("ictcountrycode")&.text
408
+ obj.city = nokogiri.css("city")&.text
409
+ obj.postcode = nokogiri.css("postcode")&.text
410
+ obj.telephone = nokogiri.css("telephone")&.text
411
+ obj.telefax = nokogiri.css("telefax")&.text
412
+ obj.email = nokogiri.css("email")&.text
413
+ obj.contact = nokogiri.css("contact")&.text
414
+ obj.field1 = nokogiri.css("field1")&.text
415
+ obj.field2 = nokogiri.css("field2")&.text
416
+ obj.field3 = nokogiri.css("field3")&.text
417
+ obj.field4 = nokogiri.css("field4")&.text
418
+ obj.field5 = nokogiri.css("field5")&.text
419
+ obj.field6 = nokogiri.css("field6")&.text
420
+ obj
421
+ end
422
+ end
423
+
424
+ attr_accessor :office, :code, :uid, :name, :shortname, :inuse, :behaviour, :modified, :touched, :beginperiod, :beginyear, :endperiod, :endyear, :website, :cocnumber, :vatnumber, :financials, :creditmanagement, :remittanceadvice, :addresses, :banks, :status
425
+
426
+ def initialize(office: nil, code: nil, uid: nil, name: nil, shortname: nil, inuse: nil, behaviour: nil, modified: nil, touched: nil, beginperiod: nil, beginyear: nil, endperiod: nil, endyear: nil, website: nil, cocnumber: nil, vatnumber: nil, financials: nil, creditmanagement: nil, remittanceadvice: nil, addresses: nil, banks: nil, status: :active)
427
+ @office = office || Twinfield.configuration.company
428
+ @status = status
429
+ @code = code
430
+ @uid = uid
431
+ @name = name
432
+ @shortname = shortname
433
+ @inuse = inuse
434
+ @behaviour = behaviour
435
+ @modified = modified
436
+ @touched = touched
437
+ @beginperiod = beginperiod
438
+ @beginyear = beginyear
439
+ @endperiod = endperiod
440
+ @endyear = endyear
441
+ @website = website
442
+ @cocnumber = cocnumber
443
+ @vatnumber = vatnumber
444
+ @financials = financials.is_a?(Hash) ? Financials.new(**financials) : financials
445
+ @creditmanagement = creditmanagement.is_a?(Hash) ? CreditManagement.new(**creditmanagement) : creditmanagement
446
+ @remittanceadvice = remittanceadvice.is_a?(Hash) ? RemittanceAdvice.new(**remittanceadvice) : remittanceadvice
447
+ @addresses = (addresses || []).map { |a| a.is_a?(Hash) ? Address.new(**a) : a }
448
+ @banks = (banks || []).map { |a| a.is_a?(Hash) ? Bank.new(**a) : a }
449
+ end
450
+
451
+ def to_h
452
+ {
453
+ office: office,
454
+ code: code,
455
+ uid: uid,
456
+ name: name,
457
+ shortname: shortname,
458
+ inuse: inuse,
459
+ behaviour: behaviour,
460
+ modified: modified,
461
+ touched: touched,
462
+ beginperiod: beginperiod,
463
+ beginyear: beginyear,
464
+ endperiod: endperiod,
465
+ endyear: endyear,
466
+ website: website,
467
+ cocnumber: cocnumber,
468
+ vatnumber: vatnumber,
469
+ financials: financials.to_h,
470
+ creditmanagement: creditmanagement.to_h,
471
+ remittanceadvice: remittanceadvice.to_h,
472
+ addresses: addresses.map(&:to_h),
473
+ banks: banks.map(&:to_h),
474
+ status: status
475
+ }
476
+ end
477
+ alias_method :to_hash, :to_h
478
+
479
+ def to_xml
480
+ Nokogiri::XML::Builder.new do |xml|
481
+ xml.dimension(status: status) do
482
+ xml.office office
483
+ xml.type "DEB"
484
+ xml.code code
485
+ xml.uid uid if uid
486
+ xml.name name
487
+ xml.shortname shortname
488
+ # xml.behaviour behaviour #temporarily disable
489
+ xml.beginperiod beginperiod if beginperiod
490
+ xml.beginyear beginyear if beginyear
491
+ xml.endperiod endperiod if endperiod
492
+ xml.endyear endyear if endyear
493
+ xml.website website
494
+ xml << financials&.to_xml
495
+ xml << creditmanagement&.to_xml if creditmanagement&.present?
496
+ xml << remittanceadvice&.to_xml
497
+ xml.addresses do
498
+ addresses.each do |line|
499
+ xml << line.to_xml
500
+ end
501
+ end
502
+ xml.banks do
503
+ banks.each do |line|
504
+ xml << line.to_xml
505
+ end
506
+ end
507
+ end
508
+ end.doc.root.to_xml
509
+ end
510
+
511
+ def save
512
+ response = Twinfield::Api::Process.request { to_xml }
513
+
514
+ xml = Nokogiri::XML(response.body[:process_xml_string_response][:process_xml_string_result])
515
+
516
+ if xml.at_css("dimension").attributes["result"].value == "1"
517
+ self.class.from_xml(xml)
518
+ else
519
+ raise Twinfield::Create::Error.new(xml.css("[msg]").map { |x| x.attributes["msg"].value }.join(" "), object: self)
520
+ end
521
+ end
522
+
523
+ def destroy
524
+ self.status = :deleted
525
+ save
526
+ end
527
+ alias_method :delete, :destroy
528
+
529
+ def transactions(filter_hash = {})
530
+ Twinfield::Browse::Transaction::Customer.where(customer_code: code, **filter_hash)
531
+ end
532
+
533
+ def load
534
+ Customer.find(code)
535
+ end
536
+
537
+ class << self
538
+ # @return Array<Twinfield::Customer>
539
+ def all
540
+ search
541
+ end
542
+
543
+ # helper method that calculates the next unused code
544
+ # @return String
545
+ def next_unused_twinfield_customer_code range = nil
546
+ current_codes = Twinfield::Customer.all.map(&:code).map(&:to_i).sort
547
+ current_codes_in_range = current_codes & range.to_a if range
548
+ latest = (current_codes_in_range || current_codes).last
549
+ latest = latest ? latest + 1 : range.to_a.first
550
+ raise "invalid new customer code" if latest == 1
551
+ latest.to_s
552
+ end
553
+
554
+ def search text = "*"
555
+ text = "*#{text}*" unless text.match?(/[?*]/)
556
+ options = {
557
+ dimtype: "DEB",
558
+ office: Twinfield.configuration.company,
559
+ pattern: text,
560
+ max_rows: 10000
561
+ }
562
+ response = Twinfield::Api::Finder.request("DIM", options)
563
+ if response.body[:search_response][:data][:total_rows].to_i == 1
564
+ resp = response.body[:search_response][:data][:items][:array_of_string][:string]
565
+ [Customer.new(name: resp[1], code: resp[0])]
566
+ elsif response.body[:search_response][:data][:total_rows].to_i > 1
567
+ response.body[:search_response][:data][:items][:array_of_string]
568
+ .map { |item| Customer.new(name: item[:string][1], code: item[:string][0]) }
569
+ else
570
+ []
571
+ end
572
+ end
573
+
574
+ def find(customercode)
575
+ options = {office: Twinfield.configuration.company, code: customercode, dimtype: "DEB"}
576
+ customer_xml = Twinfield::Api::Process.read(:dimensions, options)
577
+ from_xml(customer_xml)
578
+ end
579
+
580
+ def from_xml(nokogiri_or_string)
581
+ nokogiri = nokogiri_or_string.is_a?(Nokogiri::XML::Document) ? nokogiri_or_string : Nokogiri::XML(nokogiri_or_string)
582
+ dimension = nokogiri.css("dimension")[0]
583
+
584
+ if dimension.attributes["result"]&.text == "0"
585
+ return nil
586
+ end
587
+
588
+ obj = new(shortname: nokogiri.css("dimension > shortname").text, name: nokogiri.css("dimension > name").text, code: nokogiri.css("dimension > code").text)
589
+ obj.status = dimension.attributes["status"].text
590
+ obj.office = nokogiri.css("dimension > office").text
591
+ obj.uid = nokogiri.css("dimension > uid").text
592
+ obj.inuse = nokogiri.css("dimension > inuse").text
593
+ obj.behaviour = nokogiri.css("dimension > behaviour").text
594
+ obj.modified = parse_datetime(nokogiri.css("dimension > modified").text)
595
+ obj.touched = nokogiri.css("dimension > touched").text
596
+ obj.beginperiod = nokogiri.css("dimension > beginperiod").text
597
+ obj.beginyear = nokogiri.css("dimension > beginyear").text
598
+ obj.endperiod = nokogiri.css("dimension > endperiod").text
599
+ obj.endyear = nokogiri.css("dimension > endyear").text
600
+ obj.website = nokogiri.css("dimension > website").text
601
+ obj.cocnumber = nokogiri.css("dimension > cocnumber").text
602
+ obj.vatnumber = nokogiri.css("dimension > vatnumber").text
603
+ obj.financials = Financials.from_xml(nokogiri.css("dimension > financials")[0])
604
+ obj.creditmanagement = CreditManagement.from_xml(nokogiri.css("dimension > creditmanagement")[0]) if nokogiri.css("dimension > creditmanagement")[0]
605
+ obj.remittanceadvice = RemittanceAdvice.from_xml(nokogiri.css("dimension > remittanceadvice")[0]) if nokogiri.css("dimension > remittanceadvice")[0]
606
+ obj.addresses = nokogiri.css("dimension > addresses > address").map { |xml_fragment| Address.from_xml(xml_fragment) }
607
+ obj.banks = nokogiri.css("dimension > banks > bank").map { |xml_fragment| Bank.from_xml(xml_fragment) }
608
+ obj
609
+ end
610
+ end
611
+ end
612
+ end
@@ -0,0 +1,23 @@
1
+ module Twinfield
2
+ module Helpers
3
+ module Parsers
4
+ def parse_date string
5
+ if string && string != ""
6
+ Date.strptime(string, "%Y%m%d")
7
+ end
8
+ end
9
+
10
+ def parse_datetime string
11
+ if string && string != ""
12
+ DateTime.strptime(string, "%Y%m%d%H%M%S")
13
+ end
14
+ end
15
+
16
+ def parse_float string
17
+ if string && string != ""
18
+ Float(string)
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,40 @@
1
+ module Twinfield
2
+ class TransactionMatchError < StandardError; end
3
+
4
+ module Helpers
5
+ module TransactionMatch
6
+ def to_transaction_match_line_xml(transline)
7
+ "<line><transcode>#{code}</transcode><transnumber>#{number}</transnumber><transline>#{transline}</transline></line>"
8
+ end
9
+
10
+ def to_match_xml other_transaction
11
+ raise Twinfield::TransactionMatchError.new("This transaction and the other transaction don't add up to 0") if (value + other_transaction.value) != 0
12
+ "<match>
13
+ <set>
14
+ <matchcode>170</matchcode>
15
+ <office>#{Twinfield.configuration.company}</office>
16
+ <matchdate>#{Date.today.strftime("%Y%m%d")}</matchdate>
17
+ <lines>
18
+ #{to_transaction_match_line_xml(1)}
19
+ #{other_transaction.to_transaction_match_line_xml(2)}
20
+ </lines>
21
+ </set>
22
+ </match>"
23
+ end
24
+
25
+ def match! other_transaction
26
+ response = Twinfield::Api::Process.request do
27
+ to_match_xml(other_transaction)
28
+ end
29
+
30
+ xml = Nokogiri::XML(response.body[:process_xml_string_response][:process_xml_string_result])
31
+
32
+ if xml.at_css("match").attributes["result"].value == "1"
33
+ self
34
+ else
35
+ raise Twinfield::TransactionMatchError.new(xml.css("[msg]").map { |x| x.attributes["msg"].value }.join(" ") + "(possibly a non-matching invoice number)")
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end