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