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,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"