Restructure the src directory

It is split into
- /libgnucash (for the non-gui bits)
- /gnucash (for the gui)
- /common (misc source files used by both)
- /bindings (currently only holds python bindings)

This is the first step in restructuring the code. It will need much
more fine tuning later on.
This commit is contained in:
Geert Janssens
2017-08-10 13:56:00 +02:00
parent ffc640bada
commit 83d14e1c1c
1984 changed files with 4406 additions and 4271 deletions

View File

@@ -0,0 +1,218 @@
A Python based REST framework for the Gnucash accounting application
**Please note this is a very early work in progress and should not be run
against live or production data.**
This project is designed to allow other applications to easily interact with
data from Gnucash via a RESTful API.
The API is built on the existing Python bindings and uses Flask to serve
responses in JSON.
Currently accounts, transactions, splits, customers, vendors, invoices and
bills can be accessed to some degree via the framework:
Accounts:
GET request to /accounts - returns a complete list of accounts
GET request to /accounts/<guid> - returns an individual account
GET request to /accounts/<guid>/splits - returns splits for an individual
account
POST request to /transactions - creates a new transaction (currently with only
two splits)
POST variables:
currency - required - a gnucash currency code e.g. GBP
description - required - a description for the transaction
num - optional - a transaction number
date_posted - required - a date formatted as YYYY-MM-DD
splitvalue1 - required - the value of the split on splitaccount1
splitaccount1 - required - the GUID for the 1st account on this transaction
(can be obtained from /accounts)
splitvalue2 - required - the value of the split on splitaccount2
splitaccount2 - required - the GUID for the 2nd account on this transaction
GET request to /transactions/<guid> - returns an individual transaction
POST request to /transactions/<guid> - edits an existing transaction (accepts
the same post variables as /transactions)
DELETE request to /transactions/<guid> - deletes a transaction
GET request to /bills - returns a list of bills
GET variables:
is_paid - optional - 1 returns paid bills, 0 unpaid
is_active - optional - 1 returns active bills, 0 inactive
date_opened_from - optional - all transactions on and after a date formatted
as YYYY-MM-DD
date_opened_to - optional - all transactions up to and before a date formatted
as YYYY-MM-DD
POST request to /bills - creates a new bill
POST variables:
id - optional - if no id is specified the next available sequential id will be
used
vendor_id - required - the id of the vendor of this bill (can be obtained from
/vendors)
currency - required - a gnucash currency code
date_opened - required - a date formatted as YYYY-MM-DD
notes - optional - notes for this bill
GET request to /bills/<id> - returns an individual bill
POST request to /bills/<id> - edits (and optionally posts) a bill
POST variables:
vendor_id - required - the id of the vendor of this bill (can be obtained from
/vendors)
currency - required - a gnucash currency code
date_opened - a date formatted as YYYY-MM-DD
notes - optional - notes for this invoice
posted - optional - set to 1 if marking the invoice as posted (0 or empty
otherwise)
posted_account_guid - required if posted = 1 - the GUID for the posted account
posted_date - required if posted = 1 - a date formatted as YYYY-MM-DD
due_date - required if posted = 1 - a date formatted as YYYY-MM-DD
posted_memo - optional - a description
posted_accumulatesplits - optional - whether to accumulate splits or not (1 for
yes, 0 for no)
posted_autopay - optional - whether to auto pay or not (1 for yes, 0 for no)
PAY request to /bills/<id> - mark a bill as paid
POST variables:
posted_account_guid - required - the GUID for the posted account
transfer_account_guid - required - the GUID for the transfer account
payment_date - required - a date formatted as YYYY-MM-DD
num - optional - a number for this payment
memo - optional - a description for this payment
auto_pay - required - whether to auto pay or not (1 for yes, 0 for no)
GET request to /bills/<id>/entries - return all entries for an individual bill
POST request to /bills/<id>/entries - adds a new entry to a bill
date - required - a date formatted as YYYY-MM-DD
description - required - the description for this entry
account_guid - required - the GUID for the required accoumt
quantity - required - the quantity
price - required - the price
GET request to /invoices - returns all invoices (accepts the same get variables
as /bills)
POST request to /invoices - creates a new invoice
POST variables:
id - optional - if no id is specified the next available sequential id will be
used
customer_id - required - the id of the customer of this invoice (can be
obtained from /customers)
currency - required - a gnucash currency code
date_opened - a date formatted as YYYY-MM-DD
notes - optional - notes for this invoice
GET request to /invoices/<id> - returns an individual invoice
POST request to /invoices/<id> - edits (and optionally posts) an invoice
POST variables:
customer_id - required - the id of the customer of this bill (can be obtained
from /customers)
currency - required - a gnucash currency code
date_opened - a date formatted as YYYY-MM-DD
notes - optional - notes for this invoice
posted - optional - set to 1 if marking the invoice as posted (0 or empty
otherwise)
posted_account_guid - required if posted = 1 - the GUID for the posted account
posted_date - required if posted = 1 - a date formatted as YYYY-MM-DD
due_date - required if posted = 1 - a date formatted as YYYY-MM-DD
posted_memo - optional - a description
posted_accumulatesplits - optional - whether to accumulate splits or not (1 for
yes, 0 for no)
posted_autopay - optional - whether to auto pay or not (1 for yes, 0 for no)
PAY request to /invoices/<id> - mark an invoice as paid (accepts the same post
variables as /bills/<id>)
GET request to /invoices/<id>/entries - return all entries for an individual
invoice
POST request to /invoices/<id>/entries - adds a new entry to an invoice
(accepts the same post variables as /bills/<id>/entries)
GET request to /entries/<guid> - returns an individual entry
POST request to /entries/<guid> - edits an existing entry (accepts the same
post variables as /bills/<id>/entries)
GET request to /customers - returns a list of customers
POST request to /customers - creates a new customer
POST variables:
id - optional - if no id is specified the next available sequential id will be
used
currency - required - a gnucash currency code
name - required - the customer's name
contact - optional - a contact for this customer
address_line_1 - required - the first line of the customer's address
address_line_2 - optional - the second line of the customer's address
address_line_3 - optional - the third line of the customer's address
address_line_4 - optional - the fourth line of the customer's address
phone - optional - the customer's phone number
fax - optional - the customer's fax number
email - optional - the customer's email address
GET request to /customers/<id> - returns an individual customer
POST request to /customers/<id> - edits an existing customer (accepts the same
post variables as /customers)
GET request to /customers/<id>/invoices - returns all invoices for a single
customer
GET request to /vendors - returns a list of vendors
POST request to /vendors - creates a new vendor
POST variables:
id - optional - if no id is specified the next available sequential id will be
used
currency - required - a gnucash currency code
name - required - the vendor's name
contact - optional - a contact for this vendor
address_line_1 - required - the first line of the vendor's address
address_line_2 - optional - the second line of the vendor's address
address_line_3 - optional - the third line of the vendor's address
address_line_4 - optional - the fourth line of the vendor's address
phone - optional - the vendor's phone number
fax - optional - the vendor's fax number
email - optional - the vendor's email address
GET request to /vendors/<id> - returns an individual vendor
GET request to /vendors/<id>/bills - returns all bills for a single vendor
Usage
-----
**As this is work in progress you'll need a knowledge of building Gnucash from
source and experience with python.**
Full details to be provided at a later date, but in short you'll need a copy of
Gnucash built with Python bindings and the Flask module installed.
Rrun `python ./gnucash_rest <connection string>` to start the server on
<http://localhost:5000> e.g
`python ./gnucash_rest mysql://user:password@127.0.0.1/gnucash_test`
Navigate to <http://localhost:5000/invoices> and you should get your invoices
in JSON format.
Files
-----
`gnucash_rest.py` - The Flask app which responds to REST requests with JSON
responses
`gnucash_simple.py` - A helper file to convert Gnucash objects into
dictionaries for easier conversion to JSON.
Future
------
I'm using the API already to integrate Gnucash invoices with other systems, so
I'll be adding features as and when I need them. The format of the API is
likely to change as development continues.

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,303 @@
'''
gnucash_simple.py -- A helper file to convert Gnucash objects into
dictionaries for easier conversion to JSON
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
from gnucash.gnucash_business import Entry, Split, Account
def addressToDict(address):
if address is None:
return None
else:
simple_address = {}
simple_address['name'] = address.GetName();
simple_address['line_1'] = address.GetAddr1();
simple_address['line_2'] = address.GetAddr2();
simple_address['line_3'] = address.GetAddr3();
simple_address['line_4'] = address.GetAddr4();
simple_address['phone'] = address.GetPhone();
simple_address['fax'] = address.GetFax();
simple_address['email'] = address.GetEmail();
return simple_address
def vendorToDict(vendor):
if vendor is None:
return None
else:
simple_vendor = {}
simple_vendor['name'] = vendor.GetName()
simple_vendor['id'] = vendor.GetID()
simple_vendor['guid'] = vendor.GetGUID().to_string()
simple_vendor['notes'] = vendor.GetNotes()
simple_vendor['active'] = vendor.GetActive()
simple_vendor['currency'] = vendor.GetCurrency().get_mnemonic()
simple_vendor['tax_table_override'] = vendor.GetTaxTableOverride()
simple_vendor['address'] = addressToDict(vendor.GetAddr())
simple_vendor['tax_included'] = vendor.GetTaxIncluded()
return simple_vendor
def customerToDict(customer):
if customer is None:
return None
else:
simple_customer = {}
simple_customer['name'] = customer.GetName()
simple_customer['id'] = customer.GetID()
simple_customer['guid'] = customer.GetGUID().to_string()
simple_customer['notes'] = customer.GetNotes()
simple_customer['active'] = customer.GetActive()
simple_customer['discount'] = customer.GetDiscount().to_double()
simple_customer['credit'] = customer.GetCredit().to_double()
simple_customer['currency'] = customer.GetCurrency().get_mnemonic()
simple_customer['tax_table_override'] = customer.GetTaxTableOverride()
simple_customer['address'] = addressToDict(customer.GetAddr())
simple_customer['shipping_address'] = addressToDict(
customer.GetShipAddr())
simple_customer['tax_included'] = customer.GetTaxIncluded()
return simple_customer
def transactionToDict(transaction, entities):
if transaction is None:
return None
else:
simple_transaction = {}
simple_transaction['guid'] = transaction.GetGUID().to_string()
simple_transaction['num'] = transaction.GetNum()
simple_transaction['notes'] = transaction.GetNotes()
simple_transaction['is_closing_txn'] = transaction.GetIsClosingTxn()
if 'splits' in entities:
simple_transaction['splits'] = []
for split in transaction.GetSplitList():
if type(split) != Split:
split=Split(instance=split)
simple_transaction['splits'].append(
splitToDict(split, ['account']))
simple_transaction['count_splits'] = transaction.CountSplits()
simple_transaction['has_reconciled_splits'] = \
transaction.HasReconciledSplits()
simple_transaction['currency'] = transaction.GetCurrency(
).get_mnemonic()
simple_transaction['imbalance_value'] = transaction.GetImbalanceValue(
).to_double()
simple_transaction['is_balanced'] = transaction.IsBalanced()
simple_transaction['date'] = transaction.GetDate()
simple_transaction['date_posted'] = transaction.RetDatePostedTS(
).strftime('%Y-%m-%d')
simple_transaction['date_entered'] = transaction.RetDateEnteredTS(
).strftime('%Y-%m-%d')
simple_transaction['date_due'] = transaction.RetDateDueTS().strftime(
'%Y-%m-%d')
simple_transaction['void_status'] = transaction.GetVoidStatus()
simple_transaction['void_time'] = transaction.GetVoidTime().strftime(
'%Y-%m-%d')
simple_transaction['description'] = transaction.GetDescription()
return simple_transaction
def splitToDict(split, entities):
if split is None:
return None
else:
simple_split = {}
simple_split['guid'] = split.GetGUID().to_string()
if 'account' in entities:
simple_split['account'] = accountToDict(split.GetAccount())
if 'transaction' in entities:
simple_split['transaction'] = transactionToDict(
split.GetParent(), [])
if 'other_split' in entities:
simple_split['other_split'] = splitToDict(
split.GetOtherSplit(), ['account'])
simple_split['amount'] = split.GetAmount().to_double()
simple_split['value'] = split.GetValue().to_double()
simple_split['balance'] = split.GetBalance().to_double()
simple_split['cleared_balance'] = split.GetClearedBalance().to_double()
simple_split['reconciled_balance'] = split.GetReconciledBalance(
).to_double()
return simple_split
def invoiceToDict(invoice):
if invoice is None:
return None
else:
simple_invoice = {}
simple_invoice['id'] = invoice.GetID()
simple_invoice['type'] = invoice.GetType()
simple_invoice['date_opened'] = invoice.GetDateOpened().strftime(
'%Y-%m-%d')
if invoice.GetDatePosted().strftime('%Y-%m-%d') == '1970-01-01':
simple_invoice['date_posted'] = None
else:
simple_invoice['date_posted'] = invoice.GetDatePosted().strftime(
'%Y-%m-%d')
if invoice.GetDateDue().strftime('%Y-%m-%d') == '1970-01-01':
simple_invoice['date_due'] = None
else:
simple_invoice['date_due'] = invoice.GetDateDue().strftime(
'%Y-%m-%d')
simple_invoice['notes'] = invoice.GetNotes()
simple_invoice['active'] = invoice.GetActive()
simple_invoice['currency'] = invoice.GetCurrency().get_mnemonic()
simple_invoice['owner'] = vendorToDict(invoice.GetOwner())
simple_invoice['owner_type'] = invoice.GetOwnerType()
simple_invoice['billing_id'] = invoice.GetBillingID()
simple_invoice['to_charge_amount'] = invoice.GetToChargeAmount().to_double()
simple_invoice['posted_txn'] = transactionToDict(invoice.GetPostedTxn(), [])
simple_invoice['total'] = invoice.GetTotal().to_double()
simple_invoice['total_subtotal'] = invoice.GetTotalSubtotal(
).to_double()
simple_invoice['total_tax'] = invoice.GetTotalTax().to_double()
simple_invoice['entries'] = []
for n, entry in enumerate(invoice.GetEntries()):
if type(entry) != Entry:
entry=Entry(instance=entry)
simple_invoice['entries'].append(entryToDict(entry))
simple_invoice['posted'] = invoice.IsPosted()
simple_invoice['paid'] = invoice.IsPaid()
return simple_invoice
def billToDict(bill):
if bill is None:
return None
else:
simple_bill = {}
simple_bill['id'] = bill.GetID()
simple_bill['type'] = bill.GetType()
simple_bill['date_opened'] = bill.GetDateOpened().strftime('%Y-%m-%d')
if bill.GetDatePosted().strftime('%Y-%m-%d') == '1970-01-01':
simple_bill['date_posted'] = None
else:
simple_bill['date_posted'] = bill.GetDatePosted().strftime(
'%Y-%m-%d')
if bill.GetDateDue().strftime('%Y-%m-%d') == '1970-01-01':
simple_bill['date_due'] = None
else:
simple_bill['date_due'] = bill.GetDateDue().strftime('%Y-%m-%d')
simple_bill['notes'] = bill.GetNotes()
simple_bill['active'] = bill.GetActive()
simple_bill['currency'] = bill.GetCurrency().get_mnemonic()
simple_bill['owner'] = vendorToDict(bill.GetOwner())
simple_bill['owner_type'] = bill.GetOwnerType()
simple_bill['billing_id'] = bill.GetBillingID()
simple_bill['to_charge_amount'] = bill.GetToChargeAmount().to_double()
simple_bill['total'] = bill.GetTotal().to_double()
simple_bill['total_subtotal'] = bill.GetTotalSubtotal().to_double()
simple_bill['total_tax'] = bill.GetTotalTax().to_double()
simple_bill['entries'] = []
for n, entry in enumerate(bill.GetEntries()):
if type(entry) != Entry:
entry=Entry(instance=entry)
simple_bill['entries'].append(entryToDict(entry))
simple_bill['posted'] = bill.IsPosted()
simple_bill['paid'] = bill.IsPaid()
return simple_bill
def entryToDict(entry):
if entry is None:
return None
else:
simple_entry = {}
simple_entry['guid'] = entry.GetGUID().to_string()
simple_entry['date'] = entry.GetDate().strftime('%Y-%m-%d')
simple_entry['date_entered'] = entry.GetDateEntered().strftime(
'%Y-%m-%d')
simple_entry['description'] = entry.GetDescription()
simple_entry['action'] = entry.GetAction()
simple_entry['notes'] = entry.GetNotes()
simple_entry['quantity'] = entry.GetQuantity().to_double()
if entry.GetInvAccount() == None:
simple_entry['inv_account'] = {}
else:
simple_entry['inv_account'] = accountToDict(entry.GetInvAccount())
simple_entry['inv_price'] = entry.GetInvPrice().to_double()
simple_entry['discount'] = entry.GetInvDiscount().to_double()
simple_entry['discounted_type'] = entry.GetInvDiscountType()
simple_entry['discounted_how'] = entry.GetInvDiscountHow()
simple_entry['inv_taxable'] = entry.GetInvTaxable()
simple_entry['inv_tax_included'] = entry.GetInvTaxIncluded()
simple_entry['inv_tax_table_override'] = entry.GetInvTaxTable()
if entry.GetBillAccount() == None:
simple_entry['bill_account'] = {}
else:
simple_entry['bill_account'] = accountToDict(
entry.GetBillAccount())
simple_entry['bill_price'] = entry.GetBillPrice().to_double()
simple_entry['bill_taxable'] = entry.GetBillTaxable()
simple_entry['bill_tax_included'] = entry.GetBillTaxIncluded()
simple_entry['bill_tax_table'] = entry.GetBillTaxTable()
simple_entry['billable'] = entry.GetBillable()
simple_entry['bill_payment'] = entry.GetBillPayment()
simple_entry['is_open'] = entry.IsOpen()
return simple_entry
def accountToDict(account):
commod_table = account.get_book().get_table()
gbp = commod_table.lookup('CURRENCY', 'GBP')
if account is None:
return None
else:
simple_account = {}
simple_account['name'] = account.GetName()
simple_account['type_id'] = account.GetType()
simple_account['description'] = account.GetDescription()
simple_account['guid'] = account.GetGUID().to_string()
if account.GetCommodity() == None:
simple_account['currency'] = ''
else:
simple_account['currency'] = account.GetCommodity().get_mnemonic()
simple_account['subaccounts'] = []
for n, subaccount in enumerate(account.get_children_sorted()):
simple_account['subaccounts'].append(accountToDict(subaccount))
simple_account['balance'] = account.GetBalance().to_double()
simple_account['balance_gbp'] = account.GetBalanceInCurrency(
gbp, True).to_double()
simple_account['placeholder'] = account.GetPlaceholder()
return simple_account