Add gncmerge.
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import gnucash
import datetime
import logging
ZERO = gnucash.GncNumeric()
def recurse_all_splits(account):
for acc in account.get_descendants():
for split in acc.GetSplitList():
yield split
def check_in_date(start_date, end_date, d):
return start_date <= d and d <= end_date
def check_split_date_func(start_date, end_date):
def f(split):
parent = split.GetParent()
if parent == None:
return False
d = datetime.date.fromtimestamp(split.GetParent().GetDate())
return start_date <= d and d <= end_date
return f
def generate_splits(root, start, end):
f = check_split_date_func(start, end)
return (split for split in recurse_all_splits(root) if f(split))
def generate_summary_amounts(root, start, end):
prevAccount = None
prevAccountName = "<None>"
zero = (0, ZERO, ZERO)
pos = zero
neg = zero
for split in generate_splits(root, start, end):
account = split.account
amount = split.GetAmount()
value = split.GetValue()
name = account.get_full_name()
is_neg = amount.negative_p()
if account.get_full_name() != prevAccountName:
if prevAccount != None:
yield (prevAccount,) + pos
yield (prevAccount,) + neg
prevAccount = account
prevAccountName = name
if is_neg:
pos = zero
neg = (1, amount, value)
else:
pos = (1, amount, value)
neg = zero
else:
tmp = neg if is_neg else pos
tmp = (tmp[0]+1,
tmp[1].add(amount, gnucash.GNC_DENOM_AUTO, gnucash.GNC_HOW_DENOM_EXACT),
tmp[2].add(value, gnucash.GNC_DENOM_AUTO, gnucash.GNC_HOW_DENOM_EXACT))
if is_neg:
neg = tmp
else:
pos = tmp
if prevAccount != None:
yield (prevAccount,) + pos
yield (prevAccount,) + neg
def transaction_to_string(trans, currency):
result = str(datetime.date.fromtimestamp(trans.GetDate()))
result += " " + trans.GetDescription() + "\n"
for split in trans.GetSplitList():
result += "\t" + split.account.get_full_name() + " "
result += " "
c = split.account.GetCommodity()
a = split.GetAmount()
result += str(a.to_double()) + " " + c.get_nice_symbol()
if not currency.equal(c):
v = split.GetValue()
result += " => " + str(v.to_double()) + " " + currency.get_nice_symbol()
result += " ( " + str(v.div(a, gnucash.GNC_DENOM_AUTO, gnucash.GNC_HOW_DENOM_EXACT).to_double())
result += " " + c.get_nice_symbol() + " )"
result += "\n"
imbl = trans.GetImbalanceValue()
result += "Balance: " + str(imbl.to_double()) + " " + currency.get_nice_symbol()
if not imbl.equal(ZERO):
for c, v in trans.GetImbalance():
result += "\n\t"
result += " => " + str(v.to_double()) + " " + c.get_nice_symbol()
return result
def delete_transactions(root, start, end, filt):
removed = 0
for split in generate_splits(root, start, end):
t = split.GetParent()
if filt(t):
removed += t.CountSplits()
t.Destroy()
logging.info("%d splits removed.", removed)
def transaction_not_equal_p(trans):
gtrans = trans.GetGUID()
def f(t):
return not gtrans.equal(t.GetGUID())
return f
def generate_summary_splits(root, start, end):
book = root.get_book()
for account, count, amount, value in generate_summary_amounts(root, start, end):
if not amount.zero_p():
split = gnucash.Split(book)
split.SetAccount(account)
price = value.div(amount, gnucash.GNC_DENOM_AUTO, gnucash.GNC_HOW_DENOM_EXACT)
split.SetSharePriceAndAmount(price, amount)
yield split, count
def create_summary(root, start, end, currency, title):
logging.info("Creating summary %s for %s between %s and %s",
title, root.get_full_name(), start, end)
book = root.get_book()
trans = gnucash.Transaction(book)
trans.BeginEdit()
trans.SetDate(end.day, end.month, end.year)
trans.SetDescription(title)
trans.SetCurrency(currency)
total = 0
for split, count in generate_summary_splits(root, start, end):
split.SetParent(trans)
total += count
if trans.CountSplits() > 0:
logging.info("%d splits summary into one transaction '%s'.", total, title)
logging.debug(transaction_to_string(trans, currency))
assert(trans.IsBalanced())
trans.CommitEdit()
if total > 0:
delete_transactions(root, start, end, transaction_not_equal_p(trans))
else:
logging.info("No splits found for this date")
def make_summary(root, currency, start, end, title):
create_summary(root, start, end, currency, title)
def lookup_currency(book, currency):
return book.get_table().lookup("CURRENCY", currency)
## Datetime support
ONEDAY = datetime.timedelta(days=1)
def first_day_of_month(d):
return datetime.date(d.year, d.month, 1)
def first_day_of_year(d):
return datetime.date(d.year, 1, 1)
def next_day(d):
return d + ONEDAY
def next_month(d):
if d.month == 12:
return datetime.date(d.year+1, 1, 1)
else:
return datetime.date(d.year, d.month + 1, 1)
def next_year(d):
return datetime.date(d.year + 1, 1, 1)
def datetime_iter(start, end, next_f):
d = start
while end == None or d < end:
yield d
d = next_f(d)
def datetime_iter_range(start, end, next_f):
for d in datetime_iter(start, end, next_f):
yield d, next_f(d) - ONEDAY
def day_iter(start, end=None):
return (d for d in datetime_iter(start, end, next_day))
def month_iter(start, end=None):
return (d for d in datetime_iter(first_day_of_month(start), end, next_month))
def year_iter(start, end=None):
return (d for d in datetime_iter(first_day_of_year(start), end, next_year))
def day_range_iter(start, end=None):
return ((d,d) for d in datetime_iter(start, end, next_day))
def month_range_iter(start, end=None):
return (d for d in datetime_iter_range(first_day_of_month(start), end, next_month))
def year_range_iter(start, end=None):
return (d for d in datetime_iter_range(first_day_of_year(start), end, next_year))
def main(filename, title, currency, daterange):
try:
session = gnucash.Session(filename, is_new=False)
root = session.book.get_root_account()
curr = lookup_currency(session.book, currency)
for s, e in daterange:
make_summary(root, curr, s, e, title)
session.save()
session.end()
except:
logging.exception("Exception occured")
finally:
if 'session' in locals():
session.destroy()
def usage(msg):
import sys
print msg
print "Usage:", sys.argv[0], \
"filename.gnc", "transaction_title", "currency", \
"yearly|monthly|daily", \
"start_year start_month start_day", \
"end_year end_month end_day"
exit(-1)
if __name__ == '__main__':
# logging.basicConfig(level=logging.DEBUG)
import sys
if len(sys.argv) != 11:
usage("Invalid number of arguments")
exit(-1)
cmd, filename, title, currency, period, syear, smonth, sday, eyear, emonth, eday = sys.argv
start = datetime.date(int(syear), int(smonth), int(sday))
end = datetime.date(int(eyear), int(emonth), int(eday))
if period == "daily":
daterange = day_range_iter(start, end)
elif period == "monthly":
daterange = month_range_iter(start, end)
elif period == "yearly":
daterange = year_range_iter(start, end)
else:
usage("Invalid period '%s'" % (period,))
main(filename, title, currency, daterange)