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