crunchaccounting-api 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (3) hide show
  1. checksums.yaml +7 -0
  2. data/lib/crunchaccounting-api.rb +483 -0
  3. metadata +58 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 49a5d2789710062012d399193d9bceccbeb37585
4
+ data.tar.gz: ef876975580ed903538622672712f8ae9235fe43
5
+ SHA512:
6
+ metadata.gz: df041d42161e0bb8d2454b6b94f4acefe8e0ecdaa9b3be8d76c4fbbb2ca668c56c47bd312013c2afd79fee898eecfbca923a050a1db843daa9a9d10d1879e917
7
+ data.tar.gz: d736fada0e4933383349cd0110e63edd8d01d057fc5a807d728d0f80c71736a1917bc4bfb57d86fc646e50278c787f88c065ef499b8c2c28bfc1a9041b31f186
@@ -0,0 +1,483 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'oauth'
4
+ require 'json'
5
+ require 'base64'
6
+
7
+ VAT_RATE_DEFAULT = 20
8
+
9
+ class CrunchAPI
10
+ attr_reader :oauth_token
11
+ attr_reader :oauth_token_secret
12
+
13
+ def initialize(params={})
14
+ @vat_rate = VAT_RATE_DEFAULT
15
+
16
+ params.each do |key, value|
17
+ instance_variable_set("@" + key.to_s, value)
18
+ end
19
+
20
+ if @oauth_token and @oauth_token_secret
21
+ initialise_consumer
22
+ end
23
+ end
24
+
25
+ def initialise_consumer
26
+ @consumer = get_consumer(@api_endpoint)
27
+
28
+ @access_token = OAuth::AccessToken.from_hash(
29
+ @consumer, {
30
+ oauth_token: @oauth_token,
31
+ oauth_token_secret: @oauth_token_secret
32
+ }
33
+ )
34
+ end
35
+
36
+ def authenticated?
37
+ @access_token ? true : false
38
+ end
39
+
40
+ def get_consumer(endpoint)
41
+ consumer = OAuth::Consumer.new(
42
+ @consumer_key,
43
+ @consumer_secret,
44
+ :scheme => :header,
45
+ :site => endpoint,
46
+ :request_token_path => "/crunch-core/oauth/request_token",
47
+ :authorize_path => "/crunch-core/login/oauth-login.seam",
48
+ :access_token_path => "/crunch-core/oauth/access_token"
49
+ )
50
+
51
+ @debug and consumer.http.set_debug_output($stdout)
52
+
53
+ consumer
54
+ end
55
+
56
+ def get_auth_url
57
+ @consumer = get_consumer(@auth_endpoint)
58
+
59
+ request_token = @consumer.get_request_token(:oauth_callback => "")
60
+
61
+ @request_token = request_token.token
62
+ @request_secret = request_token.secret
63
+
64
+ return request_token.authorize_url(:oauth_callback => "")
65
+ end
66
+
67
+ def verify_token(oauth_verifier)
68
+ hash = { oauth_token: @request_token, oauth_token_secret: @request_secret }
69
+
70
+ request_token = OAuth::RequestToken.from_hash(@consumer, hash)
71
+
72
+ @access_token = request_token.get_access_token(:oauth_verifier => oauth_verifier)
73
+ @oauth_token = @access_token.params[:oauth_token]
74
+ @oauth_token_secret = @access_token.params[:oauth_token_secret]
75
+
76
+ initialise_consumer
77
+
78
+ true
79
+ end
80
+
81
+ def get(uri)
82
+ !@consumer and @consumer = get_consumer(@api_endpoint)
83
+
84
+ resp = @access_token.get(uri, { "Accept" => "application/json" } )
85
+
86
+ JSON.parse(resp.body)
87
+ end
88
+
89
+ def post(uri, params={})
90
+ !@consumer and @consumer = get_consumer(@api_endpoint)
91
+
92
+ resp = @access_token.post(uri, params.to_json, { "Content-Type" => "application/json", "Accept" => "application/json" } )
93
+
94
+ JSON.parse(resp.body)
95
+ end
96
+
97
+ def put(uri, params={})
98
+ !@consumer and @consumer = get_consumer(@api_endpoint)
99
+
100
+ resp = @access_token.put(uri, params.to_json, { "Content-Type" => "application/json", "Accept" => "application/json" } )
101
+ end
102
+
103
+ def delete(uri)
104
+ !@consumer and @consumer = get_consumer(@api_endpoint)
105
+
106
+ resp = @access_token.delete(uri, { "Accept" => "application/json" } )
107
+
108
+ JSON.parse(resp.body)
109
+ end
110
+
111
+ def accounts(type: nil)
112
+ if type
113
+ return get("/rest/v2/accounts/#{type}")
114
+ else
115
+ @accounts = {}
116
+
117
+ resp = get("/rest/v2/accounts")
118
+
119
+ resp["bankAccounts"].each do |account|
120
+ if !@accounts[account["account"]]
121
+ @accounts[account["account"]] = account
122
+ end
123
+ end
124
+
125
+ return resp
126
+ end
127
+ end
128
+
129
+ def account_by_name(name)
130
+ if @accounts
131
+ return @accounts[name]
132
+ end
133
+
134
+ accounts
135
+ account_by_name name
136
+ end
137
+
138
+ def client_payments
139
+ get "/rest/v2/client_payments"
140
+ end
141
+
142
+ def add_client_payment(params={})
143
+ post "/rest/v2/client_payments", params
144
+ end
145
+
146
+ def expense_types
147
+ get "/rest/v2/expense_types"
148
+ end
149
+
150
+ def expenses
151
+ resp = get "/rest/v2/expenses"
152
+
153
+ @expenses = []
154
+
155
+ resp["expense"].each do |expense|
156
+ @expenses.push expense
157
+ end
158
+
159
+ @expenses
160
+ end
161
+
162
+ def suppliers
163
+ resp = get "/rest/v2/suppliers"
164
+
165
+ @suppliers = {}
166
+
167
+ resp["supplier"].each do |supplier|
168
+ if !@suppliers[supplier["name"]]
169
+ @suppliers[supplier["name"]] = supplier
170
+ end
171
+ end
172
+
173
+ @suppliers
174
+ end
175
+
176
+ def supplier_by_name(name)
177
+ if @suppliers
178
+ return @suppliers[name]
179
+ end
180
+
181
+ suppliers
182
+
183
+ supplier_by_name(name)
184
+ end
185
+
186
+ def delete_supplier(supplier_id)
187
+ delete "/rest/v2/suppliers/#{supplier_id}"
188
+ end
189
+
190
+ def subject_to_vat?(expense_type)
191
+ if [
192
+ "GENERAL_INSURANCE",
193
+ "MILEAGE_ALLOWANCE"
194
+ ].include? expense_type
195
+ return false
196
+ end
197
+
198
+ if [
199
+ "ACCOUNTANCY"
200
+ ].include? expense_type
201
+ return true
202
+ end
203
+
204
+ raise "Don't know if #{expense_type} is subject to VAT"
205
+ end
206
+
207
+ def add_expense(supplier_id:, date:, payment_date:, payment_method: nil, bank_account_id:, amount:, expense_type:, description:, director_id:nil, invoice:nil)
208
+ !payment_method and payment_method = "EFT"
209
+
210
+ if subject_to_vat?(expense_type)
211
+ gross_amount = amount
212
+ vat_amount = (amount / 100) * @vat_rate
213
+ net_amount = amount - vat_amount
214
+ else
215
+ gross_amount = amount
216
+ vat_amount = 0
217
+ net_amount = amount
218
+ end
219
+
220
+ expense = {
221
+ "amount" => amount,
222
+ "expenseDetails" => {
223
+ "supplier" => {
224
+ "supplierId" => supplier_id
225
+ },
226
+ "postingDate" => date
227
+ },
228
+ "paymentDetails" => {
229
+ "payment" => [{
230
+ "paymentDate" => payment_date,
231
+ "paymentMethod" => payment_method,
232
+ "bankAccount" => {
233
+ "accountId" => bank_account_id
234
+ },
235
+ "amount" => amount
236
+ }]
237
+ },
238
+ "expenseLineItems" => {
239
+ "count": 1,
240
+ "lineItemGrossTotal": gross_amount,
241
+ "expenseLineItems" => [
242
+ {
243
+ "expenseType" => expense_type,
244
+ "benefitingDirector": director_id,
245
+ "lineItemDescription" => description,
246
+ "lineItemAmount" => {
247
+ "currencyCode": "GBP",
248
+ "netAmount" => net_amount,
249
+ "grossAmount" => gross_amount,
250
+ "vatAmount" => vat_amount
251
+ }
252
+ }
253
+ ]
254
+ }
255
+ }
256
+
257
+ if invoice
258
+ mimetype = file_mimetype(invoice)
259
+ filename = invoice.split("/").last
260
+
261
+ expense["receipts"] = {
262
+ "count" => 1,
263
+ "receipt" => [
264
+ {
265
+ "fileName" => filename,
266
+ "contentType" => mimetype,
267
+ "fileData": Base64.encode64(File.read(invoice))
268
+ }
269
+ ]
270
+ }
271
+ end
272
+
273
+ post "/rest/v2/expenses", expense
274
+ end
275
+
276
+ def file_mimetype(filename)
277
+ esc = Shellwords.escape(filename)
278
+ `/usr/bin/file -bi #{esc}`.chomp.gsub(/;.*\z/, '')
279
+ end
280
+
281
+ def find_expense(supplier_id:, date:, payment_date:, payment_method: nil, bank_account_id:, amount:, expense_type:, ignore_ids:[])
282
+ !payment_method and payment_method = "EFT"
283
+
284
+ if !@expenses
285
+ expenses
286
+ end
287
+
288
+ @expenses.each do |expense|
289
+ if ignore_ids.include?(expense["expenseId"])
290
+ next
291
+ end
292
+
293
+ if expense["expenseDetails"]["supplier"]["supplierId"] == supplier_id and
294
+ expense["expenseDetails"]["postingDate"] == date and
295
+ expense["paymentDetails"]["payment"][0]["paymentDate"] == payment_date and
296
+ expense["paymentDetails"]["payment"][0]["paymentMethod"] == payment_method and
297
+ expense["paymentDetails"]["payment"][0]["bankAccount"]["accountId"] == bank_account_id and
298
+ expense["paymentDetails"]["payment"][0]["amount"] == amount and
299
+ expense["expenseLineItems"]["expenseLineItems"][0]["expenseType"] == expense_type
300
+
301
+ return expense
302
+ end
303
+ end
304
+
305
+ false
306
+ end
307
+
308
+ def clients
309
+ get "/rest/v2/clients"
310
+ end
311
+
312
+ def find_client(name)
313
+ clients["client"].each do |client|
314
+ if client["name"] == name
315
+ return client
316
+ end
317
+ end
318
+
319
+ false
320
+ end
321
+
322
+ def invoices
323
+ get "/rest/v2/sales_invoices"
324
+ end
325
+
326
+ def find_outstanding_client_invoice(client_id, amount)
327
+ invoices["salesInvoice"].each do |invoice|
328
+ total = 0
329
+
330
+ invoice["salesInvoiceLineItems"]["salesInvoiceLineItem"].each do |item|
331
+ total += item["lineItemAmount"]["grossAmount"]
332
+ end
333
+
334
+ if invoice["salesInvoiceDetails"]["client"]["clientId"] == client_id and
335
+ total == amount and
336
+ invoice["salesInvoiceDetails"]["state"] != "SETTLED"
337
+
338
+ return invoice
339
+ end
340
+ end
341
+
342
+ false
343
+ end
344
+
345
+ def find_draft_invoice(client_id, date)
346
+ invoices["salesInvoice"].each do |invoice|
347
+ if invoice["salesInvoiceDetails"]["client"]["clientId"] == client_id and
348
+ invoice["salesInvoiceDetails"]["issuedDate"] == date and
349
+ invoice["salesInvoiceDetails"]["state"] == "DRAFT"
350
+
351
+ return invoice
352
+ end
353
+ end
354
+
355
+ false
356
+ end
357
+
358
+ def issue_invoice(invoice)
359
+ put "/rest/v2/sales_invoices/#{invoice["salesInvoiceId"]}/issue"
360
+ end
361
+
362
+ def find_client_payment(client_id:, date:, payment_method: nil, bank_account_id:, amount:, invoice_id:)
363
+ !payment_method and payment_method = "EFT"
364
+
365
+ client_payments.each do |client_payment|
366
+ if client_payment["paymentDate"] == date and
367
+ client_payment["paymentMethod"] == payment_method and
368
+ client_payment["bankAccount"]["accountId"] == bank_account_id and
369
+ client_payment["amount"] == amount and
370
+ client_payment["client"]["clientId"] == client_id and
371
+ client_payment["salesInvoices"]["salesInvoice"][0]["salesInvoiceId"] == invoice_id
372
+
373
+ return client_payment
374
+ end
375
+ end
376
+
377
+ false
378
+ end
379
+
380
+ def add_client_payment(client_id:, date:, payment_method: nil, bank_account_id:, amount:, invoice_id:)
381
+ !payment_method and payment_method = "EFT"
382
+
383
+ payment = {
384
+ "paymentDate" => date,
385
+ "paymentMethod" => payment_method,
386
+ "bankAccount" => {
387
+ "accountId" => bank_account_id,
388
+ },
389
+ "amount" => amount,
390
+ "currency" => "GBP",
391
+ "client" => {
392
+ "clientId" => client_id,
393
+ },
394
+ "salesInvoices" => {
395
+ "salesInvoice" => [
396
+ {
397
+ "salesInvoiceId" => invoice_id,
398
+ "allocatedAmount" => amount,
399
+ }
400
+ ]
401
+ }
402
+ }
403
+
404
+ post "/rest/v2/client_payments", payment
405
+ end
406
+
407
+ def get_next_client_ref_for_client(client)
408
+ abbr = ""
409
+
410
+ for i in 0...client.length
411
+ if client["name"][i].match(/[A-Z]/)
412
+ abbr += client["name"][i]
413
+ end
414
+ end
415
+
416
+ used = []
417
+
418
+ invoices["salesInvoice"].each do |invoice|
419
+ if invoice["salesInvoiceDetails"]["client"]["clientId"] == client["clientId"]
420
+ used.push invoice["salesInvoiceDetails"]["clientReference"]
421
+ end
422
+ end
423
+
424
+ n = 1
425
+ ref = "#{abbr}#{n.to_s.rjust(3,'0')}"
426
+
427
+ while used.include? ref
428
+ n += 1
429
+ ref = "#{abbr}#{n.to_s.rjust(3,'0')}"
430
+ end
431
+
432
+ ref
433
+ end
434
+
435
+ def raise_invoice(client_id:,date:,client_ref:nil,description:,rate:,quantity:,add_vat:true)
436
+ amount = rate * quantity
437
+
438
+ if add_vat
439
+ vat = (amount / 100) * @vat_rate
440
+ vat_type = "STANDARD"
441
+ else
442
+ vat = 0
443
+ vat_type = "OUTSIDE_SCOPE"
444
+ end
445
+
446
+ client = get "/rest/v2/clients/#{client_id}"
447
+
448
+ if !client_ref
449
+ client_ref = get_next_client_ref_for_client(client)
450
+ end
451
+
452
+ invoice = {
453
+ "currency" => "GBP",
454
+ "salesInvoiceLineItems" => {
455
+ "salesInvoiceLineItem" => [
456
+ {
457
+ "lineItemDescription" => description,
458
+ "quantity" => quantity,
459
+ "rate" => rate,
460
+ "lineItemAmount" => {
461
+ "netAmount" => amount,
462
+ "grossAmount" => amount + vat,
463
+ "vatAmount" => vat,
464
+ "vatRate" => @vat_rate
465
+ },
466
+ "vatType" => vat_type
467
+ },
468
+ ],
469
+ "count" => 1
470
+ },
471
+ "salesInvoiceDetails" => {
472
+ "client" => {
473
+ "clientId" => client_id,
474
+ },
475
+ "clientReference" => client_ref,
476
+ "issuedDate" => date,
477
+ "paymentTermsDays" => client["paymentTermsDays"],
478
+ }
479
+ }
480
+
481
+ post "/rest/v2/sales_invoices", invoice
482
+ end
483
+ end
metadata ADDED
@@ -0,0 +1,58 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: crunchaccounting-api
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - m4rkw
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2017-07-20 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: oauth
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ description: Crunch Accounting API gem
28
+ email: m@rkw.io
29
+ executables: []
30
+ extensions: []
31
+ extra_rdoc_files: []
32
+ files:
33
+ - lib/crunchaccounting-api.rb
34
+ homepage: https://github.com/m4rkw/crunchaccounting-api
35
+ licenses:
36
+ - MIT
37
+ metadata: {}
38
+ post_install_message:
39
+ rdoc_options: []
40
+ require_paths:
41
+ - lib
42
+ required_ruby_version: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: '0'
47
+ required_rubygems_version: !ruby/object:Gem::Requirement
48
+ requirements:
49
+ - - ">="
50
+ - !ruby/object:Gem::Version
51
+ version: '0'
52
+ requirements: []
53
+ rubyforge_project:
54
+ rubygems_version: 2.5.2
55
+ signing_key:
56
+ specification_version: 4
57
+ summary: Crunch Accounting API
58
+ test_files: []