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