def _update_investment(self, acct, ofx, stmt): """ Update a single OFX file for this Investment account. :param acct: the Account this statement is for :type acct: biweeklybudget.models.account.Account :param ofx: Ofx instance for parsed file :type ofx: ``ofxparse.ofxparse.Ofx`` :param stmt: the OFXStatement for this statement :type stmt: biweeklybudget.models.ofx_statement.OFXStatement :returns: the OFXStatement object :rtype: biweeklybudget.models.ofx_statement.OFXStatement """ logger.debug('Updating Investment account') stmt.type = 'Investment' value = 0 earliest_dt = datetime.max for pos in ofx.account.statement.positions: if hasattr(pos, 'date') and isinstance(pos.date, datetime): if pos.date < earliest_dt: earliest_dt = pos.date if hasattr(pos, 'market_value'): value += pos.market_value else: value += (pos.units * pos.unit_price) if earliest_dt != datetime.max: stmt.ledger_bal_as_of = earliest_dt.replace(tzinfo=UTC) if value != 0: stmt.ledger_bal = value db_session.add(stmt) acct.set_balance(overall_date=stmt.as_of, ledger=stmt.ledger_bal, ledger_date=stmt.ledger_bal_as_of) return stmt
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 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 """ 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 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 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 post(self): """ Handle form submission; create or update models in the DB. Raises an Exception for any errors. :return: message describing changes to DB (i.e. link to created record) :rtype: str """ data = request.get_json(force=True, silent=True) if data is None: logger.error('Error parsing request JSON') return jsonify({ 'success': False, 'error_message': 'Error parsing JSON' }) setting = db_session.query(DBSetting).get('credit-payoff') if setting is None: setting = DBSetting(name='credit-payoff') logger.info('new DBSetting name=credit-payoff') else: logger.info('Existing DBSetting name=credit-payoff value=%s', setting.value) fixeddata = {'increases': [], 'onetimes': []} for key in ['increases', 'onetimes']: for d in sorted(data[key], key=lambda k: k['date']): if d['date'] == '' or d['amount'] == '': continue fixeddata[key].append(d) val = json.dumps(fixeddata, sort_keys=True, cls=MagicJSONEncoder) try: parse_payoff_settings_json(val) except Exception as ex: logger.error('Error converting payoff settings JSON', exc_info=True) return jsonify({ 'success': False, 'error_message': 'Error parsing JSON: %s' % ex }) logger.info('Changing setting value to: %s', val) setting.value = val db_session.add(setting) db_session.commit() return jsonify({ 'success': True, 'success_message': 'Successfully updated setting ' '"credit-payoff" in database.' })
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 _update_bank_or_credit(self, acct, ofx, stmt): """ Update a single OFX file for this Bank or Credit account. :param acct: the Account this statement is for :type acct: biweeklybudget.models.account.Account :param ofx: Ofx instance for parsed file :type ofx: ``ofxparse.ofxparse.Ofx`` :param stmt: the OFXStatement for this statement :type stmt: biweeklybudget.models.ofx_statement.OFXStatement :returns: the OFXStatement object :rtype: biweeklybudget.models.ofx_statement.OFXStatement """ # Note that as of 0.16, OfxParser returns tz-naive UTC datetimes logger.debug('Updating Bank/Credit account') if hasattr(ofx.account.statement, 'available_balance'): stmt.avail_bal = ofx.account.statement.available_balance if hasattr(ofx.account.statement, 'available_balance_date'): stmt.avail_bal_as_of = \ ofx.account.statement.available_balance_date.replace(tzinfo=UTC) stmt.ledger_bal = ofx.account.statement.balance stmt.ledger_bal_as_of = \ ofx.account.statement.balance_date.replace(tzinfo=UTC) db_session.add(stmt) acct.set_balance( overall_date=stmt.as_of, ledger=stmt.ledger_bal, ledger_date=stmt.ledger_bal_as_of, avail=stmt.avail_bal, avail_date=stmt.avail_bal_as_of ) for txn in ofx.account.statement.transactions: try: kwargs = OFXTransaction.params_from_ofxparser_transaction( txn, acct.id, stmt, cat_memo=acct.ofx_cat_memo_to_name ) except RuntimeError as ex: logger.error(ex) continue upsert_record( OFXTransaction, ['account_id', 'fitid'], **kwargs ) return stmt
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 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 BoMItem item = db_session.query(BoMItem).get(int(data['id'])) if item is None: raise RuntimeError("Error: no BoMItem with ID " "%s" % data['id']) action = 'updating BoMItem ' + data['id'] else: item = BoMItem() action = 'creating new BoMItem' item.project = db_session.query(Project).get(int(data['project_id'])) item.name = data['name'].strip() item.notes = data['notes'].strip() item.quantity = int(data['quantity'].strip()) item.unit_cost = float(data['unit_cost'].strip()) item.url = data['url'].strip() if data['is_active'] == 'true': item.is_active = True else: item.is_active = False logger.info('%s: %s', action, item.as_dict) db_session.add(item) db_session.commit() return { 'success_message': 'Successfully saved BoMItem %d ' 'in database.' % item.id, 'success': True, 'id': item.id }
def _do_project(self, list_url, project): """ Update a project with information from its wishlist. :param list_url: Amazon wishlist URL :type list_url: str :param project: the project to update :type project: Project :return: whether or not the update was successful :rtype: bool """ logger.debug('Handling project: %s', project) pitems = self._project_items(project) witems = self._wishlist_items(list_url) logger.debug('Project has %d items; wishlist has %d', len(pitems), len(witems)) for url, item in pitems.items(): if url not in witems: logger.info( '%s (%s) removed from amazon list; setting inactive', item, url ) item.is_active = False db_session.add(item) for url, item in witems.items(): if url in pitems: bitem = pitems[url] logger.info('Updating %s from Amazon wishlist', bitem) else: bitem = BoMItem() bitem.project = project logger.info('Adding new BoMItem for wishlist %s', url) bitem.url = url bitem.is_active = True bitem.quantity = item['quantity'] bitem.unit_cost = item['cost'] bitem.name = item['name'] db_session.add(bitem) logger.info('Committing changes for project %s url %s', project, list_url) db_session.commit() return True
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 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(ScheduledTransaction).get(int(data['id'])) if trans is None: raise RuntimeError("Error: no ScheduledTransaction with ID " "%s" % data['id']) action = 'updating ScheduledTransaction ' + data['id'] else: trans = ScheduledTransaction() action = 'creating new ScheduledTransaction' trans.description = data['description'].strip() if data['type'] == 'monthly': trans.day_of_month = int(data['day_of_month']) elif data['type'] == 'per_period': trans.num_per_period = int(data['num_per_period']) else: # date trans.date = datetime.strptime(data['date'], '%Y-%m-%d').date() trans.amount = float(data['amount']) trans.account_id = int(data['account']) trans.budget_id = int(data['budget']) trans.notes = data['notes'].strip() if data['is_active'] == 'true': trans.is_active = True else: trans.is_active = False logger.info('%s: %s', action, trans.as_dict) db_session.add(trans) db_session.commit() return 'Successfully saved ScheduledTransaction' \ ' %d in database.' % trans.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 """ new_acct = False if 'id' in data and data['id'].strip() != '': # updating an existing account account = db_session.query(Account).get(int(data['id'])) if account is None: raise RuntimeError("Error: no Account with ID %s" % data['id']) action = 'updating Account ' + data['id'] else: new_acct = True account = Account() action = 'creating new Account' account.name = data['name'].strip() account.description = self.fix_string(data['description']) account.acct_type = getattr(AcctType, data['acct_type']) account.ofx_cat_memo_to_name = data['ofx_cat_memo_to_name'] account.vault_creds_path = self.fix_string(data['vault_creds_path']) account.ofxgetter_config_json = self.fix_string( data['ofxgetter_config_json']) account.negate_ofx_amounts = data['negate_ofx_amounts'] account.reconcile_trans = data['reconcile_trans'] if account.acct_type == AcctType.Credit: if data['credit_limit'].strip() != '': account.credit_limit = Decimal(data['credit_limit']) else: account.credit_limit = None if data['apr'].strip() != '': account.apr = Decimal(data['apr']) if account.apr > Decimal('1'): account.apr = account.apr * Decimal('0.01') else: account.apr = None if data['prime_rate_margin'].strip() != '': account.prime_rate_margin = Decimal(data['prime_rate_margin']) if account.prime_rate_margin > Decimal('1'): account.prime_rate_margin = account.prime_rate_margin * \ Decimal('0.01') else: account.prime_rate_margin = None account.interest_class_name = data['interest_class_name'] account.min_payment_class_name = data['min_payment_class_name'] account.is_active = data['is_active'] for f in RE_FIELD_NAMES: data[f] = data[f].strip() if data[f] == '': data[f] = None setattr(account, f, data[f]) logger.info('%s: %s', action, account.as_dict) db_session.add(account) db_session.commit() if new_acct: account.set_balance(ledger=Decimal('0'), avail=Decimal('0')) db_session.add(account) db_session.commit() return 'Successfully saved Account %d in database.' % account.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 """ total = float(data['total_cost']) dt = datetime.strptime(data['date'], '%Y-%m-%d').date() veh_name = db_session.query(Vehicle).get(int(data['vehicle'])).name fill = FuelFill() fill.date = dt fill.vehicle_id = int(data['vehicle']) fill.odometer_miles = int(data['odometer_miles']) fill.reported_miles = int(data['reported_miles']) fill.level_before = int(data['level_before']) fill.level_after = int(data['level_after']) fill.fill_location = data['fill_location'].strip() fill.cost_per_gallon = float(data['cost_per_gallon']) fill.total_cost = total fill.gallons = float(data['gallons']) fill.reported_mpg = float(data['reported_mpg']) fill.notes = data['notes'].strip() logger.info('Creating new FuelFill: %s', fill.as_dict) db_session.add(fill) db_session.commit() fill.calculate_mpg() db_session.commit() if data['add_trans'] != 'true': return { 'success_message': 'Successfully saved FuelFill %d ' 'in database.' % fill.id, 'success': True, 'fill_id': fill.id, 'calculated_mpg': fill.calculated_mpg } trans = Transaction() budg = db_session.query(Budget).get(int(data['budget'])) trans.description = '%s - FuelFill #%d (%s)' % ( data['fill_location'].strip(), fill.id, veh_name) trans.date = dt trans.actual_amount = total trans.account_id = int(data['account']) trans.budget = budg trans.notes = data['notes'].strip() logger.info('Creating new Transaction for FuelFill: %s', trans.as_dict) db_session.add(trans) db_session.commit() return { 'success_message': 'Successfully saved FuelFill %d ' ' and Transaction %d in database.' % (fill.id, trans.id), 'success': True, 'trans_id': trans.id, 'fill_id': fill.id, 'calculated_mpg': fill.calculated_mpg }
def post(self): """ Handle POST ``/ajax/reconcile`` Request is a JSON dict with two keys, "reconciled" and "ofxIgnored". "reconciled" value is a dict of integer transaction ID keys, to values which are either a string reason why the Transaction is being reconciled as "No OFX" or a 2-item list of OFXTransaction acct_id and fitid. "ofxIgnored" is a dict with string keys which are strings identifying an OFXTransaction in the form "<ACCT_ID>%<FITID>", and values are a string reason why the OFXTransaction is being reconciled without a matching Transaction. Response is a JSON dict. Keys are ``success`` (boolean) and either ``error_message`` (string) or ``success_message`` (string). :return: JSON response """ raw = request.get_json() data = { 'reconciled': {int(x): raw['reconciled'][x] for x in raw['reconciled']}, 'ofxIgnored': raw.get('ofxIgnored', {}) } logger.debug('POST /ajax/reconcile: %s', data) rec_count = 0 for trans_id in sorted(data['reconciled'].keys()): trans = db_session.query(Transaction).get(trans_id) if trans is None: logger.error('Invalid transaction ID: %s', trans_id) return jsonify({ 'success': False, 'error_message': 'Invalid Transaction ID: %s' % trans_id }), 400 if not isinstance(data['reconciled'][trans_id], type([])): # it's a string; reconcile without OFX db_session.add( TxnReconcile(txn_id=trans_id, note=data['reconciled'][trans_id])) logger.info('Reconcile %s as NoOFX; note=%s', trans, data['reconciled'][trans_id]) rec_count += 1 continue # else reconcile with OFX ofx_key = (data['reconciled'][trans_id][0], data['reconciled'][trans_id][1]) ofx = db_session.query(OFXTransaction).get(ofx_key) if ofx is None: logger.error('Invalid OFXTransaction: %s', ofx_key) return jsonify({ 'success': False, 'error_message': 'Invalid OFXTransaction: (%s, \'%s\')' % (ofx_key[0], ofx_key[1]) }), 400 db_session.add( TxnReconcile(txn_id=trans_id, ofx_account_id=data['reconciled'][trans_id][0], ofx_fitid=data['reconciled'][trans_id][1])) logger.info('Reconcile %s with %s', trans, ofx) rec_count += 1 # handle OFXTransactions to reconcile with no Transaction for ofxkey in sorted(data['ofxIgnored'].keys()): note = data['ofxIgnored'][ofxkey] acct_id, fitid = ofxkey.split('%', 1) db_session.add( TxnReconcile(ofx_account_id=acct_id, ofx_fitid=fitid, note=note)) logger.info( 'Reconcile OFXTransaction (%s, %s) as NoTransaction; note=%s', acct_id, fitid, note) rec_count += 1 try: db_session.flush() db_session.commit() except Exception as ex: logger.error('Exception committing transaction reconcile', exc_info=True) return jsonify({ 'success': False, 'error_message': 'Exception committing reconcile(s): %s' % ex }), 400 return jsonify({ 'success': True, 'success_message': 'Successfully reconciled ' '%d transactions' % rec_count })