mirror of
https://github.com/Gnucash/gnucash.git
synced 2025-02-25 18:55:30 -06:00
1916 lines
60 KiB
Python
1916 lines
60 KiB
Python
#!/usr/bin/env python3
|
|
|
|
'''
|
|
|
|
gnucash_rest.py -- A Flask app which responds to REST requests
|
|
with JSON responses
|
|
|
|
Copyright (C) 2013 Tom Lofts <dev@loftx.co.uk>
|
|
|
|
This program is free software; you can redistribute it and/or
|
|
modify it under the terms of the GNU General Public License as
|
|
published by the Free Software Foundation; either version 2 of
|
|
the License, or (at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, contact:
|
|
|
|
Free Software Foundation Voice: +1-617-542-5942
|
|
51 Franklin Street, Fifth Floor Fax: +1-617-542-2652
|
|
Boston, MA 02110-1301, USA gnu@gnu.org
|
|
|
|
@author Tom Lofts <dev@loftx.co.uk>
|
|
|
|
'''
|
|
|
|
import gnucash
|
|
import gnucash_simple
|
|
import json
|
|
import atexit
|
|
from flask import Flask, abort, request, Response
|
|
import sys
|
|
import getopt
|
|
|
|
from decimal import Decimal
|
|
|
|
from gnucash.gnucash_business import Vendor, Bill, Entry, GncNumeric, \
|
|
Customer, Invoice, Split, Account, Transaction
|
|
|
|
import datetime
|
|
|
|
from gnucash import \
|
|
QOF_QUERY_AND, \
|
|
QOF_QUERY_OR, \
|
|
QOF_QUERY_NAND, \
|
|
QOF_QUERY_NOR, \
|
|
QOF_QUERY_XOR
|
|
|
|
from gnucash import \
|
|
QOF_STRING_MATCH_NORMAL, \
|
|
QOF_STRING_MATCH_CASEINSENSITIVE
|
|
|
|
from gnucash import \
|
|
QOF_COMPARE_LT, \
|
|
QOF_COMPARE_LTE, \
|
|
QOF_COMPARE_EQUAL, \
|
|
QOF_COMPARE_GT, \
|
|
QOF_COMPARE_GTE, \
|
|
QOF_COMPARE_NEQ
|
|
|
|
from gnucash import \
|
|
INVOICE_TYPE
|
|
|
|
from gnucash import \
|
|
INVOICE_IS_PAID
|
|
|
|
from gnucash import SessionOpenMode
|
|
|
|
app = Flask(__name__)
|
|
app.debug = True
|
|
|
|
@app.route('/accounts', methods=['GET', 'POST'])
|
|
def api_accounts():
|
|
|
|
if request.method == 'GET':
|
|
|
|
accounts = getAccounts(session.book)
|
|
|
|
return Response(json.dumps(accounts), mimetype='application/json')
|
|
|
|
elif request.method == 'POST':
|
|
|
|
try:
|
|
account = addAccount(session.books)
|
|
except Error as error:
|
|
return Response(json.dumps({'errors': [{'type' : error.type,
|
|
'message': error.message, 'data': error.data}]}), status=400,
|
|
mimetype='application/json')
|
|
else:
|
|
return Response(json.dumps(account), status=201,
|
|
mimetype='application/json')
|
|
|
|
else:
|
|
abort(405)
|
|
|
|
@app.route('/accounts/<guid>', methods=['GET'])
|
|
def api_account(guid):
|
|
|
|
account = getAccount(session.book, guid)
|
|
|
|
if account is None:
|
|
abort(404)
|
|
else:
|
|
return Response(json.dumps(account), mimetype='application/json')
|
|
|
|
@app.route('/accounts/<guid>/splits', methods=['GET'])
|
|
def api_account_splits(guid):
|
|
|
|
date_posted_from = request.args.get('date_posted_from', None)
|
|
date_posted_to = request.args.get('date_posted_to', None)
|
|
|
|
# check account exists
|
|
account = getAccount(session.book, guid)
|
|
|
|
if account is None:
|
|
abort(404)
|
|
|
|
splits = getAccountSplits(session.book, guid, date_posted_from,
|
|
date_posted_to)
|
|
|
|
return Response(json.dumps(splits), mimetype='application/json')
|
|
|
|
|
|
@app.route('/transactions', methods=['POST'])
|
|
def api_transactions():
|
|
|
|
if request.method == 'POST':
|
|
|
|
currency = str(request.form.get('currency', ''))
|
|
description = str(request.form.get('description', ''))
|
|
num = str(request.form.get('num', ''))
|
|
date_posted = str(request.form.get('date_posted', ''))
|
|
|
|
splitvalue1 = int(request.form.get('splitvalue1', ''))
|
|
splitaccount1 = str(request.form.get('splitaccount1', ''))
|
|
splitvalue2 = int(request.form.get('splitvalue2', ''))
|
|
splitaccount2 = str(request.form.get('splitaccount2', ''))
|
|
|
|
splits = [
|
|
{'value': splitvalue1, 'account_guid': splitaccount1},
|
|
{'value': splitvalue2, 'account_guid': splitaccount2}]
|
|
|
|
try:
|
|
transaction = addTransaction(session.book, num, description,
|
|
date_posted, currency, splits)
|
|
except Error as error:
|
|
return Response(json.dumps({'errors': [{'type' : error.type,
|
|
'message': error.message, 'data': error.data}]}), status=400,
|
|
mimetype='application/json')
|
|
else:
|
|
return Response(json.dumps(transaction), status=201,
|
|
mimetype='application/json')
|
|
|
|
else:
|
|
abort(405)
|
|
|
|
@app.route('/transactions/<guid>', methods=['GET', 'POST', 'DELETE'])
|
|
def api_transaction(guid):
|
|
|
|
if request.method == 'GET':
|
|
|
|
transaction = getTransaction(session.book, guid)
|
|
|
|
if transaction is None:
|
|
abort(404)
|
|
|
|
return Response(json.dumps(transaction), mimetype='application/json')
|
|
|
|
elif request.method == 'POST':
|
|
|
|
currency = str(request.form.get('currency', ''))
|
|
description = str(request.form.get('description', ''))
|
|
num = str(request.form.get('num', ''))
|
|
date_posted = str(request.form.get('date_posted', ''))
|
|
|
|
splitguid1 = str(request.form.get('splitguid1', ''))
|
|
splitvalue1 = int(request.form.get('splitvalue1', ''))
|
|
splitaccount1 = str(request.form.get('splitaccount1', ''))
|
|
splitguid2 = str(request.form.get('splitguid2', ''))
|
|
splitvalue2 = int(request.form.get('splitvalue2', ''))
|
|
splitaccount2 = str(request.form.get('splitaccount2', ''))
|
|
|
|
splits = [
|
|
{'guid': splitguid1,
|
|
'value': splitvalue1,
|
|
'account_guid': splitaccount1},
|
|
{'guid': splitguid2,
|
|
'value': splitvalue2,
|
|
'account_guid': splitaccount2}
|
|
]
|
|
|
|
try:
|
|
transaction = editTransaction(session.book, guid, num, description,
|
|
date_posted, currency, splits)
|
|
except Error as error:
|
|
return Response(json.dumps({'errors': [{'type' : error.type,
|
|
'message': error.message, 'data': error.data}]}), status=400, mimetype='application/json')
|
|
else:
|
|
return Response(json.dumps(transaction), status=200,
|
|
mimetype='application/json')
|
|
|
|
elif request.method == 'DELETE':
|
|
|
|
deleteTransaction(session.book, guid)
|
|
|
|
return Response('', status=200, mimetype='application/json')
|
|
|
|
else:
|
|
abort(405)
|
|
|
|
@app.route('/bills', methods=['GET', 'POST'])
|
|
def api_bills():
|
|
|
|
if request.method == 'GET':
|
|
|
|
is_paid = request.args.get('is_paid', None)
|
|
is_active = request.args.get('is_active', None)
|
|
date_opened_to = request.args.get('date_opened_to', None)
|
|
date_opened_from = request.args.get('date_opened_from', None)
|
|
|
|
if is_paid == '1':
|
|
is_paid = 1
|
|
elif is_paid == '0':
|
|
is_paid = 0
|
|
else:
|
|
is_paid = None
|
|
|
|
if is_active == '1':
|
|
is_active = 1
|
|
elif is_active == '0':
|
|
is_active = 0
|
|
else:
|
|
is_active = None
|
|
|
|
bills = getBills(session.book, None, is_paid, is_active,
|
|
date_opened_from, date_opened_to)
|
|
|
|
return Response(json.dumps(bills), mimetype='application/json')
|
|
|
|
elif request.method == 'POST':
|
|
|
|
id = str(request.form.get('id', None))
|
|
|
|
if id == '':
|
|
id = None
|
|
elif id != None:
|
|
id = str(id)
|
|
|
|
vendor_id = str(request.form.get('vendor_id', ''))
|
|
currency = str(request.form.get('currency', ''))
|
|
date_opened = str(request.form.get('date_opened', ''))
|
|
notes = str(request.form.get('notes', ''))
|
|
|
|
try:
|
|
bill = addBill(session.book, id, vendor_id, currency, date_opened,
|
|
notes)
|
|
except Error as error:
|
|
# handle incorrect parameter errors
|
|
return Response(json.dumps({'errors': [{'type' : error.type,
|
|
'message': error.message, 'data': error.data}]}), status=400, mimetype='application/json')
|
|
else:
|
|
return Response(json.dumps(bill), status=201,
|
|
mimetype='application/json')
|
|
|
|
else:
|
|
abort(405)
|
|
|
|
@app.route('/bills/<id>', methods=['GET', 'POST', 'PAY'])
|
|
def api_bill(id):
|
|
|
|
if request.method == 'GET':
|
|
|
|
bill = getBill(session.book, id)
|
|
|
|
if bill is None:
|
|
abort(404)
|
|
else:
|
|
return Response(json.dumps(bill), mimetype='application/json')
|
|
|
|
elif request.method == 'POST':
|
|
|
|
vendor_id = str(request.form.get('vendor_id', ''))
|
|
currency = str(request.form.get('currency', ''))
|
|
date_opened = request.form.get('date_opened', None)
|
|
notes = str(request.form.get('notes', ''))
|
|
posted = request.form.get('posted', None)
|
|
posted_account_guid = str(request.form.get('posted_account_guid', ''))
|
|
posted_date = request.form.get('posted_date', '')
|
|
due_date = request.form.get('due_date', '')
|
|
posted_memo = str(request.form.get('posted_memo', ''))
|
|
posted_accumulatesplits = request.form.get('posted_accumulatesplits',
|
|
'')
|
|
posted_autopay = request.form.get('posted_autopay', '')
|
|
|
|
if posted == '1':
|
|
posted = 1
|
|
else:
|
|
posted = 0
|
|
|
|
if (posted_accumulatesplits == '1'
|
|
or posted_accumulatesplits == 'true'
|
|
or posted_accumulatesplits == 'True'
|
|
or posted_accumulatesplits == True):
|
|
posted_accumulatesplits = True
|
|
else:
|
|
posted_accumulatesplits = False
|
|
|
|
if posted_autopay == '1':
|
|
posted_autopay = True
|
|
else:
|
|
posted_autopay = False
|
|
try:
|
|
bill = updateBill(session.book, id, vendor_id, currency,
|
|
date_opened, notes, posted, posted_account_guid, posted_date,
|
|
due_date, posted_memo, posted_accumulatesplits, posted_autopay)
|
|
except Error as error:
|
|
return Response(json.dumps({'errors': [{'type' : error.type,
|
|
'message': error.message, 'data': error.data}]}), status=400,
|
|
mimetype='application/json')
|
|
else:
|
|
return Response(json.dumps(bill), status=200,
|
|
mimetype='application/json')
|
|
|
|
if bill is None:
|
|
abort(404)
|
|
else:
|
|
return Response(json.dumps(bill),
|
|
mimetype='application/json')
|
|
|
|
elif request.method == 'PAY':
|
|
|
|
posted_account_guid = str(request.form.get('posted_account_guid', ''))
|
|
transfer_account_guid = str(request.form.get('transfer_account_guid',
|
|
''))
|
|
payment_date = request.form.get('payment_date', '')
|
|
num = str(request.form.get('num', ''))
|
|
memo = str(request.form.get('posted_memo', ''))
|
|
auto_pay = request.form.get('auto_pay', '')
|
|
|
|
try:
|
|
bill = payBill(session.book, id, posted_account_guid,
|
|
transfer_account_guid, payment_date, memo, num, auto_pay)
|
|
except Error as error:
|
|
return Response(json.dumps({'errors': [{'type' : error.type,
|
|
'message': error.message, 'data': error.data}]}), status=400,
|
|
mimetype='application/json')
|
|
else:
|
|
return Response(json.dumps(bill), status=200,
|
|
mimetype='application/json')
|
|
|
|
else:
|
|
abort(405)
|
|
|
|
@app.route('/bills/<id>/entries', methods=['GET', 'POST'])
|
|
def api_bill_entries(id):
|
|
|
|
bill = getBill(session.book, id)
|
|
|
|
if bill is None:
|
|
abort(404)
|
|
else:
|
|
if request.method == 'GET':
|
|
return Response(json.dumps(bill['entries']), mimetype='application/json')
|
|
elif request.method == 'POST':
|
|
|
|
date = str(request.form.get('date', ''))
|
|
description = str(request.form.get('description', ''))
|
|
account_guid = str(request.form.get('account_guid', ''))
|
|
quantity = str(request.form.get('quantity', ''))
|
|
price = str(request.form.get('price', ''))
|
|
|
|
try:
|
|
entry = addBillEntry(session.book, id, date, description,
|
|
account_guid, quantity, price)
|
|
except Error as error:
|
|
return Response(json.dumps({'errors': [{'type' : error.type,
|
|
'message': error.message, 'data': error.data}]}),
|
|
status=400, mimetype='application/json')
|
|
else:
|
|
return Response(json.dumps(entry), status=201,
|
|
mimetype='application/json')
|
|
|
|
else:
|
|
abort(405)
|
|
|
|
@app.route('/invoices', methods=['GET', 'POST'])
|
|
def api_invoices():
|
|
|
|
if request.method == 'GET':
|
|
|
|
is_paid = request.args.get('is_paid', None)
|
|
is_active = request.args.get('is_active', None)
|
|
date_due_to = request.args.get('date_due_to', None)
|
|
date_due_from = request.args.get('date_due_from', None)
|
|
|
|
if is_paid == '1':
|
|
is_paid = 1
|
|
elif is_paid == '0':
|
|
is_paid = 0
|
|
else:
|
|
is_paid = None
|
|
|
|
if is_active == '1':
|
|
is_active = 1
|
|
elif is_active == '0':
|
|
is_active = 0
|
|
else:
|
|
is_active = None
|
|
|
|
invoices = getInvoices(session.book, None, is_paid, is_active,
|
|
date_due_from, date_due_to)
|
|
|
|
return Response(json.dumps(invoices), mimetype='application/json')
|
|
|
|
elif request.method == 'POST':
|
|
|
|
id = str(request.form.get('id', None))
|
|
|
|
if id == '':
|
|
id = None
|
|
elif id != None:
|
|
id = str(id)
|
|
|
|
customer_id = str(request.form.get('customer_id', ''))
|
|
currency = str(request.form.get('currency', ''))
|
|
date_opened = str(request.form.get('date_opened', ''))
|
|
notes = str(request.form.get('notes', ''))
|
|
|
|
try:
|
|
invoice = addInvoice(session.book, id, customer_id, currency,
|
|
date_opened, notes)
|
|
except Error as error:
|
|
return Response(json.dumps({'errors': [{'type' : error.type,
|
|
'message': error.message, 'data': error.data}]}), status=400,
|
|
mimetype='application/json')
|
|
else:
|
|
return Response(json.dumps(invoice), status=201,
|
|
mimetype='application/json')
|
|
|
|
else:
|
|
abort(405)
|
|
|
|
@app.route('/invoices/<id>', methods=['GET', 'POST', 'PAY'])
|
|
def api_invoice(id):
|
|
|
|
if request.method == 'GET':
|
|
|
|
invoice = getInvoice(session.book, id)
|
|
|
|
if invoice is None:
|
|
abort(404)
|
|
else:
|
|
return Response(json.dumps(invoice), mimetype='application/json')
|
|
|
|
elif request.method == 'POST':
|
|
|
|
customer_id = str(request.form.get('customer_id', ''))
|
|
currency = str(request.form.get('currency', ''))
|
|
date_opened = request.form.get('date_opened', None)
|
|
notes = str(request.form.get('notes', ''))
|
|
posted = request.form.get('posted', None)
|
|
posted_account_guid = str(request.form.get('posted_account_guid', ''))
|
|
posted_date = request.form.get('posted_date', '')
|
|
due_date = request.form.get('due_date', '')
|
|
posted_memo = str(request.form.get('posted_memo', ''))
|
|
posted_accumulatesplits = request.form.get('posted_accumulatesplits',
|
|
'')
|
|
posted_autopay = request.form.get('posted_autopay', '')
|
|
|
|
if posted == '1':
|
|
posted = 1
|
|
else:
|
|
posted = 0
|
|
|
|
if (posted_accumulatesplits == '1'
|
|
or posted_accumulatesplits == 'true'
|
|
or posted_accumulatesplits == 'True'
|
|
or posted_accumulatesplits == True):
|
|
posted_accumulatesplits = True
|
|
else:
|
|
posted_accumulatesplits = False
|
|
|
|
if posted_autopay == '1':
|
|
posted_autopay = True
|
|
else:
|
|
posted_autopay = False
|
|
try:
|
|
invoice = updateInvoice(session.book, id, customer_id, currency,
|
|
date_opened, notes, posted, posted_account_guid, posted_date,
|
|
due_date, posted_memo, posted_accumulatesplits, posted_autopay)
|
|
except Error as error:
|
|
return Response(json.dumps({'errors': [{'type' : error.type,
|
|
'message': error.message, 'data': error.data}]}), status=400,
|
|
mimetype='application/json')
|
|
else:
|
|
return Response(json.dumps(invoice), status=200,
|
|
mimetype='application/json')
|
|
|
|
if invoice is None:
|
|
abort(404)
|
|
else:
|
|
return Response(json.dumps(invoice), mimetype='application/json')
|
|
|
|
elif request.method == 'PAY':
|
|
|
|
posted_account_guid = str(request.form.get('posted_account_guid', ''))
|
|
transfer_account_guid = str(request.form.get('transfer_account_guid',
|
|
''))
|
|
payment_date = request.form.get('payment_date', '')
|
|
num = str(request.form.get('num', ''))
|
|
memo = str(request.form.get('posted_memo', ''))
|
|
auto_pay = request.form.get('auto_pay', '')
|
|
|
|
try:
|
|
invoice = payInvoice(session.book, id, posted_account_guid,
|
|
transfer_account_guid, payment_date, memo, num, auto_pay)
|
|
except Error as error:
|
|
return Response(json.dumps({'errors': [{'type' : error.type,
|
|
'message': error.message, 'data': error.data}]}), status=400,
|
|
mimetype='application/json')
|
|
else:
|
|
return Response(json.dumps(invoice), status=200,
|
|
mimetype='application/json')
|
|
|
|
else:
|
|
abort(405)
|
|
|
|
@app.route('/invoices/<id>/entries', methods=['GET', 'POST'])
|
|
def api_invoice_entries(id):
|
|
|
|
invoice = getInvoice(session.book, id)
|
|
|
|
if invoice is None:
|
|
abort(404)
|
|
else:
|
|
if request.method == 'GET':
|
|
return Response(json.dumps(invoice['entries']),
|
|
mimetype='application/json')
|
|
elif request.method == 'POST':
|
|
|
|
date = str(request.form.get('date', ''))
|
|
description = str(request.form.get('description', ''))
|
|
account_guid = str(request.form.get('account_guid', ''))
|
|
quantity = str(request.form.get('quantity', ''))
|
|
price = str(request.form.get('price', ''))
|
|
|
|
try:
|
|
entry = addEntry(session.book, id, date, description,
|
|
account_guid, quantity, price)
|
|
except Error as error:
|
|
return Response(json.dumps({'errors': [{'type' : error.type,
|
|
'message': error.message, 'data': error.data}]}),
|
|
status=400, mimetype='application/json')
|
|
else:
|
|
return Response(json.dumps(entry), status=201,
|
|
mimetype='application/json')
|
|
|
|
else:
|
|
abort(405)
|
|
|
|
@app.route('/entries/<guid>', methods=['GET', 'POST', 'DELETE'])
|
|
def api_entry(guid):
|
|
|
|
entry = getEntry(session.book, guid)
|
|
|
|
if entry is None:
|
|
abort(404)
|
|
else:
|
|
if request.method == 'GET':
|
|
return Response(json.dumps(entry), mimetype='application/json')
|
|
elif request.method == 'POST':
|
|
|
|
date = str(request.form.get('date', ''))
|
|
description = str(request.form.get('description', ''))
|
|
account_guid = str(request.form.get('account_guid', ''))
|
|
quantity = str(request.form.get('quantity', ''))
|
|
price = str(request.form.get('price', ''))
|
|
|
|
try:
|
|
entry = updateEntry(session.book, guid, date, description,
|
|
account_guid, quantity, price)
|
|
except Error as error:
|
|
return Response(json.dumps({'errors': [{'type' : error.type,
|
|
'message': error.message, 'data': error.data}]}),
|
|
status=400, mimetype='application/json')
|
|
else:
|
|
return Response(json.dumps(entry), status=200,
|
|
mimetype='application/json')
|
|
|
|
elif request.method == 'DELETE':
|
|
|
|
deleteEntry(session.book, guid)
|
|
|
|
return Response('', status=201, mimetype='application/json')
|
|
|
|
else:
|
|
abort(405)
|
|
|
|
@app.route('/customers', methods=['GET', 'POST'])
|
|
def api_customers():
|
|
|
|
if request.method == 'GET':
|
|
customers = getCustomers(session.book)
|
|
return Response(json.dumps(customers), mimetype='application/json')
|
|
elif request.method == 'POST':
|
|
|
|
id = str(request.form.get('id', None))
|
|
|
|
if id == '':
|
|
id = None
|
|
elif id != None:
|
|
id = str(id)
|
|
|
|
currency = str(request.form.get('currency', ''))
|
|
name = str(request.form.get('name', ''))
|
|
contact = str(request.form.get('contact', ''))
|
|
address_line_1 = str(request.form.get('address_line_1', ''))
|
|
address_line_2 = str(request.form.get('address_line_2', ''))
|
|
address_line_3 = str(request.form.get('address_line_3', ''))
|
|
address_line_4 = str(request.form.get('address_line_4', ''))
|
|
phone = str(request.form.get('phone', ''))
|
|
fax = str(request.form.get('fax', ''))
|
|
email = str(request.form.get('email', ''))
|
|
|
|
try:
|
|
customer = addCustomer(session.book, id, currency, name, contact,
|
|
address_line_1, address_line_2, address_line_3, address_line_4,
|
|
phone, fax, email)
|
|
except Error as error:
|
|
return Response(json.dumps({'errors': [{'type' : error.type,
|
|
'message': error.message, 'data': error.data}]}), status=400,
|
|
mimetype='application/json')
|
|
else:
|
|
return Response(json.dumps(customer), status=201,
|
|
mimetype='application/json')
|
|
|
|
else:
|
|
abort(405)
|
|
|
|
@app.route('/customers/<id>', methods=['GET', 'POST'])
|
|
def api_customer(id):
|
|
|
|
if request.method == 'GET':
|
|
|
|
customer = getCustomer(session.book, id)
|
|
|
|
if customer is None:
|
|
abort(404)
|
|
else:
|
|
return Response(json.dumps(customer), mimetype='application/json')
|
|
|
|
elif request.method == 'POST':
|
|
|
|
id = str(request.form.get('id', None))
|
|
|
|
name = str(request.form.get('name', ''))
|
|
contact = str(request.form.get('contact', ''))
|
|
address_line_1 = str(request.form.get('address_line_1', ''))
|
|
address_line_2 = str(request.form.get('address_line_2', ''))
|
|
address_line_3 = str(request.form.get('address_line_3', ''))
|
|
address_line_4 = str(request.form.get('address_line_4', ''))
|
|
phone = str(request.form.get('phone', ''))
|
|
fax = str(request.form.get('fax', ''))
|
|
email = str(request.form.get('email', ''))
|
|
|
|
try:
|
|
customer = updateCustomer(session.book, id, name, contact,
|
|
address_line_1, address_line_2, address_line_3, address_line_4,
|
|
phone, fax, email)
|
|
except Error as error:
|
|
if error.type == 'NoCustomer':
|
|
return Response(json.dumps({'errors': [{'type' : error.type,
|
|
'message': error.message, 'data': error.data}]}),
|
|
status=404, mimetype='application/json')
|
|
else:
|
|
return Response(json.dumps({'errors': [{'type' : error.type,
|
|
'message': error.message, 'data': error.data}]}),
|
|
status=400, mimetype='application/json')
|
|
else:
|
|
return Response(json.dumps(customer), status=200,
|
|
mimetype='application/json')
|
|
|
|
else:
|
|
abort(405)
|
|
|
|
@app.route('/customers/<id>/invoices', methods=['GET'])
|
|
def api_customer_invoices(id):
|
|
|
|
customer = getCustomer(session.book, id)
|
|
|
|
if customer is None:
|
|
abort(404)
|
|
|
|
invoices = getInvoices(session.book, customer['guid'], None, None, None,
|
|
None)
|
|
|
|
return Response(json.dumps(invoices), mimetype='application/json')
|
|
|
|
@app.route('/vendors', methods=['GET', 'POST'])
|
|
def api_vendors():
|
|
|
|
if request.method == 'GET':
|
|
vendors = getVendors(session.book)
|
|
return Response(json.dumps(vendors), mimetype='application/json')
|
|
elif request.method == 'POST':
|
|
|
|
id = str(request.form.get('id', None))
|
|
|
|
if id == '':
|
|
id = None
|
|
elif id != None:
|
|
id = str(id)
|
|
|
|
currency = str(request.form.get('currency', ''))
|
|
name = str(request.form.get('name', ''))
|
|
contact = str(request.form.get('contact', ''))
|
|
address_line_1 = str(request.form.get('address_line_1', ''))
|
|
address_line_2 = str(request.form.get('address_line_2', ''))
|
|
address_line_3 = str(request.form.get('address_line_3', ''))
|
|
address_line_4 = str(request.form.get('address_line_4', ''))
|
|
phone = str(request.form.get('phone', ''))
|
|
fax = str(request.form.get('fax', ''))
|
|
email = str(request.form.get('email', ''))
|
|
|
|
try:
|
|
vendor = addVendor(session.book, id, currency, name, contact,
|
|
address_line_1, address_line_2, address_line_3, address_line_4,
|
|
phone, fax, email)
|
|
except Error as error:
|
|
return Response(json.dumps({'errors': [{'type' : error.type,
|
|
'message': error.message, 'data': error.data}]}), status=400,
|
|
mimetype='application/json')
|
|
else:
|
|
return Response(json.dumps(vendor), status=201,
|
|
mimetype='application/json')
|
|
|
|
else:
|
|
abort(405)
|
|
|
|
@app.route('/vendors/<id>', methods=['GET', 'POST'])
|
|
def api_vendor(id):
|
|
|
|
if request.method == 'GET':
|
|
|
|
vendor = getVendor(session.book, id)
|
|
|
|
if vendor is None:
|
|
abort(404)
|
|
else:
|
|
return Response(json.dumps(vendor), mimetype='application/json')
|
|
else:
|
|
abort(405)
|
|
|
|
@app.route('/vendors/<id>/bills', methods=['GET'])
|
|
def api_vendor_bills(id):
|
|
|
|
vendor = getVendor(session.book, id)
|
|
|
|
if vendor is None:
|
|
abort(404)
|
|
|
|
bills = getBills(session.book, vendor['guid'], None, None, None, None)
|
|
|
|
return Response(json.dumps(bills), mimetype='application/json')
|
|
|
|
def getCustomers(book):
|
|
|
|
query = gnucash.Query()
|
|
query.search_for('gncCustomer')
|
|
query.set_book(book)
|
|
customers = []
|
|
|
|
for result in query.run():
|
|
customers.append(gnucash_simple.customerToDict(
|
|
gnucash.gnucash_business.Customer(instance=result)))
|
|
|
|
query.destroy()
|
|
|
|
return customers
|
|
|
|
def getCustomer(book, id):
|
|
|
|
customer = book.CustomerLookupByID(id)
|
|
|
|
if customer is None:
|
|
return None
|
|
else:
|
|
return gnucash_simple.customerToDict(customer)
|
|
|
|
def getVendors(book):
|
|
|
|
query = gnucash.Query()
|
|
query.search_for('gncVendor')
|
|
query.set_book(book)
|
|
vendors = []
|
|
|
|
for result in query.run():
|
|
vendors.append(gnucash_simple.vendorToDict(
|
|
gnucash.gnucash_business.Vendor(instance=result)))
|
|
|
|
query.destroy()
|
|
|
|
return vendors
|
|
|
|
def getVendor(book, id):
|
|
|
|
vendor = book.VendorLookupByID(id)
|
|
|
|
if vendor is None:
|
|
return None
|
|
else:
|
|
return gnucash_simple.vendorToDict(vendor)
|
|
|
|
def getAccounts(book):
|
|
|
|
accounts = gnucash_simple.accountToDict(book.get_root_account())
|
|
|
|
return accounts
|
|
|
|
def getAccountsFlat(book):
|
|
|
|
accounts = gnucash_simple.accountToDict(book.get_root_account())
|
|
|
|
flat_accounts = getSubAccounts(accounts)
|
|
|
|
for n, account in enumerate(flat_accounts):
|
|
account.pop('subaccounts')
|
|
|
|
filtered_flat_account = []
|
|
|
|
type_ids = [9]
|
|
|
|
for n, account in enumerate(flat_accounts):
|
|
if account['type_id'] in type_ids:
|
|
filtered_flat_account.append(account)
|
|
print(account['name'] + ' ' + str(account['type_id']))
|
|
|
|
return filtered_flat_account
|
|
|
|
def getSubAccounts(account):
|
|
|
|
flat_accounts = []
|
|
|
|
if 'subaccounts' in list(account.keys()):
|
|
for n, subaccount in enumerate(account['subaccounts']):
|
|
flat_accounts.append(subaccount)
|
|
flat_accounts = flat_accounts + getSubAccounts(subaccount)
|
|
|
|
return flat_accounts
|
|
|
|
def getAccount(book, guid):
|
|
|
|
account_guid = gnucash.gnucash_core.GUID()
|
|
gnucash.gnucash_core.GUIDString(guid, account_guid)
|
|
|
|
account = account_guid.AccountLookup(book)
|
|
|
|
if account is None:
|
|
return None
|
|
|
|
account = gnucash_simple.accountToDict(account)
|
|
|
|
if account is None:
|
|
return None
|
|
else:
|
|
return account
|
|
|
|
|
|
def getTransaction(book, guid):
|
|
|
|
transaction_guid = gnucash.gnucash_core.GUID()
|
|
gnucash.gnucash_core.GUIDString(guid, transaction_guid)
|
|
|
|
transaction = transaction_guid.TransactionLookup(book)
|
|
|
|
if transaction is None:
|
|
return None
|
|
|
|
transaction = gnucash_simple.transactionToDict(transaction, ['splits'])
|
|
|
|
if transaction is None:
|
|
return None
|
|
else:
|
|
return transaction
|
|
|
|
def getTransactions(book, account_guid, date_posted_from, date_posted_to):
|
|
|
|
query = gnucash.Query()
|
|
|
|
query.search_for('Trans')
|
|
query.set_book(book)
|
|
|
|
transactions = []
|
|
|
|
for transaction in query.run():
|
|
transactions.append(gnucash_simple.transactionToDict(
|
|
gnucash.gnucash_business.Transaction(instance=transaction)))
|
|
|
|
query.destroy()
|
|
|
|
return transactions
|
|
|
|
def getAccountSplits(book, guid, date_posted_from, date_posted_to):
|
|
|
|
account_guid = gnucash.gnucash_core.GUID()
|
|
gnucash.gnucash_core.GUIDString(guid, account_guid)
|
|
|
|
query = gnucash.Query()
|
|
query.search_for('Split')
|
|
query.set_book(book)
|
|
|
|
SPLIT_TRANS= 'trans'
|
|
|
|
QOF_DATE_MATCH_NORMAL = 1
|
|
|
|
TRANS_DATE_POSTED = 'date-posted'
|
|
|
|
if date_posted_from != None:
|
|
pred_data = gnucash.gnucash_core.QueryDatePredicate(
|
|
QOF_COMPARE_GTE, QOF_DATE_MATCH_NORMAL, datetime.datetime.strptime(
|
|
date_posted_from, "%Y-%m-%d").date())
|
|
param_list = [SPLIT_TRANS, TRANS_DATE_POSTED]
|
|
query.add_term(param_list, pred_data, QOF_QUERY_AND)
|
|
|
|
if date_posted_to != None:
|
|
pred_data = gnucash.gnucash_core.QueryDatePredicate(
|
|
QOF_COMPARE_LTE, QOF_DATE_MATCH_NORMAL, datetime.datetime.strptime(
|
|
date_posted_to, "%Y-%m-%d").date())
|
|
param_list = [SPLIT_TRANS, TRANS_DATE_POSTED]
|
|
query.add_term(param_list, pred_data, QOF_QUERY_AND)
|
|
|
|
SPLIT_ACCOUNT = 'account'
|
|
QOF_PARAM_GUID = 'guid'
|
|
|
|
if guid != None:
|
|
gnucash.gnucash_core.GUIDString(guid, account_guid)
|
|
query.add_guid_match(
|
|
[SPLIT_ACCOUNT, QOF_PARAM_GUID], account_guid, QOF_QUERY_AND)
|
|
|
|
splits = []
|
|
|
|
for split in query.run():
|
|
splits.append(gnucash_simple.splitToDict(
|
|
gnucash.gnucash_business.Split(instance=split),
|
|
['account', 'transaction', 'other_split']))
|
|
|
|
query.destroy()
|
|
|
|
return splits
|
|
|
|
def getInvoices(book, customer, is_paid, is_active, date_due_from,
|
|
date_due_to):
|
|
|
|
query = gnucash.Query()
|
|
query.search_for('gncInvoice')
|
|
query.set_book(book)
|
|
|
|
if is_paid == 0:
|
|
query.add_boolean_match([INVOICE_IS_PAID], False, QOF_QUERY_AND)
|
|
elif is_paid == 1:
|
|
query.add_boolean_match([INVOICE_IS_PAID], True, QOF_QUERY_AND)
|
|
|
|
# active = JOB_IS_ACTIVE
|
|
if is_active == 0:
|
|
query.add_boolean_match(['active'], False, QOF_QUERY_AND)
|
|
elif is_active == 1:
|
|
query.add_boolean_match(['active'], True, QOF_QUERY_AND)
|
|
|
|
QOF_PARAM_GUID = 'guid'
|
|
INVOICE_OWNER = 'owner'
|
|
|
|
if customer != None:
|
|
customer_guid = gnucash.gnucash_core.GUID()
|
|
gnucash.gnucash_core.GUIDString(customer, customer_guid)
|
|
query.add_guid_match(
|
|
[INVOICE_OWNER, QOF_PARAM_GUID], customer_guid, QOF_QUERY_AND)
|
|
|
|
if date_due_from != None:
|
|
pred_data = gnucash.gnucash_core.QueryDatePredicate(
|
|
QOF_COMPARE_GTE, 2, datetime.datetime.strptime(
|
|
date_due_from, "%Y-%m-%d").date())
|
|
query.add_term(['date_due'], pred_data, QOF_QUERY_AND)
|
|
|
|
if date_due_to != None:
|
|
pred_data = gnucash.gnucash_core.QueryDatePredicate(
|
|
QOF_COMPARE_LTE, 2, datetime.datetime.strptime(
|
|
date_due_to, "%Y-%m-%d").date())
|
|
query.add_term(['date_due'], pred_data, QOF_QUERY_AND)
|
|
|
|
# return only invoices (1 = invoices)
|
|
pred_data = gnucash.gnucash_core.QueryInt32Predicate(QOF_COMPARE_EQUAL, 1)
|
|
query.add_term([INVOICE_TYPE], pred_data, QOF_QUERY_AND)
|
|
|
|
invoices = []
|
|
|
|
for result in query.run():
|
|
invoices.append(gnucash_simple.invoiceToDict(
|
|
gnucash.gnucash_business.Invoice(instance=result)))
|
|
|
|
query.destroy()
|
|
|
|
return invoices
|
|
|
|
def getBills(book, customer, is_paid, is_active, date_opened_from,
|
|
date_opened_to):
|
|
|
|
query = gnucash.Query()
|
|
query.search_for('gncInvoice')
|
|
query.set_book(book)
|
|
|
|
if is_paid == 0:
|
|
query.add_boolean_match([INVOICE_IS_PAID], False, QOF_QUERY_AND)
|
|
elif is_paid == 1:
|
|
query.add_boolean_match([INVOICE_IS_PAID], True, QOF_QUERY_AND)
|
|
|
|
# active = JOB_IS_ACTIVE
|
|
if is_active == 0:
|
|
query.add_boolean_match(['active'], False, QOF_QUERY_AND)
|
|
elif is_active == 1:
|
|
query.add_boolean_match(['active'], True, QOF_QUERY_AND)
|
|
|
|
QOF_PARAM_GUID = 'guid'
|
|
INVOICE_OWNER = 'owner'
|
|
|
|
if customer != None:
|
|
customer_guid = gnucash.gnucash_core.GUID()
|
|
gnucash.gnucash_core.GUIDString(customer, customer_guid)
|
|
query.add_guid_match(
|
|
[INVOICE_OWNER, QOF_PARAM_GUID], customer_guid, QOF_QUERY_AND)
|
|
|
|
if date_opened_from != None:
|
|
pred_data = gnucash.gnucash_core.QueryDatePredicate(
|
|
QOF_COMPARE_GTE, 2, datetime.datetime.strptime(
|
|
date_opened_from, "%Y-%m-%d").date())
|
|
query.add_term(['date_opened'], pred_data, QOF_QUERY_AND)
|
|
|
|
if date_opened_to != None:
|
|
pred_data = gnucash.gnucash_core.QueryDatePredicate(
|
|
QOF_COMPARE_LTE, 2, datetime.datetime.strptime(
|
|
date_opened_to, "%Y-%m-%d").date())
|
|
query.add_term(['date_opened'], pred_data, QOF_QUERY_AND)
|
|
|
|
# return only bills (2 = bills)
|
|
pred_data = gnucash.gnucash_core.QueryInt32Predicate(QOF_COMPARE_EQUAL, 2)
|
|
query.add_term([INVOICE_TYPE], pred_data, QOF_QUERY_AND)
|
|
|
|
bills = []
|
|
|
|
for result in query.run():
|
|
bills.append(gnucash_simple.billToDict(
|
|
gnucash.gnucash_business.Bill(instance=result)))
|
|
|
|
query.destroy()
|
|
|
|
return bills
|
|
|
|
def getGnuCashInvoice(book ,id):
|
|
|
|
# we don't use book.InvoicelLookupByID(id) as this is identical to
|
|
# book.BillLookupByID(id) so can return the same object if they share IDs
|
|
|
|
query = gnucash.Query()
|
|
query.search_for('gncInvoice')
|
|
query.set_book(book)
|
|
|
|
# return only invoices (1 = invoices)
|
|
pred_data = gnucash.gnucash_core.QueryInt32Predicate(QOF_COMPARE_EQUAL, 1)
|
|
query.add_term([INVOICE_TYPE], pred_data, QOF_QUERY_AND)
|
|
|
|
INVOICE_ID = 'id'
|
|
|
|
pred_data = gnucash.gnucash_core.QueryStringPredicate(
|
|
QOF_COMPARE_EQUAL, id, QOF_STRING_MATCH_NORMAL, False)
|
|
query.add_term([INVOICE_ID], pred_data, QOF_QUERY_AND)
|
|
|
|
invoice = None
|
|
|
|
for result in query.run():
|
|
invoice = gnucash.gnucash_business.Invoice(instance=result)
|
|
|
|
query.destroy()
|
|
|
|
return invoice
|
|
|
|
def getGnuCashBill(book ,id):
|
|
|
|
# we don't use book.InvoicelLookupByID(id) as this is identical to
|
|
# book.BillLookupByID(id) so can return the same object if they share IDs
|
|
|
|
query = gnucash.Query()
|
|
query.search_for('gncInvoice')
|
|
query.set_book(book)
|
|
|
|
# return only bills (2 = bills)
|
|
pred_data = gnucash.gnucash_core.QueryInt32Predicate(QOF_COMPARE_EQUAL, 2)
|
|
query.add_term([INVOICE_TYPE], pred_data, QOF_QUERY_AND)
|
|
|
|
INVOICE_ID = 'id'
|
|
|
|
pred_data = gnucash.gnucash_core.QueryStringPredicate(
|
|
QOF_COMPARE_EQUAL, id, QOF_STRING_MATCH_NORMAL, False)
|
|
query.add_term([INVOICE_ID], pred_data, QOF_QUERY_AND)
|
|
|
|
bill = None
|
|
|
|
for result in query.run():
|
|
bill = gnucash.gnucash_business.Bill(instance=result)
|
|
|
|
query.destroy()
|
|
|
|
return bill
|
|
|
|
def getInvoice(book, id):
|
|
|
|
return gnucash_simple.invoiceToDict(getGnuCashInvoice(book, id))
|
|
|
|
def payInvoice(book, id, posted_account_guid, transfer_account_guid,
|
|
payment_date, memo, num, auto_pay):
|
|
|
|
invoice = getGnuCashInvoice(book, id)
|
|
|
|
account_guid2 = gnucash.gnucash_core.GUID()
|
|
gnucash.gnucash_core.GUIDString(transfer_account_guid, account_guid2)
|
|
|
|
xfer_acc = account_guid2.AccountLookup(session.book)
|
|
|
|
invoice.ApplyPayment(None, xfer_acc, invoice.GetTotal(), GncNumeric(0),
|
|
datetime.datetime.strptime(payment_date, '%Y-%m-%d'), memo, num)
|
|
|
|
return gnucash_simple.invoiceToDict(invoice)
|
|
|
|
def payBill(book, id, posted_account_guid, transfer_account_guid, payment_date,
|
|
memo, num, auto_pay):
|
|
|
|
bill = getGnuCashBill(book, id)
|
|
|
|
account_guid = gnucash.gnucash_core.GUID()
|
|
gnucash.gnucash_core.GUIDString(transfer_account_guid, account_guid)
|
|
|
|
xfer_acc = account_guid.AccountLookup(session.book)
|
|
|
|
# We pay the negative total as the bill as this seemed to cause issues
|
|
# with the split not being set correctly and not being marked as paid
|
|
bill.ApplyPayment(None, xfer_acc, bill.GetTotal().neg(), GncNumeric(0),
|
|
datetime.datetime.strptime(payment_date, '%Y-%m-%d'), memo, num)
|
|
|
|
return gnucash_simple.billToDict(bill)
|
|
|
|
def getBill(book, id):
|
|
|
|
return gnucash_simple.billToDict(getGnuCashBill(book, id))
|
|
|
|
def addVendor(book, id, currency_mnumonic, name, contact, address_line_1,
|
|
address_line_2, address_line_3, address_line_4, phone, fax, email):
|
|
|
|
if name == '':
|
|
raise Error('NoVendorName', 'A name must be entered for this company',
|
|
{'field': 'name'})
|
|
|
|
if (address_line_1 == ''
|
|
and address_line_2 == ''
|
|
and address_line_3 == ''
|
|
and address_line_4 == ''):
|
|
raise Error('NoVendorAddress',
|
|
'An address must be entered for this company',
|
|
{'field': 'address'})
|
|
|
|
commod_table = book.get_table()
|
|
currency = commod_table.lookup('CURRENCY', currency_mnumonic)
|
|
|
|
if currency is None:
|
|
raise Error('InvalidVendorCurrency',
|
|
'A valid currency must be supplied for this vendor',
|
|
{'field': 'currency'})
|
|
|
|
if id is None:
|
|
id = book.VendorNextID()
|
|
|
|
vendor = Vendor(session.book, id, currency, name)
|
|
|
|
address = vendor.GetAddr()
|
|
address.SetName(contact)
|
|
address.SetAddr1(address_line_1)
|
|
address.SetAddr2(address_line_2)
|
|
address.SetAddr3(address_line_3)
|
|
address.SetAddr4(address_line_4)
|
|
address.SetPhone(phone)
|
|
address.SetFax(fax)
|
|
address.SetEmail(email)
|
|
|
|
return gnucash_simple.vendorToDict(vendor)
|
|
|
|
def addCustomer(book, id, currency_mnumonic, name, contact, address_line_1,
|
|
address_line_2, address_line_3, address_line_4, phone, fax, email):
|
|
|
|
if name == '':
|
|
raise Error('NoCustomerName',
|
|
'A name must be entered for this company', {'field': 'name'})
|
|
|
|
if (address_line_1 == ''
|
|
and address_line_2 == ''
|
|
and address_line_3 == ''
|
|
and address_line_4 == ''):
|
|
raise Error('NoCustomerAddress',
|
|
'An address must be entered for this company',
|
|
{'field': 'address'})
|
|
|
|
commod_table = book.get_table()
|
|
currency = commod_table.lookup('CURRENCY', currency_mnumonic)
|
|
|
|
if currency is None:
|
|
raise Error('InvalidCustomerCurrency',
|
|
'A valid currency must be supplied for this customer',
|
|
{'field': 'currency'})
|
|
|
|
if id is None:
|
|
id = book.CustomerNextID()
|
|
|
|
customer = Customer(session.book, id, currency, name)
|
|
|
|
address = customer.GetAddr()
|
|
address.SetName(contact)
|
|
address.SetAddr1(address_line_1)
|
|
address.SetAddr2(address_line_2)
|
|
address.SetAddr3(address_line_3)
|
|
address.SetAddr4(address_line_4)
|
|
address.SetPhone(phone)
|
|
address.SetFax(fax)
|
|
address.SetEmail(email)
|
|
|
|
return gnucash_simple.customerToDict(customer)
|
|
|
|
def updateCustomer(book, id, name, contact, address_line_1, address_line_2,
|
|
address_line_3, address_line_4, phone, fax, email):
|
|
|
|
customer = book.CustomerLookupByID(id)
|
|
|
|
if customer is None:
|
|
raise Error('NoCustomer', 'A customer with this ID does not exist',
|
|
{'field': 'id'})
|
|
|
|
if name == '':
|
|
raise Error('NoCustomerName',
|
|
'A name must be entered for this company', {'field': 'name'})
|
|
|
|
if (address_line_1 == ''
|
|
and address_line_2 == ''
|
|
and address_line_3 == ''
|
|
and address_line_4 == ''):
|
|
raise Error('NoCustomerAddress',
|
|
'An address must be entered for this company',
|
|
{'field': 'address'})
|
|
|
|
customer.SetName(name)
|
|
|
|
address = customer.GetAddr()
|
|
address.SetName(contact)
|
|
address.SetAddr1(address_line_1)
|
|
address.SetAddr2(address_line_2)
|
|
address.SetAddr3(address_line_3)
|
|
address.SetAddr4(address_line_4)
|
|
address.SetPhone(phone)
|
|
address.SetFax(fax)
|
|
address.SetEmail(email)
|
|
|
|
return gnucash_simple.customerToDict(customer)
|
|
|
|
def addInvoice(book, id, customer_id, currency_mnumonic, date_opened, notes):
|
|
|
|
customer = book.CustomerLookupByID(customer_id)
|
|
|
|
if customer is None:
|
|
raise Error('NoCustomer',
|
|
'A customer with this ID does not exist', {'field': 'id'})
|
|
|
|
if id is None:
|
|
id = book.InvoiceNextID(customer)
|
|
|
|
try:
|
|
date_opened = datetime.datetime.strptime(date_opened, "%Y-%m-%d")
|
|
except ValueError:
|
|
raise Error('InvalidDateOpened',
|
|
'The date opened must be provided in the form YYYY-MM-DD',
|
|
{'field': 'date_opened'})
|
|
|
|
if currency_mnumonic is None:
|
|
currency_mnumonic = customer.GetCurrency().get_mnemonic()
|
|
|
|
commod_table = book.get_table()
|
|
currency = commod_table.lookup('CURRENCY', currency_mnumonic)
|
|
|
|
if currency is None:
|
|
raise Error('InvalidCustomerCurrency',
|
|
'A valid currency must be supplied for this customer',
|
|
{'field': 'currency'})
|
|
|
|
invoice = Invoice(book, id, currency, customer, date_opened.date())
|
|
|
|
invoice.SetNotes(notes)
|
|
|
|
return gnucash_simple.invoiceToDict(invoice)
|
|
|
|
def updateInvoice(book, id, customer_id, currency_mnumonic, date_opened,
|
|
notes, posted, posted_account_guid, posted_date, due_date, posted_memo,
|
|
posted_accumulatesplits, posted_autopay):
|
|
|
|
invoice = getGnuCashInvoice(book, id)
|
|
|
|
if invoice is None:
|
|
raise Error('NoInvoice',
|
|
'An invoice with this ID does not exist',
|
|
{'field': 'id'})
|
|
|
|
customer = book.CustomerLookupByID(customer_id)
|
|
|
|
if customer is None:
|
|
raise Error('NoCustomer', 'A customer with this ID does not exist',
|
|
{'field': 'customer_id'})
|
|
|
|
try:
|
|
date_opened = datetime.datetime.strptime(date_opened, "%Y-%m-%d")
|
|
except ValueError:
|
|
raise Error('InvalidDateOpened',
|
|
'The date opened must be provided in the form YYYY-MM-DD',
|
|
{'field': 'date_opened'})
|
|
|
|
if posted_date == '':
|
|
if posted == 1:
|
|
raise Error('NoDatePosted',
|
|
'The date posted must be supplied when posted=1',
|
|
{'field': 'date_posted'})
|
|
else:
|
|
try:
|
|
posted_date = datetime.datetime.strptime(posted_date, "%Y-%m-%d")
|
|
except ValueError:
|
|
raise Error('InvalidDatePosted',
|
|
'The date posted must be provided in the form YYYY-MM-DD',
|
|
{'field': 'posted_date'})
|
|
|
|
if due_date == '':
|
|
if posted == 1:
|
|
raise Error('NoDatePosted',
|
|
'The due date must be supplied when posted=1',
|
|
{'field': 'date_posted'})
|
|
else:
|
|
try:
|
|
due_date = datetime.datetime.strptime(due_date, "%Y-%m-%d")
|
|
except ValueError:
|
|
raise Error('InvalidDatePosted',
|
|
'The due date must be provided in the form YYYY-MM-DD',
|
|
{'field': 'due_date'})
|
|
|
|
if posted_account_guid == '':
|
|
if posted == 1:
|
|
raise Error('NoPostedAccountGuid',
|
|
'The posted account GUID must be supplied when posted=1',
|
|
{'field': 'posted_account_guid'})
|
|
else:
|
|
guid = gnucash.gnucash_core.GUID()
|
|
gnucash.gnucash_core.GUIDString(posted_account_guid, guid)
|
|
|
|
posted_account = guid.AccountLookup(book)
|
|
|
|
if posted_account is None:
|
|
raise Error('NoAccount',
|
|
'No account exists with the posted account GUID',
|
|
{'field': 'posted_account_guid'})
|
|
|
|
invoice.SetOwner(customer)
|
|
invoice.SetDateOpened(date_opened)
|
|
invoice.SetNotes(notes)
|
|
|
|
# post if currently unposted and posted=1
|
|
if (invoice.GetDatePosted().strftime('%Y-%m-%d') == '1970-01-01'
|
|
and posted == 1):
|
|
invoice.PostToAccount(posted_account, posted_date, due_date,
|
|
posted_memo, posted_accumulatesplits, posted_autopay)
|
|
|
|
return gnucash_simple.invoiceToDict(invoice)
|
|
|
|
def updateBill(book, id, vendor_id, currency_mnumonic, date_opened, notes,
|
|
posted, posted_account_guid, posted_date, due_date, posted_memo,
|
|
posted_accumulatesplits, posted_autopay):
|
|
|
|
bill = getGnuCashBill(book, id)
|
|
|
|
if bill is None:
|
|
raise Error('NoBill', 'A bill with this ID does not exist',
|
|
{'field': 'id'})
|
|
|
|
vendor = book.VendorLookupByID(vendor_id)
|
|
|
|
if vendor is None:
|
|
raise Error('NoVendor',
|
|
'A vendor with this ID does not exist',
|
|
{'field': 'vendor_id'})
|
|
|
|
try:
|
|
date_opened = datetime.datetime.strptime(date_opened, "%Y-%m-%d")
|
|
except ValueError:
|
|
raise Error('InvalidDateOpened',
|
|
'The date opened must be provided in the form YYYY-MM-DD',
|
|
{'field': 'date_opened'})
|
|
|
|
if posted_date == '':
|
|
if posted == 1:
|
|
raise Error('NoDatePosted',
|
|
'The date posted must be supplied when posted=1',
|
|
{'field': 'date_posted'})
|
|
else:
|
|
try:
|
|
posted_date = datetime.datetime.strptime(posted_date, "%Y-%m-%d")
|
|
except ValueError:
|
|
raise Error('InvalidDatePosted',
|
|
'The date posted must be provided in the form YYYY-MM-DD',
|
|
{'field': 'posted_date'})
|
|
|
|
if due_date == '':
|
|
if posted == 1:
|
|
raise Error('NoDatePosted',
|
|
'The due date must be supplied when posted=1',
|
|
{'field': 'date_posted'})
|
|
else:
|
|
try:
|
|
due_date = datetime.datetime.strptime(due_date, "%Y-%m-%d")
|
|
except ValueError:
|
|
raise Error('InvalidDatePosted',
|
|
'The due date must be provided in the form YYYY-MM-DD',
|
|
{'field': 'due_date'})
|
|
|
|
if posted_account_guid == '':
|
|
if posted == 1:
|
|
raise Error('NoPostedAccountGuid',
|
|
'The posted account GUID must be supplied when posted=1',
|
|
{'field': 'posted_account_guid'})
|
|
else:
|
|
guid = gnucash.gnucash_core.GUID()
|
|
gnucash.gnucash_core.GUIDString(posted_account_guid, guid)
|
|
|
|
posted_account = guid.AccountLookup(book)
|
|
|
|
if posted_account is None:
|
|
raise Error('NoAccount',
|
|
'No account exists with the posted account GUID',
|
|
{'field': 'posted_account_guid'})
|
|
|
|
bill.SetOwner(vendor)
|
|
bill.SetDateOpened(date_opened)
|
|
bill.SetNotes(notes)
|
|
|
|
# post if currently unposted and posted=1
|
|
if bill.GetDatePosted().strftime('%Y-%m-%d') == '1970-01-01' and posted == 1:
|
|
bill.PostToAccount(posted_account, posted_date, due_date, posted_memo,
|
|
posted_accumulatesplits, posted_autopay)
|
|
|
|
return gnucash_simple.billToDict(bill)
|
|
|
|
def addEntry(book, invoice_id, date, description, account_guid, quantity, price):
|
|
|
|
invoice = getGnuCashInvoice(book, invoice_id)
|
|
|
|
if invoice is None:
|
|
raise Error('NoInvoice',
|
|
'No invoice exists with this ID', {'field': 'invoice_id'})
|
|
|
|
try:
|
|
date = datetime.datetime.strptime(date, "%Y-%m-%d")
|
|
except ValueError:
|
|
raise Error('InvalidDateOpened',
|
|
'The date opened must be provided in the form YYYY-MM-DD',
|
|
{'field': 'date'})
|
|
|
|
guid = gnucash.gnucash_core.GUID()
|
|
gnucash.gnucash_core.GUIDString(account_guid, guid)
|
|
|
|
account = guid.AccountLookup(book)
|
|
|
|
if account is None:
|
|
raise Error('NoAccount', 'No account exists with this GUID',
|
|
{'field': 'account_guid'})
|
|
|
|
try:
|
|
quantity = Decimal(quantity).quantize(Decimal('.01'))
|
|
except ArithmeticError:
|
|
raise Error('InvalidQuantity', 'This quantity is not valid',
|
|
{'field': 'quantity'})
|
|
|
|
try:
|
|
price = Decimal(price).quantize(Decimal('.01'))
|
|
except ArithmeticError:
|
|
raise Error('InvalidPrice', 'This price is not valid',
|
|
{'field': 'price'})
|
|
|
|
entry = Entry(book, invoice, date.date())
|
|
entry.SetDateEntered(datetime.datetime.now())
|
|
entry.SetDescription(description)
|
|
entry.SetInvAccount(account)
|
|
entry.SetQuantity(gnc_numeric_from_decimal(quantity))
|
|
entry.SetInvPrice(gnc_numeric_from_decimal(price))
|
|
|
|
return gnucash_simple.entryToDict(entry)
|
|
|
|
def addBillEntry(book, bill_id, date, description, account_guid, quantity,
|
|
price):
|
|
|
|
bill = getGnuCashBill(book,bill_id)
|
|
|
|
if bill is None:
|
|
raise Error('NoBill', 'No bill exists with this ID',
|
|
{'field': 'bill_id'})
|
|
|
|
try:
|
|
date = datetime.datetime.strptime(date, "%Y-%m-%d")
|
|
except ValueError:
|
|
raise Error('InvalidDateOpened',
|
|
'The date opened must be provided in the form YYYY-MM-DD',
|
|
{'field': 'date'})
|
|
|
|
guid = gnucash.gnucash_core.GUID()
|
|
gnucash.gnucash_core.GUIDString(account_guid, guid)
|
|
|
|
account = guid.AccountLookup(book)
|
|
|
|
if account is None:
|
|
raise Error('NoAccount', 'No account exists with this GUID',
|
|
{'field': 'account_guid'})
|
|
|
|
try:
|
|
quantity = Decimal(quantity).quantize(Decimal('.01'))
|
|
except ArithmeticError:
|
|
raise Error('InvalidQuantity', 'This quantity is not valid',
|
|
{'field': 'quantity'})
|
|
|
|
try:
|
|
price = Decimal(price).quantize(Decimal('.01'))
|
|
except ArithmeticError:
|
|
raise Error('InvalidPrice', 'This price is not valid',
|
|
{'field': 'price'})
|
|
|
|
entry = Entry(book, bill, date.date())
|
|
entry.SetDateEntered(datetime.datetime.now())
|
|
entry.SetDescription(description)
|
|
entry.SetBillAccount(account)
|
|
entry.SetQuantity(gnc_numeric_from_decimal(quantity))
|
|
entry.SetBillPrice(gnc_numeric_from_decimal(price))
|
|
|
|
return gnucash_simple.entryToDict(entry)
|
|
|
|
def getEntry(book, entry_guid):
|
|
|
|
guid = gnucash.gnucash_core.GUID()
|
|
gnucash.gnucash_core.GUIDString(entry_guid, guid)
|
|
|
|
entry = book.EntryLookup(guid)
|
|
|
|
if entry is None:
|
|
return None
|
|
else:
|
|
return gnucash_simple.entryToDict(entry)
|
|
|
|
def updateEntry(book, entry_guid, date, description, account_guid, quantity,
|
|
price):
|
|
|
|
guid = gnucash.gnucash_core.GUID()
|
|
gnucash.gnucash_core.GUIDString(entry_guid, guid)
|
|
|
|
entry = book.EntryLookup(guid)
|
|
|
|
if entry is None:
|
|
raise Error('NoEntry', 'No entry exists with this GUID',
|
|
{'field': 'entry_guid'})
|
|
|
|
try:
|
|
date = datetime.datetime.strptime(date, "%Y-%m-%d")
|
|
except ValueError:
|
|
raise Error('InvalidDateOpened',
|
|
'The date opened must be provided in the form YYYY-MM-DD',
|
|
{'field': 'date'})
|
|
|
|
gnucash.gnucash_core.GUIDString(account_guid, guid)
|
|
|
|
account = guid.AccountLookup(book)
|
|
|
|
if account is None:
|
|
raise Error('NoAccount', 'No account exists with this GUID',
|
|
{'field': 'account_guid'})
|
|
|
|
entry.SetDate(date.date())
|
|
entry.SetDateEntered(datetime.datetime.now())
|
|
entry.SetDescription(description)
|
|
entry.SetInvAccount(account)
|
|
entry.SetQuantity(
|
|
gnc_numeric_from_decimal(Decimal(quantity).quantize(Decimal('.01'))))
|
|
entry.SetInvPrice(
|
|
gnc_numeric_from_decimal(Decimal(price).quantize(Decimal('.01'))))
|
|
|
|
return gnucash_simple.entryToDict(entry)
|
|
|
|
def deleteEntry(book, entry_guid):
|
|
|
|
guid = gnucash.gnucash_core.GUID()
|
|
gnucash.gnucash_core.GUIDString(entry_guid, guid)
|
|
|
|
entry = book.EntryLookup(guid)
|
|
|
|
invoice = entry.GetInvoice()
|
|
bill = entry.GetBill()
|
|
|
|
if invoice != None and entry != None:
|
|
invoice.RemoveEntry(entry)
|
|
elif bill != None and entry != None:
|
|
bill.RemoveEntry(entry)
|
|
|
|
if entry != None:
|
|
entry.Destroy()
|
|
|
|
def deleteTransaction(book, transaction_guid):
|
|
|
|
guid = gnucash.gnucash_core.GUID()
|
|
gnucash.gnucash_core.GUIDString(transaction_guid, guid)
|
|
|
|
transaction = guid.TransLookup(book)
|
|
|
|
if transaction != None :
|
|
transaction.Destroy()
|
|
|
|
def addBill(book, id, vendor_id, currency_mnumonic, date_opened, notes):
|
|
|
|
vendor = book.VendorLookupByID(vendor_id)
|
|
|
|
if vendor is None:
|
|
raise Error('NoVendor', 'A vendor with this ID does not exist',
|
|
{'field': 'id'})
|
|
|
|
if id is None:
|
|
id = book.BillNextID(vendor)
|
|
|
|
try:
|
|
date_opened = datetime.datetime.strptime(date_opened, "%Y-%m-%d")
|
|
except ValueError:
|
|
raise Error('InvalidVendorDateOpened',
|
|
'The date opened must be provided in the form YYYY-MM-DD',
|
|
{'field': 'date_opened'})
|
|
|
|
if currency_mnumonic is None:
|
|
currency_mnumonic = vendor.GetCurrency().get_mnemonic()
|
|
|
|
commod_table = book.get_table()
|
|
currency = commod_table.lookup('CURRENCY', currency_mnumonic)
|
|
|
|
if currency is None:
|
|
raise Error('InvalidVendorCurrency',
|
|
'A valid currency must be supplied for this vendor',
|
|
{'field': 'currency'})
|
|
|
|
bill = Bill(book, id, currency, vendor, date_opened.date())
|
|
|
|
bill.SetNotes(notes)
|
|
|
|
return gnucash_simple.billToDict(bill)
|
|
|
|
def addAccount(book, name, currency_mnumonic, account_guid):
|
|
|
|
from gnucash.gnucash_core_c import \
|
|
ACCT_TYPE_ASSET, ACCT_TYPE_RECEIVABLE, ACCT_TYPE_INCOME, \
|
|
GNC_OWNER_CUSTOMER, ACCT_TYPE_LIABILITY
|
|
|
|
root_account = book.get_root_account()
|
|
|
|
commod_table = book.get_table()
|
|
currency = commod_table.lookup('CURRENCY', currency_mnumonic)
|
|
|
|
if currency is None:
|
|
raise Error('InvalidCustomerCurrency',
|
|
'A valid currency must be supplied for this customer',
|
|
{'field': 'currency'})
|
|
|
|
account = Account(book)
|
|
root_account.append_child(root_account)
|
|
account.SetName(name)
|
|
account.SetType(ACCT_TYPE_ASSET)
|
|
account.SetCommodity(currency)
|
|
|
|
def addTransaction(book, num, description, date_posted, currency_mnumonic, splits):
|
|
|
|
transaction = Transaction(book)
|
|
|
|
transaction.BeginEdit()
|
|
|
|
commod_table = book.get_table()
|
|
currency = commod_table.lookup('CURRENCY', currency_mnumonic)
|
|
|
|
if currency is None:
|
|
raise Error('InvalidTransactionCurrency',
|
|
'A valid currency must be supplied for this transaction',
|
|
{'field': 'currency'})
|
|
|
|
try:
|
|
date_posted = datetime.datetime.strptime(date_posted, "%Y-%m-%d")
|
|
except ValueError:
|
|
raise Error('InvalidDatePosted',
|
|
'The date posted must be provided in the form YYYY-MM-DD',
|
|
{'field': 'date_posted'})
|
|
|
|
|
|
for split_values in splits:
|
|
account_guid = gnucash.gnucash_core.GUID()
|
|
gnucash.gnucash_core.GUIDString(split_values['account_guid'], account_guid)
|
|
|
|
account = account_guid.AccountLookup(book)
|
|
|
|
if account is None:
|
|
raise Error('InvalidSplitAccount',
|
|
'A valid account must be supplied for this split',
|
|
{'field': 'account'})
|
|
|
|
split = Split(book)
|
|
split.SetValue(GncNumeric(split_values['value'], 100))
|
|
split.SetAccount(account)
|
|
split.SetParent(transaction)
|
|
|
|
transaction.SetCurrency(currency)
|
|
transaction.SetDescription(description)
|
|
transaction.SetNum(num)
|
|
|
|
transaction.SetDatePostedTS(date_posted)
|
|
|
|
transaction.CommitEdit()
|
|
|
|
return gnucash_simple.transactionToDict(transaction, ['splits'])
|
|
|
|
def getTransaction(book, transaction_guid):
|
|
|
|
guid = gnucash.gnucash_core.GUID()
|
|
gnucash.gnucash_core.GUIDString(transaction_guid, guid)
|
|
|
|
transaction = guid.TransLookup(book)
|
|
|
|
if transaction is None:
|
|
return None
|
|
else:
|
|
return gnucash_simple.transactionToDict(transaction, ['splits'])
|
|
|
|
def editTransaction(book, transaction_guid, num, description, date_posted,
|
|
currency_mnumonic, splits):
|
|
|
|
guid = gnucash.gnucash_core.GUID()
|
|
gnucash.gnucash_core.GUIDString(transaction_guid, guid)
|
|
|
|
transaction = guid.TransLookup(book)
|
|
|
|
if transaction is None:
|
|
raise Error('NoCustomer',
|
|
'A transaction with this GUID does not exist',
|
|
{'field': 'guid'})
|
|
|
|
transaction.BeginEdit()
|
|
|
|
commod_table = book.get_table()
|
|
currency = commod_table.lookup('CURRENCY', currency_mnumonic)
|
|
|
|
if currency is None:
|
|
raise Error('InvalidTransactionCurrency',
|
|
'A valid currency must be supplied for this transaction',
|
|
{'field': 'currency'})
|
|
|
|
|
|
try:
|
|
date_posted = datetime.datetime.strptime(date_posted, "%Y-%m-%d")
|
|
except ValueError:
|
|
raise Error('InvalidDatePosted',
|
|
'The date posted must be provided in the form YYYY-MM-DD',
|
|
{'field': 'date_posted'})
|
|
|
|
for split_values in splits:
|
|
|
|
split_guid = gnucash.gnucash_core.GUID()
|
|
gnucash.gnucash_core.GUIDString(split_values['guid'], split_guid)
|
|
|
|
split = split_guid.SplitLookup(book)
|
|
|
|
if split is None:
|
|
raise Error('InvalidSplitGuid',
|
|
'A valid guid must be supplied for this split',
|
|
{'field': 'guid'})
|
|
|
|
account_guid = gnucash.gnucash_core.GUID()
|
|
gnucash.gnucash_core.GUIDString(
|
|
split_values['account_guid'], account_guid)
|
|
|
|
account = account_guid.AccountLookup(book)
|
|
|
|
if account is None:
|
|
raise Error('InvalidSplitAccount',
|
|
'A valid account must be supplied for this split',
|
|
{'field': 'account'})
|
|
|
|
split.SetValue(GncNumeric(split_values['value'], 100))
|
|
split.SetAccount(account)
|
|
split.SetParent(transaction)
|
|
|
|
transaction.SetCurrency(currency)
|
|
transaction.SetDescription(description)
|
|
transaction.SetNum(num)
|
|
|
|
transaction.SetDatePostedTS(date_posted)
|
|
|
|
transaction.CommitEdit()
|
|
|
|
return gnucash_simple.transactionToDict(transaction, ['splits'])
|
|
|
|
def gnc_numeric_from_decimal(decimal_value):
|
|
sign, digits, exponent = decimal_value.as_tuple()
|
|
|
|
# convert decimal digits to a fractional numerator
|
|
# equivlent to
|
|
# numerator = int(''.join(digits))
|
|
# but without the wated conversion to string and back,
|
|
# this is probably the same algorithm int() uses
|
|
numerator = 0
|
|
TEN = int(Decimal(0).radix()) # this is always 10
|
|
numerator_place_value = 1
|
|
# add each digit to the final value multiplied by the place value
|
|
# from least significant to most sigificant
|
|
for i in range(len(digits)-1,-1,-1):
|
|
numerator += digits[i] * numerator_place_value
|
|
numerator_place_value *= TEN
|
|
|
|
if decimal_value.is_signed():
|
|
numerator = -numerator
|
|
|
|
# if the exponent is negative, we use it to set the denominator
|
|
if exponent < 0 :
|
|
denominator = TEN ** (-exponent)
|
|
# if the exponent isn't negative, we bump up the numerator
|
|
# and set the denominator to 1
|
|
else:
|
|
numerator *= TEN ** exponent
|
|
denominator = 1
|
|
|
|
return GncNumeric(numerator, denominator)
|
|
|
|
def shutdown():
|
|
session.save()
|
|
session.end()
|
|
session.destroy()
|
|
print('Shutdown')
|
|
|
|
class Error(Exception):
|
|
"""Base class for exceptions in this module."""
|
|
def __init__(self, type, message, data):
|
|
self.type = type
|
|
self.message = message
|
|
self.data = data
|
|
|
|
try:
|
|
options, arguments = getopt.getopt(sys.argv[1:], 'nh:', ['host=', 'new='])
|
|
except getopt.GetoptError as err:
|
|
print(str(err)) # will print something like "option -a not recognized"
|
|
print('Usage: python-rest.py <connection string>')
|
|
sys.exit(2)
|
|
|
|
if len(arguments) == 0:
|
|
print('Usage: python-rest.py <connection string>')
|
|
sys.exit(2)
|
|
|
|
#set default host for Flask
|
|
host = '127.0.0.1'
|
|
|
|
#allow host option to be changed
|
|
for option, value in options:
|
|
if option in ("-h", "--host"):
|
|
host = value
|
|
|
|
is_new = False
|
|
|
|
# allow a new database to be used
|
|
for option, value in options:
|
|
if option in ("-n", "--new"):
|
|
is_new = True
|
|
|
|
|
|
#start gnucash session base on connection string argument
|
|
if is_new:
|
|
session = gnucash.Session(arguments[0], SessionOpenMode.SESSION_NEW_STORE)
|
|
|
|
# seem to get errors if we use the session directly, so save it and
|
|
#destroy it so it's no longer new
|
|
|
|
session.save()
|
|
session.end()
|
|
session.destroy()
|
|
|
|
# unsure about SESSION_BREAK_LOCK - it used to be ignore_lock=True
|
|
session = gnucash.Session(arguments[0], SessionOpenMode.SESSION_BREAK_LOCK)
|
|
|
|
# register method to close gnucash connection gracefully
|
|
atexit.register(shutdown)
|
|
|
|
app.debug = False
|
|
|
|
# log to console
|
|
if not app.debug:
|
|
import logging
|
|
from logging import StreamHandler
|
|
stream_handler = StreamHandler()
|
|
stream_handler.setLevel(logging.ERROR)
|
|
app.logger.addHandler(stream_handler)
|
|
|
|
# start Flask server
|
|
app.run(host=host)
|