twinfieldrb 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.github/workflows/codeql-analysis.yml +70 -0
- data/.github/workflows/rspec.yml +33 -0
- data/.gitignore +6 -0
- data/.rspec +1 -0
- data/CHANGELOG.md +15 -0
- data/Gemfile +4 -0
- data/README.md +120 -0
- data/Rakefile +1 -0
- data/lib/twinfield/abstract_model.rb +7 -0
- data/lib/twinfield/api/base_api.rb +50 -0
- data/lib/twinfield/api/finder.rb +45 -0
- data/lib/twinfield/api/o_auth_session.rb +58 -0
- data/lib/twinfield/api/process.rb +44 -0
- data/lib/twinfield/api/session.rb +170 -0
- data/lib/twinfield/browse/transaction/cost_center.rb +145 -0
- data/lib/twinfield/browse/transaction/customer.rb +413 -0
- data/lib/twinfield/browse/transaction/general_ledger.rb +144 -0
- data/lib/twinfield/configuration.rb +49 -0
- data/lib/twinfield/create/cost_center.rb +39 -0
- data/lib/twinfield/create/creditor.rb +88 -0
- data/lib/twinfield/create/debtor.rb +88 -0
- data/lib/twinfield/create/error.rb +30 -0
- data/lib/twinfield/create/general_ledger.rb +39 -0
- data/lib/twinfield/create/transaction.rb +97 -0
- data/lib/twinfield/customer.rb +612 -0
- data/lib/twinfield/helpers/parsers.rb +23 -0
- data/lib/twinfield/helpers/transaction_match.rb +40 -0
- data/lib/twinfield/request/find.rb +149 -0
- data/lib/twinfield/request/list.rb +66 -0
- data/lib/twinfield/request/read.rb +111 -0
- data/lib/twinfield/sales_invoice.rb +409 -0
- data/lib/twinfield/transaction.rb +112 -0
- data/lib/twinfield/version.rb +5 -0
- data/lib/twinfield.rb +89 -0
- data/script/boot.rb +58 -0
- data/script/console +2 -0
- data/spec/fixtures/cluster/finder/ivt.xml +1 -0
- data/spec/fixtures/cluster/processxml/columns/sales_transactions.xml +312 -0
- data/spec/fixtures/cluster/processxml/customer/create_success.xml +100 -0
- data/spec/fixtures/cluster/processxml/customer/read_success.xml +93 -0
- data/spec/fixtures/cluster/processxml/customer/update_success.xml +1 -0
- data/spec/fixtures/cluster/processxml/invoice/create_error.xml +8 -0
- data/spec/fixtures/cluster/processxml/invoice/create_final_error.xml +67 -0
- data/spec/fixtures/cluster/processxml/invoice/create_success.xml +64 -0
- data/spec/fixtures/cluster/processxml/invoice/read_not_found.xml +1 -0
- data/spec/fixtures/cluster/processxml/invoice/read_success.xml +106 -0
- data/spec/fixtures/cluster/processxml/read/deb.xml +12 -0
- data/spec/fixtures/cluster/processxml/response.xml +8 -0
- data/spec/fixtures/login/session/wsdl.xml +210 -0
- data/spec/spec_helper.rb +17 -0
- data/spec/stubs/finder_stubs.rb +19 -0
- data/spec/stubs/processxml_stubs.rb +41 -0
- data/spec/stubs/session_stubs.rb +28 -0
- data/spec/twinfield/api/oauth_session_spec.rb +37 -0
- data/spec/twinfield/api/process_spec.rb +7 -0
- data/spec/twinfield/browse/transaction/cost_center_spec.rb +60 -0
- data/spec/twinfield/browse/transaction/general_ledger_spec.rb +60 -0
- data/spec/twinfield/browse/transaction/transaction_spec.rb +72 -0
- data/spec/twinfield/config_spec.rb +60 -0
- data/spec/twinfield/customer_spec.rb +326 -0
- data/spec/twinfield/request/find_spec.rb +24 -0
- data/spec/twinfield/request/list_spec.rb +58 -0
- data/spec/twinfield/request/read_spec.rb +26 -0
- data/spec/twinfield/sales_invoice_spec.rb +253 -0
- data/spec/twinfield/session_spec.rb +77 -0
- data/spec/twinfield/transaction_spec.rb +149 -0
- data/twinfieldrb.gemspec +24 -0
- data/wsdls/accounting/finder.wsdl +157 -0
- data/wsdls/accounting/process.wsdl +199 -0
- data/wsdls/accounting/session.wsdl +452 -0
- data/wsdls/accounting2/finder.wsdl +157 -0
- data/wsdls/accounting2/process.wsdl +199 -0
- data/wsdls/api.accounting/finder.wsdl +157 -0
- data/wsdls/api.accounting/process.wsdl +199 -0
- data/wsdls/session.wsdl +210 -0
- data/wsdls/update +10 -0
- 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
|
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