twinfieldrb 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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,409 @@
1
+ module Twinfield
2
+ class SalesInvoice < Twinfield::AbstractModel
3
+ extend Twinfield::Helpers::Parsers
4
+
5
+ class Financials
6
+ attr_accessor :code, :number
7
+
8
+ def initialize(code:, number:)
9
+ @code = code
10
+ @number = number
11
+ end
12
+
13
+ def to_h
14
+ {
15
+ code: code,
16
+ number: number
17
+ }
18
+ end
19
+ end
20
+
21
+ class VatLine
22
+ attr_accessor :vatcode, :vatvalue, :performancetype, :performancedate, :vatname, :invoice
23
+
24
+ def initialize(vatcode:, vatvalue:, performancetype:, performancedate:, vatname:)
25
+ @vatcode = vatcode
26
+ @vatvalue = vatvalue
27
+ @performancetype = performancetype
28
+ @performancedate = performancedate
29
+ @vatname = vatname
30
+ end
31
+
32
+ def to_h
33
+ {
34
+ vatcode: vatcode,
35
+ vatvalue: vatvalue,
36
+ performancetype: performancetype,
37
+ performancedate: performancedate,
38
+ vatname: vatname
39
+ }
40
+ end
41
+ end
42
+
43
+ class Line < Twinfield::AbstractModel
44
+ attr_accessor :id, :article, :subarticle, :quantity, :units, :allowdiscountorpremium, :description, :unitspriceexcl, :unitspriceinc, :freetext1, :freetext2, :freetext3, :dim1, :vatcode, :performancetype, :performancedate, :valueexcl, :vatvalue, :valueinc, :invoice
45
+
46
+ def initialize(id: nil, article: "-", subarticle: nil, quantity: 1, units: nil, allowdiscountorpremium: true, description: nil, unitspriceexcl: nil, unitspriceinc: nil, freetext1: nil, freetext2: nil, freetext3: nil, dim1: nil, vatcode: nil, performancetype: nil, performancedate: nil, valueinc: nil, vatvalue: nil, valueexcl: nil)
47
+ @id = id
48
+ @article = article # article "-" is an article-less article in Twinfield
49
+ @subarticle = subarticle if subarticle.to_s != ""
50
+ @quantity = Float(quantity) unless article == "-"
51
+ @units = Integer(units) if units && units != ""
52
+ @allowdiscountorpremium = allowdiscountorpremium unless article == "-"
53
+ @description = description
54
+ @unitspriceexcl = Float(unitspriceexcl) if unitspriceexcl
55
+ @unitspriceinc = Float(unitspriceinc) if unitspriceinc
56
+ @freetext1 = freetext1 if freetext1.to_s != ""
57
+ @freetext2 = freetext2 if freetext2.to_s != ""
58
+ @freetext3 = freetext3 if freetext3.to_s != ""
59
+ @dim1 = dim1 if dim1.to_s != ""
60
+ @vatcode = vatcode if vatcode.to_s != ""
61
+ @performancetype = performancetype if performancetype.to_s != ""
62
+ @performancedate = performancedate if performancedate.to_s != ""
63
+ @valueinc = valueinc
64
+ @vatvalue = vatvalue
65
+ @valueexcl = valueexcl
66
+ end
67
+
68
+ def to_xml(lineid = id)
69
+ Nokogiri::XML::Builder.new do |xml|
70
+ xml.line(id: lineid) {
71
+ xml.article article
72
+ xml.subarticle subarticle if subarticle
73
+ xml.quantity quantity if quantity
74
+ xml.units units if units
75
+ xml.allowdiscountorpremium allowdiscountorpremium if allowdiscountorpremium
76
+ xml.description description if description
77
+ xml.unitspriceexcl unitspriceexcl if unitspriceexcl
78
+ xml.unitspriceinc unitspriceinc if unitspriceinc
79
+ xml.freetext1 freetext1 if freetext1
80
+ xml.freetext2 freetext2 if freetext2
81
+ xml.freetext3 freetext3 if freetext3
82
+ xml.dim1 dim1 if dim1
83
+ xml.vatcode vatcode if vatcode
84
+ xml.performancetype performancetype if performancetype
85
+ xml.performancedate performancedate.strftime("%Y%m%d") if performancedate
86
+ }
87
+ end.doc.root.to_xml
88
+ end
89
+
90
+ def to_h
91
+ {
92
+ id: id,
93
+ article: article,
94
+ subarticle: subarticle,
95
+ quantity: quantity,
96
+ units: units,
97
+ allowdiscountorpremium: allowdiscountorpremium,
98
+ description: description,
99
+ unitspriceexcl: unitspriceexcl,
100
+ unitspriceinc: unitspriceinc,
101
+ freetext1: freetext1,
102
+ freetext2: freetext2,
103
+ freetext3: freetext3,
104
+ dim1: dim1,
105
+ vatcode: vatcode,
106
+ performancetype: performancetype,
107
+ performancedate: performancedate,
108
+ valueexcl: valueexcl,
109
+ valueinc: valueinc,
110
+ vatvalue: vatvalue
111
+ }
112
+ end
113
+ end
114
+
115
+ class << self
116
+ def find(invoicenumber, invoicetype:)
117
+ options = {office: Twinfield.configuration.company, code: invoicetype, invoicenumber: invoicenumber}
118
+
119
+ invoice_xml = Twinfield::Api::Process.read(:salesinvoice, options)
120
+
121
+ invoice = Twinfield::SalesInvoice.new(
122
+ office: invoice_xml.css("header office").text,
123
+ invoicetype: invoice_xml.css("header invoicetype").text,
124
+ invoicedate: parse_date(invoice_xml.css("header invoicedate").text),
125
+ duedate: parse_date(invoice_xml.css("header duedate").text),
126
+ bank: invoice_xml.css("header bank").text,
127
+ deliveraddressnumber: invoice_xml.css("header deliveraddressnumber").text&.to_i,
128
+ invoiceaddressnumber: invoice_xml.css("header invoiceaddressnumber").text&.to_i,
129
+ customer: invoice_xml.css("header customer").text,
130
+ period: invoice_xml.css("header period").text,
131
+ currency: invoice_xml.css("header currency").text,
132
+ status: invoice_xml.css("header status").text,
133
+ paymentmethod: invoice_xml.css("header paymentmethod").text,
134
+ headertext: invoice_xml.css("header headertext").text,
135
+ footertext: invoice_xml.css("header footertext").text
136
+ )
137
+
138
+ invoice.invoicenumber = invoice_xml.css("header invoicenumber").text
139
+
140
+ return nil if invoice.invoicenumber.strip != invoicenumber.to_s
141
+
142
+ invoice.financials = Financials.new(code: invoice_xml.css("financials code").text, number: invoice_xml.css("financials number").text)
143
+
144
+ invoice_xml.css("lines line").each do |xml_line|
145
+ line = Line.new(
146
+ id: xml_line.attributes["id"].text,
147
+ article: xml_line.css("article").text,
148
+ subarticle: xml_line.css("subarticle").text,
149
+ quantity: parse_float(xml_line.css("quantity").text),
150
+ units: xml_line.css("units").text,
151
+ allowdiscountorpremium: xml_line.css("allowdiscountorpremium").text,
152
+ description: xml_line.css("description").text,
153
+ unitspriceexcl: parse_float(xml_line.css("unitspriceexcl").text),
154
+ freetext1: xml_line.css("freetext1").text,
155
+ freetext2: xml_line.css("freetext2").text,
156
+ freetext3: xml_line.css("freetext3").text,
157
+ dim1: xml_line.css("dim1").text,
158
+ vatcode: xml_line.css("vatcode").text
159
+ )
160
+ line.valueexcl = (xml_line.css("valueexcl").text == "") ? nil : xml_line.css("valueexcl").text.to_f
161
+ line.vatvalue = (xml_line.css("vatvalue").text == "") ? nil : xml_line.css("vatvalue").text.to_f
162
+ line.valueinc = (xml_line.css("valueinc").text == "") ? nil : xml_line.css("valueinc").text.to_f
163
+ invoice.lines << line
164
+ end
165
+
166
+ invoice_xml.css("vatlines vatline").each do |xml_line|
167
+ line = VatLine.new(
168
+ vatcode: xml_line.css("vatcode").text,
169
+ vatname: xml_line.css("vatcode")[0].attributes["name"].text,
170
+ vatvalue: parse_float(xml_line.css("vatvalue").text),
171
+ performancetype: xml_line.css("performancetype").text,
172
+ performancedate: xml_line.css("performancedate").text
173
+ )
174
+
175
+ invoice.vat_lines << line
176
+ end
177
+
178
+ invoice
179
+ end
180
+
181
+ def search(options = {})
182
+ Twinfield::Api::Process.request(:process_xml_string) do
183
+ %(
184
+ <columns code="000">
185
+ <sort>
186
+ <field>fin.trs.head.code</field>
187
+ </sort>
188
+ <column>
189
+ <field>fin.trs.head.yearperiod</field>
190
+ <label>Period</label>
191
+ <visible>true</visible>
192
+ <ask>true</ask>
193
+ <operator>between</operator>
194
+ <from>2021/01</from>
195
+ <to>2021/12</to>
196
+ </column>
197
+ <column>
198
+ <field>fin.trs.head.code</field>
199
+ <label>Transaction type</label>
200
+ <visible>true</visible>
201
+ </column>
202
+ <column>
203
+ <field>fin.trs.head.shortname</field>
204
+ <label>Name</label>
205
+ <visible>true</visible>
206
+ </column>
207
+ <column>
208
+ <field>fin.trs.head.number</field>
209
+ <label>Trans. no.</label>
210
+ <visible>true</visible>
211
+ </column>
212
+ <column>
213
+ <field>fin.trs.line.dim1</field>
214
+ <label>General ledger</label>
215
+ <visible>true</visible>
216
+ <ask>true</ask>
217
+ <operator>between</operator>
218
+ <from>1300</from>
219
+ <to>1300</to>
220
+ </column>
221
+ <column>
222
+ <field>fin.trs.head.curcode</field>
223
+ <label>Currency</label>
224
+ <visible>true</visible>
225
+ </column>
226
+ <column>
227
+ <field>fin.trs.line.valuesigned</field>
228
+ <label>Value</label>
229
+ <visible>true</visible>
230
+ </column>
231
+ <column>
232
+ <field>fin.trs.line.description</field>
233
+ <label>Description</label>
234
+ <visible>true</visible>
235
+ </column>
236
+
237
+ </columns>
238
+ )
239
+ # <column>
240
+ # <field>fin.trs.line.dim2</field>
241
+ # <label>Debtor</label><visible>true</visible><from>#{code}</from><to>#{code}</to><operator>between</operator>
242
+ # </column>
243
+ end
244
+ end
245
+ end
246
+
247
+ attr_accessor :invoicetype, :invoicedate, :duedate, :performancedate, :bank, :invoiceaddressnumber, :deliveraddressnumber, :customer_code, :period, :currency, :status, :paymentmethod, :headertext, :footertext, :lines, :office, :invoicenumber, :vatvalue, :valueinc, :financials, :vat_lines
248
+
249
+ def initialize(invoicetype:, duedate: nil, invoicedate: nil, performancedate: nil, bank: nil, invoiceaddressnumber: nil, deliveraddressnumber: nil, customer: nil, customer_code: nil, period: nil, currency: nil, status: "concept", paymentmethod: nil, headertext: nil, footertext: nil, office: nil, invoicenumber: nil, financials: {}, lines: [], vat_lines: [])
250
+ self.lines = lines.collect { |a| SalesInvoice::Line.new(**a.to_h) }
251
+ self.vat_lines = vat_lines.collect { |a| SalesInvoice::VatLine.new(**a.to_h) }
252
+ self.financials = SalesInvoice::Financials.new(**financials.to_h) if financials && financials != {}
253
+ @invoicetype = invoicetype
254
+ @invoicedate = invoicedate.is_a?(String) ? Date.parse(invoicedate) : invoicedate
255
+ @duedate = duedate.is_a?(String) ? Date.parse(duedate) : duedate
256
+ @performancedate = performancedate.is_a?(String) ? Date.parse(performancedate) : performancedate
257
+ @bank = bank
258
+ @invoiceaddressnumber = invoiceaddressnumber
259
+ @deliveraddressnumber = deliveraddressnumber
260
+ self.customer = customer || customer_code
261
+ raise ArgumentError.new("missing keyword: :customer or :customer_code") unless self.customer_code
262
+ @period = period
263
+ @currency = currency
264
+ @status = status
265
+ @paymentmethod = paymentmethod
266
+ @headertext = headertext
267
+ @footertext = footertext
268
+ @office = office || Twinfield.configuration.company
269
+ @invoicenumber = invoicenumber
270
+ end
271
+
272
+ def associated_lines
273
+ lines.map { |a|
274
+ a.invoice = self
275
+ a
276
+ }
277
+ end
278
+
279
+ def raisewarning
280
+ @raisewarning || false
281
+ end
282
+
283
+ def autobalancevat
284
+ @autobalancevat || true
285
+ end
286
+
287
+ def generate_lines
288
+ line_id = 0
289
+ lines.map do |line|
290
+ line_id += 1
291
+ line.to_xml(line_id)
292
+ end
293
+ end
294
+
295
+ def final?
296
+ ["final", :final].include? status
297
+ end
298
+
299
+ def customer= customer
300
+ if customer.is_a?(String) || customer.is_a?(Numeric)
301
+ @customer_code = customer.to_i
302
+ elsif customer.is_a? Twinfield::Customer
303
+ @customer_code = customer.code
304
+ @customer = customer
305
+ end
306
+ end
307
+
308
+ def customer
309
+ @customer ||= Twinfield::Customer.find(customer_code)
310
+ end
311
+
312
+ def deliver_address
313
+ customer.addresses[deliveraddressnumber - 1] if deliveraddressnumber
314
+ end
315
+
316
+ def invoice_address
317
+ customer.addresses[invoiceaddressnumber - 1] if invoiceaddressnumber
318
+ end
319
+
320
+ def transaction
321
+ @transaction ||= financials&.number ? Twinfield::Browse::Transaction::Customer.find(number: financials.number, code: financials.code) : nil
322
+ end
323
+
324
+ # helper method to calculate a total price
325
+ def totalinc
326
+ lines.map(&:valueinc).compact.sum
327
+ end
328
+
329
+ alias_method :total, :totalinc
330
+
331
+ # helper method to calculate a total excl price
332
+ def totalexcl
333
+ lines.map(&:valueexcl).compact.sum
334
+ end
335
+
336
+ def to_xml
337
+ Nokogiri::XML::Builder.new do |xml|
338
+ xml.salesinvoice(raisewarning: raisewarning, autobalancevat: autobalancevat) {
339
+ xml.header do
340
+ xml.office office
341
+ xml.invoicenumber invoicenumber if invoicenumber
342
+ xml.invoicetype invoicetype
343
+ xml.invoicedate invoicedate&.strftime("%Y%m%d")
344
+ xml.duedate duedate&.strftime("%Y%m%d")
345
+ xml.performancedate performancedate.strftime("%Y%m%d") if performancedate
346
+ xml.bank bank
347
+ xml.invoiceaddressnumber invoiceaddressnumber if invoiceaddressnumber
348
+ xml.deliveraddressnumber deliveraddressnumber if deliveraddressnumber
349
+ xml.customer customer_code
350
+ xml.period period if period
351
+ xml.currency currency
352
+ xml.status status
353
+ xml.paymentmethod paymentmethod
354
+ xml.headertext headertext
355
+ xml.footertext footertext
356
+ end
357
+ xml.lines do
358
+ generate_lines.each do |line|
359
+ xml << line
360
+ end
361
+ end
362
+ }
363
+ end.doc.root.to_xml
364
+ end
365
+
366
+ def to_h
367
+ {
368
+ lines: lines.collect(&:to_h),
369
+ vat_lines: vat_lines.collect(&:to_h),
370
+ financials: financials&.to_h,
371
+ invoicetype: invoicetype,
372
+ invoicedate: invoicedate,
373
+ duedate: duedate,
374
+ performancedate: performancedate,
375
+ bank: bank,
376
+ invoiceaddressnumber: invoiceaddressnumber,
377
+ deliveraddressnumber: deliveraddressnumber,
378
+ customer_code: customer_code,
379
+ period: period,
380
+ currency: currency,
381
+ status: status,
382
+ paymentmethod: paymentmethod,
383
+ headertext: headertext,
384
+ footertext: footertext,
385
+ office: office || Twinfield.configuration.company,
386
+ invoicenumber: invoicenumber
387
+ }
388
+ end
389
+
390
+ def save
391
+ response = Twinfield::Api::Process.request do
392
+ to_xml
393
+ end
394
+
395
+ xml = Nokogiri::XML(response.body[:process_xml_string_response][:process_xml_string_result])
396
+
397
+ if xml.at_css("salesinvoice").attributes["result"].value == "1"
398
+ self.invoicenumber = xml.at_css("invoicenumber").content
399
+ self
400
+ elsif xml.css("header status").text == "final"
401
+ raise Twinfield::Create::Finalized.new(xml.css("[msg]").map { |x| x.attributes["msg"].value }.join(" "), object: self)
402
+ elsif lines.count == 0
403
+ raise Twinfield::Create::EmptyInvoice.new(xml.css("[msg]").map { |x| x.attributes["msg"].value }.join(" "), object: self)
404
+ else
405
+ raise Twinfield::Create::Error.new(xml.css("[msg]").map { |x| x.attributes["msg"].value }.join(" "), object: self)
406
+ end
407
+ end
408
+ end
409
+ end
@@ -0,0 +1,112 @@
1
+ module Twinfield
2
+ class Transaction < Twinfield::AbstractModel
3
+ extend Twinfield::Helpers::Parsers
4
+ include Twinfield::Helpers::TransactionMatch
5
+
6
+ class Line
7
+ attr_accessor :dim1, :dim2, :value, :debitcredit, :description, :invoicenumber, :vatcode, :type
8
+
9
+ def initialize(dim1: nil, dim2: nil, value: nil, debitcredit: nil, description: nil, invoicenumber: nil, vatcode: nil, customer_code: nil, balance_code: nil, type: :detail)
10
+ self.dim1 = dim1 || balance_code
11
+ self.dim2 = dim2 || customer_code
12
+ self.value = value.to_f
13
+ self.debitcredit = debitcredit
14
+ self.description = description
15
+ self.invoicenumber = invoicenumber
16
+ self.vatcode = vatcode
17
+ self.type = type.to_sym
18
+ end
19
+
20
+ def balance_code
21
+ dim1
22
+ end
23
+
24
+ def customer_code
25
+ dim2
26
+ end
27
+
28
+ def to_xml
29
+ Nokogiri::XML::Builder.new do |xml|
30
+ xml.line(type: type) {
31
+ xml.dim1 dim1 if dim1
32
+ xml.dim2 dim2 if dim2
33
+ xml.value value if value
34
+ xml.description description if description && type != :total
35
+ xml.debitcredit debitcredit if debitcredit
36
+ xml.invoicenumber invoicenumber if invoicenumber
37
+ xml.vatcode vatcode if vatcode
38
+ xml.currencydate Date.today.strftime("%Y%m%d")
39
+ }
40
+ end.doc.root.to_xml
41
+ end
42
+
43
+ def detail?
44
+ type == :detail
45
+ end
46
+
47
+ def total?
48
+ type == :total
49
+ end
50
+
51
+ def credit?
52
+ debitcredit == :credit
53
+ end
54
+ end
55
+
56
+ attr_accessor :office, :code, :currency, :date, :period, :lines, :destiny, :number
57
+
58
+ def initialize(code:, office: nil, currency: "EUR", date: Date.today, period: nil, destiny: :final, lines: [], number: nil)
59
+ self.office = office || Twinfield.configuration.company
60
+ self.code = code
61
+ self.currency = currency
62
+ self.date = date
63
+ self.period = period || "#{date.year}/#{"%02d" % date.month}"
64
+ self.lines = lines.collect { |a| a.is_a?(Line) ? a : Line.new(**a) }
65
+ self.destiny = destiny
66
+ self.number = number
67
+ end
68
+
69
+ def value
70
+ 0.0 - lines.select { |l| l.credit? && l.detail? }.map(&:value).sum
71
+ end
72
+
73
+ def save
74
+ response = Twinfield::Api::Process.request do
75
+ to_xml
76
+ end
77
+
78
+ xml = Nokogiri::XML(response.body[:process_xml_string_response][:process_xml_string_result])
79
+
80
+ if xml.at_css("transaction").attributes["result"].value == "1"
81
+ self.number = xml.at_css("header number").content
82
+ self
83
+ else
84
+ raise Twinfield::Create::Error.new(xml.css("[msg]").map { |x| x.attributes["msg"].value }.join(" "), object: self)
85
+ end
86
+ end
87
+
88
+ def to_xml
89
+ Nokogiri::XML::Builder.new do |xml|
90
+ xml.transaction(destiny: "final") do
91
+ xml.header do
92
+ xml.office office
93
+ xml.code code
94
+ xml.number number if number
95
+ xml.currency currency
96
+ xml.date date.strftime("%Y%m%d")
97
+ xml.period period
98
+ end
99
+ xml.lines do
100
+ lines.select(&:total?).each do |line|
101
+ xml << line.to_xml
102
+ end
103
+
104
+ lines.select(&:detail?).each do |line|
105
+ xml << line.to_xml
106
+ end
107
+ end
108
+ end
109
+ end.doc.root.to_xml
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,5 @@
1
+ module Twinfield
2
+ module Version
3
+ VERSION = "0.3.0"
4
+ end
5
+ end
data/lib/twinfield.rb ADDED
@@ -0,0 +1,89 @@
1
+ require "savon"
2
+
3
+ module Twinfield
4
+ WSDLS = {
5
+ :session => File.join(__dir__, "..", "wsdls", "session.wsdl"),
6
+ "accounting" => {
7
+ session: File.join(__dir__, "..", "wsdls", "session.wsdl"),
8
+ process: File.join(__dir__, "..", "wsdls", "accounting", "process.wsdl"),
9
+ finder: File.join(__dir__, "..", "wsdls", "accounting", "finder.wsdl")
10
+ },
11
+ "accounting2" => {
12
+ process: File.join(__dir__, "..", "wsdls", "accounting2", "process.wsdl"),
13
+ finder: File.join(__dir__, "..", "wsdls", "accounting2", "finder.wsdl")
14
+ },
15
+ "api.accounting" => {
16
+ process: File.join(__dir__, "..", "wsdls", "api.accounting", "process.wsdl"),
17
+ finder: File.join(__dir__, "..", "wsdls", "api.accounting", "finder.wsdl")
18
+ }
19
+
20
+ }
21
+
22
+ ERRORS = {
23
+ 100 => "Unexpected exception.",
24
+ 101 => "Session header is missing.",
25
+ 102 => "Access is denied.",
26
+ 103 => "The log-on credentials are not valid anymore.",
27
+ 104 => "The log-on has been deleted.",
28
+ 105 => "The log-on has been disabled.",
29
+ 106 => "The organisation is no longer active.",
30
+ 107 => "SMS failed to send.",
31
+ 108 => "Access to this server is not allowed because the cluster is invalid.",
32
+ 109 => "You need access to at least one company to log on.",
33
+ 110 => "Login is not allowed on this server"
34
+ }
35
+
36
+ class << self
37
+ # Holds the configuration for easy access to settings
38
+ attr_accessor :configuration
39
+
40
+ # Configures gem options
41
+ def configure
42
+ self.configuration ||= Twinfield::Configuration.new
43
+ reset_sessions!
44
+ yield(configuration)
45
+ end
46
+
47
+ def reset_sessions!
48
+ Twinfield::Api::Process.session = nil
49
+ Twinfield::Api::Finder.session = nil
50
+ end
51
+ end
52
+ end
53
+
54
+ # Helpers
55
+ require "twinfield/helpers/parsers"
56
+ require "twinfield/helpers/transaction_match"
57
+
58
+ require "twinfield/configuration"
59
+ require "twinfield/abstract_model"
60
+ require "twinfield/version"
61
+
62
+ # API Helpers
63
+ require "twinfield/api/base_api"
64
+
65
+ require "twinfield/api/o_auth_session"
66
+ require "twinfield/api/session"
67
+ require "twinfield/api/process"
68
+ require "twinfield/api/finder"
69
+
70
+ # New style models
71
+ require "twinfield/browse/transaction/customer"
72
+ require "twinfield/browse/transaction/cost_center"
73
+ require "twinfield/browse/transaction/general_ledger"
74
+ require "twinfield/sales_invoice"
75
+ require "twinfield/transaction"
76
+ require "twinfield/customer"
77
+
78
+ # Create services (old style)
79
+ require "twinfield/create/cost_center"
80
+ require "twinfield/create/general_ledger"
81
+ require "twinfield/create/debtor"
82
+ require "twinfield/create/creditor"
83
+ require "twinfield/create/error"
84
+ require "twinfield/create/transaction"
85
+
86
+ # Requests services (old style)
87
+ require "twinfield/request/find"
88
+ require "twinfield/request/list"
89
+ require "twinfield/request/read"
data/script/boot.rb ADDED
@@ -0,0 +1,58 @@
1
+ $:.unshift File.expand_path("../../lib", __FILE__)
2
+
3
+ require "rubygems"
4
+ require "twinfield"
5
+ require File.expand_path("../config", __FILE__)
6
+ #
7
+ # @session = Twinfield::Api::Session.new
8
+ # @session.logon
9
+ #
10
+ # @process = Twinfield::Api::Process.new(@session.session_id, @session.cluster)
11
+ #
12
+ # Twinfield::Request::List.browsefields
13
+ #
14
+ # @dimensions = Twinfield::Request::List.dimensions( { dimtype: "DEB", office: Twinfield.configuration.company } )
15
+ #
16
+ @invoice = Twinfield::SalesInvoice.new
17
+
18
+ # Mandatory parameters:
19
+ @invoice.code = "VRK"
20
+ @invoice.currency = "EUR"
21
+ @invoice.date = DateTime.now
22
+ @invoice.duedate = DateTime.now + 30
23
+ @invoice.invoicenumber = 110021
24
+
25
+ # Obligatory parameters:
26
+ @invoice.number = 201500016
27
+ @invoice.destiny = "final"
28
+ @invoice.raisewarning = false
29
+ @invoice.autobalancevat = true
30
+
31
+ @invoice.lines = [
32
+ {
33
+ type: "total",
34
+ id: 1,
35
+ dim1: 1300,
36
+ dim2: "D10000881",
37
+ value: 1000000,
38
+ debitcredit: "debit",
39
+ description: "Totaal regel"
40
+ },
41
+ {
42
+ # Mandatory parameters:
43
+ type: "detail",
44
+ id: 2,
45
+ dim1: 8000,
46
+ dim2: "A0000",
47
+ dim3: "M0000",
48
+ value: 1000000,
49
+ debitcredit: "credit",
50
+ description: "Rekening betaald",
51
+
52
+ # Obligatory parameters:
53
+ vatvalue: "0",
54
+ vatcode: "VL"
55
+ }
56
+ ]
57
+ # @result = @invoice.save
58
+ # puts @result
data/script/console ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env ruby
2
+ system "irb -r ./script/boot.rb"