GnuCash  5.6-150-g038405b370+
gnucash_rest.py
1 #!/usr/bin/env python3
2 
3 '''
4 
5 gnucash_rest.py -- A Flask app which responds to REST requests
6 with JSON responses
7 
8 Copyright (C) 2013 Tom Lofts <dev@loftx.co.uk>
9 
10 This program is free software; you can redistribute it and/or
11 modify it under the terms of the GNU General Public License as
12 published by the Free Software Foundation; either version 2 of
13 the License, or (at your option) any later version.
14 
15 This program is distributed in the hope that it will be useful,
16 but WITHOUT ANY WARRANTY; without even the implied warranty of
17 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 GNU General Public License for more details.
19 
20 You should have received a copy of the GNU General Public License
21 along with this program; if not, contact:
22 
23 Free Software Foundation Voice: +1-617-542-5942
24 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652
25 Boston, MA 02110-1301, USA gnu@gnu.org
26 
27 @author Tom Lofts <dev@loftx.co.uk>
28 
29 '''
30 
31 import gnucash
32 import gnucash_simple
33 import json
34 import atexit
35 from flask import Flask, abort, request, Response
36 import sys
37 import getopt
38 
39 from decimal import Decimal
40 
41 from gnucash.gnucash_business import Vendor, Bill, Entry, GncNumeric, \
42  Customer, Invoice, Split, Account, Transaction
43 
44 import datetime
45 
46 from gnucash import \
47  QOF_QUERY_AND, \
48  QOF_QUERY_OR, \
49  QOF_QUERY_NAND, \
50  QOF_QUERY_NOR, \
51  QOF_QUERY_XOR
52 
53 from gnucash import \
54  QOF_STRING_MATCH_NORMAL, \
55  QOF_STRING_MATCH_CASEINSENSITIVE
56 
57 from gnucash import \
58  QOF_COMPARE_LT, \
59  QOF_COMPARE_LTE, \
60  QOF_COMPARE_EQUAL, \
61  QOF_COMPARE_GT, \
62  QOF_COMPARE_GTE, \
63  QOF_COMPARE_NEQ
64 
65 from gnucash import \
66  QOF_DATE_MATCH_NORMAL
67 
68 from gnucash import \
69  INVOICE_TYPE
70 
71 from gnucash import \
72  INVOICE_IS_PAID
73 
74 from gnucash import SessionOpenMode
75 
76 app = Flask(__name__)
77 app.debug = True
78 
79 @app.route('/accounts', methods=['GET', 'POST'])
80 def api_accounts():
81 
82  if request.method == 'GET':
83 
84  accounts = getAccounts(session.book)
85 
86  return Response(json.dumps(accounts), mimetype='application/json')
87 
88  elif request.method == 'POST':
89 
90  try:
91  account = addAccount(session.books)
92  except Error as error:
93  return Response(json.dumps({'errors': [{'type' : error.type,
94  'message': error.message, 'data': error.data}]}), status=400,
95  mimetype='application/json')
96  else:
97  return Response(json.dumps(account), status=201,
98  mimetype='application/json')
99 
100  else:
101  abort(405)
102 
103 @app.route('/accounts/<guid>', methods=['GET'])
104 def api_account(guid):
105 
106  account = getAccount(session.book, guid)
107 
108  if account is None:
109  abort(404)
110  else:
111  return Response(json.dumps(account), mimetype='application/json')
112 
113 @app.route('/accounts/<guid>/splits', methods=['GET'])
114 def api_account_splits(guid):
115 
116  date_posted_from = request.args.get('date_posted_from', None)
117  date_posted_to = request.args.get('date_posted_to', None)
118 
119  # check account exists
120  account = getAccount(session.book, guid)
121 
122  if account is None:
123  abort(404)
124 
125  splits = getAccountSplits(session.book, guid, date_posted_from,
126  date_posted_to)
127 
128  return Response(json.dumps(splits), mimetype='application/json')
129 
130 
131 @app.route('/transactions', methods=['POST'])
132 def api_transactions():
133 
134  if request.method == 'POST':
135 
136  currency = str(request.form.get('currency', ''))
137  description = str(request.form.get('description', ''))
138  num = str(request.form.get('num', ''))
139  date_posted = str(request.form.get('date_posted', ''))
140 
141  splitvalue1 = int(request.form.get('splitvalue1', ''))
142  splitaccount1 = str(request.form.get('splitaccount1', ''))
143  splitvalue2 = int(request.form.get('splitvalue2', ''))
144  splitaccount2 = str(request.form.get('splitaccount2', ''))
145 
146  splits = [
147  {'value': splitvalue1, 'account_guid': splitaccount1},
148  {'value': splitvalue2, 'account_guid': splitaccount2}]
149 
150  try:
151  transaction = addTransaction(session.book, num, description,
152  date_posted, currency, splits)
153  except Error as error:
154  return Response(json.dumps({'errors': [{'type' : error.type,
155  'message': error.message, 'data': error.data}]}), status=400,
156  mimetype='application/json')
157  else:
158  return Response(json.dumps(transaction), status=201,
159  mimetype='application/json')
160 
161  else:
162  abort(405)
163 
164 @app.route('/transactions/<guid>', methods=['GET', 'POST', 'DELETE'])
165 def api_transaction(guid):
166 
167  if request.method == 'GET':
168 
169  transaction = getTransaction(session.book, guid)
170 
171  if transaction is None:
172  abort(404)
173 
174  return Response(json.dumps(transaction), mimetype='application/json')
175 
176  elif request.method == 'POST':
177 
178  currency = str(request.form.get('currency', ''))
179  description = str(request.form.get('description', ''))
180  num = str(request.form.get('num', ''))
181  date_posted = str(request.form.get('date_posted', ''))
182 
183  splitguid1 = str(request.form.get('splitguid1', ''))
184  splitvalue1 = int(request.form.get('splitvalue1', ''))
185  splitaccount1 = str(request.form.get('splitaccount1', ''))
186  splitguid2 = str(request.form.get('splitguid2', ''))
187  splitvalue2 = int(request.form.get('splitvalue2', ''))
188  splitaccount2 = str(request.form.get('splitaccount2', ''))
189 
190  splits = [
191  {'guid': splitguid1,
192  'value': splitvalue1,
193  'account_guid': splitaccount1},
194  {'guid': splitguid2,
195  'value': splitvalue2,
196  'account_guid': splitaccount2}
197  ]
198 
199  try:
200  transaction = editTransaction(session.book, guid, num, description,
201  date_posted, currency, splits)
202  except Error as error:
203  return Response(json.dumps({'errors': [{'type' : error.type,
204  'message': error.message, 'data': error.data}]}), status=400, mimetype='application/json')
205  else:
206  return Response(json.dumps(transaction), status=200,
207  mimetype='application/json')
208 
209  elif request.method == 'DELETE':
210 
211  deleteTransaction(session.book, guid)
212 
213  return Response('', status=200, mimetype='application/json')
214 
215  else:
216  abort(405)
217 
218 @app.route('/bills', methods=['GET', 'POST'])
219 def api_bills():
220 
221  if request.method == 'GET':
222 
223  is_paid = request.args.get('is_paid', None)
224  is_active = request.args.get('is_active', None)
225  date_opened_to = request.args.get('date_opened_to', None)
226  date_opened_from = request.args.get('date_opened_from', None)
227 
228  if is_paid == '1':
229  is_paid = 1
230  elif is_paid == '0':
231  is_paid = 0
232  else:
233  is_paid = None
234 
235  if is_active == '1':
236  is_active = 1
237  elif is_active == '0':
238  is_active = 0
239  else:
240  is_active = None
241 
242  bills = getBills(session.book, None, is_paid, is_active,
243  date_opened_from, date_opened_to)
244 
245  return Response(json.dumps(bills), mimetype='application/json')
246 
247  elif request.method == 'POST':
248 
249  id = str(request.form.get('id', None))
250 
251  if id == '':
252  id = None
253  elif id != None:
254  id = str(id)
255 
256  vendor_id = str(request.form.get('vendor_id', ''))
257  currency = str(request.form.get('currency', ''))
258  date_opened = str(request.form.get('date_opened', ''))
259  notes = str(request.form.get('notes', ''))
260 
261  try:
262  bill = addBill(session.book, id, vendor_id, currency, date_opened,
263  notes)
264  except Error as error:
265  # handle incorrect parameter errors
266  return Response(json.dumps({'errors': [{'type' : error.type,
267  'message': error.message, 'data': error.data}]}), status=400, mimetype='application/json')
268  else:
269  return Response(json.dumps(bill), status=201,
270  mimetype='application/json')
271 
272  else:
273  abort(405)
274 
275 @app.route('/bills/<id>', methods=['GET', 'POST', 'PAY'])
276 def api_bill(id):
277 
278  if request.method == 'GET':
279 
280  bill = getBill(session.book, id)
281 
282  if bill is None:
283  abort(404)
284  else:
285  return Response(json.dumps(bill), mimetype='application/json')
286 
287  elif request.method == 'POST':
288 
289  vendor_id = str(request.form.get('vendor_id', ''))
290  currency = str(request.form.get('currency', ''))
291  date_opened = request.form.get('date_opened', None)
292  notes = str(request.form.get('notes', ''))
293  posted = request.form.get('posted', None)
294  posted_account_guid = str(request.form.get('posted_account_guid', ''))
295  posted_date = request.form.get('posted_date', '')
296  due_date = request.form.get('due_date', '')
297  posted_memo = str(request.form.get('posted_memo', ''))
298  posted_accumulatesplits = request.form.get('posted_accumulatesplits',
299  '')
300  posted_autopay = request.form.get('posted_autopay', '')
301 
302  if posted == '1':
303  posted = 1
304  else:
305  posted = 0
306 
307  if (posted_accumulatesplits == '1'
308  or posted_accumulatesplits == 'true'
309  or posted_accumulatesplits == 'True'
310  or posted_accumulatesplits == True):
311  posted_accumulatesplits = True
312  else:
313  posted_accumulatesplits = False
314 
315  if posted_autopay == '1':
316  posted_autopay = True
317  else:
318  posted_autopay = False
319  try:
320  bill = updateBill(session.book, id, vendor_id, currency,
321  date_opened, notes, posted, posted_account_guid, posted_date,
322  due_date, posted_memo, posted_accumulatesplits, posted_autopay)
323  except Error as error:
324  return Response(json.dumps({'errors': [{'type' : error.type,
325  'message': error.message, 'data': error.data}]}), status=400,
326  mimetype='application/json')
327  else:
328  return Response(json.dumps(bill), status=200,
329  mimetype='application/json')
330 
331  if bill is None:
332  abort(404)
333  else:
334  return Response(json.dumps(bill),
335  mimetype='application/json')
336 
337  elif request.method == 'PAY':
338 
339  posted_account_guid = str(request.form.get('posted_account_guid', ''))
340  transfer_account_guid = str(request.form.get('transfer_account_guid',
341  ''))
342  payment_date = request.form.get('payment_date', '')
343  num = str(request.form.get('num', ''))
344  memo = str(request.form.get('posted_memo', ''))
345  auto_pay = request.form.get('auto_pay', '')
346 
347  try:
348  bill = payBill(session.book, id, posted_account_guid,
349  transfer_account_guid, payment_date, memo, num, auto_pay)
350  except Error as error:
351  return Response(json.dumps({'errors': [{'type' : error.type,
352  'message': error.message, 'data': error.data}]}), status=400,
353  mimetype='application/json')
354  else:
355  return Response(json.dumps(bill), status=200,
356  mimetype='application/json')
357 
358  else:
359  abort(405)
360 
361 @app.route('/bills/<id>/entries', methods=['GET', 'POST'])
362 def api_bill_entries(id):
363 
364  bill = getBill(session.book, id)
365 
366  if bill is None:
367  abort(404)
368  else:
369  if request.method == 'GET':
370  return Response(json.dumps(bill['entries']), mimetype='application/json')
371  elif request.method == 'POST':
372 
373  date = str(request.form.get('date', ''))
374  description = str(request.form.get('description', ''))
375  account_guid = str(request.form.get('account_guid', ''))
376  quantity = str(request.form.get('quantity', ''))
377  price = str(request.form.get('price', ''))
378 
379  try:
380  entry = addBillEntry(session.book, id, date, description,
381  account_guid, quantity, price)
382  except Error as error:
383  return Response(json.dumps({'errors': [{'type' : error.type,
384  'message': error.message, 'data': error.data}]}),
385  status=400, mimetype='application/json')
386  else:
387  return Response(json.dumps(entry), status=201,
388  mimetype='application/json')
389 
390  else:
391  abort(405)
392 
393 @app.route('/invoices', methods=['GET', 'POST'])
394 def api_invoices():
395 
396  if request.method == 'GET':
397 
398  is_paid = request.args.get('is_paid', None)
399  is_active = request.args.get('is_active', None)
400  date_due_to = request.args.get('date_due_to', None)
401  date_due_from = request.args.get('date_due_from', None)
402 
403  if is_paid == '1':
404  is_paid = 1
405  elif is_paid == '0':
406  is_paid = 0
407  else:
408  is_paid = None
409 
410  if is_active == '1':
411  is_active = 1
412  elif is_active == '0':
413  is_active = 0
414  else:
415  is_active = None
416 
417  invoices = getInvoices(session.book, None, is_paid, is_active,
418  date_due_from, date_due_to)
419 
420  return Response(json.dumps(invoices), mimetype='application/json')
421 
422  elif request.method == 'POST':
423 
424  id = str(request.form.get('id', None))
425 
426  if id == '':
427  id = None
428  elif id != None:
429  id = str(id)
430 
431  customer_id = str(request.form.get('customer_id', ''))
432  currency = str(request.form.get('currency', ''))
433  date_opened = str(request.form.get('date_opened', ''))
434  notes = str(request.form.get('notes', ''))
435 
436  try:
437  invoice = addInvoice(session.book, id, customer_id, currency,
438  date_opened, notes)
439  except Error as error:
440  return Response(json.dumps({'errors': [{'type' : error.type,
441  'message': error.message, 'data': error.data}]}), status=400,
442  mimetype='application/json')
443  else:
444  return Response(json.dumps(invoice), status=201,
445  mimetype='application/json')
446 
447  else:
448  abort(405)
449 
450 @app.route('/invoices/<id>', methods=['GET', 'POST', 'PAY'])
451 def api_invoice(id):
452 
453  if request.method == 'GET':
454 
455  invoice = getInvoice(session.book, id)
456 
457  if invoice is None:
458  abort(404)
459  else:
460  return Response(json.dumps(invoice), mimetype='application/json')
461 
462  elif request.method == 'POST':
463 
464  customer_id = str(request.form.get('customer_id', ''))
465  currency = str(request.form.get('currency', ''))
466  date_opened = request.form.get('date_opened', None)
467  notes = str(request.form.get('notes', ''))
468  posted = request.form.get('posted', None)
469  posted_account_guid = str(request.form.get('posted_account_guid', ''))
470  posted_date = request.form.get('posted_date', '')
471  due_date = request.form.get('due_date', '')
472  posted_memo = str(request.form.get('posted_memo', ''))
473  posted_accumulatesplits = request.form.get('posted_accumulatesplits',
474  '')
475  posted_autopay = request.form.get('posted_autopay', '')
476 
477  if posted == '1':
478  posted = 1
479  else:
480  posted = 0
481 
482  if (posted_accumulatesplits == '1'
483  or posted_accumulatesplits == 'true'
484  or posted_accumulatesplits == 'True'
485  or posted_accumulatesplits == True):
486  posted_accumulatesplits = True
487  else:
488  posted_accumulatesplits = False
489 
490  if posted_autopay == '1':
491  posted_autopay = True
492  else:
493  posted_autopay = False
494  try:
495  invoice = updateInvoice(session.book, id, customer_id, currency,
496  date_opened, notes, posted, posted_account_guid, posted_date,
497  due_date, posted_memo, posted_accumulatesplits, posted_autopay)
498  except Error as error:
499  return Response(json.dumps({'errors': [{'type' : error.type,
500  'message': error.message, 'data': error.data}]}), status=400,
501  mimetype='application/json')
502  else:
503  return Response(json.dumps(invoice), status=200,
504  mimetype='application/json')
505 
506  if invoice is None:
507  abort(404)
508  else:
509  return Response(json.dumps(invoice), mimetype='application/json')
510 
511  elif request.method == 'PAY':
512 
513  posted_account_guid = str(request.form.get('posted_account_guid', ''))
514  transfer_account_guid = str(request.form.get('transfer_account_guid',
515  ''))
516  payment_date = request.form.get('payment_date', '')
517  num = str(request.form.get('num', ''))
518  memo = str(request.form.get('posted_memo', ''))
519  auto_pay = request.form.get('auto_pay', '')
520 
521  try:
522  invoice = payInvoice(session.book, id, posted_account_guid,
523  transfer_account_guid, payment_date, memo, num, auto_pay)
524  except Error as error:
525  return Response(json.dumps({'errors': [{'type' : error.type,
526  'message': error.message, 'data': error.data}]}), status=400,
527  mimetype='application/json')
528  else:
529  return Response(json.dumps(invoice), status=200,
530  mimetype='application/json')
531 
532  else:
533  abort(405)
534 
535 @app.route('/invoices/<id>/entries', methods=['GET', 'POST'])
536 def api_invoice_entries(id):
537 
538  invoice = getInvoice(session.book, id)
539 
540  if invoice is None:
541  abort(404)
542  else:
543  if request.method == 'GET':
544  return Response(json.dumps(invoice['entries']),
545  mimetype='application/json')
546  elif request.method == 'POST':
547 
548  date = str(request.form.get('date', ''))
549  description = str(request.form.get('description', ''))
550  account_guid = str(request.form.get('account_guid', ''))
551  quantity = str(request.form.get('quantity', ''))
552  price = str(request.form.get('price', ''))
553 
554  try:
555  entry = addEntry(session.book, id, date, description,
556  account_guid, quantity, price)
557  except Error as error:
558  return Response(json.dumps({'errors': [{'type' : error.type,
559  'message': error.message, 'data': error.data}]}),
560  status=400, mimetype='application/json')
561  else:
562  return Response(json.dumps(entry), status=201,
563  mimetype='application/json')
564 
565  else:
566  abort(405)
567 
568 @app.route('/entries/<guid>', methods=['GET', 'POST', 'DELETE'])
569 def api_entry(guid):
570 
571  entry = getEntry(session.book, guid)
572 
573  if entry is None:
574  abort(404)
575  else:
576  if request.method == 'GET':
577  return Response(json.dumps(entry), mimetype='application/json')
578  elif request.method == 'POST':
579 
580  date = str(request.form.get('date', ''))
581  description = str(request.form.get('description', ''))
582  account_guid = str(request.form.get('account_guid', ''))
583  quantity = str(request.form.get('quantity', ''))
584  price = str(request.form.get('price', ''))
585 
586  try:
587  entry = updateEntry(session.book, guid, date, description,
588  account_guid, quantity, price)
589  except Error as error:
590  return Response(json.dumps({'errors': [{'type' : error.type,
591  'message': error.message, 'data': error.data}]}),
592  status=400, mimetype='application/json')
593  else:
594  return Response(json.dumps(entry), status=200,
595  mimetype='application/json')
596 
597  elif request.method == 'DELETE':
598 
599  deleteEntry(session.book, guid)
600 
601  return Response('', status=201, mimetype='application/json')
602 
603  else:
604  abort(405)
605 
606 @app.route('/customers', methods=['GET', 'POST'])
607 def api_customers():
608 
609  if request.method == 'GET':
610  customers = getCustomers(session.book)
611  return Response(json.dumps(customers), mimetype='application/json')
612  elif request.method == 'POST':
613 
614  id = str(request.form.get('id', None))
615 
616  if id == '':
617  id = None
618  elif id != None:
619  id = str(id)
620 
621  currency = str(request.form.get('currency', ''))
622  name = str(request.form.get('name', ''))
623  contact = str(request.form.get('contact', ''))
624  address_line_1 = str(request.form.get('address_line_1', ''))
625  address_line_2 = str(request.form.get('address_line_2', ''))
626  address_line_3 = str(request.form.get('address_line_3', ''))
627  address_line_4 = str(request.form.get('address_line_4', ''))
628  phone = str(request.form.get('phone', ''))
629  fax = str(request.form.get('fax', ''))
630  email = str(request.form.get('email', ''))
631 
632  try:
633  customer = addCustomer(session.book, id, currency, name, contact,
634  address_line_1, address_line_2, address_line_3, address_line_4,
635  phone, fax, email)
636  except Error as error:
637  return Response(json.dumps({'errors': [{'type' : error.type,
638  'message': error.message, 'data': error.data}]}), status=400,
639  mimetype='application/json')
640  else:
641  return Response(json.dumps(customer), status=201,
642  mimetype='application/json')
643 
644  else:
645  abort(405)
646 
647 @app.route('/customers/<id>', methods=['GET', 'POST'])
648 def api_customer(id):
649 
650  if request.method == 'GET':
651 
652  customer = getCustomer(session.book, id)
653 
654  if customer is None:
655  abort(404)
656  else:
657  return Response(json.dumps(customer), mimetype='application/json')
658 
659  elif request.method == 'POST':
660 
661  id = str(request.form.get('id', None))
662 
663  name = str(request.form.get('name', ''))
664  contact = str(request.form.get('contact', ''))
665  address_line_1 = str(request.form.get('address_line_1', ''))
666  address_line_2 = str(request.form.get('address_line_2', ''))
667  address_line_3 = str(request.form.get('address_line_3', ''))
668  address_line_4 = str(request.form.get('address_line_4', ''))
669  phone = str(request.form.get('phone', ''))
670  fax = str(request.form.get('fax', ''))
671  email = str(request.form.get('email', ''))
672 
673  try:
674  customer = updateCustomer(session.book, id, name, contact,
675  address_line_1, address_line_2, address_line_3, address_line_4,
676  phone, fax, email)
677  except Error as error:
678  if error.type == 'NoCustomer':
679  return Response(json.dumps({'errors': [{'type' : error.type,
680  'message': error.message, 'data': error.data}]}),
681  status=404, mimetype='application/json')
682  else:
683  return Response(json.dumps({'errors': [{'type' : error.type,
684  'message': error.message, 'data': error.data}]}),
685  status=400, mimetype='application/json')
686  else:
687  return Response(json.dumps(customer), status=200,
688  mimetype='application/json')
689 
690  else:
691  abort(405)
692 
693 @app.route('/customers/<id>/invoices', methods=['GET'])
694 def api_customer_invoices(id):
695 
696  customer = getCustomer(session.book, id)
697 
698  if customer is None:
699  abort(404)
700 
701  invoices = getInvoices(session.book, customer['guid'], None, None, None,
702  None)
703 
704  return Response(json.dumps(invoices), mimetype='application/json')
705 
706 @app.route('/vendors', methods=['GET', 'POST'])
707 def api_vendors():
708 
709  if request.method == 'GET':
710  vendors = getVendors(session.book)
711  return Response(json.dumps(vendors), mimetype='application/json')
712  elif request.method == 'POST':
713 
714  id = str(request.form.get('id', None))
715 
716  if id == '':
717  id = None
718  elif id != None:
719  id = str(id)
720 
721  currency = str(request.form.get('currency', ''))
722  name = str(request.form.get('name', ''))
723  contact = str(request.form.get('contact', ''))
724  address_line_1 = str(request.form.get('address_line_1', ''))
725  address_line_2 = str(request.form.get('address_line_2', ''))
726  address_line_3 = str(request.form.get('address_line_3', ''))
727  address_line_4 = str(request.form.get('address_line_4', ''))
728  phone = str(request.form.get('phone', ''))
729  fax = str(request.form.get('fax', ''))
730  email = str(request.form.get('email', ''))
731 
732  try:
733  vendor = addVendor(session.book, id, currency, name, contact,
734  address_line_1, address_line_2, address_line_3, address_line_4,
735  phone, fax, email)
736  except Error as error:
737  return Response(json.dumps({'errors': [{'type' : error.type,
738  'message': error.message, 'data': error.data}]}), status=400,
739  mimetype='application/json')
740  else:
741  return Response(json.dumps(vendor), status=201,
742  mimetype='application/json')
743 
744  else:
745  abort(405)
746 
747 @app.route('/vendors/<id>', methods=['GET', 'POST'])
748 def api_vendor(id):
749 
750  if request.method == 'GET':
751 
752  vendor = getVendor(session.book, id)
753 
754  if vendor is None:
755  abort(404)
756  else:
757  return Response(json.dumps(vendor), mimetype='application/json')
758  else:
759  abort(405)
760 
761 @app.route('/vendors/<id>/bills', methods=['GET'])
762 def api_vendor_bills(id):
763 
764  vendor = getVendor(session.book, id)
765 
766  if vendor is None:
767  abort(404)
768 
769  bills = getBills(session.book, vendor['guid'], None, None, None, None)
770 
771  return Response(json.dumps(bills), mimetype='application/json')
772 
773 def getCustomers(book):
774 
775  query = gnucash.Query()
776  query.search_for('gncCustomer')
777  query.set_book(book)
778  customers = []
779 
780  for result in query.run():
781  customers.append(gnucash_simple.customerToDict(
782  gnucash.gnucash_business.Customer(instance=result)))
783 
784  query.destroy()
785 
786  return customers
787 
788 def getCustomer(book, id):
789 
790  customer = book.CustomerLookupByID(id)
791 
792  if customer is None:
793  return None
794  else:
795  return gnucash_simple.customerToDict(customer)
796 
797 def getVendors(book):
798 
799  query = gnucash.Query()
800  query.search_for('gncVendor')
801  query.set_book(book)
802  vendors = []
803 
804  for result in query.run():
805  vendors.append(gnucash_simple.vendorToDict(
806  gnucash.gnucash_business.Vendor(instance=result)))
807 
808  query.destroy()
809 
810  return vendors
811 
812 def getVendor(book, id):
813 
814  vendor = book.VendorLookupByID(id)
815 
816  if vendor is None:
817  return None
818  else:
819  return gnucash_simple.vendorToDict(vendor)
820 
821 def getAccounts(book):
822 
823  accounts = gnucash_simple.accountToDict(book.get_root_account())
824 
825  return accounts
826 
827 def getAccountsFlat(book):
828 
829  accounts = gnucash_simple.accountToDict(book.get_root_account())
830 
831  flat_accounts = getSubAccounts(accounts)
832 
833  for n, account in enumerate(flat_accounts):
834  account.pop('subaccounts')
835 
836  filtered_flat_account = []
837 
838  type_ids = [9]
839 
840  for n, account in enumerate(flat_accounts):
841  if account['type_id'] in type_ids:
842  filtered_flat_account.append(account)
843  print(account['name'] + ' ' + str(account['type_id']))
844 
845  return filtered_flat_account
846 
847 def getSubAccounts(account):
848 
849  flat_accounts = []
850 
851  if 'subaccounts' in list(account.keys()):
852  for n, subaccount in enumerate(account['subaccounts']):
853  flat_accounts.append(subaccount)
854  flat_accounts = flat_accounts + getSubAccounts(subaccount)
855 
856  return flat_accounts
857 
858 def getAccount(book, guid):
859 
860  account_guid = gnucash.gnucash_core.GUID()
861  gnucash.gnucash_core.GUIDString(guid, account_guid)
862 
863  account = account_guid.AccountLookup(book)
864 
865  if account is None:
866  return None
867 
868  account = gnucash_simple.accountToDict(account)
869 
870  if account is None:
871  return None
872  else:
873  return account
874 
875 
876 def getTransaction(book, guid):
877 
878  transaction_guid = gnucash.gnucash_core.GUID()
879  gnucash.gnucash_core.GUIDString(guid, transaction_guid)
880 
881  transaction = transaction_guid.TransactionLookup(book)
882 
883  if transaction is None:
884  return None
885 
886  transaction = gnucash_simple.transactionToDict(transaction, ['splits'])
887 
888  if transaction is None:
889  return None
890  else:
891  return transaction
892 
893 def getTransactions(book, account_guid, date_posted_from, date_posted_to):
894 
895  query = gnucash.Query()
896 
897  query.search_for('Trans')
898  query.set_book(book)
899 
900  transactions = []
901 
902  for transaction in query.run():
903  transactions.append(gnucash_simple.transactionToDict(
904  gnucash.gnucash_business.Transaction(instance=transaction)))
905 
906  query.destroy()
907 
908  return transactions
909 
910 def getAccountSplits(book, guid, date_posted_from, date_posted_to):
911 
912  account_guid = gnucash.gnucash_core.GUID()
913  gnucash.gnucash_core.GUIDString(guid, account_guid)
914 
915  query = gnucash.Query()
916  query.search_for('Split')
917  query.set_book(book)
918 
919  SPLIT_TRANS= 'trans'
920 
921  TRANS_DATE_POSTED = 'date-posted'
922 
923  if date_posted_from != None:
924  pred_data = gnucash.gnucash_core.QueryDatePredicate(
925  QOF_COMPARE_GTE, QOF_DATE_MATCH_NORMAL, datetime.datetime.strptime(
926  date_posted_from, "%Y-%m-%d").date())
927  param_list = [SPLIT_TRANS, TRANS_DATE_POSTED]
928  query.add_term(param_list, pred_data, QOF_QUERY_AND)
929 
930  if date_posted_to != None:
931  pred_data = gnucash.gnucash_core.QueryDatePredicate(
932  QOF_COMPARE_LTE, QOF_DATE_MATCH_NORMAL, datetime.datetime.strptime(
933  date_posted_to, "%Y-%m-%d").date())
934  param_list = [SPLIT_TRANS, TRANS_DATE_POSTED]
935  query.add_term(param_list, pred_data, QOF_QUERY_AND)
936 
937  SPLIT_ACCOUNT = 'account'
938  QOF_PARAM_GUID = 'guid'
939 
940  if guid != None:
941  gnucash.gnucash_core.GUIDString(guid, account_guid)
942  query.add_guid_match(
943  [SPLIT_ACCOUNT, QOF_PARAM_GUID], account_guid, QOF_QUERY_AND)
944 
945  splits = []
946 
947  for split in query.run():
948  splits.append(gnucash_simple.splitToDict(
949  gnucash.gnucash_business.Split(instance=split),
950  ['account', 'transaction', 'other_split']))
951 
952  query.destroy()
953 
954  return splits
955 
956 def getInvoices(book, customer, is_paid, is_active, date_due_from,
957  date_due_to):
958 
959  query = gnucash.Query()
960  query.search_for('gncInvoice')
961  query.set_book(book)
962 
963  if is_paid == 0:
964  query.add_boolean_match([INVOICE_IS_PAID], False, QOF_QUERY_AND)
965  elif is_paid == 1:
966  query.add_boolean_match([INVOICE_IS_PAID], True, QOF_QUERY_AND)
967 
968  # active = JOB_IS_ACTIVE
969  if is_active == 0:
970  query.add_boolean_match(['active'], False, QOF_QUERY_AND)
971  elif is_active == 1:
972  query.add_boolean_match(['active'], True, QOF_QUERY_AND)
973 
974  QOF_PARAM_GUID = 'guid'
975  INVOICE_OWNER = 'owner'
976 
977  if customer != None:
978  customer_guid = gnucash.gnucash_core.GUID()
979  gnucash.gnucash_core.GUIDString(customer, customer_guid)
980  query.add_guid_match(
981  [INVOICE_OWNER, QOF_PARAM_GUID], customer_guid, QOF_QUERY_AND)
982 
983  if date_due_from != None:
984  pred_data = gnucash.gnucash_core.QueryDatePredicate(
985  QOF_COMPARE_GTE, 2, datetime.datetime.strptime(
986  date_due_from, "%Y-%m-%d").date())
987  query.add_term(['date_due'], pred_data, QOF_QUERY_AND)
988 
989  if date_due_to != None:
990  pred_data = gnucash.gnucash_core.QueryDatePredicate(
991  QOF_COMPARE_LTE, 2, datetime.datetime.strptime(
992  date_due_to, "%Y-%m-%d").date())
993  query.add_term(['date_due'], pred_data, QOF_QUERY_AND)
994 
995  # return only invoices (1 = invoices)
996  pred_data = gnucash.gnucash_core.QueryInt32Predicate(QOF_COMPARE_EQUAL, 1)
997  query.add_term([INVOICE_TYPE], pred_data, QOF_QUERY_AND)
998 
999  invoices = []
1000 
1001  for result in query.run():
1002  invoices.append(gnucash_simple.invoiceToDict(
1003  gnucash.gnucash_business.Invoice(instance=result)))
1004 
1005  query.destroy()
1006 
1007  return invoices
1008 
1009 def getBills(book, customer, is_paid, is_active, date_opened_from,
1010  date_opened_to):
1011 
1012  query = gnucash.Query()
1013  query.search_for('gncInvoice')
1014  query.set_book(book)
1015 
1016  if is_paid == 0:
1017  query.add_boolean_match([INVOICE_IS_PAID], False, QOF_QUERY_AND)
1018  elif is_paid == 1:
1019  query.add_boolean_match([INVOICE_IS_PAID], True, QOF_QUERY_AND)
1020 
1021  # active = JOB_IS_ACTIVE
1022  if is_active == 0:
1023  query.add_boolean_match(['active'], False, QOF_QUERY_AND)
1024  elif is_active == 1:
1025  query.add_boolean_match(['active'], True, QOF_QUERY_AND)
1026 
1027  QOF_PARAM_GUID = 'guid'
1028  INVOICE_OWNER = 'owner'
1029 
1030  if customer != None:
1031  customer_guid = gnucash.gnucash_core.GUID()
1032  gnucash.gnucash_core.GUIDString(customer, customer_guid)
1033  query.add_guid_match(
1034  [INVOICE_OWNER, QOF_PARAM_GUID], customer_guid, QOF_QUERY_AND)
1035 
1036  if date_opened_from != None:
1037  pred_data = gnucash.gnucash_core.QueryDatePredicate(
1038  QOF_COMPARE_GTE, 2, datetime.datetime.strptime(
1039  date_opened_from, "%Y-%m-%d").date())
1040  query.add_term(['date_opened'], pred_data, QOF_QUERY_AND)
1041 
1042  if date_opened_to != None:
1043  pred_data = gnucash.gnucash_core.QueryDatePredicate(
1044  QOF_COMPARE_LTE, 2, datetime.datetime.strptime(
1045  date_opened_to, "%Y-%m-%d").date())
1046  query.add_term(['date_opened'], pred_data, QOF_QUERY_AND)
1047 
1048  # return only bills (2 = bills)
1049  pred_data = gnucash.gnucash_core.QueryInt32Predicate(QOF_COMPARE_EQUAL, 2)
1050  query.add_term([INVOICE_TYPE], pred_data, QOF_QUERY_AND)
1051 
1052  bills = []
1053 
1054  for result in query.run():
1055  bills.append(gnucash_simple.billToDict(
1056  gnucash.gnucash_business.Bill(instance=result)))
1057 
1058  query.destroy()
1059 
1060  return bills
1061 
1062 def getGnuCashInvoice(book ,id):
1063 
1064  # we don't use book.InvoicelLookupByID(id) as this is identical to
1065  # book.BillLookupByID(id) so can return the same object if they share IDs
1066 
1067  query = gnucash.Query()
1068  query.search_for('gncInvoice')
1069  query.set_book(book)
1070 
1071  # return only invoices (1 = invoices)
1072  pred_data = gnucash.gnucash_core.QueryInt32Predicate(QOF_COMPARE_EQUAL, 1)
1073  query.add_term([INVOICE_TYPE], pred_data, QOF_QUERY_AND)
1074 
1075  INVOICE_ID = 'id'
1076 
1077  pred_data = gnucash.gnucash_core.QueryStringPredicate(
1078  QOF_COMPARE_EQUAL, id, QOF_STRING_MATCH_NORMAL, False)
1079  query.add_term([INVOICE_ID], pred_data, QOF_QUERY_AND)
1080 
1081  invoice = None
1082 
1083  for result in query.run():
1084  invoice = gnucash.gnucash_business.Invoice(instance=result)
1085 
1086  query.destroy()
1087 
1088  return invoice
1089 
1090 def getGnuCashBill(book ,id):
1091 
1092  # we don't use book.InvoicelLookupByID(id) as this is identical to
1093  # book.BillLookupByID(id) so can return the same object if they share IDs
1094 
1095  query = gnucash.Query()
1096  query.search_for('gncInvoice')
1097  query.set_book(book)
1098 
1099  # return only bills (2 = bills)
1100  pred_data = gnucash.gnucash_core.QueryInt32Predicate(QOF_COMPARE_EQUAL, 2)
1101  query.add_term([INVOICE_TYPE], pred_data, QOF_QUERY_AND)
1102 
1103  INVOICE_ID = 'id'
1104 
1105  pred_data = gnucash.gnucash_core.QueryStringPredicate(
1106  QOF_COMPARE_EQUAL, id, QOF_STRING_MATCH_NORMAL, False)
1107  query.add_term([INVOICE_ID], pred_data, QOF_QUERY_AND)
1108 
1109  bill = None
1110 
1111  for result in query.run():
1112  bill = gnucash.gnucash_business.Bill(instance=result)
1113 
1114  query.destroy()
1115 
1116  return bill
1117 
1118 def getInvoice(book, id):
1119 
1120  return gnucash_simple.invoiceToDict(getGnuCashInvoice(book, id))
1121 
1122 def payInvoice(book, id, posted_account_guid, transfer_account_guid,
1123  payment_date, memo, num, auto_pay):
1124 
1125  invoice = getGnuCashInvoice(book, id)
1126 
1127  account_guid2 = gnucash.gnucash_core.GUID()
1128  gnucash.gnucash_core.GUIDString(transfer_account_guid, account_guid2)
1129 
1130  xfer_acc = account_guid2.AccountLookup(session.book)
1131 
1132  invoice.ApplyPayment(None, xfer_acc, invoice.GetTotal(), GncNumeric(0),
1133  datetime.datetime.strptime(payment_date, '%Y-%m-%d'), memo, num)
1134 
1135  return gnucash_simple.invoiceToDict(invoice)
1136 
1137 def payBill(book, id, posted_account_guid, transfer_account_guid, payment_date,
1138  memo, num, auto_pay):
1139 
1140  bill = getGnuCashBill(book, id)
1141 
1142  account_guid = gnucash.gnucash_core.GUID()
1143  gnucash.gnucash_core.GUIDString(transfer_account_guid, account_guid)
1144 
1145  xfer_acc = account_guid.AccountLookup(session.book)
1146 
1147  # We pay the negative total as the bill as this seemed to cause issues
1148  # with the split not being set correctly and not being marked as paid
1149  bill.ApplyPayment(None, xfer_acc, bill.GetTotal().neg(), GncNumeric(0),
1150  datetime.datetime.strptime(payment_date, '%Y-%m-%d'), memo, num)
1151 
1152  return gnucash_simple.billToDict(bill)
1153 
1154 def getBill(book, id):
1155 
1156  return gnucash_simple.billToDict(getGnuCashBill(book, id))
1157 
1158 def addVendor(book, id, currency_mnumonic, name, contact, address_line_1,
1159  address_line_2, address_line_3, address_line_4, phone, fax, email):
1160 
1161  if name == '':
1162  raise Error('NoVendorName', 'A name must be entered for this company',
1163  {'field': 'name'})
1164 
1165  if (address_line_1 == ''
1166  and address_line_2 == ''
1167  and address_line_3 == ''
1168  and address_line_4 == ''):
1169  raise Error('NoVendorAddress',
1170  'An address must be entered for this company',
1171  {'field': 'address'})
1172 
1173  commod_table = book.get_table()
1174  currency = commod_table.lookup('CURRENCY', currency_mnumonic)
1175 
1176  if currency is None:
1177  raise Error('InvalidVendorCurrency',
1178  'A valid currency must be supplied for this vendor',
1179  {'field': 'currency'})
1180 
1181  if id is None:
1182  id = book.VendorNextID()
1183 
1184  vendor = Vendor(session.book, id, currency, name)
1185 
1186  address = vendor.GetAddr()
1187  address.SetName(contact)
1188  address.SetAddr1(address_line_1)
1189  address.SetAddr2(address_line_2)
1190  address.SetAddr3(address_line_3)
1191  address.SetAddr4(address_line_4)
1192  address.SetPhone(phone)
1193  address.SetFax(fax)
1194  address.SetEmail(email)
1195 
1196  return gnucash_simple.vendorToDict(vendor)
1197 
1198 def addCustomer(book, id, currency_mnumonic, name, contact, address_line_1,
1199  address_line_2, address_line_3, address_line_4, phone, fax, email):
1200 
1201  if name == '':
1202  raise Error('NoCustomerName',
1203  'A name must be entered for this company', {'field': 'name'})
1204 
1205  if (address_line_1 == ''
1206  and address_line_2 == ''
1207  and address_line_3 == ''
1208  and address_line_4 == ''):
1209  raise Error('NoCustomerAddress',
1210  'An address must be entered for this company',
1211  {'field': 'address'})
1212 
1213  commod_table = book.get_table()
1214  currency = commod_table.lookup('CURRENCY', currency_mnumonic)
1215 
1216  if currency is None:
1217  raise Error('InvalidCustomerCurrency',
1218  'A valid currency must be supplied for this customer',
1219  {'field': 'currency'})
1220 
1221  if id is None:
1222  id = book.CustomerNextID()
1223 
1224  customer = Customer(session.book, id, currency, name)
1225 
1226  address = customer.GetAddr()
1227  address.SetName(contact)
1228  address.SetAddr1(address_line_1)
1229  address.SetAddr2(address_line_2)
1230  address.SetAddr3(address_line_3)
1231  address.SetAddr4(address_line_4)
1232  address.SetPhone(phone)
1233  address.SetFax(fax)
1234  address.SetEmail(email)
1235 
1236  return gnucash_simple.customerToDict(customer)
1237 
1238 def updateCustomer(book, id, name, contact, address_line_1, address_line_2,
1239  address_line_3, address_line_4, phone, fax, email):
1240 
1241  customer = book.CustomerLookupByID(id)
1242 
1243  if customer is None:
1244  raise Error('NoCustomer', 'A customer with this ID does not exist',
1245  {'field': 'id'})
1246 
1247  if name == '':
1248  raise Error('NoCustomerName',
1249  'A name must be entered for this company', {'field': 'name'})
1250 
1251  if (address_line_1 == ''
1252  and address_line_2 == ''
1253  and address_line_3 == ''
1254  and address_line_4 == ''):
1255  raise Error('NoCustomerAddress',
1256  'An address must be entered for this company',
1257  {'field': 'address'})
1258 
1259  customer.SetName(name)
1260 
1261  address = customer.GetAddr()
1262  address.SetName(contact)
1263  address.SetAddr1(address_line_1)
1264  address.SetAddr2(address_line_2)
1265  address.SetAddr3(address_line_3)
1266  address.SetAddr4(address_line_4)
1267  address.SetPhone(phone)
1268  address.SetFax(fax)
1269  address.SetEmail(email)
1270 
1271  return gnucash_simple.customerToDict(customer)
1272 
1273 def addInvoice(book, id, customer_id, currency_mnumonic, date_opened, notes):
1274 
1275  customer = book.CustomerLookupByID(customer_id)
1276 
1277  if customer is None:
1278  raise Error('NoCustomer',
1279  'A customer with this ID does not exist', {'field': 'id'})
1280 
1281  if id is None:
1282  id = book.InvoiceNextID(customer)
1283 
1284  try:
1285  date_opened = datetime.datetime.strptime(date_opened, "%Y-%m-%d")
1286  except ValueError:
1287  raise Error('InvalidDateOpened',
1288  'The date opened must be provided in the form YYYY-MM-DD',
1289  {'field': 'date_opened'})
1290 
1291  if currency_mnumonic is None:
1292  currency_mnumonic = customer.GetCurrency().get_mnemonic()
1293 
1294  commod_table = book.get_table()
1295  currency = commod_table.lookup('CURRENCY', currency_mnumonic)
1296 
1297  if currency is None:
1298  raise Error('InvalidCustomerCurrency',
1299  'A valid currency must be supplied for this customer',
1300  {'field': 'currency'})
1301 
1302  invoice = Invoice(book, id, currency, customer, date_opened.date())
1303 
1304  invoice.SetNotes(notes)
1305 
1306  return gnucash_simple.invoiceToDict(invoice)
1307 
1308 def updateInvoice(book, id, customer_id, currency_mnumonic, date_opened,
1309  notes, posted, posted_account_guid, posted_date, due_date, posted_memo,
1310  posted_accumulatesplits, posted_autopay):
1311 
1312  invoice = getGnuCashInvoice(book, id)
1313 
1314  if invoice is None:
1315  raise Error('NoInvoice',
1316  'An invoice with this ID does not exist',
1317  {'field': 'id'})
1318 
1319  customer = book.CustomerLookupByID(customer_id)
1320 
1321  if customer is None:
1322  raise Error('NoCustomer', 'A customer with this ID does not exist',
1323  {'field': 'customer_id'})
1324 
1325  try:
1326  date_opened = datetime.datetime.strptime(date_opened, "%Y-%m-%d")
1327  except ValueError:
1328  raise Error('InvalidDateOpened',
1329  'The date opened must be provided in the form YYYY-MM-DD',
1330  {'field': 'date_opened'})
1331 
1332  if posted_date == '':
1333  if posted == 1:
1334  raise Error('NoDatePosted',
1335  'The date posted must be supplied when posted=1',
1336  {'field': 'date_posted'})
1337  else:
1338  try:
1339  posted_date = datetime.datetime.strptime(posted_date, "%Y-%m-%d")
1340  except ValueError:
1341  raise Error('InvalidDatePosted',
1342  'The date posted must be provided in the form YYYY-MM-DD',
1343  {'field': 'posted_date'})
1344 
1345  if due_date == '':
1346  if posted == 1:
1347  raise Error('NoDatePosted',
1348  'The due date must be supplied when posted=1',
1349  {'field': 'date_posted'})
1350  else:
1351  try:
1352  due_date = datetime.datetime.strptime(due_date, "%Y-%m-%d")
1353  except ValueError:
1354  raise Error('InvalidDatePosted',
1355  'The due date must be provided in the form YYYY-MM-DD',
1356  {'field': 'due_date'})
1357 
1358  if posted_account_guid == '':
1359  if posted == 1:
1360  raise Error('NoPostedAccountGuid',
1361  'The posted account GUID must be supplied when posted=1',
1362  {'field': 'posted_account_guid'})
1363  else:
1364  guid = gnucash.gnucash_core.GUID()
1365  gnucash.gnucash_core.GUIDString(posted_account_guid, guid)
1366 
1367  posted_account = guid.AccountLookup(book)
1368 
1369  if posted_account is None:
1370  raise Error('NoAccount',
1371  'No account exists with the posted account GUID',
1372  {'field': 'posted_account_guid'})
1373 
1374  invoice.SetOwner(customer)
1375  invoice.SetDateOpened(date_opened)
1376  invoice.SetNotes(notes)
1377 
1378  # post if currently unposted and posted=1
1379  if (invoice.GetDatePosted().strftime('%Y-%m-%d') == '1970-01-01'
1380  and posted == 1):
1381  invoice.PostToAccount(posted_account, posted_date, due_date,
1382  posted_memo, posted_accumulatesplits, posted_autopay)
1383 
1384  return gnucash_simple.invoiceToDict(invoice)
1385 
1386 def updateBill(book, id, vendor_id, currency_mnumonic, date_opened, notes,
1387  posted, posted_account_guid, posted_date, due_date, posted_memo,
1388  posted_accumulatesplits, posted_autopay):
1389 
1390  bill = getGnuCashBill(book, id)
1391 
1392  if bill is None:
1393  raise Error('NoBill', 'A bill with this ID does not exist',
1394  {'field': 'id'})
1395 
1396  vendor = book.VendorLookupByID(vendor_id)
1397 
1398  if vendor is None:
1399  raise Error('NoVendor',
1400  'A vendor with this ID does not exist',
1401  {'field': 'vendor_id'})
1402 
1403  try:
1404  date_opened = datetime.datetime.strptime(date_opened, "%Y-%m-%d")
1405  except ValueError:
1406  raise Error('InvalidDateOpened',
1407  'The date opened must be provided in the form YYYY-MM-DD',
1408  {'field': 'date_opened'})
1409 
1410  if posted_date == '':
1411  if posted == 1:
1412  raise Error('NoDatePosted',
1413  'The date posted must be supplied when posted=1',
1414  {'field': 'date_posted'})
1415  else:
1416  try:
1417  posted_date = datetime.datetime.strptime(posted_date, "%Y-%m-%d")
1418  except ValueError:
1419  raise Error('InvalidDatePosted',
1420  'The date posted must be provided in the form YYYY-MM-DD',
1421  {'field': 'posted_date'})
1422 
1423  if due_date == '':
1424  if posted == 1:
1425  raise Error('NoDatePosted',
1426  'The due date must be supplied when posted=1',
1427  {'field': 'date_posted'})
1428  else:
1429  try:
1430  due_date = datetime.datetime.strptime(due_date, "%Y-%m-%d")
1431  except ValueError:
1432  raise Error('InvalidDatePosted',
1433  'The due date must be provided in the form YYYY-MM-DD',
1434  {'field': 'due_date'})
1435 
1436  if posted_account_guid == '':
1437  if posted == 1:
1438  raise Error('NoPostedAccountGuid',
1439  'The posted account GUID must be supplied when posted=1',
1440  {'field': 'posted_account_guid'})
1441  else:
1442  guid = gnucash.gnucash_core.GUID()
1443  gnucash.gnucash_core.GUIDString(posted_account_guid, guid)
1444 
1445  posted_account = guid.AccountLookup(book)
1446 
1447  if posted_account is None:
1448  raise Error('NoAccount',
1449  'No account exists with the posted account GUID',
1450  {'field': 'posted_account_guid'})
1451 
1452  bill.SetOwner(vendor)
1453  bill.SetDateOpened(date_opened)
1454  bill.SetNotes(notes)
1455 
1456  # post if currently unposted and posted=1
1457  if bill.GetDatePosted().strftime('%Y-%m-%d') == '1970-01-01' and posted == 1:
1458  bill.PostToAccount(posted_account, posted_date, due_date, posted_memo,
1459  posted_accumulatesplits, posted_autopay)
1460 
1461  return gnucash_simple.billToDict(bill)
1462 
1463 def addEntry(book, invoice_id, date, description, account_guid, quantity, price):
1464 
1465  invoice = getGnuCashInvoice(book, invoice_id)
1466 
1467  if invoice is None:
1468  raise Error('NoInvoice',
1469  'No invoice exists with this ID', {'field': 'invoice_id'})
1470 
1471  try:
1472  date = datetime.datetime.strptime(date, "%Y-%m-%d")
1473  except ValueError:
1474  raise Error('InvalidDateOpened',
1475  'The date opened must be provided in the form YYYY-MM-DD',
1476  {'field': 'date'})
1477 
1478  guid = gnucash.gnucash_core.GUID()
1479  gnucash.gnucash_core.GUIDString(account_guid, guid)
1480 
1481  account = guid.AccountLookup(book)
1482 
1483  if account is None:
1484  raise Error('NoAccount', 'No account exists with this GUID',
1485  {'field': 'account_guid'})
1486 
1487  try:
1488  quantity = Decimal(quantity).quantize(Decimal('.01'))
1489  except ArithmeticError:
1490  raise Error('InvalidQuantity', 'This quantity is not valid',
1491  {'field': 'quantity'})
1492 
1493  try:
1494  price = Decimal(price).quantize(Decimal('.01'))
1495  except ArithmeticError:
1496  raise Error('InvalidPrice', 'This price is not valid',
1497  {'field': 'price'})
1498 
1499  entry = Entry(book, invoice, date.date())
1500  entry.SetDateEntered(datetime.datetime.now())
1501  entry.SetDescription(description)
1502  entry.SetInvAccount(account)
1503  entry.SetQuantity(gnc_numeric_from_decimal(quantity))
1504  entry.SetInvPrice(gnc_numeric_from_decimal(price))
1505 
1506  return gnucash_simple.entryToDict(entry)
1507 
1508 def addBillEntry(book, bill_id, date, description, account_guid, quantity,
1509  price):
1510 
1511  bill = getGnuCashBill(book,bill_id)
1512 
1513  if bill is None:
1514  raise Error('NoBill', 'No bill exists with this ID',
1515  {'field': 'bill_id'})
1516 
1517  try:
1518  date = datetime.datetime.strptime(date, "%Y-%m-%d")
1519  except ValueError:
1520  raise Error('InvalidDateOpened',
1521  'The date opened must be provided in the form YYYY-MM-DD',
1522  {'field': 'date'})
1523 
1524  guid = gnucash.gnucash_core.GUID()
1525  gnucash.gnucash_core.GUIDString(account_guid, guid)
1526 
1527  account = guid.AccountLookup(book)
1528 
1529  if account is None:
1530  raise Error('NoAccount', 'No account exists with this GUID',
1531  {'field': 'account_guid'})
1532 
1533  try:
1534  quantity = Decimal(quantity).quantize(Decimal('.01'))
1535  except ArithmeticError:
1536  raise Error('InvalidQuantity', 'This quantity is not valid',
1537  {'field': 'quantity'})
1538 
1539  try:
1540  price = Decimal(price).quantize(Decimal('.01'))
1541  except ArithmeticError:
1542  raise Error('InvalidPrice', 'This price is not valid',
1543  {'field': 'price'})
1544 
1545  entry = Entry(book, bill, date.date())
1546  entry.SetDateEntered(datetime.datetime.now())
1547  entry.SetDescription(description)
1548  entry.SetBillAccount(account)
1549  entry.SetQuantity(gnc_numeric_from_decimal(quantity))
1550  entry.SetBillPrice(gnc_numeric_from_decimal(price))
1551 
1552  return gnucash_simple.entryToDict(entry)
1553 
1554 def getEntry(book, entry_guid):
1555 
1556  guid = gnucash.gnucash_core.GUID()
1557  gnucash.gnucash_core.GUIDString(entry_guid, guid)
1558 
1559  entry = book.EntryLookup(guid)
1560 
1561  if entry is None:
1562  return None
1563  else:
1564  return gnucash_simple.entryToDict(entry)
1565 
1566 def updateEntry(book, entry_guid, date, description, account_guid, quantity,
1567  price):
1568 
1569  guid = gnucash.gnucash_core.GUID()
1570  gnucash.gnucash_core.GUIDString(entry_guid, guid)
1571 
1572  entry = book.EntryLookup(guid)
1573 
1574  if entry is None:
1575  raise Error('NoEntry', 'No entry exists with this GUID',
1576  {'field': 'entry_guid'})
1577 
1578  try:
1579  date = datetime.datetime.strptime(date, "%Y-%m-%d")
1580  except ValueError:
1581  raise Error('InvalidDateOpened',
1582  'The date opened must be provided in the form YYYY-MM-DD',
1583  {'field': 'date'})
1584 
1585  gnucash.gnucash_core.GUIDString(account_guid, guid)
1586 
1587  account = guid.AccountLookup(book)
1588 
1589  if account is None:
1590  raise Error('NoAccount', 'No account exists with this GUID',
1591  {'field': 'account_guid'})
1592 
1593  entry.SetDate(date.date())
1594  entry.SetDateEntered(datetime.datetime.now())
1595  entry.SetDescription(description)
1596  entry.SetInvAccount(account)
1597  entry.SetQuantity(
1598  gnc_numeric_from_decimal(Decimal(quantity).quantize(Decimal('.01'))))
1599  entry.SetInvPrice(
1600  gnc_numeric_from_decimal(Decimal(price).quantize(Decimal('.01'))))
1601 
1602  return gnucash_simple.entryToDict(entry)
1603 
1604 def deleteEntry(book, entry_guid):
1605 
1606  guid = gnucash.gnucash_core.GUID()
1607  gnucash.gnucash_core.GUIDString(entry_guid, guid)
1608 
1609  entry = book.EntryLookup(guid)
1610 
1611  invoice = entry.GetInvoice()
1612  bill = entry.GetBill()
1613 
1614  if invoice != None and entry != None:
1615  invoice.RemoveEntry(entry)
1616  elif bill != None and entry != None:
1617  bill.RemoveEntry(entry)
1618 
1619  if entry != None:
1620  entry.Destroy()
1621 
1622 def deleteTransaction(book, transaction_guid):
1623 
1624  guid = gnucash.gnucash_core.GUID()
1625  gnucash.gnucash_core.GUIDString(transaction_guid, guid)
1626 
1627  transaction = guid.TransLookup(book)
1628 
1629  if transaction != None :
1630  transaction.Destroy()
1631 
1632 def addBill(book, id, vendor_id, currency_mnumonic, date_opened, notes):
1633 
1634  vendor = book.VendorLookupByID(vendor_id)
1635 
1636  if vendor is None:
1637  raise Error('NoVendor', 'A vendor with this ID does not exist',
1638  {'field': 'id'})
1639 
1640  if id is None:
1641  id = book.BillNextID(vendor)
1642 
1643  try:
1644  date_opened = datetime.datetime.strptime(date_opened, "%Y-%m-%d")
1645  except ValueError:
1646  raise Error('InvalidVendorDateOpened',
1647  'The date opened must be provided in the form YYYY-MM-DD',
1648  {'field': 'date_opened'})
1649 
1650  if currency_mnumonic is None:
1651  currency_mnumonic = vendor.GetCurrency().get_mnemonic()
1652 
1653  commod_table = book.get_table()
1654  currency = commod_table.lookup('CURRENCY', currency_mnumonic)
1655 
1656  if currency is None:
1657  raise Error('InvalidVendorCurrency',
1658  'A valid currency must be supplied for this vendor',
1659  {'field': 'currency'})
1660 
1661  bill = Bill(book, id, currency, vendor, date_opened.date())
1662 
1663  bill.SetNotes(notes)
1664 
1665  return gnucash_simple.billToDict(bill)
1666 
1667 def addAccount(book, name, currency_mnumonic, account_guid):
1668 
1669  from gnucash.gnucash_core_c import \
1670  ACCT_TYPE_ASSET, ACCT_TYPE_RECEIVABLE, ACCT_TYPE_INCOME, \
1671  GNC_OWNER_CUSTOMER, ACCT_TYPE_LIABILITY
1672 
1673  root_account = book.get_root_account()
1674 
1675  commod_table = book.get_table()
1676  currency = commod_table.lookup('CURRENCY', currency_mnumonic)
1677 
1678  if currency is None:
1679  raise Error('InvalidCustomerCurrency',
1680  'A valid currency must be supplied for this customer',
1681  {'field': 'currency'})
1682 
1683  account = Account(book)
1684  root_account.append_child(root_account)
1685  account.SetName(name)
1686  account.SetType(ACCT_TYPE_ASSET)
1687  account.SetCommodity(currency)
1688 
1689 def addTransaction(book, num, description, date_posted, currency_mnumonic, splits):
1690 
1691  transaction = Transaction(book)
1692 
1693  transaction.BeginEdit()
1694 
1695  commod_table = book.get_table()
1696  currency = commod_table.lookup('CURRENCY', currency_mnumonic)
1697 
1698  if currency is None:
1699  raise Error('InvalidTransactionCurrency',
1700  'A valid currency must be supplied for this transaction',
1701  {'field': 'currency'})
1702 
1703  try:
1704  date_posted = datetime.datetime.strptime(date_posted, "%Y-%m-%d")
1705  except ValueError:
1706  raise Error('InvalidDatePosted',
1707  'The date posted must be provided in the form YYYY-MM-DD',
1708  {'field': 'date_posted'})
1709 
1710 
1711  for split_values in splits:
1712  account_guid = gnucash.gnucash_core.GUID()
1713  gnucash.gnucash_core.GUIDString(split_values['account_guid'], account_guid)
1714 
1715  account = account_guid.AccountLookup(book)
1716 
1717  if account is None:
1718  raise Error('InvalidSplitAccount',
1719  'A valid account must be supplied for this split',
1720  {'field': 'account'})
1721 
1722  split = Split(book)
1723  split.SetValue(GncNumeric(split_values['value'], 100))
1724  split.SetAccount(account)
1725  split.SetParent(transaction)
1726 
1727  transaction.SetCurrency(currency)
1728  transaction.SetDescription(description)
1729  transaction.SetNum(num)
1730 
1731  transaction.SetDatePostedTS(date_posted)
1732 
1733  transaction.CommitEdit()
1734 
1735  return gnucash_simple.transactionToDict(transaction, ['splits'])
1736 
1737 def getTransaction(book, transaction_guid):
1738 
1739  guid = gnucash.gnucash_core.GUID()
1740  gnucash.gnucash_core.GUIDString(transaction_guid, guid)
1741 
1742  transaction = guid.TransLookup(book)
1743 
1744  if transaction is None:
1745  return None
1746  else:
1747  return gnucash_simple.transactionToDict(transaction, ['splits'])
1748 
1749 def editTransaction(book, transaction_guid, num, description, date_posted,
1750  currency_mnumonic, splits):
1751 
1752  guid = gnucash.gnucash_core.GUID()
1753  gnucash.gnucash_core.GUIDString(transaction_guid, guid)
1754 
1755  transaction = guid.TransLookup(book)
1756 
1757  if transaction is None:
1758  raise Error('NoCustomer',
1759  'A transaction with this GUID does not exist',
1760  {'field': 'guid'})
1761 
1762  transaction.BeginEdit()
1763 
1764  commod_table = book.get_table()
1765  currency = commod_table.lookup('CURRENCY', currency_mnumonic)
1766 
1767  if currency is None:
1768  raise Error('InvalidTransactionCurrency',
1769  'A valid currency must be supplied for this transaction',
1770  {'field': 'currency'})
1771 
1772 
1773  try:
1774  date_posted = datetime.datetime.strptime(date_posted, "%Y-%m-%d")
1775  except ValueError:
1776  raise Error('InvalidDatePosted',
1777  'The date posted must be provided in the form YYYY-MM-DD',
1778  {'field': 'date_posted'})
1779 
1780  for split_values in splits:
1781 
1782  split_guid = gnucash.gnucash_core.GUID()
1783  gnucash.gnucash_core.GUIDString(split_values['guid'], split_guid)
1784 
1785  split = split_guid.SplitLookup(book)
1786 
1787  if split is None:
1788  raise Error('InvalidSplitGuid',
1789  'A valid guid must be supplied for this split',
1790  {'field': 'guid'})
1791 
1792  account_guid = gnucash.gnucash_core.GUID()
1793  gnucash.gnucash_core.GUIDString(
1794  split_values['account_guid'], account_guid)
1795 
1796  account = account_guid.AccountLookup(book)
1797 
1798  if account is None:
1799  raise Error('InvalidSplitAccount',
1800  'A valid account must be supplied for this split',
1801  {'field': 'account'})
1802 
1803  split.SetValue(GncNumeric(split_values['value'], 100))
1804  split.SetAccount(account)
1805  split.SetParent(transaction)
1806 
1807  transaction.SetCurrency(currency)
1808  transaction.SetDescription(description)
1809  transaction.SetNum(num)
1810 
1811  transaction.SetDatePostedTS(date_posted)
1812 
1813  transaction.CommitEdit()
1814 
1815  return gnucash_simple.transactionToDict(transaction, ['splits'])
1816 
1817 def gnc_numeric_from_decimal(decimal_value):
1818  sign, digits, exponent = decimal_value.as_tuple()
1819 
1820  # convert decimal digits to a fractional numerator
1821  # equivalent to
1822  # numerator = int(''.join(digits))
1823  # but without the wated conversion to string and back,
1824  # this is probably the same algorithm int() uses
1825  numerator = 0
1826  TEN = int(Decimal(0).radix()) # this is always 10
1827  numerator_place_value = 1
1828  # add each digit to the final value multiplied by the place value
1829  # from least significant to most significant
1830  for i in range(len(digits)-1,-1,-1):
1831  numerator += digits[i] * numerator_place_value
1832  numerator_place_value *= TEN
1833 
1834  if decimal_value.is_signed():
1835  numerator = -numerator
1836 
1837  # if the exponent is negative, we use it to set the denominator
1838  if exponent < 0 :
1839  denominator = TEN ** (-exponent)
1840  # if the exponent isn't negative, we bump up the numerator
1841  # and set the denominator to 1
1842  else:
1843  numerator *= TEN ** exponent
1844  denominator = 1
1845 
1846  return GncNumeric(numerator, denominator)
1847 
1848 def shutdown():
1849  session.save()
1850  session.end()
1851  session.destroy()
1852  print('Shutdown')
1853 
1854 class Error(Exception):
1855  """Base class for exceptions in this module."""
1856  def __init__(self, type, message, data):
1857  self.type = type
1858  self.message = message
1859  self.data = data
1860 
1861 try:
1862  options, arguments = getopt.getopt(sys.argv[1:], 'nh:', ['host=', 'new='])
1863 except getopt.GetoptError as err:
1864  print(str(err)) # will print something like "option -a not recognized"
1865  print('Usage: python-rest.py <connection string>')
1866  sys.exit(2)
1867 
1868 if len(arguments) == 0:
1869  print('Usage: python-rest.py <connection string>')
1870  sys.exit(2)
1871 
1872 #set default host for Flask
1873 host = '127.0.0.1'
1874 
1875 #allow host option to be changed
1876 for option, value in options:
1877  if option in ("-h", "--host"):
1878  host = value
1879 
1880 is_new = False
1881 
1882 # allow a new database to be used
1883 for option, value in options:
1884  if option in ("-n", "--new"):
1885  is_new = True
1886 
1887 
1888 #start gnucash session base on connection string argument
1889 if is_new:
1890  session = gnucash.Session(arguments[0], SessionOpenMode.SESSION_NEW_STORE)
1891 
1892  # seem to get errors if we use the session directly, so save it and
1893  #destroy it so it's no longer new
1894 
1895  session.save()
1896  session.end()
1897  session.destroy()
1898 
1899 # unsure about SESSION_BREAK_LOCK - it used to be ignore_lock=True
1900 session = gnucash.Session(arguments[0], SessionOpenMode.SESSION_BREAK_LOCK)
1901 
1902 # register method to close gnucash connection gracefully
1903 atexit.register(shutdown)
1904 
1905 app.debug = False
1906 
1907 # log to console
1908 if not app.debug:
1909  import logging
1910  from logging import StreamHandler
1911  stream_handler = StreamHandler()
1912  stream_handler.setLevel(logging.ERROR)
1913  app.logger.addHandler(stream_handler)
1914 
1915 # start Flask server
1916 app.run(host=host)
STRUCTS.
def transactionToDict(transaction, entities)
def invoiceToDict(invoice)
def splitToDict(split, entities)
def vendorToDict(vendor)
def entryToDict(entry)
def billToDict(bill)
def customerToDict(customer)
def accountToDict(account)