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