def command(args): import os from subprocess import check_call from sr.tools import spending from sr.tools.budget import BudgetTree root = spending.find_root() budget = spending.load_budget_with_spending(root) line = budget.path(args.budgetline) if isinstance(line, BudgetTree): print("Summary for tree", args.budgetline) budgeted = sum([x.cost for x in line.walk()]) spent = sum([x.spent for x in line.walk()]) else: print("Summary for line", args.budgetline) budgeted = line.cost spent = line.spent print("Budgeted:\t", budgeted) print("Spent:\t\t", spent) if spent > budgeted: print("OVER BUDGET") print("Transactions:") account = spending.budget_line_to_account(args.budgetline) check_call(["ledger", "--file", os.path.join(root, "spending.dat"), "reg", account])
def command(args): import os from subprocess import check_call from sr.tools import spending from sr.tools.budget import BudgetTree root = spending.find_root() budget = spending.load_budget_with_spending(root) line = budget.path(args.budgetline) if isinstance(line, BudgetTree): print("Summary for tree", args.budgetline) budgeted = sum([x.cost for x in line.walk()]) spent = sum([x.spent for x in line.walk()]) else: print("Summary for line", args.budgetline) budgeted = line.cost spent = line.spent print("Budgeted:\t", budgeted) print("Spent:\t\t", spent) if spent > budgeted: print("OVER BUDGET") print("Transactions:") account = spending.budget_line_to_account(args.budgetline) check_call([ "ledger", "--file", os.path.join(root, "spending.dat"), "reg", account ])
def command(args): from decimal import Decimal as D import sys from sr.tools import spending from sr.tools.budget import BudgetTree try: root = spending.find_root() except spending.NotSpendingRepo: print("Please run in spending.git", file=sys.stderr) sys.exit(1) bud = spending.load_budget_with_spending(root) if args.path != "/": try: budline = bud.path(args.path) except KeyError: print("Budget line '" + args.path + "' not found", file=sys.stderr) sys.exit(1) else: budline = bud total = D(0) spent = D(0) total_unspent = D(0) if isinstance(budline, BudgetTree): for line in budline.walk(): spent += line.spent total += line.cost unspent = line.cost - line.spent if unspent != D(0): print(line.name, unspent) total_unspent += unspent print("-" * 10) else: total_unspent = budline.cost - budline.spent print("Total budget:\t", total) print("Total unspent:\t", total_unspent)
def command(args): from decimal import Decimal as D import os from subprocess import check_output import sys import yaml from sr.tools import budget, spending from sr.tools.config import Config config = Config() try: root = budget.find_root() except budget.NotBudgetRepo: print("Error: Please run in budget.git", file=sys.stderr) exit(1) os.chdir(root) b = budget.load_budget("./") try: f = open("funds-in.yaml", "r") except IOError: if os.path.exists("check"): os.execv("./check", ["./check"] + sys.argv[1:]) exit(1) funds_in = yaml.load(f) total_in = D(0) for i in funds_in["incoming"]: # Convert to str to avoid some rounding issues! total_in += D(str(i["amount"])) # All the lines in funds-in.yaml are divided by 1.1 at the moment MAX = total_in * budget.FUDGE_FACTOR # Calculate how much has been budgeted in total budgeted = D(0) for line in b.walk(): budgeted += (D(1) + line.uncertainty) * line.cost if budgeted > MAX: print("Fail: Budget is £%s too high" % (budgeted - MAX), file=sys.stderr) exit(1) print("OK: ", end='') if budgeted == MAX: print("Budget is at maximum.") else: print("Budget is £%s below maximum." % (MAX - budgeted)) def list_referenced_lines(root): """Return a list of budget lines referenced in ``spending.git``.""" cmd = [ "ledger", "--file", os.path.join(root, "spending.dat"), "reg", "^Expenses:", "--format", "%A\n" ] output = check_output(cmd, universal_newlines=True).strip() lines = output.splitlines() return [spending.account_to_budget_line(line) for line in lines] spending_root = args.spending if spending_root is None: spending_root_config = config.get("spending") if spending_root_config is not None: spending_root = os.path.expanduser(spending_root_config) if spending_root is not None: # Check stuff with spending.git inv_budget_lines = set() # Check that all transactions reference valid budget lines for line in list_referenced_lines(spending_root): try: b.path(line) except budget.InvalidPath: inv_budget_lines.add(line) if len(inv_budget_lines): print("{} non-existent budget lines referenced from spending.git:". format(len(inv_budget_lines))) for line in inv_budget_lines: print(" -", line) else: print("OK: All spending.git budget line references are valid.") # Lines that have been closed but don't match their spends close_fail = {} # Lines that are over budget over_budget = {} # Load the budget again with spending information b = spending.load_budget_with_spending(spending_root) # Check that the spends line up with the budget moneywise for bline in b.walk(): if bline.closed: # Closed lines must match their spent amount if bline.cost != bline.spent: close_fail[bline] = bline.spent continue line_max = bline.cost * (D(1) + bline.uncertainty) if bline.spent > line_max: over_budget[bline] = bline.spent if len(close_fail): print("{0} closed lines do not match the amount spent against " "them:".format(len(close_fail))) for line, spent in close_fail.items(): print(" - {0}: £{1} spent, £{2} allocated".format( line.name, spent, line.cost)) else: print("OK: All closed lines have the correct value.") if len(over_budget): print("{0} lines are over budget:".format(len(over_budget))) for line, spent in over_budget.items(): print(" - {0}: £{1} spent, £{2} allocated (including fudge)". format(line.name, spent, line.cost * budget.FUDGE_FACTOR)) else: print("OK: No open budget lines are overspent.")
def command(args): import re import sys import sr.tools.spending as srspending import sr.tools.budget as srbudget try: spending_root = srspending.find_root() except srspending.NotSpendingRepo: print("Please run in spending.git repository", file=sys.stderr) exit(1) budget = srspending.load_budget_with_spending(spending_root) path = budget.path(args.LINE) if isinstance(path, srbudget.BudgetItem): items = [path] else: items = path.walk() for item in items: if item.closed: # Line is already closed; nothing to do continue if item.spent > (item.cost * srbudget.FUDGE_FACTOR): # More has been spent than the budget line's value print( "Cannot close {0}: More has been spent than budgeted.".format( item.name), file=sys.stderr) print("\tSpent: £{0}\n\tBudgeted: £{1}".format( item.spent, item.cost * srbudget.FUDGE_FACTOR), file=sys.stderr) # Skip this item continue orig_contents = open(item.fname, 'r').read() contents = re.sub(r'^(\s*cost\s*:\s*).+$', r'\g<1>{0}'.format(item.spent), orig_contents, 0, re.MULTILINE) # Check that the cost has actually been changed # within the string if it actually needed to be if item.spent != item.cost and contents == orig_contents: print("Warning: Failed to set the cost of {0} -- skipping it.". format(item.name), file=sys.stderr) continue # Mark as closed m = re.search(r"^\s*closed\s*:.*$", contents, flags=re.MULTILINE) if m is not None: # There's a closed line in there already contents = re.sub(r"^(\s*closed\s*:\s*).*$", r"\1true", contents, flags=re.MULTILINE) else: contents += "\nclosed: true\n" with open(item.fname, 'w') as file: file.write(contents)
def command(args): import re import sys import sr.tools.spending as srspending import sr.tools.budget as srbudget try: spending_root = srspending.find_root() except srspending.NotSpendingRepo: print("Please run in spending.git repository", file=sys.stderr) exit(1) budget = srspending.load_budget_with_spending(spending_root) path = budget.path(args.LINE) if isinstance(path, srbudget.BudgetItem): items = [path] else: items = path.walk() for item in items: if item.closed: # Line is already closed; nothing to do continue if item.spent > (item.cost * srbudget.FUDGE_FACTOR): # More has been spent than the budget line's value print("Cannot close {0}: More has been spent than budgeted." .format(item.name), file=sys.stderr) print("\tSpent: £{0}\n\tBudgeted: £{1}" .format(item.spent, item.cost * srbudget.FUDGE_FACTOR), file=sys.stderr) # Skip this item continue orig_contents = open(item.fname, 'r').read() contents = re.sub(r'^(\s*cost\s*:\s*).+$', r'\g<1>{0}'.format(item.spent), orig_contents, 0, re.MULTILINE) # Check that the cost has actually been changed # within the string if it actually needed to be if item.spent != item.cost and contents == orig_contents: print("Warning: Failed to set the cost of {0} -- skipping it." .format(item.name), file=sys.stderr) continue # Mark as closed m = re.search(r"^\s*closed\s*:.*$", contents, flags=re.MULTILINE) if m is not None: # There's a closed line in there already contents = re.sub(r"^(\s*closed\s*:\s*).*$", r"\1true", contents, flags=re.MULTILINE) else: contents += "\nclosed: true\n" with open(item.fname, 'w') as file: file.write(contents)
def command(args): import datetime import sr.tools.spending as srspending import sr.tools.budget as srbudget from sr.tools.trac import TracProxy, WrongServer try: spending_root = srspending.find_root() except srspending.NotSpendingRepo: print("Please run in spending.git top level directory", file=sys.stderr) sys.exit(1) try: budget = srspending.load_budget_with_spending(spending_root) except srbudget.NoBudgetConfig as nbc: print("Error:", nbc.message, file=sys.stderr) print("Have you initialised the budget submodule?", file=sys.stderr) print("If not, run 'git submodule update --init'.", file=sys.stderr) sys.exit(1) if args.spend_file is not None: spend_request = SpendRequest(args.spend_file) else: spend_request = SpendRequest.from_editor() ticket_text = "Payee: {} \\\\\n".format(spend_request.username) if spend_request.supplier_url is None: ticket_text += "Supplier: {} \\\\\n".format(spend_request.supplier) else: ticket_text += "Supplier: [{url} {supplier}] \\\\\n" \ .format(url=spend_request.supplier_url, supplier=spend_request.supplier) budget_line_totals = {} for purchase in spend_request.purchases: try: budget_line = budget.path(purchase.budget_line) except KeyError: # TODO: Move this check up into the parser so it's caught and the # user can fix it print('Budget line "{0}" not found', file=sys.stderr) sys.exit(1) bl_request_total = D(0) ticket_text += """ === Items from [budget:{budget_line}] === {summary} \\\\ ||= '''Item''' =||= '''Cost''' =|| """.format(budget_line=purchase.budget_line, summary=purchase.summary) for item in purchase.items: ticket_text += "|| {desc} || £{cost} ||\n".format(desc=item.desc, cost=item.cost) bl_request_total += item.cost # How much has already been spent against this budget line spent = budget_line.spent req_total = spent + bl_request_total # It is over the limit? if req_total > budget_line.cost: print("Warning: This purchase exceeds the budget line '{0}'" .format(purchase.budget_line)) print("\tBudget line's value: £{0}".format(budget_line.cost)) print("\tRequested Expenditure: £{0} ({1}%)" .format(req_total, 100 * (req_total) / budget_line.cost)) if not input("Continue anyway? [y/N] ").lower() == 'y': sys.exit() if purchase.budget_line not in budget_line_totals: budget_line_totals[purchase.budget_line] = D(0) budget_line_totals[purchase.budget_line] += bl_request_total ticket_text += """ === Budget Line Totals === ||= '''Budget Line''' =||= '''Total''' =|| """ for line, total in budget_line_totals.items(): ticket_text += "|| [budget:{line}] || £{total} ||\n" \ .format(line=line, total=total) print(ticket_text) if args.dry_run: print("Stopping before actually creating ticket.") sys.exit(0) try: server = TracProxy(server=args.server, port=args.port) except WrongServer: print("Error: The specified server is not a Trac instance", file=sys.stderr) sys.exit(1) ticketNum = server.ticket.create(spend_request.summary, ticket_text, {'component': "Purchasing", 'owner': "treasurer", 'type': "task"}) if not ticketNum > 0: print("Unable to create a valid ticket") sys.exit() if args.port == 443: hostname = args.server else: hostname = "{0}:{1}".format(args.server, args.port) print("Purchasing ticket created: https://{0}/trac/ticket/{1}".format( hostname, ticketNum)) print("Spending Entries:") for purchase in spend_request.purchases: print() today = datetime.date.today() i = "{date} ! {summary}\n" i += " {account}\t£{amount}\n" i += " Liabilities:{payee}\n" i += " ; trac: #{trac}" print(i.format(date=today.isoformat(), summary=purchase.summary, account=srspending.budget_line_to_account( purchase.budget_line), amount=sum([x.cost for x in purchase.items]), payee=spend_request.username, trac=ticketNum))
def command(args): from decimal import Decimal as D import os from subprocess import check_output import sys import yaml from sr.tools import budget, spending from sr.tools.config import Config config = Config() try: root = budget.find_root() except budget.NotBudgetRepo: print("Error: Please run in budget.git", file=sys.stderr) exit(1) os.chdir(root) b = budget.load_budget("./") try: f = open("funds-in.yaml", "r") except IOError: if os.path.exists("check"): os.execv("./check", ["./check"] + sys.argv[1:]) exit(1) funds_in = yaml.load(f) total_in = D(0) for i in funds_in["incoming"]: # Convert to str to avoid some rounding issues! total_in += D(str(i["amount"])) # All the lines in funds-in.yaml are divided by 1.1 at the moment MAX = total_in * budget.FUDGE_FACTOR # Calculate how much has been budgeted in total budgeted = D(0) for line in b.walk(): budgeted += (D(1) + line.uncertainty) * line.cost if budgeted > MAX: print("Fail: Budget is £%s too high" % (budgeted - MAX), file=sys.stderr) exit(1) print("OK: ", end='') if budgeted == MAX: print("Budget is at maximum.") else: print("Budget is £%s below maximum." % (MAX - budgeted)) def list_referenced_lines(root): """Return a list of budget lines referenced in ``spending.git``.""" cmd = ["ledger", "--file", os.path.join(root, "spending.dat"), "reg", "^Expenses:", "--format", "%A\n"] output = check_output(cmd, universal_newlines=True).strip() lines = output.splitlines() return [spending.account_to_budget_line(line) for line in lines] spending_root = args.spending if spending_root is None: spending_root_config = config.get("spending") if spending_root_config is not None: spending_root = os.path.expanduser(spending_root_config) if spending_root is not None: # Check stuff with spending.git inv_budget_lines = set() # Check that all transactions reference valid budget lines for line in list_referenced_lines(spending_root): try: b.path(line) except budget.InvalidPath: inv_budget_lines.add(line) if len(inv_budget_lines): print("{} non-existent budget lines referenced from spending.git:" .format(len(inv_budget_lines))) for line in inv_budget_lines: print(" -", line) else: print("OK: All spending.git budget line references are valid.") # Lines that have been closed but don't match their spends close_fail = {} # Lines that are over budget over_budget = {} # Load the budget again with spending information b = spending.load_budget_with_spending(spending_root) # Check that the spends line up with the budget moneywise for bline in b.walk(): if bline.closed: # Closed lines must match their spent amount if bline.cost != bline.spent: close_fail[bline] = bline.spent continue line_max = bline.cost * (D(1) + bline.uncertainty) if bline.spent > line_max: over_budget[bline] = bline.spent if len(close_fail): print("{0} closed lines do not match the amount spent against " "them:".format(len(close_fail))) for line, spent in close_fail.items(): print(" - {0}: £{1} spent, £{2} allocated".format( line.name, spent, line.cost)) else: print("OK: All closed lines have the correct value.") if len(over_budget): print("{0} lines are over budget:".format(len(over_budget))) for line, spent in over_budget.items(): print(" - {0}: £{1} spent, £{2} allocated (including fudge)" .format(line.name, spent, line.cost * budget.FUDGE_FACTOR)) else: print("OK: No open budget lines are overspent.")
def command(args): import datetime import sr.tools.spending as srspending import sr.tools.budget as srbudget from sr.tools.trac import TracProxy, WrongServer try: spending_root = srspending.find_root() except srspending.NotSpendingRepo: print("Please run in spending.git top level directory", file=sys.stderr) sys.exit(1) try: budget = srspending.load_budget_with_spending(spending_root) except srbudget.NoBudgetConfig as nbc: print("Error:", nbc.message, file=sys.stderr) print("Have you initialised the budget submodule?", file=sys.stderr) print("If not, run 'git submodule update --init'.", file=sys.stderr) sys.exit(1) if args.spend_file is not None: spend_request = SpendRequest(args.spend_file) else: spend_request = SpendRequest.from_editor() ticket_text = "Payee: {} \\\\\n".format(spend_request.username) if spend_request.supplier_url is None: ticket_text += "Supplier: {} \\\\\n".format(spend_request.supplier) else: ticket_text += "Supplier: [{url} {supplier}] \\\\\n" \ .format(url=spend_request.supplier_url, supplier=spend_request.supplier) budget_line_totals = {} for purchase in spend_request.purchases: try: budget_line = budget.path(purchase.budget_line) except KeyError: # TODO: Move this check up into the parser so it's caught and the # user can fix it print('Budget line "{0}" not found', file=sys.stderr) sys.exit(1) bl_request_total = D(0) ticket_text += """ === Items from [budget:{budget_line}] === {summary} \\\\ ||= '''Item''' =||= '''Cost''' =|| """.format(budget_line=purchase.budget_line, summary=purchase.summary) for item in purchase.items: ticket_text += "|| {desc} || £{cost} ||\n".format(desc=item.desc, cost=item.cost) bl_request_total += item.cost # How much has already been spent against this budget line spent = budget_line.spent req_total = spent + bl_request_total # It is over the limit? if req_total > budget_line.cost: print( "Warning: This purchase exceeds the budget line '{0}'".format( purchase.budget_line)) print("\tBudget line's value: £{0}".format(budget_line.cost)) print("\tRequested Expenditure: £{0} ({1}%)".format( req_total, 100 * (req_total) / budget_line.cost)) if not input("Continue anyway? [y/N] ").lower() == 'y': sys.exit() if purchase.budget_line not in budget_line_totals: budget_line_totals[purchase.budget_line] = D(0) budget_line_totals[purchase.budget_line] += bl_request_total ticket_text += """ === Budget Line Totals === ||= '''Budget Line''' =||= '''Total''' =|| """ for line, total in budget_line_totals.items(): ticket_text += "|| [budget:{line}] || £{total} ||\n" \ .format(line=line, total=total) print(ticket_text) if args.dry_run: print("Stopping before actually creating ticket.") sys.exit(0) try: server = TracProxy(server=args.server, port=args.port) except WrongServer: print("Error: The specified server is not a Trac instance", file=sys.stderr) sys.exit(1) ticketNum = server.ticket.create(spend_request.summary, ticket_text, { 'component': "Purchasing", 'owner': "treasurer", 'type': "task" }) if not ticketNum > 0: print("Unable to create a valid ticket") sys.exit() if args.port == 443: hostname = args.server else: hostname = "{0}:{1}".format(args.server, args.port) print("Purchasing ticket created: https://{0}/trac/ticket/{1}".format( hostname, ticketNum)) print("Spending Entries:") for purchase in spend_request.purchases: print() today = datetime.date.today() i = "{date} ! {summary}\n" i += " {account}\t£{amount}\n" i += " Liabilities:{payee}\n" i += " ; trac: #{trac}" print( i.format(date=today.isoformat(), summary=purchase.summary, account=srspending.budget_line_to_account( purchase.budget_line), amount=sum([x.cost for x in purchase.items]), payee=spend_request.username, trac=ticketNum))