def main(): args = parse_cmdline() if args.version: print VERSION exit(0) if args.verbose: loglevel = logging.DEBUG elif args.quiet: loglevel = logging.WARN else: loglevel = logging.INFO logging.basicConfig(level=loglevel) rules = readrules(args.rulesfile) account_path = re.split(':', args.ac2fix) gnucash_session = Session(args.gnucash_file, is_new=False) total = 0 imbalance = 0 fixed = 0 try: root_account = gnucash_session.book.get_root_account() orig_account = account_from_path(root_account, account_path) imbalance_pattern = re.compile(args.imbalance_ac) for split in orig_account.GetSplitList(): total += 1 trans = split.parent splits = trans.GetSplitList() trans_date = date.fromtimestamp(trans.GetDate()) trans_desc = trans.GetDescription() trans_memo = trans.GetNotes() for split in splits: ac = split.GetAccount() acname = ac.GetName() logging.debug('%s: %s => %s', trans_date, trans_desc, acname) if imbalance_pattern.match(acname): imbalance += 1 search_str = trans_desc if args.use_memo: search_str = trans_memo newac = get_ac_from_str(search_str, rules, root_account) if newac != "": logging.debug('\tChanging account to: %s', newac.GetName()) split.SetAccount(newac) fixed += 1 if not args.nochange: gnucash_session.save() logging.info('Total splits=%s, imbalance=%s, fixed=%s', total, imbalance, fixed) except Exception as ex: logging.error(ex) gnucash_session.end()
def write_transactions_to_gnucash(gnucash_file, currency, all_items, dry_run=False, date_from=None): logging.debug('Opening GnuCash file %s..', gnucash_file) session = Session(gnucash_file) book = session.book commod_tab = book.get_table() currency = commod_tab.lookup('ISO4217', currency) if date_from: date_from = datetime.datetime.strptime(date_from, '%Y-%m-%d') imported_items = set() for item in all_items: if date_from and item.date < date_from: logging.info('Skipping entry %s (%s)', item.date.strftime('%Y-%m-%d'), item.split_amount) continue if item.as_tuple() in imported_items: logging.info('Skipping entry %s (%s) --- already imported!', item.date.strftime('%Y-%m-%d'), item.split_amount) continue add_transaction(book, item, currency) imported_items.add(item.as_tuple()) if dry_run: logging.debug('** DRY-RUN **') else: logging.debug('Saving GnuCash file..') session.save() session.end()
def write_transactions_to_gnucash(gnucash_file, currency, all_items, dry_run=False, date_from=None): logging.debug("Opening GnuCash file %s..", gnucash_file) session = Session(gnucash_file) book = session.book commod_tab = book.get_table() currency = commod_tab.lookup("ISO4217", currency) if date_from: date_from = datetime.datetime.strptime(date_from, "%Y-%m-%d") imported_items = set() for item in all_items: if date_from and item.date < date_from: logging.info("Skipping entry %s (%s)", item.date.strftime("%Y-%m-%d"), item.split_amount) continue if item.as_tuple() in imported_items: logging.info( "Skipping entry %s (%s) --- already imported!", item.date.strftime("%Y-%m-%d"), item.split_amount ) continue add_transaction(book, item, currency) imported_items.add(item.as_tuple()) if dry_run: logging.debug("** DRY-RUN **") else: logging.debug("Saving GnuCash file..") session.save() session.end()
def main(args): if args.verbose: lvl = logging.DEBUG elif args.quiet: lvl = logging.WARN else: lvl = logging.INFO logging.basicConfig(level=lvl) all_items = [] for fn in args.file: logging.debug('Reading %s..', fn) with open(fn) as fd: items = qif.parse_qif(fd) logging.debug('Read %s items from %s..', len(items), fn) all_items.extend(items) logging.debug('Opening GnuCash file %s..', args.gnucash_file) session = Session(args.gnucash_file) book = session.book commod_tab = book.get_table() currency = commod_tab.lookup('ISO4217', args.currency) for item in all_items: add_transaction(book, item, currency) logging.debug('Saving GnuCash file..') session.save() session.end()
def runTest(self): self.instrument_main() try: from accregex import Account from accregex.AccountUtil import gnc_numeric_to_python_Decimal from gnucash import Session session = Session(AccregexTest.reg_doc_example, is_new=False, ignore_lock=False) parking_expense_account = Account.get_account(session.book.get_root_account(), parking_expense_account_full_name) actual_balance = gnc_numeric_to_python_Decimal(parking_expense_account.GetBalance()) expected_balance = Decimal(25) self.assertEqual(actual_balance, expected_balance) #TODO: check that the actual transaction in Assets:Current Assets:Checking Account was changed session.end() #put the gnucash file back the way we found it shutil.move(AccregexTest.reg_doc_example + ".bak", AccregexTest.reg_doc_example) except: #in case of error close the session and re-raise #equivalent to a "finally" block if "session" in locals(): session.end() raise
def main(): if len(argv) < 3: print "Usage: python make_securities.py <gnucash-file> <symbols-file>" exit(1) gnucash_file = argv[1] secs = read_symbols(argv[2]) sess = Session(gnucash_file) try: # Make sure all the commodities exist ct = sess.book.get_table() created_secs = 0 updated_secs = 0 total_secs = 0 for ns in ["CUSIP", "ISIN"]: total_secs += len(secs[ns]) for c in ct.get_commodities(ns): matched_sec = None for k in secs[ns]: sec = secs[ns][k] if sec["id"] == c.get_cusip(): matched_sec = sec break if matched_sec: matched_sec["c"] = c updated = False if c.get_fullname() != matched_sec["name"]: c.set_fullname(matched_sec["name"]) updated = True if c.get_mnemonic() != matched_sec["symbol"]: c.set_mnemonic(matched_sec["symbol"]) updated = True if updated: updated_secs += 1 print "DEBUG: Updating Commodity", sec["name"] del secs[ns][matched_sec["id"]] for k in secs[ns]: sec = secs[ns][k] c = GncCommodity(sess.book, sec["name"], ns, sec["symbol"], sec["id"], 10000) if c: ct.insert(c) created_secs += 1 print "DEBUG: Created Commodity", sec["name"], sec["id"] else: print "ERROR: Error creating Commodity", sec["name"] print "INFO:", total_secs, "commodities total,", created_secs, "created,", updated_secs, "updated." wait_for_backup_file(gnucash_file) sess.save() except BaseException as e: print "ERROR:", e finally: sess.end() sess.destroy()
class CommoditySession(TestCase): def setUp(self): self.ses = Session() self.book = self.ses.get_book() self.table = self.book.get_table() def tearDown(self): self.ses.end()
class TestCommands(TestCase): def setUp(self): self.session = Session( os.path.join(os.path.dirname(os.path.abspath(__file__)), "test.gnucash")) def tearDown(self): self.session.end() def test_create_split_transaction_vat(self): commands.create_split_transaction( self.session.book, settings.GNUCASH_BANK_ACCOUNT, "Internet", date.today(), "ADSL", Decimal("539.00"), ) def test_create_split_transaction_novat(self): commands.create_split_transaction( self.session.book, settings.GNUCASH_BANK_ACCOUNT, "Internet", date.today(), "ADSL", Decimal("539.00"), False, ) def test_pay_invoice(self): commands.pay_invoice(self.session.book, "000002", Decimal("99.99"), date.today()) def test_pay_invoice_exists(self): self.assertRaises( PaymentExists, commands.pay_invoice, self.session.book, "000001", Decimal("9.99"), date.today(), ) def test_apply_payment(self): commands.apply_payment(self.session.book, "000001", Decimal("99.99"), date.today()) def test_apply_payment_exists(self): self.assertRaises( PaymentExists, commands.apply_payment, self.session.book, "000001", Decimal("9.99"), date(2019, 11, 21), )
def account_choices(book=None): choices = [("", "---------")] if not book: session = Session(settings.GNUCASH_FILE) book = session.book for ac in queries.get_accounts(book.get_root_account()): choices.append((ac.name, ac.name)) if "session" in locals(): session.end() return choices
def customer_choices(book=None): choices = [("", "---------")] if not book: session = Session(settings.GNUCASH_FILE) book = session.book for c in queries.get_customers(book): choices.append((c.GetID(), c.GetName())) if "session" in locals(): session.end() return choices
def find_av_main(): exe = argv[0].split('/')[-1] if len(argv) < 6: print("NOT ENOUGH parameters!") print("usage: {} <book url> <year> <month> <day> <space-separated path to the account of interest>".format(exe)) print("PROGRAM EXIT!") return print("\nrunning {} at run-time: {}\n".format(exe, str(datetime.now()))) global gnucash_session try: (gnucash_file, str_year, str_month, str_day) = argv[1:5] print("find asset values in {} on {}-{}-{}".format(gnucash_file, str_year, str_month, str_day)) val_year, val_month, val_day = [int(blah) for blah in (str_year, str_month, str_day)] date_of_interest = date(val_year, val_month, val_day) account_path = argv[5:] print("account_path = {}\n".format(str(account_path))) gnucash_session = Session(gnucash_file, is_new=False) book = gnucash_session.book commod_tab = book.get_table() # noinspection PyPep8Naming CAD = commod_tab.lookup("ISO4217", "CAD") root_account = book.get_root_account() account_of_interest = account_from_path(root_account, account_path) acct_name = account_of_interest.GetName() total = get_asset_balance(account_of_interest, date_of_interest, CAD) # get the list of all descendant accounts descendants = account_of_interest.get_descendants() if len(descendants) > 0: # get the values for EACH sub-account too print("\nDescendants of {}:".format(acct_name)) for subAcct in descendants: total += get_asset_balance(subAcct, date_of_interest, CAD) print("total {} value = {}${}".format(acct_name, CAD.get_mnemonic(), total)) # no save needed, we're just reading... gnucash_session.end() except Exception as ae: print("Exception: {}".format(ae)) if "gnucash_session" in locals() and gnucash_session is not None: gnucash_session.end() print("\n >>> PROGRAM ENDED.")
def setUp(self): self.util = Util() self.account = Nubank(self.util.DEFAULT_ACCOUNT_SRC_FILE) self.ledger = Ledger(self.account, self.util.DEFAULT_CURRENCY, False, self.util.DEFAULT_GNUCASH_FILE) session = Session(self.util.DEFAULT_GNUCASH_FILE) self.book = session.book self.currency = self.book.get_table().lookup( 'ISO4217', self.util.DEFAULT_CURRENCY) session.end()
class GnucashBook(object): """docstring for GnucashBook.""" def __init__(self, file, currency, is_new=False): self.session = Session(file, is_new) try: self.book = self.session.book self.commod_tab = self.book.get_table() self.currency = self.commod_tab.lookup('ISO4217', currency) self.root = self.book.get_root_account() except Exception as ex: pass # logging.error(ex) def lookup_account_by_path(self, root, path): acc = root.lookup_by_name(path[0]) if acc.get_instance() == None: raise Exception('Account path {} not found'.format(':'.join(path))) if len(path) > 1: return GnucashBook.lookup_account_by_path(self, acc, path[1:]) return acc def lookup_account(self, name): path = name.split(':') return GnucashBook.lookup_account_by_path(self, self.root, path) def write_transactions(self, transactions): for transaction in transactions: tx = Transaction(self.book) tx.BeginEdit() tx.SetCurrency(self.currency) tx.SetDateEnteredTS(datetime.datetime.now()) tx.SetDatePostedTS(transaction.datetime) tx.SetDescription(transaction.description) tx.SetNotes(transaction.note) for split in transaction.splits: sp = Split(self.book) sp.SetParent(tx) sp.SetAccount(GnucashBook.lookup_account(self, split.account)) sp.SetMemo(split.memo) amount = int( Decimal(split.amount) * self.currency.get_fraction()) sp.SetValue(GncNumeric(amount, self.currency.get_fraction())) sp.SetAmount(GncNumeric(amount, self.currency.get_fraction())) tx.CommitEdit() def close(self, nochange): if not nochange: self.session.save() self.session.end()
def _loadarchive(self): if self._cache is not None: return session = Session(self.url, True, False, False) root = session.book.get_root_account() book = session.book account = book.get_root_account() commod_table = book.get_table() pdb = book.get_price_db() df = full_price_list(commod_table, pdb) df['value'] = df['num'] * 1.0 / df['denom'] session.end() session.destroy() self._cache = dropdupe(df, ['date', 'namespace', 'commod'])
def main(): if len(argv) < 3: print 'not enough parameters' print 'usage: new_book_with_opening_balances.py {source_book_url} {destination_book_url}' print 'examples:' print "gnucash-env python new_book_with_opening_balances.py '/home/username/test.gnucash' 'sqlite3:///home/username/new_test.gnucash'" print "gnucash-env python new_book_with_opening_balances.py '/home/username/test.gnucash' 'xml:///crypthome/username/finances/new_test.gnucash'" return #have everything in a try block to unable us to release our hold on stuff to the extent possible try: original_book_session = Session(argv[1], is_new=False) new_book_session = Session(argv[2], is_new=True) new_book = new_book_session.get_book() new_book_root = new_book.get_root_account() commodtable = new_book.get_table() # we discovered that if we didn't have this save early on, there would # be trouble later new_book_session.save() opening_balance_per_currency = {} recursivly_build_account_tree( original_book_session.get_book().get_root_account(), new_book_root, new_book, commodtable, opening_balance_per_currency, ACCOUNT_TYPES_TO_OPEN) (namespace, mnemonic) = PREFERED_CURRENCY_FOR_SIMPLE_OPENING_BALANCE if (namespace, mnemonic) in opening_balance_per_currency: opening_trans, opening_amount = opening_balance_per_currency[( namespace, mnemonic)] simple_opening_name_used = create_opening_balance_transaction( commodtable, namespace, mnemonic, new_book_root, new_book, opening_trans, opening_amount, False) del opening_balance_per_currency[ PREFERED_CURRENCY_FOR_SIMPLE_OPENING_BALANCE] else: simple_opening_name_used = False for (namespace, mnemonic), (opening_trans, opening_amount) in \ opening_balance_per_currency.iteritems() : simple_opening_name_used = create_opening_balance_transaction( commodtable, namespace, mnemonic, new_book_root, new_book, opening_trans, opening_amount, simple_opening_name_used) new_book_session.save() new_book_session.end() original_book_session.end() except: if "original_book_session" in locals(): original_book_session.end() if "new_book_session" in locals(): new_book_session.end() raise
def main(): args = parse_cmdline() if args.version: print VERSION exit(0) rules = readrules(args.rulesfile) account_path = re.split(':', args.ac2fix) gnucash_session = Session(args.gnucash_file, is_new=False) root_account = gnucash_session.book.get_root_account() orig_account = account_from_path(root_account, account_path) total = 0 imbalance = 0 fixed = 0 for split in orig_account.GetSplitList(): total += 1 trans = split.parent splits = trans.GetSplitList() trans_date = date.fromtimestamp(trans.GetDate()) trans_desc = trans.GetDescription() trans_memo = trans.GetNotes() ac = splits[0].GetAccount() acname = ac.GetName() if not args.silent: print trans_date,":", trans_desc, "=>", acname # check if acname is "Imbalance-USD" if acname == args.imbalance_ac: imbalance += 1 search_str = trans_desc if args.use_memo: search_str = trans_memo newac = get_ac_from_str(search_str, rules, root_account) if newac != "": if not args.silent: print "\t Changing account to: ", newac.GetName() splits[0].SetAccount(newac) fixed += 1 if not args.nochange: gnucash_session.save() gnucash_session.end() if not args.silent: print "Total splits=", total, " imbalance=", imbalance, " fixed=", fixed
def handle(self, *args, **options): try: s = Session(settings.GNUCASH_FILE) book = s.book with open(args[0], "r") as f: reader = csv.DictReader(f) for row in reader: number = row["invoice"] amount = Decimal(row["amount"]) date = parser.parse(row["date"]) try: pay_invoice(book, number, amount, date) except PaymentExists as e: print("%s... skipping" % e) s.save() finally: s.end()
def main(): original_book_session = Session(argv[1], False) new_book_session = Session(argv[2], True) new_book = new_book_session.get_book() new_book_root = new_book.get_root_account() commodtable = new_book.get_table() # we discovered that if we didn't have this save early on, there would # be trouble later new_book_session.save() opening_balance_per_currency = {} recursivly_build_account_tree( original_book_session.get_book().get_root_account(), new_book_root, new_book, commodtable, opening_balance_per_currency, ACCOUNT_TYPES_TO_OPEN ) (namespace, mnemonic) = PREFERED_CURRENCY_FOR_SIMPLE_OPENING_BALANCE if (namespace, mnemonic) in opening_balance_per_currency: opening_trans, opening_amount = opening_balance_per_currency[ (namespace, mnemonic)] simple_opening_name_used = create_opening_balance_transaction( commodtable, namespace, mnemonic, new_book_root, new_book, opening_trans, opening_amount, False ) del opening_balance_per_currency[ PREFERED_CURRENCY_FOR_SIMPLE_OPENING_BALANCE] else: simple_opening_name_used = False for (namespace, mnemonic), (opening_trans, opening_amount) in \ opening_balance_per_currency.iteritems() : simple_opening_name_used = create_opening_balance_transaction( commodtable, namespace, mnemonic, new_book_root, new_book, opening_trans, opening_amount, simple_opening_name_used ) new_book_session.save() new_book_session.end() original_book_session.end()
class Accounting: def __init__(self,filename): self.closed = False self.session = Session(filename) self.book = self.session.book commod_table = self.book.get_table() self.EUR = commod_table.lookup('CURRENCY', 'EUR') self.receivables = self._get_account(ACCOUNT_RECEIVABLE,ACCT_TYPE_RECEIVABLE); self.payables = self._get_account(ACCOUNT_PAYABLE,ACCT_TYPE_PAYABLE); self.btw_collected_high = self._get_account(ACCOUNT_BTW_COLLECTED_HIGH,ACCT_TYPE_LIABILITY); self.btw_reported_high = self._get_account(ACCOUNT_BTW_REPORTED_HIGH,ACCT_TYPE_LIABILITY); self.btw_collected_low = self._get_account(ACCOUNT_BTW_COLLECTED_LOW,ACCT_TYPE_LIABILITY); self.btw_reported_low = self._get_account(ACCOUNT_BTW_REPORTED_LOW,ACCT_TYPE_LIABILITY); self.btw_collected_import = self._get_account(ACCOUNT_BTW_COLLECTED_IMPORT,ACCT_TYPE_LIABILITY); self.btw_reported_import = self._get_account(ACCOUNT_BTW_REPORTED_IMPORT,ACCT_TYPE_LIABILITY); self.btw_paid_nl = self._get_account(ACCOUNT_BTW_PAID_NL,ACCT_TYPE_ASSET); self.btw_reported_nl = self._get_account(ACCOUNT_BTW_REPORTED_NL,ACCT_TYPE_ASSET); self.btw_paid_eu = self._get_account(ACCOUNT_BTW_PAID_EU,ACCT_TYPE_ASSET); self.btw_reported_eu = self._get_account(ACCOUNT_BTW_REPORTED_EU,ACCT_TYPE_ASSET); self.btw_reported_eu = self._get_account(ACCOUNT_BTW_REPORTED_EU,ACCT_TYPE_ASSET); self.tax_expense = self._get_account(ACCOUNT_TAX_EXPENSE,ACCT_TYPE_EXPENSE); self.taxtables = self.book.TaxTableGetTables() def _get_account(self,name,acct_type): root = self.book.get_root_account() acct = root.lookup_by_full_name(name) if acct.GetType() != acct_type: raise Exception('Account '+name+' does not exist with type '+str(acct_type)) return acct def save(self): self.session.save() def close(self): if not self.closed: self.session.end() self.closed = True def __del__(self): self.close()
def prepare_session(self): """ initialization needed for a Gnucash session :return: message """ self.logger.print_info("prepare_session()", BLUE) msg = TEST try: session = Session(self.gnc_file) self.book = session.book owner = self.monarch_record.get_owner() self.logger.print_info("Owner = {}".format(owner), GREEN) self.set_gnc_rec(InvestmentRecord(owner)) self.create_gnucash_info() if self.mode == PROD: self.logger.print_info("Mode = {}: COMMIT Price DB edits and Save session.".format(self.mode), GREEN) if self.domain != TRADE: self.price_db.commit_edit() # only ONE session save for the entire run session.save() session.end() session.destroy() msg = self.logger.get_log() except Exception as se: msg = "prepare_session() EXCEPTION!! '{}'".format(repr(se)) self.logger.print_error(msg) if "session" in locals() and session is not None: session.end() session.destroy() raise se return msg
def main(): original_book_session = Session(argv[1], is_new=False) new_book_session = Session(argv[2], in_new=True) new_book = new_book_session.get_book() new_book_root = new_book.get_root_account() commodtable = new_book.get_table() # we discovered that if we didn't have this save early on, there would # be trouble later new_book_session.save() opening_balance_per_currency = {} recursivly_build_account_tree( original_book_session.get_book().get_root_account(), new_book_root, new_book, commodtable, opening_balance_per_currency, ACCOUNT_TYPES_TO_OPEN) (namespace, mnemonic) = PREFERED_CURRENCY_FOR_SIMPLE_OPENING_BALANCE if (namespace, mnemonic) in opening_balance_per_currency: opening_trans, opening_amount = opening_balance_per_currency[( namespace, mnemonic)] simple_opening_name_used = create_opening_balance_transaction( commodtable, namespace, mnemonic, new_book_root, new_book, opening_trans, opening_amount, False) del opening_balance_per_currency[ PREFERED_CURRENCY_FOR_SIMPLE_OPENING_BALANCE] else: simple_opening_name_used = False for (namespace, mnemonic), (opening_trans, opening_amount) in \ opening_balance_per_currency.iteritems() : simple_opening_name_used = create_opening_balance_transaction( commodtable, namespace, mnemonic, new_book_root, new_book, opening_trans, opening_amount, simple_opening_name_used) new_book_session.save() new_book_session.end() original_book_session.end()
def prepare_session(self): """ Take the information from a transaction collection and produce Gnucash transactions to write to a Gnucash file :return: message """ print_info("prepare_session()", MAGENTA) msg = TEST try: session = Session(self.gnc_file) self.book = session.book print_info("Owner = {}".format(self.tx_coll[OWNER]), GREEN) self.report_info = InvestmentRecord(self.tx_coll[OWNER]) self.create_gnucash_info() if self.mode == PROD: msg = "Mode = {}: COMMIT Price DB edits and Save session.".format( self.mode) print_info(msg, GREEN) self.price_db.commit_edit() # only ONE session save for the entire run session.save() session.end() session.destroy() except Exception as e: msg = "prepare_session() EXCEPTION!! '{}'".format(repr(e)) print_error(msg) if "session" in locals() and session is not None: session.end() session.destroy() raise return msg
class DenhacGncSession: _path = None _session = None _root = None _commod = None _currency = None def __init__(self, path): if os.path.exists(path + '.LCK'): raise AssertionError("""Lock file exists. Is GNUCash running?\n""") self._path = path with warnings.catch_warnings(): warnings.simplefilter("ignore") self._session = Session(path, is_new = False) self._root = self.getBook().get_root_account() self._commod = self.getBook().get_table() self._currency = self._commod.lookup('CURRENCY', 'USD') def saveAndEndSession(self): self._session.save() self._session.end() def saveSession(self): self._session.save() def endSession(self): self._session.end() def cancelSession(self): self._session.end() self._session.destroy() def getBook(self): return self._session.book def getRoot(self): return self._session.book.get_root_account() def getCurrency(self): return self._currency
def main(): (gnucash_file, start_year, start_month, period_type, periods, debits_show, credits_show) = argv[1:8] start_year, start_month, periods = [int(blah) for blah in (start_year, start_month, periods) ] debits_show = debits_show == DEBITS_SHOW credits_show = credits_show == CREDITS_SHOW account_path = argv[8:] gnucash_session = Session("sqlite3://%s" % gnucash_file, is_new=False) root_account = gnucash_session.book.get_root_account() account_of_interest = account_from_path(root_account, account_path) # a list of all the periods of interest, for each period # keep the start date, end date, a list to store debits and credits, # and sums for tracking the sum of all debits and sum of all credits period_list = [ [start_date, end_date, [], # debits [], # credits ZERO, # debits sum ZERO, # credits sum ] for start_date, end_date in generate_period_boundaries( start_year, start_month, period_type, periods) ] # a copy of the above list with just the period start dates period_starts = [e[0] for e in period_list ] # insert and add all splits in the periods of interest for split in account_of_interest.GetSplitList(): split = Split(instance=split) trans = split.parent trans_date = date.fromtimestamp(trans.GetDate()) # use binary search to find the period that starts before or on # the transaction date period_index = bisect_right( period_starts, trans_date ) - 1 # ignore transactions with a date before the matching period start # (after subtracting 1 above start_index would be -1) # and after the last period_end if period_index >= 0 and \ trans_date <= period_list[len(period_list)-1][1]: # get the period bucket appropriate for the split in question period = period_list[period_index] # more specifically, we'd expect the transaction date # to be on or after the period start, and before or on the # period end, assuming the binary search (bisect_right) # assumptions from above are are right.. # # in other words, we assert our use of binary search # and the filtered results from the above if provide all the # protection we need assert( trans_date>= period[0] and trans_date <= period[1] ) split_amount = gnc_numeric_to_python_Decimal(split.GetAmount()) # if the amount is negative, this is a credit if split_amount < ZERO: debit_credit_offset = 1 # else a debit else: debit_credit_offset = 0 # store the debit or credit Split with its transaction, using the # above offset to get in the right bucket # # if we wanted to be really cool we'd keep the transactions period[2+debit_credit_offset].append( (trans, split) ) # add the debit or credit to the sum, using the above offset # to get in the right bucket period[4+debit_credit_offset] += split_amount csv_writer = csv.writer(stdout) csv_writer.writerow( ('period start', 'period end', 'debits', 'credits') ) def generate_detail_rows(values): return ( ('', '', '', '', trans.GetDescription(), gnc_numeric_to_python_Decimal(split.GetAmount())) for trans, split in values ) for start_date, end_date, debits, credits, debit_sum, credit_sum in \ period_list: csv_writer.writerow( (start_date, end_date, debit_sum, credit_sum) ) if debits_show and len(debits) > 0: csv_writer.writerow( ('DEBITS', '', '', '', 'description', 'value') ) csv_writer.writerows( generate_detail_rows(debits) ) csv_writer.writerow( () ) if credits_show and len(credits) > 0: csv_writer.writerow( ('CREDITS', '', '', '', 'description', 'value') ) csv_writer.writerows( generate_detail_rows(credits) ) csv_writer.writerow( () ) # no save needed, we're just reading.. gnucash_session.end()
def main(): if len(argv) < 10: print('not enough parameters') print('usage: account_analysis.py {book url} {start year} {start month, numeric} {period type: monthly, quarterly, or yearly} {number of periods to show, from start year and month} {whether to show debits: debits-show for true, all other values false} {whether to show credits: credits-show for true, all other values false} {space separated account path, as many nested levels as desired} ') print('examples:\n') print("The following example analyzes 12 months of 'Assets:Test Account' from /home/username/test.gnucash, starting in January of 2010, and shows both credits and debits") print("gnucash-env python account_analysis.py '/home/username/test.gnucash' 2010 1 monthly 12 debits-show credits-show Assets 'Test Account'\n") print("The following example analyzes 2 quarters of 'Liabilities:First Level:Second Level' from /home/username/test.gnucash, starting March 2011, and shows credits but not debits") print("gnucash-env python account_analysis.py '/home/username/test.gnucash' 2011 3 quarterly 2 debits-noshow credits-show Liabilities 'First Level' 'Second Level") return try: (gnucash_file, start_year, start_month, period_type, periods, debits_show, credits_show) = argv[1:8] start_year, start_month, periods = [int(blah) for blah in (start_year, start_month, periods) ] debits_show = debits_show == DEBITS_SHOW credits_show = credits_show == CREDITS_SHOW account_path = argv[8:] gnucash_session = Session(gnucash_file, is_new=False) root_account = gnucash_session.book.get_root_account() account_of_interest = account_from_path(root_account, account_path) # a list of all the periods of interest, for each period # keep the start date, end date, a list to store debits and credits, # and sums for tracking the sum of all debits and sum of all credits period_list = [ [start_date, end_date, [], # debits [], # credits ZERO, # debits sum ZERO, # credits sum ] for start_date, end_date in generate_period_boundaries( start_year, start_month, period_type, periods) ] # a copy of the above list with just the period start dates period_starts = [e[0] for e in period_list ] # insert and add all splits in the periods of interest for split in account_of_interest.GetSplitList(): trans = split.parent trans_date = date.fromtimestamp(trans.GetDate()) # use binary search to find the period that starts before or on # the transaction date period_index = bisect_right( period_starts, trans_date ) - 1 # ignore transactions with a date before the matching period start # (after subtracting 1 above start_index would be -1) # and after the last period_end if period_index >= 0 and \ trans_date <= period_list[len(period_list)-1][1]: # get the period bucket appropriate for the split in question period = period_list[period_index] # more specifically, we'd expect the transaction date # to be on or after the period start, and before or on the # period end, assuming the binary search (bisect_right) # assumptions from above are are right.. # # in other words, we assert our use of binary search # and the filtered results from the above if provide all the # protection we need assert( trans_date>= period[0] and trans_date <= period[1] ) split_amount = gnc_numeric_to_python_Decimal(split.GetAmount()) # if the amount is negative, this is a credit if split_amount < ZERO: debit_credit_offset = 1 # else a debit else: debit_credit_offset = 0 # store the debit or credit Split with its transaction, using the # above offset to get in the right bucket # # if we wanted to be really cool we'd keep the transactions period[2+debit_credit_offset].append( (trans, split) ) # add the debit or credit to the sum, using the above offset # to get in the right bucket period[4+debit_credit_offset] += split_amount csv_writer = csv.writer(stdout) csv_writer.writerow( ('period start', 'period end', 'debits', 'credits') ) def generate_detail_rows(values): return ( ('', '', '', '', trans.GetDescription(), gnc_numeric_to_python_Decimal(split.GetAmount())) for trans, split in values ) for start_date, end_date, debits, credits, debit_sum, credit_sum in \ period_list: csv_writer.writerow( (start_date, end_date, debit_sum, credit_sum) ) if debits_show and len(debits) > 0: csv_writer.writerow( ('DEBITS', '', '', '', 'description', 'value') ) csv_writer.writerows( generate_detail_rows(debits) ) csv_writer.writerow( () ) if credits_show and len(credits) > 0: csv_writer.writerow( ('CREDITS', '', '', '', 'description', 'value') ) csv_writer.writerows( generate_detail_rows(credits) ) csv_writer.writerow( () ) # no save needed, we're just reading.. gnucash_session.end() except: if "gnucash_session" in locals(): gnucash_session.end() raise
#!/usr/bin/env python3 ## @file # @brief Example Script simple sqlite create # @ingroup python_bindings_examples from gnucash import Session, Account from os.path import abspath from gnucash.gnucash_core_c import ACCT_TYPE_ASSET s = Session('sqlite3://%s' % abspath('test.blob'), is_new=True) # this seems to make a difference in more complex cases s.save() book = s.book root = book.get_root_account() a = Account(book) root.append_child(a) a.SetName('wow') a.SetType(ACCT_TYPE_ASSET) commod_table = book.get_table() a.SetCommodity( commod_table.lookup('CURRENCY', 'CAD') ) s.save() s.end()
trans.CommitEdit() tx_guid = trans.GetGUID().to_string() tx = models.ImportedTransaction() tx.account_guid = acct_guid tx.tx_guid = tx_guid tx.source_tx_id = txinfo['sourceId'] imported_transactions.append(tx) u = models.Update() u.account_guid = acct_guid u.updated = datetime.utcnow() u.balance = balance u.save() for tx in imported_transactions: tx.update = u tx.save() f.close() finally: debug_print('Ending GnuCash session') session.end() debug_print('Destroying GnuCash session') session.destroy() debug_print('Destroyed GnuCash session') debug_print('Done importing JSON file(s)')
# choose the account code to select TARGET_ACCOUNT_CODE = '1234' def mark_account_with_code_as_tax_related(account, target_code): """Looks at account to see if it has the target_account_code, if so set the account tax related flag to True and return True. If not, recursively tries to do the same to all children accounts of account. Returns False when recursion fails to find it. """ if account.GetCode() == target_code: account.SetTaxRelated(True) return True else: for child in account.get_children(): child = Account(instance=child) if mark_account_with_code_as_tax_related(child, target_code): return True return False # Change this path to your own gnucash_session = Session("xml:///home/mark/python-bindings-help/test.xac") mark_account_with_code_as_tax_related( gnucash_session.book.get_root_account(), TARGET_ACCOUNT_CODE) gnucash_session.save() gnucash_session.end()
class MonarchQrepToGncPrices: def __init__(self, fmon, gnc_file, mode): self.prod = (mode == PROD) self.mon_file = fmon self.session = Session(gnc_file) self.book = self.session.book self.root = self.book.get_root_account() self.root.get_instance() self.price_db = self.book.get_price_db() commod_tab = self.book.get_table() self.currency = commod_tab.lookup("ISO4217", "CAD") def parse_monarch_qtrep(self): """ PARSE FOR PRICES TO ADD TO THE PRICE DB loop: find: 'For the Period <date1> to <date2>' for the date for the prices find: MON_MARK or MON_LULU as OWNER find: 'Page 1' as the key to start finding prices find: 'OPEN...' or 'TFSA...' or 'RRSP...' as Plan Type use that as the key for this section of the Tx_Collection find: '(match1) - (match2) (match3)...' 1) use match1 as Fund Company, match2 as Fund Code for the account 2) use match3 as Fund Company, match2 as Fund Code for the account find: '$price' find: 'Transaction Details' as key to search for next Plan Type OR: another match of 'Fund Company & Fund Code' :return: Configuration.InvestmentRecord object """ print_info("parse_monarch_qtrep()\nRuntime = {}\n".format(strnow), MAGENTA) # re searches re_date = re.compile(r"^For the period (.*) to (\w{3}) (\d{1,2}), (\d{4})") re_mark = re.compile(".*({}).*".format(MON_MARK)) re_lulu = re.compile(".*({}).*".format(MON_LULU)) re_start = re.compile(r"^Page 1.*") re_comp1 = re.compile(r"^(.*) - ([0-9ATL]{3,5}).*") re_comp2 = re.compile(r"^(- )?(\d{3,5}) - (.*)") re_price = re.compile(r"^\$([0-9,]{1,5})\.(\d{2,4}).*") re_plan = re.compile(r"(OPEN|TFSA|RRSP)(\s?.*)") re_endplan = re.compile(r"^Transaction Details.*") re_finish = re.compile(r"^Disclosure.*") mon_state = FIND_OWNER with open(self.mon_file) as fp: ct = 0 for line in fp: ct += 1 if mon_state == FIND_OWNER: match_mark = re.match(re_mark, line) match_lulu = re.match(re_lulu, line) if match_mark or match_lulu: if match_mark: owner = match_mark.group(1) elif match_lulu: owner = match_lulu.group(1) print_info("{}/ Owner: {}".format(ct, owner), RED) tx_coll = InvestmentRecord(owner) mon_state = FIND_START continue if mon_state == FIND_START: match_start = re.match(re_start, line) if match_start: print_info("{}/ Found Start!".format(ct), GREEN) mon_state = FIND_DATE continue if mon_state == FIND_DATE: match_date = re.match(re_date, line) if match_date: day = match_date.group(3) month = match_date.group(2) year = match_date.group(4) datestring = "{}-{}-{}".format(year, month, day) pr_date = dt.strptime(datestring, '%Y-%b-%d') tx_coll.set_date(pr_date) print_info("date: {}".format(pr_date), CYAN) mon_state = FIND_PLAN continue if mon_state == FIND_PLAN: match_finish = re.match(re_finish, line) if match_finish: print_info("{}/ FINISHED!".format(ct), RED) break match_plan = re.match(re_plan, line) if match_plan: plan_type = match_plan.group(1) print_info("{}/ Plan type: {}".format(ct, plan_type), BLUE) mon_state = FIND_COMPANY continue if mon_state == FIND_COMPANY: match_endsum = re.match(re_endplan, line) if match_endsum: print_info("{}/ END of '{}' plan.".format(ct, plan_type), BLUE) mon_state = FIND_PLAN continue match_comp1 = re.match(re_comp1, line) match_comp2 = re.match(re_comp2, line) if match_comp1 or match_comp2: if match_comp1: company = match_comp1.group(1) fund_code = match_comp1.group(2) elif match_comp2: company = match_comp2.group(3) fund_code = match_comp2.group(2) curr_tx = {FUND_CMPY: company, FUND_CODE: fund_code} print_info("{}/ Fund is: '{}:{}'".format(ct, company, fund_code), MAGENTA) mon_state = FIND_PRICE continue if mon_state == FIND_PRICE: match_price = re.match(re_price, line) if match_price: dollar_str = match_price.group(1) cents_str = match_price.group(2) print_info("{}/ price = '${}.{}'".format(ct, dollar_str, cents_str), GREEN) curr_tx[DOLLARS] = dollar_str curr_tx[CENTS] = cents_str tx_coll.add_tx(plan_type, curr_tx) mon_state = FIND_COMPANY continue print_info("Found {} transactions.".format(tx_coll.get_size())) return tx_coll def get_prices_and_save(self, tx_coll): """ create Gnucash prices, load and save to the Gnucash file's PriceDB :param tx_coll: InvestmentRecord object: transactions to use to extract Gnucash prices :return: message """ print_info('get_prices_and_save()', MAGENTA) gncu = GncUtilities() msg = TEST self.price_db.begin_edit() print_info("self.price_db.begin_edit()", MAGENTA) try: for plan_type in tx_coll.plans: print_info("\n\nPlan type = {}".format(plan_type)) for tx in tx_coll.plans[plan_type]: base = pow(10, len(tx[CENTS])) int_price = int(tx[DOLLARS] + tx[CENTS]) val = GncNumeric(int_price, base) ast_parent_path = copy.copy(ACCT_PATHS[ASSET]) ast_parent_path.append(plan_type) if plan_type != PL_OPEN: if tx_coll.get_owner() == UNKNOWN: raise Exception("PROBLEM!! Trying to process plan type '{}' but NO Owner information found" " in Tx Collection!!".format(plan_type)) ast_parent_path.append(ACCT_PATHS[tx_coll.get_owner()]) print_info("ast_parent_path = {}".format(str(ast_parent_path)), BLUE) asset_parent = gncu.account_from_path(self.root, ast_parent_path) # get the asset account name name_key = tx[FUND_CMPY].split(' ')[0] print_info("name_key = {}".format(name_key), YELLOW) if name_key in FUND_NAME_CODE.keys(): name_code = FUND_NAME_CODE[name_key] # special case if name_code == ATL: asset_acct_name = ATL_O59 else: asset_acct_name = name_code + " " + tx[FUND_CODE] else: raise Exception("Could NOT find name key {}!".format(name_key)) print_info("asset_acct_name = {}".format(asset_acct_name), BLUE) # special location for Trust Asset account if asset_acct_name == TRUST_AST_ACCT: asset_parent = self.root.lookup_by_name(TRUST) print_info("asset_parent = {}".format(asset_parent.GetName()), BLUE) # get the asset account asset_acct = asset_parent.lookup_by_name(asset_acct_name) if asset_acct is None: # just skip updating cash-holding funds if str(val) == '100000/10000': continue else: raise Exception( "Could NOT find acct '{}' under parent '{}'".format(asset_acct_name, asset_parent.GetName())) print_info("Adding: {}[{}] @ ${}".format(asset_acct_name, tx_coll.get_date_str(), val), GREEN) pr = GncPrice(self.book) pr.begin_edit() pr.set_time64(tx_coll.get_date()) comm = asset_acct.GetCommodity() print_info("Commodity = {}:{}".format(comm.get_namespace(), comm.get_printname()), YELLOW) pr.set_commodity(comm) pr.set_currency(self.currency) pr.set_value(val) pr.set_source_string("user:price") pr.set_typestr('last') pr.commit_edit() if self.prod: print_info("PROD: Add Price to DB.\n", GREEN) self.price_db.add_price(pr) else: print_info("PROD: ABANDON Prices!\n", RED) if self.prod: msg = "PROD: COMMIT Price DB edits and Save session." print_info("PROD: COMMIT Price DB edits and Save session.", GREEN) self.price_db.commit_edit() # only ONE session save for the entire run self.session.save() self.session.end() self.session.destroy() except Exception as e: msg = "get_prices_and_save() EXCEPTION!! '{}'".format(repr(e)) print_error(msg) if "session" in locals() and self.session is not None: self.session.end() self.session.destroy() raise return msg
def aa_sum_main(): global gnucash_session exe = argv[0].split('/')[-1] if len(argv) < 9: print("NOT ENOUGH parameters!") print( "usage: {} <book url> <start year> <start month, numeric> <period type: 'monthly', 'quarterly', etc>" .format(exe)) print("\t\t\t <number of periods to show, from start year and month>") print( "\t\t\t <whether to show debits: debits-show for true, all other values false>" ) print( "\t\t\t <whether to show credits: credits-show for true, all other values false>" ) print( "\t\t\t <space-separated account path, as many nested levels as desired>" ) print("examples:\n") print( "The following example analyzes 12 months of Assets:TestAccount from <...>/test.gnucash, starting in January of 2018, and shows both credits and debits:" ) print( "{} <...>/test.gnucash 2018 1 monthly 12 debits-show credits-show Assets TestAccount\n" .format(exe)) print( "The following example analyzes 2 quarters of Liabilities:FirstLevel:SecondLevel from <...>/test.gnucash, starting March 2016, and shows credits but not debits:" ) print( "{} <...>/test.gnucash 2016 3 quarterly 2 debx credits-show Liabilities <FirstLevel> <SecondLevel>" .format(exe)) print("PROGRAM EXIT!") return try: (gnucash_file, start_year, start_month, period_type, periods, debits_show, credits_show) = argv[1:8] # mhs | debug print("showing {} periods of {} starting from {}-{}".format( periods, period_type, start_year, start_month)) start_year, start_month, periods = [ int(blah) for blah in (start_year, start_month, periods) ] # mhs | debug print("run-time: {}".format(str(datetime.now()))) print("running: {}".format(exe)) print("using gnucash file: {}".format(gnucash_file)) debits_show = debits_show == DEBITS_SHOW credits_show = credits_show == CREDITS_SHOW account_path = argv[8:] # mhs | debug print("account_path = {}".format(str(account_path))) gnucash_session = Session(gnucash_file, is_new=False) root_account = gnucash_session.book.get_root_account() # mhs | debug # print( "root_account = " + root_account.GetName() ) account_of_interest = account_from_path(root_account, account_path) # mhs | debug print("account_of_interest = {}".format(account_of_interest.GetName())) # a list of all the periods of interest # for each period keep the start date, end date, a list to store debits and credits, # and sums for tracking the sum of all debits and sum of all credits period_list = [ [ start_date, end_date, [], # debits [], # credits ZERO, # debits sum ZERO, # credits sum ZERO # TOTAL ] for start_date, end_date in generate_period_boundaries( start_year, start_month, period_type, periods) ] # a copy of the above list with just the period start dates period_starts = [e[0] for e in period_list] # mhs | get the list of all descendant accounts descendants = account_of_interest.get_descendants() if len(descendants) == 0: # mhs | account has no descendants so just calculate the splits directly get_splits(account_of_interest, period_starts, period_list) else: # mhs | calculate the sums of debits and credits for EACH sub-account but just keep the overall total print("Descendants of {}:".format(account_of_interest.GetName())) for subAcct in descendants: print("{} balance = {}".format(subAcct.GetName(), subAcct.GetBalance())) get_splits(subAcct, period_starts, period_list) # write out the column headers csv_writer = csv.writer(stdout) csv_writer.writerow(()) csv_writer.writerow( ('period start', 'period end', 'debits', 'credits', 'TOTAL')) def generate_detail_rows(values): return (('', '', '', '', trans.GetDescription(), gnc_numeric_to_python_decimal(split.GetAmount())) for trans, split in values) # write out the overall totals for the account of interest for start_date, end_date, debits, creds, debit_sum, credit_sum, total in period_list: csv_writer.writerow( (start_date, end_date, debit_sum, credit_sum, total)) # write the details for each credit or debit if requested on the command line if debits_show and len(debits) > 0: csv_writer.writerow( ('DEBITS', '', '', '', 'description', 'value')) csv_writer.writerows(generate_detail_rows(debits)) csv_writer.writerow(()) if credits_show and len(creds) > 0: csv_writer.writerow( ('CREDITS', '', '', '', 'description', 'value')) csv_writer.writerows(generate_detail_rows(creds)) csv_writer.writerow(()) # no save needed, we're just reading.. gnucash_session.end() except Exception as ae: if "gnucash_session" in locals() and gnucash_session is not None: gnucash_session.end() raise print("\n >>> PROGRAM ENDED.")
def main(): if len(argv) < 4: print 'Usage: python ofxml_make_commodity_accounts.py <gnucash-file> <ofxml-file> <accountmap-file>' exit(1) gnucash_file=argv[1] doc=ElementTree(file=argv[2]) acctids=doc.findall('./INVSTMTMSGSRSV1/INVSTMTTRNRS/INVSTMTRS/INVACCTFROM/ACCTID') if len(acctids)!=1: print 'ERROR: No unique account number found in OFX: found', len(acctids) return acctid=acctids[0].text.strip() acctcur='any' m=re.search('^(.*)-([A-Z]{3})$',acctid) if m: acctid=m.group(1) acctcur=m.group(2) print "INFO: Account number:", acctid, "Currency:", acctcur fitids={} for itran in doc.findall('.//INVBUY')+doc.findall('.//INVSELL')+doc.findall('.//REINVEST'): fitid=itran.find('./INVTRAN/FITID') if not fitid is None: fitid=fitid.text.strip() if fitid in fitids: print "ERROR: Non-unique FITID found:", fitid exit(1) fitids[fitid]=itran # Fantastic, the FITID is not saved by GnuCash for income transactions... # Index by (date,amount,memo) instead incometrns={} for itran in doc.findall('.//INCOME'): fields={} for path in ('INVTRAN/DTTRADE', 'INVTRAN/MEMO', 'TOTAL'): el=itran.find('./'+path) if not el is None and len(el.text.strip())>0: fields[path]=el.text.strip() if len(fields)!=3: print "ERROR: Can't create identifier for INCOME transaction, ignoring." incometrns[(fields['INVTRAN/DTTRADE'][0:8],fields['INVTRAN/MEMO'],Fraction(fields['TOTAL']))]=itran sess=Session(gnucash_file) try: # Find GNC parent account root=sess.book.get_root_account() matched_accts=find_acct_by_number_and_currency(root,acctid,acctcur) if len(matched_accts)==0: from_iban=conv_iban(acctid) if from_iban: matched_accts=find_acct_by_number_and_currency(root,from_iban,acctcur) if len(matched_accts)!=1: print 'ERROR: No unique account this number/currency; found', len(matched_accts) return acct=matched_accts[0] print 'DEBUG: Found parent account:',acct.GetName() accountmap=read_accountmap(argv[3],acct.GetCode()+'-'+acct.GetCommodity().get_mnemonic()) # Find child Stock/Mutual accounts secaccts=[] # SwigPyObject is not Hashable :( for cacct in acct.get_descendants(): atype=cacct.GetType() if atype==gnucash_core.ACCT_TYPE_STOCK or atype==gnucash_core.ACCT_TYPE_MUTUAL: secaccts.append(cacct.get_instance()) # Find income accounts incaccts=[] # SwigPyObject is not Hashable :( for typ in accountmap: if typ[0:6]=="INCOME": inst=find_acct_by_path(root,accountmap[typ]).get_instance() if not (inst is None or inst in incaccts): incaccts.append(inst) if len(incaccts)==0 and len(incometrns)>0: print 'WARNING: no income accounts defined for account',acct.GetCode()+'-'+acct.GetCommodity().get_mnemonic() print 'WARNING: income transactions will not be fixed' # Go through all transactions for tran in _py_xaccSplitListGetUniqueTransactions(acct.GetSplitList()): # Consider fixing if transaction ... # ... has exactly 2 splits # ... has 1 split with a child Stock/Mutual account # ... has 1 split with an online ID splits=tran.GetSplitList() if len(splits)==2: cashsplit=None secsplit=None incsplit=None online_id=None for split in splits: if split.GetAccount().get_instance() in secaccts: secsplit=split if split.GetAccount().get_instance() in incaccts: incsplit=split if split.GetAccount().get_instance()==acct.get_instance(): cashsplit=split oid=_py_gnc_import_get_split_online_id(sess,split) if not oid is None: if online_id is None: online_id=oid else: online_id=False if not (cashsplit is None or secsplit is None or online_id is None or online_id is False): if not online_id in fitids: # This can happen if we encounter a transaction outside of this OFX period #print 'DEBUG: FITID',online_id,'not found in OFX file.' continue fix_buysell_transaction(tran,secsplit,cashsplit,fitids[online_id],root,accountmap) elif not (cashsplit is None or incsplit is None): date=tran.RetDatePostedTS().strftime('%Y%m%d') memo=re.sub(' +',' ',tran.GetDescription()) # GnuCash importer likes to insert spaces randomly amt=gnc_numeric_to_fraction(cashsplit.GetAmount()) if not (date,memo,amt) in incometrns: # This can happen if we encounter a transaction outside of this OFX period #print "DEBUG: No match for income transaction",date,memo,amt continue fix_income_transaction(tran,incsplit,cashsplit,incometrns[(date,memo,amt)],root,accountmap) wait_for_backup_file(gnucash_file) sess.save() except BaseException as e: print 'ERROR:',e finally: sess.end() sess.destroy()
def invoice_pdf(gnc_file, invoice_number, pdf_file): uri = "xml://{0}".format(os.path.abspath(gnc_file)) ses = Session(uri, is_new=False) try: book = ses.get_book() commod_table = book.get_table() USD = commod_table.lookup("CURRENCY", "USD") invoice = book.InvoiceLookupByID(invoice_number) client = invoice.GetOwner() client_addr = client.GetAddr() pdf = Canvas(pdf_file, bottomup=False, pagesize=letter) pdf.setFont("Helvetica-Bold", 24) pdf.setFillColor(colors.lightgrey) pdf.drawCentredString(letter[0] / 2, inch * 0.75, "INVOICE") font_height = 10 pdf.setFont("Helvetica", font_height) pdf.setFillColor(colors.black) from_header = pdf.beginText(inch * 0.75, inch * 0.75) to_header = pdf.beginText(inch * 0.75, inch * 2.25) header_file = "{0}/.gnc-invoice-header".format(os.environ["HOME"]) with open(header_file, "r") as f: for line in f: from_header.textLine(line.strip()) to_fields = [ client.GetName(), client_addr.GetName(), client_addr.GetAddr1(), client_addr.GetAddr2(), client_addr.GetAddr3(), client_addr.GetAddr4(), ] for field in to_fields: if field: to_header.textLine(field) pdf.drawText(from_header) pdf.drawText(to_header) # # This is the summary table / box in the mid-upper right. # table_data = ( ("Invoice #", invoice.GetID()), ("Date", invoice.GetDatePosted().strftime("%Y-%m-%d")), ("Amount Due (USD)", "${0:0.2f}".format(invoice.GetTotal().to_double())), ) x = inch * 4.5 y = (inch * 2.25) - font_height width = inch * 3 height = inch * 0.75 num_rows = 3 num_cols = 2 col_width = width / num_cols row_height = height / num_rows for row in range(num_rows): for col in range(num_cols): rect_x = x + (col_width * col) rect_y = y + (row_height * row) pdf.setFillColor(colors.darkgrey) pdf.rect(rect_x, rect_y, col_width, row_height, stroke=True, fill=(col == 0)) pdf.setFillColor(colors.black) if col: pdf.drawAlignedString(rect_x + col_width, rect_y + font_height + 2, table_data[row][col], "%") else: pdf.drawString(rect_x + 5, rect_y + font_height + 2, table_data[row][col]) # # This is the detail table in the lower half. # table_data = [("Date", "Description", "Hours", "Rate ($)", "Line Total")] for entry in [Entry(instance=e) for e in invoice.GetEntries()]: qty = GncNumeric(instance=entry.GetQuantity()).to_double() rate = GncNumeric(instance=entry.GetInvPrice()).to_double() line_total = GncNumeric(instance=entry.ReturnValue(True)).to_double() row = [ entry.GetDate().strftime("%Y-%m-%d"), entry.GetDescription(), "{0:0.2f}".format(qty), "{0:0.2f}".format(rate), "{0:0.2f}".format(line_total), ] table_data.append(row) x = inch * 0.75 y = inch * 4.0 # Let column 1 consume the rest of the space. width = inch * 6.75 widths = [80, 0, 50, 50, 80] widths[1] = width - sum(widths) height = font_height + 2 + 2 # 2pt spacing above and below. num_rows = 1 num_cols = 5 col_width = width / num_cols row_height = height / num_rows rect_x = x rect_y = y for row_num, row in enumerate(table_data): rect_x = x for col_num, col in enumerate(row): col_width = widths[col_num] rect_y = y + (row_height * row_num) pdf.setFillColor(colors.darkgrey) pdf.rect(rect_x, rect_y, col_width, row_height, stroke=True, fill=(row_num == 0)) pdf.setFillColor(colors.black) if col_num > 1: pdf.drawAlignedString(rect_x + col_width, rect_y + font_height + 2, col, "%") else: pdf.drawString(rect_x + 5, rect_y + font_height + 2, col) rect_x = rect_x + col_width # Draw the outer detail box. detail_height = inch * 5.0 pdf.setFillColor(colors.black) pdf.rect(x, y, width, detail_height, stroke=True, fill=False) # Total box above payment terms. totalbox_text_vpad = 6 totalbox_text_height = font_height + (totalbox_text_vpad * 2) totalbox_rows = 4 totalbox_height = totalbox_text_height * (totalbox_rows + 1) totalbox_y = y + detail_height - totalbox_height pdf.rect(x, totalbox_y, width, totalbox_height, stroke=True, fill=False) # Total, balance due, etc boxes inside total box. total_amount = invoice.GetTotal().to_double() totalbox_data = [ ("Subtotal:", "${0:0.2f}".format(total_amount)), ("Total:", "${0:0.2f}".format(total_amount)), ("Amount Paid:", "$0.00"), ("Balance Due:", "${0:0.2f}".format(total_amount)), ] balance_height = row_height balance_y = totalbox_y + totalbox_height - totalbox_text_height * 2 for n in xrange(totalbox_rows): thisbox_y = totalbox_y + totalbox_text_height * n thisbox_text_y = thisbox_y + totalbox_text_height - totalbox_text_vpad pdf.setFillColor(colors.lightgrey) pdf.rect( x + width / 2, thisbox_y, width / 2, totalbox_text_height, stroke=True, fill=(n == (totalbox_rows - 1)) ) pdf.setFillColor(colors.black) pdf.drawAlignedString(x + width / 2 + (inch * 1.5), thisbox_text_y, totalbox_data[n][0], ":") pdf.drawAlignedString(x + width - 20, thisbox_text_y, totalbox_data[n][1], ".") # Payment terms in the bottom of the detail box. pdf.setFillColor(colors.black) pdf.rect(x, y + detail_height - totalbox_text_height, width, totalbox_text_height, stroke=True, fill=False) if invoice.GetTerms() and invoice.GetTerms().GetDueDays(): due = "Payment due within %d days of invoice date." % (invoice.GetTerms().GetDueDays()) pdf.setFillColor(colors.black) pdf.drawCentredString(x + (width / 2), y + detail_height - totalbox_text_vpad, due) pdf.showPage() pdf.save() finally: ses.end()
def main(): args = parse_cmdline() if args.version: print(VERSION) exit(0) if args.verbose: loglevel = logging.DEBUG elif args.quiet: loglevel = logging.WARN else: loglevel = logging.INFO logging.basicConfig(level=loglevel) rules = readrules(args.rulesfile) account_path = re.split(':', args.ac2fix) gnucash_session = Session(args.gnucash_file, is_new=False) total = 0 imbalance = 0 fixed = 0 confirmed = None try: root_account = gnucash_session.book.get_root_account() orig_account = account_from_path(root_account, account_path) imbalance_pattern = re.compile(args.imbalance_ac) for split in orig_account.GetSplitList(): total += 1 trans = split.parent splits = trans.GetSplitList() trans_date = trans.GetDate().date() trans_desc = trans.GetDescription() trans_memo = trans.GetNotes() for split in splits: ac = split.GetAccount() acname = ac.GetName() amount = eval(str(split.GetAmount())) # "Debit balance accounts" are represented with a negative sign # internally, so the amounts need their signs flipped. # Refer to section 2.1.3 Debits and Credits in the GnuCash docs, # the rearranged accounting equation to see why. if account_path[0] in ('Assets', 'Expenses'): amount = -amount logging.debug('%s: %s => %s', trans_date, trans_desc, acname) if imbalance_pattern.match(acname): imbalance += 1 search_str = trans_desc if args.use_memo: search_str = trans_memo newac = get_ac_from_str(search_str, rules, root_account) if newac != "": if args.confirm: print( bold + green + '\nFound a match!\n' + end + '{}: "{}" = {:.2f}\nFix account "{}" to "{}"?'. format(trans_date, cyan + trans_desc + end, amount, acname, cyan + newac.GetName() + end)) if confirmed != 'all': confirmed = input( "Press ENTER to fix, " "'all' to fix all remaining, " "'s' to skip, 'q' to quit: ").lower( ).strip() if confirmed == 'q': print("Quitting...") gnucash_session.end() sys.exit() elif confirmed not in ('', 'all'): # If not ENTER, 'y', or 'all', then skip! print("Skipping...") continue print("Fixing...") logging.debug('\tChanging account to: %s', newac.GetName()) split.SetAccount(newac) fixed += 1 if not args.nochange: gnucash_session.save() logging.info('Total splits=%s, imbalance=%s, fixed=%s', total, imbalance, fixed) except Exception as ex: logging.error(ex) gnucash_session.end()
def main(): if len(argv) < 3: print('not enough parameters') print('usage: new_book_with_opening_balances.py {source_book_url} {destination_book_url}') print('examples:') print("gnucash-env python new_book_with_opening_balances.py '/home/username/test.gnucash' 'sqlite3:///home/username/new_test.gnucash'") print("gnucash-env python new_book_with_opening_balances.py '/home/username/test.gnucash' 'xml:///crypthome/username/finances/new_test.gnucash'") return #have everything in a try block to unable us to release our hold on stuff to the extent possible try: original_book_session = Session(argv[1], is_new=False) new_book_session = Session(argv[2], is_new=True) new_book = new_book_session.get_book() new_book_root = new_book.get_root_account() commodtable = new_book.get_table() # we discovered that if we didn't have this save early on, there would # be trouble later new_book_session.save() opening_balance_per_currency = {} recursivly_build_account_tree( original_book_session.get_book().get_root_account(), new_book_root, new_book, commodtable, opening_balance_per_currency, ACCOUNT_TYPES_TO_OPEN ) (namespace, mnemonic) = PREFERED_CURRENCY_FOR_SIMPLE_OPENING_BALANCE if (namespace, mnemonic) in opening_balance_per_currency: opening_trans, opening_amount = opening_balance_per_currency[ (namespace, mnemonic)] simple_opening_name_used = create_opening_balance_transaction( commodtable, namespace, mnemonic, new_book_root, new_book, opening_trans, opening_amount, False ) del opening_balance_per_currency[ PREFERED_CURRENCY_FOR_SIMPLE_OPENING_BALANCE] else: simple_opening_name_used = False for (namespace, mnemonic), (opening_trans, opening_amount) in \ opening_balance_per_currency.iteritems() : simple_opening_name_used = create_opening_balance_transaction( commodtable, namespace, mnemonic, new_book_root, new_book, opening_trans, opening_amount, simple_opening_name_used ) new_book_session.save() new_book_session.end() original_book_session.end() except: if "original_book_session" in locals(): original_book_session.end() if "new_book_session" in locals(): new_book_session.end() raise
def map_customers(request): rows = request.session.get("rows", []) map = request.session.get("map", {}) data = [] for row in rows: new_row = {} for index, field in map.items(): new_row[field] = row[int(index)] if not new_row["amount"].startswith("-"): data.append(new_row) session = Session(settings.GNUCASH_FILE) CustomerFormSet = formset_factory(CustomerForm, extra=0) if request.method == "POST": formset = CustomerFormSet(request.POST, form_kwargs={"book": session.book}) try: ok = dup = 0 for form in formset.forms: if form.is_valid(): clean = form.cleaned_data try: commands.apply_payment( session.book, clean["customer"], clean["amount"], clean["date"], ) ok += 1 except PaymentExists as e: messages.warning(request, e) dup += 1 session.save() if ok: messages.info(request, "Successfully imported %s transactions" % ok) if dup: messages.warning(request, "Skipped %s duplicate transactions" % dup) except Exception as e: # messages.error(request, e) raise finally: session.end() return HttpResponseRedirect(reverse("income-finish")) else: initial = [] for row in data: customer = queries.match_customer(session.book, row.get("customer")) initial.append({ "customer": customer, "amount": row.get("amount"), "date": row.get("date"), "description": row.get("customer"), }) formset = CustomerFormSet(initial=initial, form_kwargs={"book": session.book}) session.end() return render( request, "importer/map-customers.html", { "formset": formset, "rows": zip(data, formset.forms), "gnucash": settings.GNUCASH_FILE, }, )
def map_accounts(request): rows = request.session.get("rows", []) map = request.session.get("map", {}) statement = request.session.get("statement", "bank") if statement == "bank": bank_account = settings.GNUCASH_BANK_ACCOUNT else: bank_account = settings.GNUCASH_CARD_ACCOUNT data = [] for row in rows: new_row = {} for index, field in map.items(): new_row[field] = row[int(index)] # @todo: split into debit/credit views if statement == "card" or (new_row["amount"].startswith("-") or "VIRTUALSTOCK" in new_row["account"]): data.append(new_row) AccountFormSet = formset_factory(AccountForm, extra=0) if request.method == "POST": session = Session(settings.GNUCASH_FILE) formset = AccountFormSet(request.POST, form_kwargs={'book': session.book}) root = session.book.get_root_account() if statement == "bank": bank = root.lookup_by_name(settings.GNUCASH_BANK_ACCOUNT) else: bank = root.lookup_by_name(settings.GNUCASH_CARD_ACCOUNT) check = queries.get_duplicate_check_data(bank) log.debug(check) try: ok = dup = 0 for form in formset.forms: if form.is_valid(): clean = form.cleaned_data if [clean["date"], clean["amount"]] not in check: create_split_transaction( session.book, bank_account, str(clean["account"]), clean["date"], str(clean["description"]), clean["amount"], vat_incl=clean["vat_incl"], ) ok += 1 else: log.debug( "Skipped %s %s %s" % ( clean["date"].strftime("%Y-%m-%d"), clean["description"], clean["amount"], ), ) dup += 1 session.save() messages.info(request, "Successfully imported %s transactions" % ok) if dup: messages.warning(request, "Skipped %s duplicate transactions" % dup) except Exception as e: messages.error(request, e) finally: session.end() return HttpResponseRedirect(reverse("expenses-finish")) else: initial = [] for row in data: account, vat_incl = queries.match_account(row.get("account"), row.get("amount", 0)) initial.append({ "account": account, "amount": row.get("amount"), "date": row.get("date"), "description": row.get("account"), "vat_incl": vat_incl, }) formset = AccountFormSet(initial=initial) return render( request, "importer/map-accounts.html", { "formset": formset, "rows": zip(data, formset.forms), "gnucash": settings.GNUCASH_FILE, }, )
class Gnucash: __seendict = {} def __init__(self, book_uri): self._session = Session(book_uri=book_uri) self._book = self._session.book self._root_account = self._book.get_root_account() self._commodity_table = self._book.get_table() # todo: implement as class w/ getattr self.commods = {} self.commods['USD'] = self._commodity_table.lookup("ISO4217", "USD") def __enter__(self): return self def __exit__(self, type, value, traceback): self._session.end() def save(self): self._session.save() def seen(self, acct, num): if repr(acct) not in self.__seendict: l = [] for split in acct.GetSplitList(): txn = split.parent if txn.GetNum() not in l: l.append(txn.GetNum().strip()) self.__seendict[repr(acct)] = l return num in self.__seendict[repr(acct)] def _account_from_path(self, top_account, account_path, original_path=None): if original_path==None: original_path = account_path account, account_path = account_path[0], account_path[1:] account = top_account.lookup_by_name(account) if account == None or account.get_instance() == None: raise Exception( "path " + ''.join(original_path) + " could not be found") if len(account_path) > 0 : return self._account_from_path(account, account_path, original_path) else: return account def account(self, account_path): return self._account_from_path(self._root_account, account_path.split(':')) def rat(self, value): s = int(round(value*100)) return GncNumeric(s, 100) def NewTransaction(self): t = Transaction(self._book) t.BeginEdit() t.SetCurrency(self.commods['USD']) return t def NewSplit(self, txn, acct, amount): s = Split(self._book) s.SetParent(txn) s.SetAccount(acct) s.SetAmount(self.rat(amount)) s.SetValue(self.rat(amount)) return s def TransactionReadyToCommit(self, txn): for split in txn.GetSplitList(): if split.GetValue().to_double() == 0.0: split.Destroy() return txn.IsBalanced() def InvoiceLookupByID(self, id): return self._book.InvoiceLookupByID(id) def PayInvoiceWithTransaction(self, invoice, txn, acct, gross, memo, num): invoice.BeginEdit() invoice.ApplyPayment(txn, acct, self.rat(-gross), self.rat(1), txn.RetDatePostedTS(), memo, num) invoice.CommitEdit() def ApplyPaymentToCustomer(self, customer, txn, post_acct, from_acct, gross, memo, num): print("Applying payment of %s to customer %s" % (gross, customer.GetName())) customer.BeginEdit() customer.ApplyPayment(txn, None, post_acct, from_acct, self.rat(-gross), self.rat(1), txn.RetDatePostedTS(), memo, num, True) customer.CommitEdit() def GetCustomerByEmail(self, email): q = Query() q.search_for('gncCustomer') q.set_book(self._book) c = None for result in q.run(): tmp = Customer(instance=result) if tmp.GetAddr().GetEmail().lower() == email.lower(): c = tmp break q.destroy() return c def GetCustomerByName(self, name): q = Query() q.search_for('gncCustomer') q.set_book(self._book) c = None for result in q.run(): tmp = Customer(instance=result) if tmp.GetName().lower() in name.lower(): c = tmp break q.destroy() return c
class GnuCashHelper(object): """some hany functions around gnucash api""" def __init__(self, filename): self.__session = Session(filename, is_new=False) self.__book = self.__session.book def account_from_path(self, top_account, account_path, original_path=None): """ return gnucash account object from account path in this notation <Account>.<SubAccount>.<SubAccount> Accounts are splitted by dots """ if original_path is None: original_path = account_path account, account_path = account_path[0], account_path[1:] account = top_account.lookup_by_name(account) #print(account.name) if account.get_instance() is None: raise Exception("path " + ''.join(original_path) + " could not be found") if len(account_path) > 0: return self.account_from_path(account, account_path, original_path) return account def lookup_account(self, root, name): """ find account by name """ path = name.split('.') return self.lookup_account_by_path(root, path) def lookup_account_by_path(self, root, path): """ return account given path """ acc = root.lookup_by_name(path[0]) if acc.get_instance() is None: raise Exception('Account path {} not found'.format('.'.join(path))) if len(path) > 1: return self.lookup_account_by_path(acc, path[1:]) return acc def get_root(self): """ return root account """ return self.__book.get_root_account() @staticmethod def tokenizer(entry, blacklist_tokens=None): """ cut string into tokens entry has to be in gnucach entry data format """ # like {'haben': 'Ausgleichskonto-EUR', 'soll': # 'Aktiva.PSK-Konto', 'num': '', 'description': 'Abgleich', # 'date': '2000-01-01', 'haben_value': '3002.63', 'notes': # 'None', 'soll_value': '-3002.63'} if blacklist_tokens is None: blacklist_tokens = [] tokens = re.findall(r'(?ms)\W*(\w+)', entry["description"]) tokens.extend(re.findall(r'(?ms)\W*(\w+)', entry["notes"])) tokens.extend(entry["description"].split(" ")) tokens.extend(entry["notes"].split(" ")) final_tokens = [] for token in tokens: if len(token) <= 2: continue if token not in final_tokens: final_tokens.append(token.lower()) # find some subtokens from og/123762137 for subtoken in token.split("/"): if subtoken not in final_tokens: final_tokens.append(subtoken) if entry["soll_value"] > 0.0: final_tokens.append("einnahmen") else: final_tokens.append("ausgaben") return entry["haben"], [ token for token in final_tokens if token not in blacklist_tokens ] def gnucash_export(self, path): """ export all splits of particular account given by path path should be joined by . """ account_path = path.split(".") try: root_account = self.get_root() print(root_account.name) orig_account = self.account_from_path(root_account, account_path) print(orig_account.name) print(orig_account.get_full_name()) for split in orig_account.GetSplitList(): other = split.GetOtherSplit() if other is None: print("there is not other account") sys.exit(2) other_account = other.GetAccount() if other_account is None: print("No other account found") sys.exit(1) other_name = other_account.get_full_name() other_value = other.GetValue() # 01.02.2012;Internet;;36336 0000123445 Walter oder Monika # ;Internet FE/000004174;;PSK-Konto;T;;N;15,50 ;;15,50;;; # get transaction to witch split belongs # a transaction consists of 1..n splits trans = split.parent if len(trans.GetSplitList()) > 2: print( "there are more than 2 accounts involved, not supported" ) sys.exit(3) csv_line = { "soll": orig_account.get_full_name(), "haben": other_name, "date": str(datetime.date.fromtimestamp(trans.GetDate())), "description": str(trans.GetDescription()), "notes": str(trans.GetNotes()), "num": str(trans.GetNum()), "soll_value": str(split.GetValue()), "haben_value": str(other_value), } yield csv_line #print(json.dumps(csv_line, indent=4)) #for tsplit in trans.GetSplitList(): # account = tsplit.GetAccount() # account_name = str(account.GetName()) # value = str(tsplit.GetValue()) except Exception as exc: logging.exception(exc) def get_latest_booking(self, path): """ return latest booking of this particular account """ account_path = path.split(".") try: root_account = self.get_root() print(root_account.name) orig_account = self.account_from_path(root_account, account_path) print(orig_account.name) print(orig_account.get_full_name()) split = orig_account.GetSplitList()[-1] other = split.GetOtherSplit() if other is None: print("there is not other account") sys.exit(2) other_account = other.GetAccount() if other_account is None: print("No other account found") sys.exit(1) other_name = other_account.get_full_name() other_value = other.GetValue() # 01.02.2012;Internet;;36336 00007712341 Walter oder Monika # ;Internet FE/000004174;;PSK-Konto;T;;N;15,50 ;;15,50;;; # get transaction to witch split belongs # a transaction consists of 1..n splits trans = split.parent if len(trans.GetSplitList()) > 2: print("there are more than 2 accounts involved, not supported") sys.exit(3) csv_line = { "soll": orig_account.get_full_name(), "haben": other_name, "date": str(datetime.date.fromtimestamp(trans.GetDate())), "description": str(trans.GetDescription()), "notes": str(trans.GetNotes()), "num": str(trans.GetNum()), "soll_value": str(split.GetValue()), "haben_value": str(other_value), } return csv_line except Exception as exc: logging.exception(exc) def add_transaction(self, item): """ add new transaction item must have following keys """ assert "date" in item.keys() assert "description" in item.keys() assert "notes" in item.keys() assert "soll" in item.keys() assert "soll_value" in item.keys() assert "haben" in item.keys() assert "haben_value" in item.keys() commod_tab = self.__book.get_table() currency = commod_tab.lookup('ISO4217', "EUR") logging.info('Adding transaction for account "%s" (%s %s)..', item["soll"], item["soll_value"], currency.get_mnemonic()) tx = Transaction(self.__book) tx.BeginEdit() tx.SetCurrency(currency) tx.SetDateEnteredTS(datetime.datetime.now()) tx.SetDatePostedTS(item["date"]) tx.SetDescription(item["description"]) tx.SetNotes(item["notes"]) if "num" in item.keys(): tx.SetNum(item["num"]) # soll acc = self.account_from_path(self.get_root(), item["soll"].split(".")) s1 = Split(self.__book) s1.SetParent(tx) s1.SetAccount(acc) amount = int(item["soll_value"] * currency.get_fraction()) s1.SetValue(GncNumeric(amount, currency.get_fraction())) s1.SetAmount(GncNumeric(amount, currency.get_fraction())) # haben acc2 = self.account_from_path(self.get_root(), item["haben"].split(".")) s2 = Split(self.__book) s2.SetParent(tx) s2.SetAccount(acc2) amount = int(item["haben_value"] * currency.get_fraction()) s2.SetValue(GncNumeric(amount, currency.get_fraction())) s2.SetAmount(GncNumeric(amount, currency.get_fraction())) tx.CommitEdit() def end(self): """ should be always called to prevent locking TODO: maybe there is some better way to implement this """ self.__session.end()
# choose the account code to select TARGET_ACCOUNT_CODE = '1234' def mark_account_with_code_as_tax_related(account, target_code): """Looks at account to see if it has the target_account_code, if so set the account tax related flag to True and return True. If not, recursively tries to do the same to all children accounts of account. Returns False when recursion fails to find it. """ if account.GetCode() == target_code: account.SetTaxRelated(True) return True else: for child in account.get_children(): child = Account(instance=child) if mark_account_with_code_as_tax_related(child, target_code): return True return False # Change this path to your own gnucash_session = Session("/home/mark/python-bindings-help/test.xac") mark_account_with_code_as_tax_related(gnucash_session.book.get_root_account(), TARGET_ACCOUNT_CODE) gnucash_session.save() gnucash_session.end()
def main(): (gnucash_file, start_year, start_month, period_type, periods, debits_show, credits_show) = argv[1:8] start_year, start_month, periods = [ int(blah) for blah in (start_year, start_month, periods) ] debits_show = debits_show == DEBITS_SHOW credits_show = credits_show == CREDITS_SHOW account_path = argv[8:] gnucash_session = Session(gnucash_file, is_new=False) root_account = gnucash_session.book.get_root_account() account_of_interest = account_from_path(root_account, account_path) # a list of all the periods of interest, for each period # keep the start date, end date, a list to store debits and credits, # and sums for tracking the sum of all debits and sum of all credits period_list = [ [ start_date, end_date, [], # debits [], # credits ZERO, # debits sum ZERO, # credits sum ] for start_date, end_date in generate_period_boundaries( start_year, start_month, period_type, periods) ] # a copy of the above list with just the period start dates period_starts = [e[0] for e in period_list] # insert and add all splits in the periods of interest for split in account_of_interest.GetSplitList(): trans = split.parent trans_date = date.fromtimestamp(trans.GetDate()) # use binary search to find the period that starts before or on # the transaction date period_index = bisect_right(period_starts, trans_date) - 1 # ignore transactions with a date before the matching period start # (after subtracting 1 above start_index would be -1) # and after the last period_end if period_index >= 0 and \ trans_date <= period_list[len(period_list)-1][1]: # get the period bucket appropriate for the split in question period = period_list[period_index] # more specifically, we'd expect the transaction date # to be on or after the period start, and before or on the # period end, assuming the binary search (bisect_right) # assumptions from above are are right.. # # in other words, we assert our use of binary search # and the filtered results from the above if provide all the # protection we need assert (trans_date >= period[0] and trans_date <= period[1]) split_amount = gnc_numeric_to_python_Decimal(split.GetAmount()) # if the amount is negative, this is a credit if split_amount < ZERO: debit_credit_offset = 1 # else a debit else: debit_credit_offset = 0 # store the debit or credit Split with its transaction, using the # above offset to get in the right bucket # # if we wanted to be really cool we'd keep the transactions period[2 + debit_credit_offset].append((trans, split)) # add the debit or credit to the sum, using the above offset # to get in the right bucket period[4 + debit_credit_offset] += split_amount csv_writer = csv.writer(stdout) csv_writer.writerow(('period start', 'period end', 'debits', 'credits')) def generate_detail_rows(values): return (('', '', '', '', trans.GetDescription(), gnc_numeric_to_python_Decimal(split.GetAmount())) for trans, split in values) for start_date, end_date, debits, credits, debit_sum, credit_sum in \ period_list: csv_writer.writerow((start_date, end_date, debit_sum, credit_sum)) if debits_show and len(debits) > 0: csv_writer.writerow(('DEBITS', '', '', '', 'description', 'value')) csv_writer.writerows(generate_detail_rows(debits)) csv_writer.writerow(()) if credits_show and len(credits) > 0: csv_writer.writerow( ('CREDITS', '', '', '', 'description', 'value')) csv_writer.writerows(generate_detail_rows(credits)) csv_writer.writerow(()) # no save needed, we're just reading.. gnucash_session.end()
def main(): if len(argv) < 10: print('not enough parameters') print( 'usage: account_analysis.py {book url} {start year} {start month, numeric} {period type: monthly, quarterly, or yearly} {number of periods to show, from start year and month} {whether to show debits: debits-show for true, all other values false} {whether to show credits: credits-show for true, all other values false} {space separated account path, as many nested levels as desired} ' ) print('examples:\n') print( "The following example analyzes 12 months of 'Assets:Test Account' from /home/username/test.gnucash, starting in January of 2010, and shows both credits and debits" ) print( "gnucash-env python account_analysis.py '/home/username/test.gnucash' 2010 1 monthly 12 debits-show credits-show Assets 'Test Account'\n" ) print( "The following example analyzes 2 quarters of 'Liabilities:First Level:Second Level' from /home/username/test.gnucash, starting March 2011, and shows credits but not debits" ) print( "gnucash-env python account_analysis.py '/home/username/test.gnucash' 2011 3 quarterly 2 debits-noshow credits-show Liabilities 'First Level' 'Second Level" ) return try: (gnucash_file, start_year, start_month, period_type, periods, debits_show, credits_show) = argv[1:8] start_year, start_month, periods = [ int(blah) for blah in (start_year, start_month, periods) ] debits_show = debits_show == DEBITS_SHOW credits_show = credits_show == CREDITS_SHOW account_path = argv[8:] gnucash_session = Session(gnucash_file, is_new=False) root_account = gnucash_session.book.get_root_account() account_of_interest = account_from_path(root_account, account_path) # a list of all the periods of interest, for each period # keep the start date, end date, a list to store debits and credits, # and sums for tracking the sum of all debits and sum of all credits period_list = [ [ start_date, end_date, [], # debits [], # credits ZERO, # debits sum ZERO, # credits sum ] for start_date, end_date in generate_period_boundaries( start_year, start_month, period_type, periods) ] # a copy of the above list with just the period start dates period_starts = [e[0] for e in period_list] # insert and add all splits in the periods of interest for split in account_of_interest.GetSplitList(): trans = split.parent trans_date = date.fromtimestamp(trans.GetDate()) # use binary search to find the period that starts before or on # the transaction date period_index = bisect_right(period_starts, trans_date) - 1 # ignore transactions with a date before the matching period start # (after subtracting 1 above start_index would be -1) # and after the last period_end if period_index >= 0 and \ trans_date <= period_list[len(period_list)-1][1]: # get the period bucket appropriate for the split in question period = period_list[period_index] # more specifically, we'd expect the transaction date # to be on or after the period start, and before or on the # period end, assuming the binary search (bisect_right) # assumptions from above are are right.. # # in other words, we assert our use of binary search # and the filtered results from the above if provide all the # protection we need assert (trans_date >= period[0] and trans_date <= period[1]) split_amount = gnc_numeric_to_python_Decimal(split.GetAmount()) # if the amount is negative, this is a credit if split_amount < ZERO: debit_credit_offset = 1 # else a debit else: debit_credit_offset = 0 # store the debit or credit Split with its transaction, using the # above offset to get in the right bucket # # if we wanted to be really cool we'd keep the transactions period[2 + debit_credit_offset].append((trans, split)) # add the debit or credit to the sum, using the above offset # to get in the right bucket period[4 + debit_credit_offset] += split_amount csv_writer = csv.writer(stdout) csv_writer.writerow( ('period start', 'period end', 'debits', 'credits')) def generate_detail_rows(values): return (('', '', '', '', trans.GetDescription(), gnc_numeric_to_python_Decimal(split.GetAmount())) for trans, split in values) for start_date, end_date, debits, credits, debit_sum, credit_sum in \ period_list: csv_writer.writerow((start_date, end_date, debit_sum, credit_sum)) if debits_show and len(debits) > 0: csv_writer.writerow( ('DEBITS', '', '', '', 'description', 'value')) csv_writer.writerows(generate_detail_rows(debits)) csv_writer.writerow(()) if credits_show and len(credits) > 0: csv_writer.writerow( ('CREDITS', '', '', '', 'description', 'value')) csv_writer.writerows(generate_detail_rows(credits)) csv_writer.writerow(()) # no save needed, we're just reading.. gnucash_session.end() except: if "gnucash_session" in locals(): gnucash_session.end() raise
class Gnucash: __seendict = {} def __init__(self, book_uri): self._session = Session(book_uri=book_uri) self._book = self._session.book self._root_account = self._book.get_root_account() self._commodity_table = self._book.get_table() # todo: implement as class w/ getattr self.commods = {} self.commods["USD"] = self._commodity_table.lookup("ISO4217", "USD") def __enter__(self): return self def __exit__(self, type, value, traceback): self._session.end() def save(self): self._session.save() def seen(self, acct, num): if repr(acct) not in self.__seendict: l = [] for split in acct.GetSplitList(): txn = split.parent if txn.GetNum() not in l: l.append(txn.GetNum().strip()) self.__seendict[repr(acct)] = l return num in self.__seendict[repr(acct)] def _account_from_path(self, top_account, account_path, original_path=None): if original_path == None: original_path = account_path account, account_path = account_path[0], account_path[1:] account = top_account.lookup_by_name(account) if account.get_instance() == None: raise Exception("path " + "".join(original_path) + " could not be found") if len(account_path) > 0: return self._account_from_path(account, account_path, original_path) else: return account def account(self, account_path): return self._account_from_path(self._root_account, account_path.split(":")) def rat(self, value): s = int(round(value * 100)) return GncNumeric(s, 100) def NewTransaction(self): t = Transaction(self._book) t.BeginEdit() t.SetCurrency(self.commods["USD"]) return t def NewSplit(self, txn, acct, amount): s = Split(self._book) s.SetParent(txn) s.SetAccount(acct) s.SetAmount(self.rat(amount)) s.SetValue(self.rat(amount)) return s def TransactionReadyToCommit(self, txn): for split in txn.GetSplitList(): if split.GetValue().to_double() == 0.0: split.Destroy() return txn.IsBalanced() def InvoiceLookupByID(self, id): return self._book.InvoiceLookupByID(id) def PayInvoiceWithTransaction(self, invoice, txn, acct, gross, memo, num): invoice.BeginEdit() invoice.ApplyPayment(txn, acct, self.rat(-gross), self.rat(1), txn.RetDatePostedTS(), memo, num) invoice.CommitEdit() def ApplyPaymentToCustomer(self, customer, txn, post_acct, from_acct, gross, memo, num): customer.BeginEdit() customer.ApplyPayment( txn, None, post_acct, from_acct, self.rat(-gross), self.rat(1), txn.RetDatePostedTS(), memo, num, True ) customer.CommitEdit() def GetCustomerByEmail(self, email): q = Query() q.search_for("gncCustomer") q.set_book(self._book) c = None for result in q.run(): tmp = Customer(instance=result) if tmp.GetAddr().GetEmail().lower() == email.lower(): c = tmp break q.destroy() return c
#!/usr/bin/env python ## @file # @brief Simple example for a book # @ingroup python_bindings_examples import sys from gnucash import Session # We need to tell GnuCash the data format to create the new file as (xml://) uri = "xml:///tmp/simple_book.gnucash" print("uri:", uri) ses = Session(uri, is_new=True) book = ses.get_book() #Call some methods that produce output to show that Book works book.get_root_account().SetDescription("hello, book") print("Book is saved:", not book.session_not_saved()) print("saving...") ses.save() print("Book is saved:", not book.session_not_saved()) ses.end()
class TestQueries(TestCase): def setUp(self): self.session = Session( os.path.join(os.path.dirname(os.path.abspath(__file__)), "test.gnucash")) @classmethod def setUpTestData(cls): AccountMap.objects.create(match="Telkom", account="Phone", vat_inclusive=True) AccountMap.objects.create(match="Vodacom", account="Cellphone", vat_inclusive=False) def tearDown(self): self.session.end() def test_get_accounts(self): self.assertEqual( len(queries.get_accounts(self.session.book.get_root_account())), 62) def test_get_account_ancestors(self): root = self.session.book.get_root_account() acc = root.lookup_by_name(settings.GNUCASH_BANK_ACCOUNT) self.assertEqual(len(queries.get_account_ancestors(acc)), 3) def test_get_customers(self): customers = queries.get_customers(self.session.book) self.assertEqual(len(customers), 2) def test_get_invoices(self): self.assertEqual(len(queries.get_invoices(self.session.book)), 3) def test_get_invoices_customer(self): customer = self.session.book.CustomerLookupByID("000001") self.assertEqual( len(queries.get_invoices(self.session.book, customer)), 2) def test_get_account_maps(self): self.assertEqual( queries.get_account_maps(), [("Telkom", "Phone", True), ("Vodacom", "Cellphone", False)], ) def test_match_account(self): self.assertEqual(queries.match_account("1 Telkom Ltd"), ("Phone", True)) def test_match_account_not_found(self): self.assertEqual(queries.match_account("not found"), (None, False)) def test_match_customer_name(self): self.assertEqual(queries.match_customer(self.session.book, "Acme"), "000001") def test_match_customer_invoice(self): self.assertEqual(queries.match_customer(self.session.book, "00002"), "000001") def test_match_customer_not_found(self): self.assertEqual( queries.match_customer(self.session.book, "not found"), None) def test_get_payment_refs(self): self.assertEqual(queries.get_payment_refs(self.session.book), {"000001"}) def test_get_duplicate_check_data(self): root = self.session.book.get_root_account() acc = root.lookup_by_name(settings.GNUCASH_BANK_ACCOUNT) self.assertEqual( queries.get_duplicate_check_data(acc), [[date(2019, 11, 21), Decimal("9.99")]], )
#!/usr/bin/env python ## @file # @brief Simple example for a book # @ingroup python_bindings_examples import sys from gnucash import Session # We need to tell GnuCash the data format to create the new file as (xml://) uri = "xml:///tmp/simple_book.gnucash" print "uri:", uri ses = Session(uri, is_new=True) book = ses.get_book() # Call some methods that produce output to show that Book works book.get_root_account().SetDescription("hello, book") print "Book is saved:", not book.not_saved() print "saving..." ses.save() print "Book is saved:", not book.not_saved() ses.end()
def get_exps_qtr_main(): global gnucash_session exe = argv[0].split('/')[-1] if len(argv) < 4: print("NOT ENOUGH parameters!") print("usage: {} <book url> <year> <quarter>".format(exe)) print("PROGRAM EXIT!") return print("\nrunning {} at run-time: {}\n".format(exe, str(datetime.now()))) try: (gnucash_file, str_year, str_quarter) = argv[1:4] print("find Expenses in {} for {}-Q{}".format(gnucash_file, str_year, str_quarter)) rev_year, rev_quarter = [int(blah) for blah in (str_year, str_quarter)] start_month = (rev_quarter * 3) - 2 # a list of all the periods of interest # for each period keep the start date, end date, debits and credits sums and overall total period_list = [ [ start_date, end_date, ZERO, # debits sum ZERO, # credits sum ZERO # TOTAL ] for start_date, end_date in generate_period_boundaries( rev_year, start_month, 'quarterly', 1) ] # print(period_list) # a copy of the above list with just the period start dates period_starts = [e[0] for e in period_list] # print(period_starts) gnucash_session = Session(gnucash_file, is_new=False) root_account = gnucash_session.book.get_root_account() for item in EXP_ACCTS: # reset the debit and credit totals for each individual account period_list[0][2] = 0 period_list[0][3] = 0 acct_base = EXP_ACCTS[item] # print("acct = {}".format(acct_base)) account_of_interest = account_from_path(root_account, acct_base) acct_name = account_of_interest.GetName() print("\naccount_of_interest = {}".format(acct_name)) # get the split amounts for the parent account get_splits(account_of_interest, period_starts, period_list) descendants = account_of_interest.get_descendants() if len(descendants) > 0: # for EACH sub-account add to the overall total # print("Descendants of {}:".format(account_of_interest.GetName())) for subAcct in descendants: # print("{} balance = {}".format(subAcct.GetName(), gnc_numeric_to_python_decimal(subAcct.GetBalance()))) get_splits(subAcct, period_starts, period_list) # write out the column headers csv_writer = csv.writer(stdout) # csv_writer.writerow('') csv_writer.writerow( ('period start', 'period end', 'debits', 'credits', 'TOTAL')) # write out the overall totals for the account of interest for start_date, end_date, debit_sum, credit_sum, total in period_list: csv_writer.writerow( (start_date, end_date, debit_sum, credit_sum, total)) sum_expenses = (period_list[0][2] + period_list[0][3]) print("{} Expenses for {}-Q{} = ${}".format( acct_name.split('_')[-1], str_year, str_quarter, sum_expenses)) tot_expenses = period_list[0][4] print("\n{} Expenses for {}-Q{} = ${}".format("TOTAL", str_year, str_quarter, tot_expenses)) # no save needed, we're just reading.. gnucash_session.end() except Exception as qe: if "gnucash_session" in locals() and gnucash_session is not None: gnucash_session.end() raise print("\n >>> PROGRAM ENDED.")
print('Need at least one Split to get currency info ... ') raise SystemExit cur = ac.GetSplitList()[0].GetParent().GetCurrency() # Get stock data pl = pdb.get_prices(stock,cur) if len(pl)<1: print('Need at least one database entry to clone ...') raise SystemExit pl0 = pl[0] for i in range(1,len(pl)): pdb.remove_price(pl[i]) for i in range(0,len(stock_date)): p_new = pl0.clone(book) p_new = gnucash.GncPrice(instance=p_new) print('Adding',i,stock_date[i],stock_price[i]) p_new.set_time(stock_date[i]) v = p_new.get_value() v.num = int(Fraction.from_float(stock_price[i]).limit_denominator(100000).numerator) v.denom = int(Fraction.from_float(stock_price[i]).limit_denominator(100000).denominator) p_new.set_value(v) p_new.set_source("Finance::Quotes::Historic") pdb.add_price(p_new) # Clean up session.save() session.end() session.destroy()
def main(): if len(argv) < 3: print 'Usage: python ofxml_make_commodity_accounts.py <gnucash-file> <ofxml-file> [symbols-file]' exit(1) gnucash_file=argv[1] symbols_file=False if len(argv)>=4: symbols_file=read_symbols(argv[3]) doc=ElementTree(file=argv[2]) acctids=doc.findall('./INVSTMTMSGSRSV1/INVSTMTTRNRS/INVSTMTRS/INVACCTFROM/ACCTID') if len(acctids)!=1: print 'ERROR: No unique account number found in OFX: found', len(acctids) return acctid=acctids[0].text.strip() acctcur='any' m=re.search('^(.*)-([A-Z]{3})$',acctid) if m: acctid=m.group(1) acctcur=m.group(2) print "INFO: Account number:", acctid, "Currency:", acctcur missing_symbols=False secs=[] for sec in doc.findall('./SECLISTMSGSRSV1/SECLIST/*/SECINFO'): id=sec.findall('./SECID/UNIQUEID')[0].text.strip() type=sec.findall('./SECID/UNIQUEIDTYPE')[0].text.strip() name=sec.findall('./SECNAME')[0].text.strip() symbol=sec.findall('./TICKER') if len(symbol): symbol=symbol[0].text.strip() else: symbol=None if symbols_file: if id in symbols_file[type]: name=symbols_file[type][id]['name'] symbol=symbols_file[type][id]['symbol'] else: print "WARNING: Missing symbol for", type, id, name, symbol missing_symbols=True secs.append({'id': id, 'type': type, 'name': name, 'symbol': symbol}) print "DEBUG: Found", len(secs), "commodities." sess=Session(gnucash_file) try: # Make sure all the commodities exist ct=sess.book.get_table() for ns in ['CUSIP','ISIN']: for c in ct.get_commodities(ns): matched_sec=None for sec in secs: if sec['type']==ns and sec['id']==c.get_cusip(): sec['c']=c break missing_secs=False for i,sec in enumerate(secs): if not 'c' in sec: print 'WARNING: Missing commodity', sec['type'],sec['id'],sec['name'],sec['symbol'] missing_secs=True if missing_secs or missing_symbols: print 'ERROR: Missing symbols or commodities, aborting.' return # Find GNC parent account root=sess.book.get_root_account() matched_accts=find_acct_by_number_and_currency(root,acctid,acctcur) if len(matched_accts)==0: from_iban=conv_iban(acctid) if from_iban: matched_accts=find_acct_by_number_and_currency(root,from_iban,acctcur) if len(matched_accts)!=1: print 'ERROR: No unique account this number/currency; found', len(matched_accts) return acct=matched_accts[0] print 'DEBUG: Found parent account:',acct.GetName() # Make sure the account has the appropriate stock accounts created_accounts=0 for sec in secs: matched_acct=None for secacct in acct.get_children(): if secacct.GetCommodity().get_instance()==sec['c'].get_instance(): matched_acct=secacct break if not matched_acct: secacct=Account(sess.book) if secacct: secacct.SetName(sec['name']) secacct.SetType(gnucash.ACCT_TYPE_STOCK) secacct.SetCommodity(sec['c']) secacct.SetCode(sec['id']) acct.append_child(secacct) created_accounts+=1 print 'DEBUG: Created Account',sec['name'] else: print 'ERROR: Error creating Account',sec['name'] print 'INFO:',len(secs),'accounts total',created_accounts,'created.' wait_for_backup_file(gnucash_file) sess.save() except BaseException as e: print 'ERROR:',e finally: sess.end() sess.destroy()