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,253 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Twinfield::SalesInvoice do
|
4
|
+
include SessionStubs
|
5
|
+
include ProcessxmlStubs
|
6
|
+
|
7
|
+
describe Twinfield::SalesInvoice::Line do
|
8
|
+
describe "class methods" do
|
9
|
+
describe ".new" do
|
10
|
+
it "should keep decials intact" do
|
11
|
+
expect(Twinfield::SalesInvoice::Line.new(quantity: 1.5, article: "A").quantity).to match(1.5)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
describe "#to_xml" do
|
17
|
+
it "renders xml" do
|
18
|
+
expect(Twinfield::SalesInvoice::Line.new(article: "A").to_xml(1)).to match("<line id=\"1\">")
|
19
|
+
expect(Twinfield::SalesInvoice::Line.new(article: "A", id: 2).to_xml).to match("<line id=\"2\">")
|
20
|
+
end
|
21
|
+
it "formats dates correctly" do
|
22
|
+
expect(Twinfield::SalesInvoice::Line.new(article: "A", performancedate: Date.new(2020, 12, 23)).to_xml(1)).to match("20201223")
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
describe "#invoice" do
|
27
|
+
it "returns the containers invoice" do
|
28
|
+
invoice = Twinfield::SalesInvoice.new(duedate: Time.now, customer: "1001", invoicetype: "VERKOOP")
|
29
|
+
invoice.lines = [Twinfield::SalesInvoice::Line.new(article: "A")]
|
30
|
+
expect(invoice.associated_lines.first.invoice).to eq(invoice)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
describe "class methods" do
|
36
|
+
before do
|
37
|
+
stub_create_session
|
38
|
+
stub_cluster_session_wsdl
|
39
|
+
stub_select_company
|
40
|
+
end
|
41
|
+
|
42
|
+
describe ".find" do
|
43
|
+
it "returns a sales invoice" do
|
44
|
+
stub_request(:post, "https://accounting.twinfield.com/webservices/processxml.asmx")
|
45
|
+
.with(body: /<read>\s*<type>salesinvoice<\/type>/)
|
46
|
+
.to_return(body: File.read(File.expand_path("../../fixtures/cluster/processxml/invoice/read_success.xml", __FILE__)))
|
47
|
+
invoice = Twinfield::SalesInvoice.find(13, invoicetype: "VERKOOP")
|
48
|
+
expect(invoice).to be_a(Twinfield::SalesInvoice)
|
49
|
+
expect(invoice.invoicenumber).to eq("13")
|
50
|
+
expect(invoice.financials.number).to eq("202100006")
|
51
|
+
expect(invoice.lines.first).to be_a(Twinfield::SalesInvoice::Line)
|
52
|
+
expect(invoice.lines[2].description).to eq("Custom article")
|
53
|
+
expect(invoice.vat_lines[0].vatname).to eq("BTW 21%")
|
54
|
+
end
|
55
|
+
|
56
|
+
it "returns nil when empty result" do
|
57
|
+
stub_request(:post, "https://accounting.twinfield.com/webservices/processxml.asmx")
|
58
|
+
.with(body: /<read>\s*<type>salesinvoice<\/type>/)
|
59
|
+
.to_return(body: File.read(File.expand_path("../../fixtures/cluster/processxml/invoice/read_not_found.xml", __FILE__)))
|
60
|
+
invoice = Twinfield::SalesInvoice.find(13, invoicetype: "VERKOOP")
|
61
|
+
expect(invoice).to eq(nil)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
describe ".new" do
|
66
|
+
it "initializes from a hash" do
|
67
|
+
hash = {lines: [{id: "1", article: "HUUR", subarticle: "1", quantity: 1, units: 1, allowdiscountorpremium: "true", description: "Huur", unitspriceexcl: 7.23, unitspriceinc: nil, valueinc: 8.75, freetext1: nil, freetext2: nil, freetext3: nil, dim1: "8000", vatcode: "VH", performancetype: nil, performancedate: nil}, {id: "2", article: "-", subarticle: nil, quantity: nil, units: nil, allowdiscountorpremium: nil, description: "Beschrijving", unitspriceexcl: nil, unitspriceinc: nil, freetext1: nil, freetext2: nil, freetext3: nil, dim1: nil, vatcode: nil, performancetype: nil, performancedate: nil}], vat_lines: [{vatcode: "VH", vatvalue: 1.73, performancetype: "", performancedate: "", vatname: "BTW 21%"}], invoicetype: "FACTUUR", invoicedate: "2022-06-24", duedate: "2022-07-24", performancedate: nil, bank: "BNK", invoiceaddressnumber: 1, deliveraddressnumber: 1, customer_code: 2321, period: "2022/6", currency: "EUR", status: "final", paymentmethod: "bank", headertext: "", footertext: "Footer", office: "NL123", invoicenumber: "1622"}
|
68
|
+
invoice = Twinfield::SalesInvoice.new(**hash)
|
69
|
+
expect(invoice.invoicedate).to eq(Date.new(2022, 6, 24))
|
70
|
+
expect(invoice.invoicedate).not_to eq("2022-06-24")
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
describe "instance methods" do
|
76
|
+
describe "#final?" do
|
77
|
+
it "returns false by default" do
|
78
|
+
invoice = Twinfield::SalesInvoice.new(invoicetype: "F", customer_code: 12)
|
79
|
+
expect(invoice.final?).to be_falsey
|
80
|
+
end
|
81
|
+
|
82
|
+
it "returns true when status == final" do
|
83
|
+
invoice = Twinfield::SalesInvoice.new(invoicetype: "F", customer_code: 12, status: :final)
|
84
|
+
expect(invoice.final?).to be_truthy
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
describe "#transaction" do
|
89
|
+
it "retuns no transaction when no financials info" do
|
90
|
+
stub_create_session
|
91
|
+
stub_cluster_session_wsdl
|
92
|
+
stub_select_company
|
93
|
+
|
94
|
+
invoice = Twinfield::SalesInvoice.new(duedate: Time.now, customer: "1001", invoicetype: "VERKOOP", invoicenumber: "2021-0812")
|
95
|
+
invoice.transaction
|
96
|
+
expect(invoice.transaction).to be_nil
|
97
|
+
end
|
98
|
+
|
99
|
+
it "returns a transaction" do
|
100
|
+
invoice = Twinfield::SalesInvoice.new(duedate: Time.now, customer: "1001", invoicetype: "VERKOOP", invoicenumber: "2021-0812")
|
101
|
+
invoice.financials = Twinfield::SalesInvoice::Financials.new(code: "VRK", number: "20210812")
|
102
|
+
|
103
|
+
expect(Twinfield::Browse::Transaction::Customer).to receive(:find).with(code: "VRK", number: "20210812")
|
104
|
+
invoice.transaction
|
105
|
+
end
|
106
|
+
|
107
|
+
it "returns a transaction" do
|
108
|
+
stub_create_session
|
109
|
+
stub_cluster_session_wsdl
|
110
|
+
stub_select_company
|
111
|
+
|
112
|
+
stub_request(:post, "https://accounting.twinfield.com/webservices/processxml.asmx")
|
113
|
+
.with(body: /20210812/)
|
114
|
+
.to_return(body: File.read(File.expand_path("../../fixtures/cluster/processxml/columns/sales_transactions.xml", __FILE__)))
|
115
|
+
|
116
|
+
invoice = Twinfield::SalesInvoice.new(duedate: Time.now, customer: "1001", invoicetype: "VERKOOP", invoicenumber: "2021-0812")
|
117
|
+
invoice.financials = Twinfield::SalesInvoice::Financials.new(code: "VRK", number: "20210812")
|
118
|
+
|
119
|
+
invoice.transaction
|
120
|
+
expect(invoice.transaction).to be_a(Twinfield::Browse::Transaction::Customer)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
describe "#to_xml" do
|
124
|
+
it "renders xml" do
|
125
|
+
invoice = Twinfield::SalesInvoice.new(duedate: Time.now, customer: "1001", invoicetype: "VERKOOP")
|
126
|
+
invoice.lines = [Twinfield::SalesInvoice::Line.new(article: "A")]
|
127
|
+
expect(invoice.to_xml).to match("<lines>\n <line id=\"1\">")
|
128
|
+
expect(invoice.to_xml).not_to match("<invoicenumber")
|
129
|
+
end
|
130
|
+
|
131
|
+
it "renders xml with invoicenumber when given (updating)" do
|
132
|
+
invoice = Twinfield::SalesInvoice.new(duedate: Time.now, customer: "1001", invoicetype: "VERKOOP", invoicenumber: "12")
|
133
|
+
invoice.lines = [Twinfield::SalesInvoice::Line.new(article: "A")]
|
134
|
+
expect(invoice.to_xml).to match("<invoicenumber>12</invoicenumber>")
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
describe "#to_h" do
|
139
|
+
let(:invoice) {
|
140
|
+
invoice = Twinfield::SalesInvoice.new(duedate: Time.now, customer: "1001", invoicetype: "VERKOOP")
|
141
|
+
invoice.lines = [Twinfield::SalesInvoice::Line.new(article: "A")]
|
142
|
+
invoice
|
143
|
+
}
|
144
|
+
it "renders a hash" do
|
145
|
+
expect(invoice.to_h).to be_a Hash
|
146
|
+
expect(invoice.to_h[:customer_code]).to eq(1001)
|
147
|
+
expect(invoice.to_h[:lines][0][:article]).to eq("A")
|
148
|
+
end
|
149
|
+
|
150
|
+
it "enables a roundtrip" do
|
151
|
+
invoice2 = Twinfield::SalesInvoice.new(**invoice.to_h)
|
152
|
+
expect(invoice2.duedate).to eq(invoice.duedate)
|
153
|
+
expect(invoice2.lines.first.article).to eq("A")
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
describe ":financials" do
|
158
|
+
before do
|
159
|
+
stub_create_session
|
160
|
+
stub_cluster_session_wsdl
|
161
|
+
stub_select_company
|
162
|
+
end
|
163
|
+
|
164
|
+
it "survives a roundtrip" do
|
165
|
+
stub_request(:post, "https://accounting.twinfield.com/webservices/processxml.asmx")
|
166
|
+
.with(body: /<read>\s*<type>salesinvoice<\/type>/)
|
167
|
+
.to_return(body: File.read(File.expand_path("../../fixtures/cluster/processxml/invoice/read_success.xml", __FILE__)))
|
168
|
+
invoice = Twinfield::SalesInvoice.find(13, invoicetype: "VERKOOP")
|
169
|
+
expect(invoice.financials).to be_a Twinfield::SalesInvoice::Financials
|
170
|
+
financials_number = "202100006"
|
171
|
+
|
172
|
+
expect(invoice.financials.number).to eq financials_number
|
173
|
+
expect(invoice.to_h[:financials][:number]).to eq financials_number
|
174
|
+
|
175
|
+
Twinfield::SalesInvoice.new(**invoice.to_h)
|
176
|
+
expect(invoice.financials.number).to eq financials_number
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
describe "#save" do
|
181
|
+
before do
|
182
|
+
stub_create_session
|
183
|
+
stub_cluster_session_wsdl
|
184
|
+
stub_select_company
|
185
|
+
end
|
186
|
+
|
187
|
+
it "reports errors" do
|
188
|
+
stub_request(:post, "https://accounting.twinfield.com/webservices/processxml.asmx")
|
189
|
+
.with(body: /<customer>1001<\/customer>/)
|
190
|
+
.to_return(body: File.read(File.expand_path("../../fixtures/cluster/processxml/invoice/create_error.xml", __FILE__)))
|
191
|
+
invoice = Twinfield::SalesInvoice.new(duedate: Time.now, customer: "1001", invoicetype: "VERKOOP")
|
192
|
+
expect { invoice.save }.to raise_error(Twinfield::Create::Error)
|
193
|
+
invoice = Twinfield::SalesInvoice.new(duedate: Time.now, customer: "1001", invoicetype: "VERKOOP")
|
194
|
+
expect { invoice.save }.to raise_error(Twinfield::Create::EmptyInvoice)
|
195
|
+
end
|
196
|
+
|
197
|
+
it "reports errors when fixed because finalized" do
|
198
|
+
stub_request(:post, "https://accounting.twinfield.com/webservices/processxml.asmx")
|
199
|
+
.with(body: /<customer>1001<\/customer>/)
|
200
|
+
.to_return(body: File.read(File.expand_path("../../fixtures/cluster/processxml/invoice/create_final_error.xml", __FILE__)))
|
201
|
+
invoice = Twinfield::SalesInvoice.new(duedate: Time.now, customer: "1001", invoicetype: "VERKOOP")
|
202
|
+
expect { invoice.save }.to raise_error(Twinfield::Create::Error)
|
203
|
+
invoice = Twinfield::SalesInvoice.new(duedate: Time.now, customer: "1001", invoicetype: "VERKOOP")
|
204
|
+
expect { invoice.save }.to raise_error(Twinfield::Create::Finalized)
|
205
|
+
end
|
206
|
+
|
207
|
+
it "succeeds" do
|
208
|
+
stub_request(:post, "https://accounting.twinfield.com/webservices/processxml.asmx")
|
209
|
+
.with(body: /<customer>1001<\/customer>/)
|
210
|
+
.to_return(body: File.read(File.expand_path("../../fixtures/cluster/processxml/invoice/create_success.xml", __FILE__)))
|
211
|
+
invoice = Twinfield::SalesInvoice.new(duedate: Time.now, customer: "1001", invoicetype: "VERKOOP")
|
212
|
+
saved_invoice = invoice.save
|
213
|
+
expect(saved_invoice.invoicenumber).to eq("3")
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
describe "totals" do
|
218
|
+
before do
|
219
|
+
stub_create_session
|
220
|
+
stub_cluster_session_wsdl
|
221
|
+
stub_select_company
|
222
|
+
stub_request(:post, "https://accounting.twinfield.com/webservices/processxml.asmx")
|
223
|
+
.with(body: /<read>\s*<type>salesinvoice<\/type>/)
|
224
|
+
.to_return(body: File.read(File.expand_path("../../fixtures/cluster/processxml/invoice/read_success.xml", __FILE__)))
|
225
|
+
@invoice = Twinfield::SalesInvoice.find(13, invoicetype: "VERKOOP")
|
226
|
+
end
|
227
|
+
|
228
|
+
describe "#total" do
|
229
|
+
it "0 on an empty invoice" do
|
230
|
+
expect(Twinfield::SalesInvoice.new(invoicetype: "VERKOOP", customer: "1001").total).to eq(0)
|
231
|
+
end
|
232
|
+
|
233
|
+
it "sums lines perfectly" do
|
234
|
+
expect(@invoice.total).to eq(221.0)
|
235
|
+
end
|
236
|
+
|
237
|
+
it "defaults to total incl vat" do
|
238
|
+
expect(@invoice.totalinc).to eq(@invoice.total)
|
239
|
+
end
|
240
|
+
end
|
241
|
+
|
242
|
+
describe "#totalexcl" do
|
243
|
+
it "0 on an empty invoice" do
|
244
|
+
expect(Twinfield::SalesInvoice.new(invoicetype: "VERKOOP", customer: "1001").total).to eq(0)
|
245
|
+
end
|
246
|
+
|
247
|
+
it "sums lines perfectly" do
|
248
|
+
expect(@invoice.totalexcl).to eq(200.0)
|
249
|
+
end
|
250
|
+
end
|
251
|
+
end
|
252
|
+
end
|
253
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Twinfield::Api::Session do
|
4
|
+
include SessionStubs
|
5
|
+
|
6
|
+
after do
|
7
|
+
reset_config
|
8
|
+
end
|
9
|
+
|
10
|
+
describe "successful logon" do
|
11
|
+
before(:all) do
|
12
|
+
stub_create_session
|
13
|
+
stub_cluster_session_wsdl
|
14
|
+
stub_select_company
|
15
|
+
@session = Twinfield::Api::Session.new
|
16
|
+
@session.logon
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should return successful message" do
|
20
|
+
expect(@session.status).to eq "Ok"
|
21
|
+
end
|
22
|
+
|
23
|
+
it "should return that the current session already is connected" do
|
24
|
+
expect(@session.logon).to eq "already connected"
|
25
|
+
end
|
26
|
+
|
27
|
+
it "should return that the current session already is connected" do
|
28
|
+
stub_create_session
|
29
|
+
stub_cluster_session_wsdl
|
30
|
+
stub_select_company
|
31
|
+
expect(@session.relog).to eq "Ok"
|
32
|
+
end
|
33
|
+
|
34
|
+
it "should have a session_id after successful logon" do
|
35
|
+
expect(@session.session_id).not_to eq nil
|
36
|
+
end
|
37
|
+
|
38
|
+
it "should have a cluster after successful logon" do
|
39
|
+
expect(@session.cluster).not_to eq nil
|
40
|
+
end
|
41
|
+
|
42
|
+
it "should return true for connected" do
|
43
|
+
expect(@session.connected?).to eq true
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
describe "invalid logon" do
|
48
|
+
before(:all) do
|
49
|
+
username = "not_valid_username"
|
50
|
+
|
51
|
+
stub_create_session username: username, response: "Invalid"
|
52
|
+
|
53
|
+
Twinfield.configure do |config|
|
54
|
+
config.username = username
|
55
|
+
end
|
56
|
+
|
57
|
+
@session = Twinfield::Api::Session.new
|
58
|
+
@session.logon
|
59
|
+
end
|
60
|
+
|
61
|
+
it "should return invalid message" do
|
62
|
+
expect(@session.status).to eq "Invalid"
|
63
|
+
end
|
64
|
+
|
65
|
+
it "should not have a session_id" do
|
66
|
+
expect(@session.session_id).to eq nil
|
67
|
+
end
|
68
|
+
|
69
|
+
it "should not have a cluster" do
|
70
|
+
expect(@session.cluster).to eq nil
|
71
|
+
end
|
72
|
+
|
73
|
+
it "should return false for connected" do
|
74
|
+
expect(@session.connected?).to eq false
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,149 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Twinfield::Transaction do
|
4
|
+
include SessionStubs
|
5
|
+
include ProcessxmlStubs
|
6
|
+
|
7
|
+
let(:today_string) do
|
8
|
+
Date.today.strftime("%Y%m%d")
|
9
|
+
end
|
10
|
+
let(:payment_transaction) do
|
11
|
+
trans = Twinfield::Transaction.new(code: "PIN", currency: "EUR", date: Date.new(2021, 1, 1))
|
12
|
+
trans.lines << Twinfield::Transaction::Line.new(type: :detail, balance_code: 1300, value: 60.5, debitcredit: :credit, customer_code: 1003, invoicenumber: 14)
|
13
|
+
trans.lines << Twinfield::Transaction::Line.new(type: :detail, balance_code: 1234, value: 60.5, debitcredit: :debit)
|
14
|
+
trans.lines << Twinfield::Transaction::Line.new(type: :total, balance_code: "1230", value: 0.0, debitcredit: :debit)
|
15
|
+
trans
|
16
|
+
end
|
17
|
+
|
18
|
+
describe Twinfield::Transaction::Line do
|
19
|
+
describe "#to_xml" do
|
20
|
+
it "converts a detail line to xml" do
|
21
|
+
expect(Twinfield::Transaction::Line.new(type: :detail, balance_code: 1055, value: 60.5, debitcredit: :debit).to_xml).to eq("<line type=\"detail\">\n <dim1>1055</dim1>\n <value>60.5</value>\n <debitcredit>debit</debitcredit>\n <currencydate>#{today_string}</currencydate>\n</line>")
|
22
|
+
expect(Twinfield::Transaction::Line.new(type: :detail, balance_code: 8020, value: 50, debitcredit: :credit, vatcode: "VH").to_xml).to eq("<line type=\"detail\">\n <dim1>8020</dim1>\n <value>50.0</value>\n <debitcredit>credit</debitcredit>\n <vatcode>VH</vatcode>\n <currencydate>#{today_string}</currencydate>\n</line>")
|
23
|
+
end
|
24
|
+
it "converts a total line to xml" do
|
25
|
+
expect(Twinfield::Transaction::Line.new(type: :total, balance_code: "0000", value: 0.0, debitcredit: :debit).to_xml).to eq("<line type=\"total\">\n <dim1>0000</dim1>\n <value>0.0</value>\n <debitcredit>debit</debitcredit>\n <currencydate>#{today_string}</currencydate>\n</line>")
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
describe "#value" do
|
31
|
+
it "returns the negative value" do
|
32
|
+
expect(payment_transaction.value).to eq(-60.5)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
describe "#to_xml" do
|
37
|
+
let(:expected_xml) {
|
38
|
+
"<transaction destiny=\"final\">
|
39
|
+
<header>
|
40
|
+
<office>company</office>
|
41
|
+
<code>PIN</code>
|
42
|
+
<currency>EUR</currency>
|
43
|
+
<date>20210101</date>
|
44
|
+
<period>2021/01</period>
|
45
|
+
</header>
|
46
|
+
<lines>
|
47
|
+
<line type=\"total\">
|
48
|
+
<dim1>1230</dim1>
|
49
|
+
<value>0.0</value>
|
50
|
+
<debitcredit>debit</debitcredit>
|
51
|
+
<currencydate>#{today_string}</currencydate>
|
52
|
+
</line>
|
53
|
+
<line type=\"detail\">
|
54
|
+
<dim1>1300</dim1>
|
55
|
+
<dim2>1003</dim2>
|
56
|
+
<value>60.5</value>
|
57
|
+
<debitcredit>credit</debitcredit>
|
58
|
+
<invoicenumber>14</invoicenumber>
|
59
|
+
<currencydate>#{today_string}</currencydate>
|
60
|
+
</line>
|
61
|
+
<line type=\"detail\">
|
62
|
+
<dim1>1234</dim1>
|
63
|
+
<value>60.5</value>
|
64
|
+
<debitcredit>debit</debitcredit>
|
65
|
+
<currencydate>#{today_string}</currencydate>
|
66
|
+
</line>
|
67
|
+
</lines>
|
68
|
+
</transaction>"
|
69
|
+
}
|
70
|
+
|
71
|
+
it "converts a PIN transaction to xml" do
|
72
|
+
expect(payment_transaction.to_xml).to eq(expected_xml)
|
73
|
+
end
|
74
|
+
|
75
|
+
context "with hash" do
|
76
|
+
let(:payment_transaction) do
|
77
|
+
trans_h = {
|
78
|
+
code: "PIN", currency: "EUR", date: Date.new(2021, 1, 1), lines: [
|
79
|
+
{type: :total, balance_code: "1230", value: 0.0, debitcredit: :debit},
|
80
|
+
{type: :detail, balance_code: 1300, value: 60.5, debitcredit: :credit, customer_code: 1003, invoicenumber: 14},
|
81
|
+
{type: :detail, balance_code: 1234, value: 60.5, debitcredit: :debit}
|
82
|
+
]
|
83
|
+
}
|
84
|
+
Twinfield::Transaction.new(**trans_h)
|
85
|
+
end
|
86
|
+
|
87
|
+
it "converts a PIN transaction to xml" do
|
88
|
+
expect(payment_transaction.to_xml).to eq(expected_xml)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
describe "#to_transaction_match_line_xml" do
|
94
|
+
it "returns a valid piece of xml" do
|
95
|
+
xml = Nokogiri::XML(Twinfield::Transaction.new(code: "PIN", number: "20210121").to_transaction_match_line_xml(2))
|
96
|
+
xml.css("line transcode").text
|
97
|
+
xml.css("line transnumber").text
|
98
|
+
xml.css("line transline").text == "1"
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
describe "#to_match_xml" do
|
103
|
+
it "doesn't work if values don't match" do
|
104
|
+
expect { payment_transaction.to_match_xml(Twinfield::Browse::Transaction::Customer.new(value: 20, code: "VRK")) }.to raise_error(Twinfield::TransactionMatchError)
|
105
|
+
end
|
106
|
+
|
107
|
+
it "returns xml if values match" do
|
108
|
+
matchdatestring = Date.today.strftime("%Y%m%d")
|
109
|
+
expect(payment_transaction.to_match_xml(Twinfield::Browse::Transaction::Customer.new(value: 60.5, code: "VRK"))).to eq("<match>
|
110
|
+
<set>
|
111
|
+
<matchcode>170</matchcode>
|
112
|
+
<office>company</office>
|
113
|
+
<matchdate>#{matchdatestring}</matchdate>
|
114
|
+
<lines>
|
115
|
+
<line><transcode>PIN</transcode><transnumber></transnumber><transline>1</transline></line>
|
116
|
+
<line><transcode>VRK</transcode><transnumber></transnumber><transline>2</transline></line>
|
117
|
+
</lines>
|
118
|
+
</set>
|
119
|
+
</match>")
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
describe "#match!" do
|
124
|
+
let(:error_body) { '<?xml version="1.0" encoding="utf-8"?><soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><soap:Body><ProcessXmlStringResponse xmlns="http://www.twinfield.com/"><ProcessXmlStringResult><match result="0"><set result="0"><matchcode name="Debiteuren afletteren">170</matchcode><office>NLA002058</office><matchdate>20220203</matchdate><lines result="0"><line msgtype="error" msg="Boeking PIN 202200003 regel 1 is niet beschikbaar voor afletteren." result="0"><transcode>PIN</transcode><transnumber>202200003</transnumber><transline>1</transline><origin>import</origin><status>final</status></line><line msgtype="error" msg="Boeking VRK 202100008 regel 2 is niet beschikbaar voor afletteren." result="0"><transcode>VRK</transcode><transnumber>202100008</transnumber><transline>2</transline><origin>invoice</origin><status>final</status></line></lines></set></match></ProcessXmlStringResult></ProcessXmlStringResponse></soap:Body></soap:Envelope>' }
|
125
|
+
let(:success_body) { '<?xml version="1.0" encoding="utf-8"?><soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><soap:Body><ProcessXmlStringResponse xmlns="http://www.twinfield.com/"><ProcessXmlStringResult><match result="1"><set result="1"><matchcode name="Debiteuren afletteren">170</matchcode><office>NLA002058</office><matchdate>20220203</matchdate><lines><line result="1"><transcode name="Verkoopfactuur" shortname="Verkoop">VRK</transcode><transnumber>202100008</transnumber><transline>1</transline><origin>invoice</origin><status>final</status><dim1>1300</dim1><dim2>1003</dim2><matchlevel>2</matchlevel><basevalueopen>221.00</basevalueopen><repvalueopen>221.00</repvalueopen><valueopen>221.00</valueopen><matchvalue>221.00</matchvalue><matchvaluerep>221.00</matchvaluerep><matchvaluecur>221.00</matchvaluecur></line><line result="1"><transcode name="Pintransacties" shortname="Pin">PIN</transcode><transnumber>202200004</transnumber><transline>2</transline><origin>import</origin><status>final</status><dim1>1300</dim1><dim2>1003</dim2><matchlevel>2</matchlevel><basevalueopen>-221.00</basevalueopen><repvalueopen>-221.00</repvalueopen><valueopen>-221.00</valueopen><matchvalue>-221.00</matchvalue><matchvaluerep>-221.00</matchvaluerep><matchvaluecur>-221.00</matchvaluecur></line></lines><dimensions><dimension level="2" matchnumber="2">1003</dimension></dimensions><value>0.00</value></set></match></ProcessXmlStringResult></ProcessXmlStringResponse></soap:Body></soap:Envelope>' }
|
126
|
+
|
127
|
+
before do
|
128
|
+
stub_create_session
|
129
|
+
stub_cluster_session_wsdl
|
130
|
+
stub_select_company
|
131
|
+
end
|
132
|
+
|
133
|
+
it "raises an error when they don't match" do
|
134
|
+
stub_request(:post, "https://accounting.twinfield.com/webservices/processxml.asmx")
|
135
|
+
.to_return(body: error_body)
|
136
|
+
|
137
|
+
expect { Twinfield::Browse::Transaction::Customer.new(value: 60.5, code: "VRK").match!(payment_transaction) }.to raise_error(Twinfield::TransactionMatchError)
|
138
|
+
end
|
139
|
+
|
140
|
+
it "returns the match when they match" do
|
141
|
+
stub_request(:post, "https://accounting.twinfield.com/webservices/processxml.asmx")
|
142
|
+
.to_return(body: success_body)
|
143
|
+
|
144
|
+
Twinfield::Browse::Transaction::Customer.new(value: 60.5, code: "VRK").match!(payment_transaction)
|
145
|
+
|
146
|
+
# nothing is really stored in the match it seems; but side effect is that the transactions change from available to matched
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
data/twinfieldrb.gemspec
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
$:.push File.expand_path("../lib", __FILE__)
|
2
|
+
require "twinfield/version"
|
3
|
+
|
4
|
+
Gem::Specification.new do |s|
|
5
|
+
s.name = "twinfieldrb"
|
6
|
+
s.version = Twinfield::Version::VERSION
|
7
|
+
s.authors = ["Ernst Rijsdijk", "Stephan van Diepen", "Joris Reijrink", "Maarten Brouwers"]
|
8
|
+
s.email = ["ernst.rijsdijk@holder.nl", "s.vandiepen@noxa.nl", "joris@sprintict.nl", "maarten@murb.nl"]
|
9
|
+
s.homepage = "https://github.com/murb/twinfield"
|
10
|
+
s.summary = "A simple client for the Twinfield SOAP-based API (continuation of the twinfield gem)"
|
11
|
+
s.description = "Twinfield is an international Web service for collaborative online accounting. The Twinfield gem is a simple client for their SOAP-based API."
|
12
|
+
|
13
|
+
# s.rubyforge_project = "twinfield"
|
14
|
+
|
15
|
+
s.files = `git ls-files`.split("\n")
|
16
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) }
|
17
|
+
s.require_paths = ["lib"]
|
18
|
+
|
19
|
+
s.add_runtime_dependency "savon", "~> 2.10"
|
20
|
+
s.add_runtime_dependency "nokogiri", "~> 1.6"
|
21
|
+
s.add_development_dependency "rspec"
|
22
|
+
s.add_development_dependency "standard"
|
23
|
+
s.add_development_dependency "webmock"
|
24
|
+
end
|