twinfieldrb 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (78) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/codeql-analysis.yml +70 -0
  3. data/.github/workflows/rspec.yml +33 -0
  4. data/.gitignore +6 -0
  5. data/.rspec +1 -0
  6. data/CHANGELOG.md +15 -0
  7. data/Gemfile +4 -0
  8. data/README.md +120 -0
  9. data/Rakefile +1 -0
  10. data/lib/twinfield/abstract_model.rb +7 -0
  11. data/lib/twinfield/api/base_api.rb +50 -0
  12. data/lib/twinfield/api/finder.rb +45 -0
  13. data/lib/twinfield/api/o_auth_session.rb +58 -0
  14. data/lib/twinfield/api/process.rb +44 -0
  15. data/lib/twinfield/api/session.rb +170 -0
  16. data/lib/twinfield/browse/transaction/cost_center.rb +145 -0
  17. data/lib/twinfield/browse/transaction/customer.rb +413 -0
  18. data/lib/twinfield/browse/transaction/general_ledger.rb +144 -0
  19. data/lib/twinfield/configuration.rb +49 -0
  20. data/lib/twinfield/create/cost_center.rb +39 -0
  21. data/lib/twinfield/create/creditor.rb +88 -0
  22. data/lib/twinfield/create/debtor.rb +88 -0
  23. data/lib/twinfield/create/error.rb +30 -0
  24. data/lib/twinfield/create/general_ledger.rb +39 -0
  25. data/lib/twinfield/create/transaction.rb +97 -0
  26. data/lib/twinfield/customer.rb +612 -0
  27. data/lib/twinfield/helpers/parsers.rb +23 -0
  28. data/lib/twinfield/helpers/transaction_match.rb +40 -0
  29. data/lib/twinfield/request/find.rb +149 -0
  30. data/lib/twinfield/request/list.rb +66 -0
  31. data/lib/twinfield/request/read.rb +111 -0
  32. data/lib/twinfield/sales_invoice.rb +409 -0
  33. data/lib/twinfield/transaction.rb +112 -0
  34. data/lib/twinfield/version.rb +5 -0
  35. data/lib/twinfield.rb +89 -0
  36. data/script/boot.rb +58 -0
  37. data/script/console +2 -0
  38. data/spec/fixtures/cluster/finder/ivt.xml +1 -0
  39. data/spec/fixtures/cluster/processxml/columns/sales_transactions.xml +312 -0
  40. data/spec/fixtures/cluster/processxml/customer/create_success.xml +100 -0
  41. data/spec/fixtures/cluster/processxml/customer/read_success.xml +93 -0
  42. data/spec/fixtures/cluster/processxml/customer/update_success.xml +1 -0
  43. data/spec/fixtures/cluster/processxml/invoice/create_error.xml +8 -0
  44. data/spec/fixtures/cluster/processxml/invoice/create_final_error.xml +67 -0
  45. data/spec/fixtures/cluster/processxml/invoice/create_success.xml +64 -0
  46. data/spec/fixtures/cluster/processxml/invoice/read_not_found.xml +1 -0
  47. data/spec/fixtures/cluster/processxml/invoice/read_success.xml +106 -0
  48. data/spec/fixtures/cluster/processxml/read/deb.xml +12 -0
  49. data/spec/fixtures/cluster/processxml/response.xml +8 -0
  50. data/spec/fixtures/login/session/wsdl.xml +210 -0
  51. data/spec/spec_helper.rb +17 -0
  52. data/spec/stubs/finder_stubs.rb +19 -0
  53. data/spec/stubs/processxml_stubs.rb +41 -0
  54. data/spec/stubs/session_stubs.rb +28 -0
  55. data/spec/twinfield/api/oauth_session_spec.rb +37 -0
  56. data/spec/twinfield/api/process_spec.rb +7 -0
  57. data/spec/twinfield/browse/transaction/cost_center_spec.rb +60 -0
  58. data/spec/twinfield/browse/transaction/general_ledger_spec.rb +60 -0
  59. data/spec/twinfield/browse/transaction/transaction_spec.rb +72 -0
  60. data/spec/twinfield/config_spec.rb +60 -0
  61. data/spec/twinfield/customer_spec.rb +326 -0
  62. data/spec/twinfield/request/find_spec.rb +24 -0
  63. data/spec/twinfield/request/list_spec.rb +58 -0
  64. data/spec/twinfield/request/read_spec.rb +26 -0
  65. data/spec/twinfield/sales_invoice_spec.rb +253 -0
  66. data/spec/twinfield/session_spec.rb +77 -0
  67. data/spec/twinfield/transaction_spec.rb +149 -0
  68. data/twinfieldrb.gemspec +24 -0
  69. data/wsdls/accounting/finder.wsdl +157 -0
  70. data/wsdls/accounting/process.wsdl +199 -0
  71. data/wsdls/accounting/session.wsdl +452 -0
  72. data/wsdls/accounting2/finder.wsdl +157 -0
  73. data/wsdls/accounting2/process.wsdl +199 -0
  74. data/wsdls/api.accounting/finder.wsdl +157 -0
  75. data/wsdls/api.accounting/process.wsdl +199 -0
  76. data/wsdls/session.wsdl +210 -0
  77. data/wsdls/update +10 -0
  78. 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>&lt;match result="0"&gt;&lt;set result="0"&gt;&lt;matchcode name="Debiteuren afletteren"&gt;170&lt;/matchcode&gt;&lt;office&gt;NLA002058&lt;/office&gt;&lt;matchdate&gt;20220203&lt;/matchdate&gt;&lt;lines result="0"&gt;&lt;line msgtype="error" msg="Boeking PIN 202200003 regel 1 is niet beschikbaar voor afletteren." result="0"&gt;&lt;transcode&gt;PIN&lt;/transcode&gt;&lt;transnumber&gt;202200003&lt;/transnumber&gt;&lt;transline&gt;1&lt;/transline&gt;&lt;origin&gt;import&lt;/origin&gt;&lt;status&gt;final&lt;/status&gt;&lt;/line&gt;&lt;line msgtype="error" msg="Boeking VRK 202100008 regel 2 is niet beschikbaar voor afletteren." result="0"&gt;&lt;transcode&gt;VRK&lt;/transcode&gt;&lt;transnumber&gt;202100008&lt;/transnumber&gt;&lt;transline&gt;2&lt;/transline&gt;&lt;origin&gt;invoice&lt;/origin&gt;&lt;status&gt;final&lt;/status&gt;&lt;/line&gt;&lt;/lines&gt;&lt;/set&gt;&lt;/match&gt;</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>&lt;match result="1"&gt;&lt;set result="1"&gt;&lt;matchcode name="Debiteuren afletteren"&gt;170&lt;/matchcode&gt;&lt;office&gt;NLA002058&lt;/office&gt;&lt;matchdate&gt;20220203&lt;/matchdate&gt;&lt;lines&gt;&lt;line result="1"&gt;&lt;transcode name="Verkoopfactuur" shortname="Verkoop"&gt;VRK&lt;/transcode&gt;&lt;transnumber&gt;202100008&lt;/transnumber&gt;&lt;transline&gt;1&lt;/transline&gt;&lt;origin&gt;invoice&lt;/origin&gt;&lt;status&gt;final&lt;/status&gt;&lt;dim1&gt;1300&lt;/dim1&gt;&lt;dim2&gt;1003&lt;/dim2&gt;&lt;matchlevel&gt;2&lt;/matchlevel&gt;&lt;basevalueopen&gt;221.00&lt;/basevalueopen&gt;&lt;repvalueopen&gt;221.00&lt;/repvalueopen&gt;&lt;valueopen&gt;221.00&lt;/valueopen&gt;&lt;matchvalue&gt;221.00&lt;/matchvalue&gt;&lt;matchvaluerep&gt;221.00&lt;/matchvaluerep&gt;&lt;matchvaluecur&gt;221.00&lt;/matchvaluecur&gt;&lt;/line&gt;&lt;line result="1"&gt;&lt;transcode name="Pintransacties" shortname="Pin"&gt;PIN&lt;/transcode&gt;&lt;transnumber&gt;202200004&lt;/transnumber&gt;&lt;transline&gt;2&lt;/transline&gt;&lt;origin&gt;import&lt;/origin&gt;&lt;status&gt;final&lt;/status&gt;&lt;dim1&gt;1300&lt;/dim1&gt;&lt;dim2&gt;1003&lt;/dim2&gt;&lt;matchlevel&gt;2&lt;/matchlevel&gt;&lt;basevalueopen&gt;-221.00&lt;/basevalueopen&gt;&lt;repvalueopen&gt;-221.00&lt;/repvalueopen&gt;&lt;valueopen&gt;-221.00&lt;/valueopen&gt;&lt;matchvalue&gt;-221.00&lt;/matchvalue&gt;&lt;matchvaluerep&gt;-221.00&lt;/matchvaluerep&gt;&lt;matchvaluecur&gt;-221.00&lt;/matchvaluecur&gt;&lt;/line&gt;&lt;/lines&gt;&lt;dimensions&gt;&lt;dimension level="2" matchnumber="2"&gt;1003&lt;/dimension&gt;&lt;/dimensions&gt;&lt;value&gt;0.00&lt;/value&gt;&lt;/set&gt;&lt;/match&gt;</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
@@ -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