def validate(self, data): """ Validate the form data. Return None if it is valid, or else a hash of field names to list of error strings for each field. :param data: submitted form data :type data: dict :return: None if no errors, or hash of field name to errors for that field """ _id = int(data['id']) # make sure the ID is valid db_session.query(ScheduledTransaction).get(_id) d = datetime.strptime(data['date'], '%Y-%m-%d').date() pp = BiweeklyPayPeriod.period_for_date(d, db_session) have_errors = False errors = {k: [] for k in data.keys()} if data.get('description', '').strip() == '': errors['description'].append('Description cannot be empty') have_errors = True if float(data['amount']) == 0: errors['amount'].append('Amount cannot be zero') have_errors = True if d < pp.start_date or d > pp.end_date: errors['date'].append('Date must be in current pay period') have_errors = True if have_errors: return errors return None
def get(self): accounts = { x.id: x.name for x in db_session.query(Account).all() } acct_names = accounts.values() datedict = {x: None for x in acct_names} data = {} for bal in db_session.query(AccountBalance).order_by( asc(AccountBalance.overall_date) ).all(): ds = bal.overall_date.strftime('%Y-%m-%d') if ds not in data: data[ds] = copy(datedict) data[ds]['date'] = ds if bal.ledger is None: data[ds][bal.account.name] = 0.0 else: data[ds][bal.account.name] = float(bal.ledger) resdata = [] last = None for k in sorted(data.keys()): if last is None: last = data[k] continue d = copy(data[k]) for subk in acct_names: if d[subk] is None: d[subk] = last[subk] last = d resdata.append(d) res = { 'data': resdata, 'keys': sorted(acct_names) } return jsonify(res)
def get(self): standing = db_session.query(Budget).filter( Budget.is_active.__eq__(True), Budget.is_periodic.__eq__(False)).order_by(Budget.name).all() pp = BiweeklyPayPeriod.period_for_date(dtnow(), db_session) pp_curr_idx = 1 pp_next_idx = 2 pp_following_idx = 3 periods = [pp] x = pp # add another for i in range(0, 8): x = x.next periods.append(x) # trigger calculation/cache of data before passing on to jinja for p in periods: p.overall_sums return render_template( 'index.html', bank_accounts=db_session.query(Account).filter( Account.acct_type == AcctType.Bank, Account.is_active == True).all(), # noqa credit_accounts=db_session.query(Account).filter( Account.acct_type == AcctType.Credit, Account.is_active == True).all(), # noqa investment_accounts=db_session.query(Account).filter( Account.acct_type == AcctType.Investment, Account.is_active == True).all(), # noqa standing_budgets=standing, periods=periods, curr_pp=pp, pp_curr_idx=pp_curr_idx, pp_next_idx=pp_next_idx, pp_following_idx=pp_following_idx)
def validate(self, data): """ Validate the form data. Return None if it is valid, or else a hash of field names to list of error strings for each field. :param data: submitted form data :type data: dict :return: None if no errors, or hash of field name to errors for that field """ logger.warning(data) have_errors = False errors = {k: [] for k in data.keys()} if data.get('name', '').strip() == '': errors['name'].append('Name cannot be empty') have_errors = True if data.get('id', '').strip() == '': v = db_session.query(Vehicle).filter( Vehicle.name.__eq__(data.get('name'))).all() if len(v) > 0: errors['name'].append('Name must be unique') have_errors = True else: v = db_session.query(Vehicle).filter( Vehicle.name.__eq__(data.get('name')), Vehicle.id.__ne__(int(data.get('id')))).all() if len(v) > 0: errors['name'].append('Name must be unique') have_errors = True if have_errors: return errors return None
def submit(self, data): """ Handle form submission; create or update models in the DB. Raises an Exception for any errors. :param data: submitted form data :type data: dict :return: message describing changes to DB (i.e. link to created record) :rtype: str """ action = data.get('action', None) if action == 'add': proj = Project() proj.name = data['name'].strip() proj.notes = data['notes'].strip() elif action == 'activate': proj = db_session.query(Project).get(int(data['id'])) logger.info('Activate %s', proj) proj.is_active = True elif action == 'deactivate': proj = db_session.query(Project).get(int(data['id'])) logger.info('Deactivate %s', proj) proj.is_active = False else: raise RuntimeError('Invalid action: %s' % action) db_session.add(proj) db_session.commit() if action == 'add': logger.info('Created Project %s', proj) return 'Successfully saved Project %d in database.' % proj.id
def get(self): accts = {a.name: a.id for a in db_session.query(Account).all()} budgets = {} active_budgets = {} for b in db_session.query(Budget).all(): k = b.name if b.is_income: k = '%s (i)' % b.name budgets[b.id] = k if b.is_active: active_budgets[b.id] = k return render_template( 'accounts.html', bank_accounts=db_session.query(Account).filter( Account.acct_type == AcctType.Bank, Account.is_active == True).all(), # noqa credit_accounts=db_session.query(Account).filter( Account.acct_type == AcctType.Credit, Account.is_active == True).all(), # noqa investment_accounts=db_session.query(Account).filter( Account.acct_type == AcctType.Investment, Account.is_active == True).all(), # noqa interest_class_names=INTEREST_CALCULATION_NAMES.keys(), min_pay_class_names=MIN_PAYMENT_FORMULA_NAMES.keys(), accts=accts, budgets=budgets, active_budgets=active_budgets)
def get(self, acct_id, fitid): txn = db_session.query(OFXTransaction).get((acct_id, fitid)) stmt = txn.statement.as_dict res = { 'acct_name': db_session.query(Account).get(acct_id).name, 'acct_id': txn.account_id, 'txn': txn.as_dict, 'stmt': stmt, 'account_amount': txn.account_amount } return jsonify(res)
def get(self, period_date): d = datetime.strptime(period_date, '%Y-%m-%d').date() pp = BiweeklyPayPeriod.period_for_date(d, db_session) curr_pp = BiweeklyPayPeriod.period_for_date(dtnow(), db_session) budgets = {} for b in db_session.query(Budget).all(): k = b.name if b.is_income: k = '%s (i)' % b.name budgets[b.id] = k standing = { b.id: b.current_balance for b in db_session.query(Budget).filter( Budget.is_periodic.__eq__(False), Budget.is_active.__eq__( True)).all() } periodic = { b.id: b.current_balance for b in db_session.query(Budget).filter( Budget.is_periodic.__eq__(True), Budget.is_active.__eq__( True)).all() } accts = {a.name: a.id for a in db_session.query(Account).all()} txfr_date_str = dtnow().strftime('%Y-%m-%d') if dtnow().date() < pp.start_date or dtnow().date() > pp.end_date: # If we're looking at a non-current pay period, default the # transfer modal date to the start of the period. txfr_date_str = pp.start_date.strftime('%Y-%m-%d') return render_template( 'payperiod.html', pp=pp, pp_prev_date=pp.previous.start_date, pp_prev_sums=pp.previous.overall_sums, pp_prev_suffix=self.suffix_for_period(curr_pp, pp.previous), pp_curr_date=pp.start_date, pp_curr_sums=pp.overall_sums, pp_curr_suffix=self.suffix_for_period(curr_pp, pp), pp_next_date=pp.next.start_date, pp_next_sums=pp.next.overall_sums, pp_next_suffix=self.suffix_for_period(curr_pp, pp.next), pp_following_date=pp.next.next.start_date, pp_following_sums=pp.next.next.overall_sums, pp_following_suffix=self.suffix_for_period(curr_pp, pp.next.next), pp_last_date=pp.next.next.next.start_date, pp_last_sums=pp.next.next.next.overall_sums, pp_last_suffix=self.suffix_for_period(curr_pp, pp.next.next.next), budget_sums=pp.budget_sums, budgets=budgets, standing=standing, periodic=periodic, transactions=pp.transactions_list, accts=accts, txfr_date_str=txfr_date_str)
def submit(self, data): """ Handle form submission; create or update models in the DB. Raises an Exception for any errors. :param data: submitted form data :type data: dict :return: message describing changes to DB (i.e. link to created record) :rtype: str """ # get the data trans_date = datetime.strptime(data['date'], '%Y-%m-%d').date() amt = Decimal(data['amount']) from_acct = db_session.query(Account).get(int(data['from_account'])) if from_acct is None: raise RuntimeError("Error: no Account with ID %s" % data['from_account']) to_acct = db_session.query(Account).get(int(data['to_account'])) if to_acct is None: raise RuntimeError("Error: no Account with ID %s" % data['to_account']) budget = db_session.query(Budget).get(int(data['budget'])) if budget is None: raise RuntimeError("Error: no Budget with ID %s" % data['budget']) notes = data['notes'].strip() desc = 'Account Transfer - %s from %s (%d) to %s (%d)' % ( amt, from_acct.name, from_acct.id, to_acct.name, to_acct.id) logger.info(desc) t1 = Transaction(date=trans_date, budget_amounts={budget: amt}, budgeted_amount=amt, description=desc, account=from_acct, notes=notes, planned_budget=budget) db_session.add(t1) t2 = Transaction(date=trans_date, budget_amounts={budget: (-1 * amt)}, budgeted_amount=(-1 * amt), description=desc, account=to_acct, notes=notes, planned_budget=budget) db_session.add(t2) t1.transfer = t2 db_session.add(t1) t2.transfer = t1 db_session.add(t2) db_session.commit() return 'Successfully saved Transactions %d and %d in database.' % ( t1.id, t2.id)
def get(self): return render_template( 'accounts.html', bank_accounts=db_session.query(Account).filter( Account.acct_type == AcctType.Bank, Account.is_active == True).all(), # noqa credit_accounts=db_session.query(Account).filter( Account.acct_type == AcctType.Credit, Account.is_active == True).all(), # noqa investment_accounts=db_session.query(Account).filter( Account.acct_type == AcctType.Investment, Account.is_active == True).all(), # noqa interest_class_names=INTEREST_CALCULATION_NAMES.keys(), min_pay_class_names=MIN_PAYMENT_FORMULA_NAMES.keys())
def get(self): budgets = {} active_budgets = {} for b in db_session.query(Budget).all(): k = b.name if b.is_income: k = '%s (i)' % b.name budgets[b.id] = k if b.is_active: active_budgets[b.id] = k accts = {a.name: a.id for a in db_session.query(Account).all()} return render_template('reconcile.html', budgets=budgets, accts=accts, active_budgets=active_budgets)
def get(self): """ Render the GET /transactions view using the ``transactions.html`` template. """ accts = {a.name: a.id for a in db_session.query(Account).all()} budgets = {} for b in db_session.query(Budget).all(): if b.is_income: budgets['%s (income)' % b.name] = b.id else: budgets[b.name] = b.id return render_template('transactions.html', accts=accts, budgets=budgets)
def _by_pay_period(self): min_txn = db_session.query(Transaction).order_by( Transaction.date.asc() ).first() budget_names = self._budget_names() logger.debug('budget_names=%s', budget_names) records = [] budgets_present = set() pp = BiweeklyPayPeriod.period_for_date(min_txn.date, db_session) dt_now = dtnow().date() while pp.end_date <= dt_now: sums = pp.budget_sums logger.debug('sums=%s', sums) records.append({ budget_names[y]: sums[y]['spent'] for y in sums.keys() if y in budget_names }) records[-1]['date'] = pp.start_date.strftime('%Y-%m-%d') budgets_present.update( [budget_names[y] for y in sums.keys() if y in budget_names] ) pp = pp.next res = { 'data': records, 'keys': sorted(list(budgets_present)) } return jsonify(res)
def _by_month(self): dt_now = dtnow().date() budget_names = self._budget_names() logger.debug('budget_names=%s', budget_names) records = {} budgets_present = set() for t in db_session.query(Transaction).filter( Transaction.budget.has(is_income=False), Transaction.date.__le__(dt_now) ).all(): if t.budget_id not in budget_names: continue budgets_present.add(t.budget.name) ds = t.date.strftime('%Y-%m') if ds not in records: records[ds] = {'date': ds} if t.budget.name not in records[ds]: records[ds][t.budget.name] = Decimal('0') records[ds][t.budget.name] += t.actual_amount result = [records[k] for k in sorted(records.keys())] res = { 'data': result, 'keys': sorted(list(budgets_present)) } return jsonify(res)
def submit(self, data): """ Handle form submission; create or update models in the DB. Raises an Exception for any errors. :param data: submitted form data :type data: dict :return: message describing changes to DB (i.e. link to created record) :rtype: str """ st_id = int(data['id']) st = db_session.query(ScheduledTransaction).get(st_id) d = datetime.strptime(data['payperiod_start_date'], '%Y-%m-%d').date() desc = 'Skip ScheduledTransaction %d in period %s' % ( st_id, data['payperiod_start_date']) t = Transaction(date=d, budget_amounts={st.budget: Decimal('0.0')}, budgeted_amount=Decimal('0.0'), description=desc, notes=data['notes'], account=st.account, scheduled_trans=st, planned_budget=st.budget) db_session.add(t) db_session.add(TxnReconcile(transaction=t, note=desc)) db_session.commit() logger.info( 'Created Transaction %d to skip ' 'ScheduledTransaction %d', t.id, st.id) return 'Successfully created Transaction %d to skip ' \ 'ScheduledTransaction %d.' % (t.id, st.id)
def get(self, reconcile_id): rec = db_session.query(TxnReconcile).get(reconcile_id) res = { 'reconcile': rec.as_dict, 'transaction': rec.transaction.as_dict } res['transaction']['budgets'] = [{ 'name': bt.budget.name, 'id': bt.budget_id, 'amount': bt.amount, 'is_income': bt.budget.is_income } for bt in sorted(rec.transaction.budget_transactions, key=lambda x: x.amount, reverse=True)] if rec.ofx_trans is not None: res['ofx_trans'] = rec.ofx_trans.as_dict res['ofx_stmt'] = rec.ofx_trans.statement.as_dict res['acct_id'] = rec.ofx_trans.account_id res['acct_name'] = rec.ofx_trans.account.name else: res['ofx_trans'] = None res['ofx_stmt'] = None res['acct_id'] = rec.transaction.account_id res['acct_name'] = rec.transaction.account.name return jsonify(res)
def get(self): """ Render and return JSON response for GET /ajax/ofx """ args = request.args.to_dict() args_dict = self._args_dict(args) if self._have_column_search(args_dict) and args['search[value]'] == '': args['search[value]'] = 'FILTERHACK' table = DataTable(args, Transaction, db_session.query(Transaction), [('date', lambda i: i.date.strftime('%Y-%m-%d')), ('amount', 'actual_amount', lambda a: float(a.actual_amount)), 'description', ('account', 'account.name', lambda i: "{} ({})".format(i.name, i.id)), ('scheduled', 'scheduled_trans_id'), ('budgeted_amount'), ('reconcile_id', 'reconcile', lambda i: None if i.reconcile is None else i.reconcile.id)]) table.add_data( acct_id=lambda o: o.account_id, budgets=lambda o: [{ 'name': bt.budget.name, 'id': bt.budget_id, 'amount': bt.amount, 'is_income': bt.budget.is_income } for bt in sorted( o.budget_transactions, key=lambda x: x.amount, reverse=True)], id=lambda o: o.id) if args['search[value]'] != '': table.searchable(lambda qs, s: self._filterhack(qs, s, args_dict)) return jsonify(table.json())
def get(self): """ Render and return JSON response for GET /ajax/ofx """ args = request.args.to_dict() args_dict = self._args_dict(args) if self._have_column_search(args_dict) and args['search[value]'] == '': args['search[value]'] = 'FILTERHACK' table = DataTable( args, OFXTransaction, db_session.query(OFXTransaction), [('date', 'date_posted', lambda i: i.date_posted.strftime('%Y-%m-%d')), ('amount', lambda a: float(a.amount)), ('account', 'account.name', lambda i: "{} ({})".format(i.name, i.id)), ('type', 'trans_type'), 'name', 'memo', 'description', 'fitid', ('last_stmt', 'statement.id'), ('last_stmt_date', 'statement.as_of', lambda i: i.as_of.strftime('%Y-%m-%d')), ('reconcile_id', 'reconcile', lambda i: None if i.reconcile is None else i.reconcile.id)]) table.add_data(acct_id=lambda o: o.account_id) if args['search[value]'] != '': table.searchable(lambda qs, s: self._filterhack(qs, s, args_dict)) return jsonify(table.json())
def submit(self, data): """ Handle form submission; create or update models in the DB. Raises an Exception for any errors. :param data: submitted form data :type data: dict :return: message describing changes to DB (i.e. link to created record) :rtype: str """ if 'id' in data and data['id'].strip() != '': # updating an existing Vehicle veh = db_session.query(Vehicle).get(int(data['id'])) if veh is None: raise RuntimeError("Error: no Vehicle with ID %s" % data['id']) action = 'updating Vehicle ' + data['id'] else: veh = Vehicle() action = 'creating new Vehicle' veh.name = data['name'].strip() if data['is_active'] == 'true': veh.is_active = True else: veh.is_active = False logger.info('%s: %s', action, veh.as_dict) db_session.add(veh) db_session.commit() return 'Successfully saved Vehicle %d in database.' % veh.id
def submit(self, data): """ Handle form submission; create or update models in the DB. Raises an Exception for any errors. :param data: submitted form data :type data: dict :return: message describing changes to DB (i.e. link to created record) :rtype: str """ st_id = int(data['id']) st = db_session.query(ScheduledTransaction).get(st_id) d = datetime.strptime(data['date'], '%Y-%m-%d').date() t = Transaction(date=d, budget_amounts={st.budget: Decimal(data['amount'])}, budgeted_amount=st.amount, description=data['description'], notes=data['notes'], account=st.account, planned_budget=st.budget, scheduled_trans=st) db_session.add(t) db_session.commit() logger.info('Created Transaction %d for ScheduledTransaction %d', t.id, st.id) return 'Successfully created Transaction %d ' \ 'for ScheduledTransaction %d.' % (t.id, st.id)
def _budget_names(self): return { x.id: x.name for x in db_session.query(Budget).filter( Budget.is_income.__eq__(False), Budget.omit_from_graphs.__eq__(False)).all() }
def validate(self, data): """ Validate the form data. Return None if it is valid, or else a hash of field names to list of error strings for each field. :param data: submitted form data :type data: dict :return: None if no errors, or hash of field name to errors for that field """ errors = {k: [] for k in data.keys()} errors = self._validate_date_ymd('date', data, errors) if data['add_trans'] == 'true': if data['account'] == 'None': errors['account'].append('Transactions must have an account') if data['budget'] == 'None': errors['budget'].append('Transactions must have a budget') if db_session.query(Vehicle).get(int(data['vehicle'])) is None: errors['vehicle'].append('Invalid Vehicle ID %s' % data['vehicle']) errors = self._validate_int('odometer_miles', data, errors) errors = self._validate_int('reported_miles', data, errors) errors = self._validate_not_empty('fill_location', data, errors) errors = self._validate_float('cost_per_gallon', data, errors) errors = self._validate_float('total_cost', data, errors) errors = self._validate_float('gallons', data, errors) errors = self._validate_float('reported_mpg', data, errors) errors = self._validate_int('vehicle', data, errors) errors = self._validate_int('level_before', data, errors) errors = self._validate_int('level_after', data, errors) if len(errors['total_cost']) == 0 and float(data['total_cost']) == 0: errors['total_cost'].append('Total Cost cannot be zero') for v in errors.values(): if len(v) > 0: return errors return None
def submit(self, data): """ Handle form submission; create or update models in the DB. Raises an Exception for any errors. :param data: submitted form data :type data: dict :return: message describing changes to DB (i.e. link to created record) :rtype: str """ if 'id' in data and data['id'].strip() != '': # updating an existing budget budget = db_session.query(Budget).get(int(data['id'])) if budget is None: raise RuntimeError("Error: no Budget with ID %s" % data['id']) action = 'updating Budget ' + data['id'] else: budget = Budget() action = 'creating new Budget' budget.name = data['name'].strip() budget.description = data['description'].strip() budget.is_periodic = data['is_periodic'] if data['is_periodic'] is True: budget.starting_balance = Decimal(data['starting_balance']) else: budget.current_balance = Decimal(data['current_balance']) budget.is_active = data['is_active'] budget.is_income = data['is_income'] budget.omit_from_graphs = data['omit_from_graphs'] logger.info('%s: %s', action, budget.as_dict) db_session.add(budget) db_session.commit() return 'Successfully saved Budget %d in database.' % budget.id
def get(self, project_id): proj = db_session.query(Project).get(project_id) return render_template('bomitem.html', project_id=project_id, project_name=proj.name, project_notes=proj.notes, remaining=proj.remaining_cost, total=proj.total_cost)
def submit(self, data): """ Handle form submission; create or update models in the DB. Raises an Exception for any errors. :param data: submitted form data :type data: dict :return: message describing changes to DB (i.e. link to created record) :rtype: str """ acct = db_session.query(Account).get(int(data['id'])) if acct is None: raise RuntimeError('ERROR: No Account with ID %s' % data['id']) stmt = db_session.query(OFXStatement).filter( OFXStatement.account_id.__eq__(acct.id), OFXStatement.filename.__eq__(data['filename']) ).one() if stmt is None: raise RuntimeError( 'ERROR: No OFXStatement for account %d with filename %s' % ( acct.id, data['filename'] ) ) int_amt = Decimal(data['interest_amt']) if int_amt < Decimal('0'): int_amt = int_amt * Decimal('-1') trans = OFXTransaction( account=acct, statement=stmt, fitid='%s-MANUAL-CCPAYOFF' % dtnow().strftime('%Y%m%d%H%M%S'), trans_type='debit', date_posted=stmt.as_of, amount=int_amt, name='Interest Charged - MANUALLY ENTERED', is_interest_charge=True ) logger.info( 'Adding manual interest transaction to OFXTransactions: ' 'account_id=%d statement_filename=%s statement=%s ' 'OFXTransaction=%s', acct.id, data['filename'], stmt, trans ) db_session.add(trans) db_session.commit() return 'Successfully saved OFXTransaction with FITID %s in database' \ '.' % trans.fitid
def submit(self, data): """ Handle form submission; create or update models in the DB. Raises an Exception for any errors. :param data: submitted form data :type data: dict :return: message describing changes to DB (i.e. link to created record) :rtype: str """ if 'id' in data and data['id'].strip() != '': # updating an existing budget trans = db_session.query(Transaction).get(int(data['id'])) if trans is None: raise RuntimeError("Error: no Transaction with ID " "%s" % data['id']) if trans.reconcile is not None: raise RuntimeError( "Transaction %d is already reconciled; cannot be edited." "" % trans.id) action = 'updating Transaction ' + data['id'] else: trans = Transaction() action = 'creating new Transaction' trans.description = data['description'].strip() trans.date = datetime.strptime(data['date'], '%Y-%m-%d').date() trans.account_id = int(data['account']) trans.notes = data['notes'].strip() budg_amts = {} for bid, budg_amt in data['budgets'].items(): budg = db_session.query(Budget).get(int(bid)) budg_amts[budg] = Decimal(budg_amt) trans.set_budget_amounts(budg_amts) logger.info('%s: %s', action, trans.as_dict) db_session.add(trans) db_session.commit() return { 'success_message': 'Successfully saved Transaction %d in database.' '' % trans.id, 'success': True, 'trans_id': trans.id }
def get(self, sched_trans_id): """ Render the GET /scheduled/<int:sched_trans_id> view using the ``scheduled.html`` template. """ accts = {a.name: a.id for a in db_session.query(Account).all()} budgets = {} for b in db_session.query(Budget).all(): if b.is_income: budgets['%s (income)' % b.name] = b.id else: budgets[b.name] = b.id return render_template( 'scheduled.html', accts=accts, budgets=budgets, sched_trans_id=sched_trans_id )
def get(self): resdata = [] prices = db_session.query(FuelFill).filter( FuelFill.cost_per_gallon.__ne__(None)).order_by(asc(FuelFill.date)) for point in prices.all(): ds = point.date.strftime('%Y-%m-%d') resdata.append({'date': ds, 'price': float(point.cost_per_gallon)}) res = {'data': resdata} return jsonify(res)
def get(self): accts = {a.name: a.id for a in db_session.query(Account).all()} budgets = {} for b in db_session.query(Budget).all(): if b.is_income: budgets['%s (income)' % b.name] = b.id else: budgets[b.name] = b.id vehicles = { v.id: { 'name': v.name, 'is_active': v.is_active } for v in db_session.query(Vehicle).all() } return render_template('fuel.html', accts=accts, budgets=budgets, vehicles=vehicles)
def get(self): total_active = 0.0 remain_active = 0.0 for p in db_session.query(Project).filter( Project.is_active.__eq__(True)).all(): total_active += p.total_cost remain_active += p.remaining_cost return render_template('projects.html', total_active=total_active, remain_active=remain_active)