def gen_transaction(self, row): print(self.tradetime(row)) trade_time = row[0] trade_time = row[0].value trade_type = row[1].value trade_partner = row[2].value product_description = row[3].value income_outcome = row[4].value category = row[5].value amount = str(row[6].value) project = row[7].value account = row[8].value comment = row[9].value tags = set() if project is not None: tags.add(project) if comment is not None and comment != '/': product_description += ' @' + comment meta = {} meta['trade_time'] = str(trade_time) Transaction() entry = Transaction(meta, trade_time.strftime('%Y-%m-%d'), '*', trade_partner, product_description, tags, data.EMPTY_SET, []) # 生成基础posting base_post_amount = amount if income_outcome == '支出' or income_outcome == '转出': base_post_amount = '-' + amount data.create_simple_posting(entry, get_base_account(account), base_post_amount, 'CNY') if income_outcome == '收入': data.create_simple_posting(entry, get_income_account(category), '-' + amount, 'CNY') if income_outcome == '支出': data.create_simple_posting(entry, get_expenses_account(category), amount, 'CNY') if income_outcome == '转出': data.create_simple_posting(entry, get_base_account(category), amount, 'CNY') if income_outcome == '转入': data.create_simple_posting(entry, get_base_account(category), '-' + amount, 'CNY') if income_outcome == '退款': data.create_simple_posting(entry, get_expenses_account(category), '-' + amount, 'CNY') print(entry) return entry
def test_serialise(app): assert serialise(None) is None txn = Transaction( {}, datetime.date(2017, 12, 12), "*", "Test3", "asdfasd", frozenset(["tag"]), frozenset(["link"]), [], ) create_simple_posting(txn, "Assets:ETrade:Cash", "100", "USD") create_simple_posting(txn, "Assets:ETrade:GLD", None, None) json_txn = { "date": "2017-12-12", "flag": "*", "meta": {}, "narration": "asdfasd #tag ^link", "payee": "Test3", "type": "Transaction", "postings": [ { "account": "Assets:ETrade:Cash", "amount": "100 USD" }, { "account": "Assets:ETrade:GLD", "amount": "" }, ], } with app.test_request_context(): serialised = loads(dumps(serialise(txn))) assert serialised == json_txn txn = txn._replace(payee="") json_txn["payee"] = "" serialised = loads(dumps(serialise(txn))) assert serialised == json_txn txn = txn._replace(payee=None) serialised = loads(dumps(serialise(txn))) assert serialised == json_txn
def test_add_ignored(tmpdir): journal_path = create_journal( tmpdir, """ 2015-01-01 * "Test transaction 1" Assets:Account-A 100 USD Assets:Account-B """) ignored_path = create_journal( tmpdir, """ 2015-03-01 * "Test transaction 2" Assets:Account-A 100 USD Assets:Account-B """, name='ignored.beancount') editor = journal_editor.JournalEditor(journal_path, ignored_path) stage = editor.stage_changes() new_transaction = Transaction( meta=None, date=datetime.date(2015, 4, 1), flag='*', payee=None, narration='New transaction', tags=EMPTY_SET, links=EMPTY_SET, postings=[ Posting( account='Assets:Account-A', units=Amount(Decimal(3), 'USD'), cost=None, price=None, flag=None, meta=None), Posting( account='Assets:Account-B', units=MISSING, cost=None, price=None, flag=None, meta=None), ], ) stage.add_entry(new_transaction, ignored_path) result = stage.apply() check_file_contents( journal_path, """ 2015-01-01 * "Test transaction 1" Assets:Account-A 100 USD Assets:Account-B """) check_file_contents( ignored_path, """ 2015-03-01 * "Test transaction 2" Assets:Account-A 100 USD Assets:Account-B 2015-04-01 * "New transaction" Assets:Account-A 3 USD Assets:Account-B """) check_journal_entries(editor)
def per_marked_transaction(tx: Transaction, tx_orig: Transaction, config: Config) -> List[Transaction]: account_prefix: str total_income = sum_income(tx) total_expenses = sum_expenses(tx) total_value: Amount if total_expenses.is_empty() and total_income.is_empty(): return tx_orig # If tx nor postings are not marked, bail early. elif not total_expenses.is_empty() and total_income.is_empty(): account_prefix = config.account_debtors + ":" total_value = total_expenses.get_currency_units( tx.postings[0].units.currency) elif total_expenses.is_empty() and not total_income.is_empty(): account_prefix = config.account_creditors + ":" total_value = total_income.get_currency_units( tx.postings[0].units.currency) else: raise RuntimeError( 'Plugin "share" doesn\'t work on transactions that has both income and expense: please split it up into two transactions instead.' ) # 4. Per posting, split it up based on marks. new_postings = [] for posting in tx.postings: with posting_error_handler(tx_orig, posting, PluginExampleError): new_postings.extend( per_marked_posting(posting, config, account_prefix, total_value)) for account in new_accounts: new_postings = merge_postings(account, new_postings, config.meta_name) return [tx._replace(postings=new_postings)]
def run(self): try: # remove anything that was there previously self.destination.clear() for account, entries in self.source.items(): print_stderr(f"Processing {account}") new_entries = [] for entry in entries: if type(entry) in SUPPORTED_DIRECTIVES: categorised_account = self.attempt_categorise(entry) if categorised_account: posting = Posting(categorised_account, None, None, None, None, None) new_postings = entry.postings + [posting] else: new_postings = entry.postings new_entry = Transaction( entry.meta, entry.date, entry.flag, entry.payee, entry.narration, entry.tags, entry.links, new_postings, ) else: new_entry = entry new_entries.append(new_entry) # this assignment needs to happen just once self.destination[account] = new_entries finally: # close the database self.destination.close() print_stderr("Written to $destination")
def test_json_to_entry(): valid_accounts = ['Assets:US:ETrade:Cash', 'Assets:US:ETrade:GLD'] json_txn = { 'type': 'transaction', 'date': '2017-12-12', 'flag': '*', 'payee': 'Test3', 'narration': '', 'metadata': {}, 'postings': [ { 'account': 'Assets:US:ETrade:Cash', 'number': '100', 'currency': 'USD', }, { 'account': 'Assets:US:ETrade:GLD', }, ], } txn = Transaction({}, datetime.date(2017, 12, 12), '*', 'Test3', '', frozenset(), frozenset(), []) create_simple_posting(txn, 'Assets:US:ETrade:Cash', '100', 'USD') create_simple_posting(txn, 'Assets:US:ETrade:GLD', None, None) assert json_to_entry(json_txn, valid_accounts) == txn
def test_serialise(app): assert serialise(None) is None txn = Transaction({}, datetime.date(2017, 12, 12), '*', 'Test3', 'asdfasd', frozenset(['tag']), frozenset(['link']), []) create_simple_posting(txn, 'Assets:ETrade:Cash', '100', 'USD') create_simple_posting(txn, 'Assets:ETrade:GLD', None, None) json_txn = { 'type': 'Transaction', 'date': '2017-12-12', 'flag': '*', 'payee': 'Test3', 'narration': 'asdfasd #tag ^link', 'meta': {}, } with app.test_request_context(): serialised = loads(dumps(serialise(txn))) for key, value in json_txn.items(): assert serialised[key] == value or str(serialised[key]) == value assert serialised['postings'][0]['account'] == 'Assets:ETrade:Cash' assert serialised['postings'][0]['units'] == { 'currency': 'USD', 'number': 100, }
def test_deserialise(): postings = [ { 'account': 'Assets:ETrade:Cash', 'number': '100', 'currency': 'USD', }, { 'account': 'Assets:ETrade:GLD', }, ] json_txn = { 'type': 'Transaction', 'date': '2017-12-12', 'flag': '*', 'payee': 'Test3', 'narration': 'asdfasd #tag ^link', 'meta': {}, 'postings': postings, } txn = Transaction({}, datetime.date(2017, 12, 12), '*', 'Test3', 'asdfasd', frozenset(['tag']), frozenset(['link']), []) create_simple_posting(txn, 'Assets:ETrade:Cash', '100', 'USD') create_simple_posting(txn, 'Assets:ETrade:GLD', None, None) assert deserialise(json_txn) == txn with pytest.raises(KeyError): deserialise({}) with pytest.raises(FavaAPIException): deserialise({'type': 'NoEntry'})
def gen_transaction(self, row): trade_type = row['交易类型'] trade_amount = row['金额(元)'].replace('¥', '') trade_status = row['当前状态'] trade_time = dateparser.parse(row['交易时间']) trade_account = row['支付方式'] trade_partner = row['交易对方'] trade_description = row['商品'] trade_comment = row['备注'] meta = {} meta['trade_time'] = row['交易时间'] if trade_comment != '/': meta['comment'] = trade_comment entry = Transaction(meta, trade_time.strftime('%Y-%m-%d'), '*', trade_partner, trade_description, data.EMPTY_SET, data.EMPTY_SET, []) amount1 = trade_amount if row['收/支'] == '支出': amount1 = '-' + trade_amount data.create_simple_posting(entry, accounts.get_reality_account(row, 'wechat', trade_account), amount1, 'CNY') if trade_type == '商户消费' or trade_type == '扫二维码付款': data.create_simple_posting(entry, accounts.get_account(trade_partner, trade_description, trade_time, trade_comment), trade_amount, 'CNY') return entry
def merge_transactions( txns: List[Transaction], edges: Dict[int, Tuple[Transaction, str]], logger: ErrorLogger) -> Optional[Transaction]: date = None flag = None payee = None narration = '' tags = set() links = set() meta = {} postings = [] compatible = True last_txn = None for txn in txns: last_txn = txn if date and txn.date and txn.date != date: compatible = False date = date or txn.date if flag and txn.flag and txn.flag != flag: compatible = False flag = flag or txn.flag if payee and txn.payee and txn.payee != payee: compatible = False payee = payee or txn.payee if narration and txn.narration and txn.narration != narration: compatible = False narration = narration or txn.narration for k, v in txn.meta.items(): mv = meta.get(k, None) meta[k] = mv or v if mv and mv != v and k not in ('filename', 'lineno'): compatible = False break if not compatible: break accounts_to_remove = set(edge[1] for edge in edges[id(txn)]) for posting in txn.postings: if utils.main_account(posting.account) not in accounts_to_remove: postings.append(posting) if not compatible: logger.log_error(UnresolvedLinkError( last_txn.meta, 'Transaction and its complement do not agree on flag, payee, ' 'narration or meta', last_txn, )) return None txn = Transaction( date=date, meta=meta, flag=flag, payee=payee, narration=narration, tags=tags, links=links, postings=postings, ) return txn
def test_serialise(app): assert serialise(None) is None txn = Transaction({}, datetime.date(2017, 12, 12), '*', 'Test3', 'asdfasd', frozenset(['tag']), frozenset(['link']), []) create_simple_posting(txn, 'Assets:ETrade:Cash', '100', 'USD') create_simple_posting(txn, 'Assets:ETrade:GLD', None, None) json_txn = { 'date': '2017-12-12', 'flag': '*', 'meta': {}, 'narration': 'asdfasd #tag ^link', 'payee': 'Test3', 'type': 'Transaction', 'postings': [ { 'account': 'Assets:ETrade:Cash', 'amount': '100 USD', }, { 'account': 'Assets:ETrade:GLD', 'amount': '', }, ], } with app.test_request_context(): serialised = loads(dumps(serialise(txn))) assert serialised == json_txn
def test_deserialise(): postings = [ {"account": "Assets:ETrade:Cash", "amount": "100 USD"}, {"account": "Assets:ETrade:GLD"}, ] json_txn = { "type": "Transaction", "date": "2017-12-12", "flag": "*", "payee": "Test3", "narration": "asdfasd #tag ^link", "meta": {}, "postings": postings, } txn = Transaction( {}, datetime.date(2017, 12, 12), "*", "Test3", "asdfasd", frozenset(["tag"]), frozenset(["link"]), [], ) create_simple_posting(txn, "Assets:ETrade:Cash", "100", "USD") create_simple_posting(txn, "Assets:ETrade:GLD", None, None) assert deserialise(json_txn) == txn with pytest.raises(KeyError): deserialise({}) with pytest.raises(FavaAPIException): deserialise({"type": "NoEntry"})
def _make_transfer_trade_journal_entry(self, t: TradeConfirmation): return Transaction(meta=collections.OrderedDict(), date=t.settlement_date, flag='*', payee=self.payee, narration='Transfer due to stock sale', tags=EMPTY_SET, links=EMPTY_SET, postings=[ Posting( account=self.asset_cash_account, units=-t.net_amount, cost=None, meta=collections.OrderedDict([ (POSTING_DATE_KEY, t.settlement_date), (TRADE_REFERENCE_NUMBER_KEY, '>' + t.reference_number), ]), price=None, flag=None, ), Posting( account=FIXME_ACCOUNT, units=t.net_amount, cost=None, meta=None, price=None, flag=None, ), ])
def _make_transfer_journal_entry(self, r: Release): date = r.settlement_date or r.release_date return Transaction( meta=collections.OrderedDict(), date=date, flag='*', payee=self.payee, narration='Stock Vesting - %s' % r.transfer_description, tags=EMPTY_SET, links=EMPTY_SET, postings=[ Posting( account=self.asset_cash_account, units=-r.transfer_amount, cost=None, meta=collections.OrderedDict([ (POSTING_DATE_KEY, date), (AWARD_ID_KEY, '>' + r.award_id), (AWARD_NOTE_KEY, r.transfer_description), ]), price=None, flag=None, ), Posting( account=FIXME_ACCOUNT, units=r.transfer_amount, cost=None, meta=None, price=None, flag=None, ), ])
def execute(self, csv_line, transaction=None): return ( False, Transaction( meta=None, date=None, flag="*", payee=None, narration=None, tags=None, links=None, postings=[ Posting( account=None, units=None, cost=None, price=None, flag=None, meta=None, ), Posting( account=None, units=None, cost=None, price=None, flag=None, meta=None, ), ], ), )
def test_process_match(self): self.skipTest("wip") narration = "Amazon.com*MA4TS16T0" tx = Transaction(narration=narration, date=None, flag=None, payee=None, tags={}, links={}, postings=[ Posting(account="Liablities:Card", units=Amount(D(100), "USD"), cost=None, price=None, flag="*", meta={}), Posting(account="Expenses:FIXME", units=Amount(D(-100), "USD"), cost=None, price=None, flag="!", meta={}) ], meta={ 'file_name': '', 'lineno': 0 }) m = Matcher([AMAZON_RULE]) results = m.process([tx]) self.assertEqual(len(results), 1) result = results[0] print(yaml.dump(AMAZON_RULE))
def _transform_transaction(self, truelayer_txn, beancount_account: Text, is_pending: bool = False) -> Transaction: """Transforms TrueLayer Transaction to beancount Transaction.""" number = abs(currency_to_decimal(truelayer_txn['amount'])) if truelayer_txn['transaction_type'] == 'DEBIT': number = -number elif truelayer_txn['transaction_type'] == 'CREDIT': pass else: assert False posting = Posting( account=beancount_account, units=Amount(number, truelayer_txn['currency']), cost=None, price=None, flag=None, meta=None, ) payee = (truelayer_txn.get('merchant_name', None) or truelayer_txn['meta'].get('provider_merchant_name', None)) return Transaction( meta=new_metadata('', 0), date=dateutil.parser.parse( truelayer_txn['timestamp']).astimezone().date(), flag='!' if is_pending else '*', payee=payee, narration=truelayer_txn['description'], tags=set(), links=set(), postings=[posting], )
def _make_import_result(mint_entry: MintEntry) -> ImportResult: transaction = Transaction( meta=None, date=mint_entry.date, flag=FLAG_OKAY, payee=None, narration=mint_entry.source_desc, tags=EMPTY_SET, links=EMPTY_SET, postings=[ Posting( account=mint_entry.account, units=mint_entry.amount, cost=None, price=None, flag=None, meta=collections.OrderedDict( source_desc=mint_entry.source_desc, date=mint_entry.date, )), Posting( account=FIXME_ACCOUNT, units=-mint_entry.amount, cost=None, price=None, flag=None, meta=None, ), ]) return ImportResult( date=mint_entry.date, info=get_info(mint_entry), entries=[transaction])
def test_match_rule(self): self.skipTest("wip") narration = "Amazon.com*MA4TS16T0" tx = Transaction(narration=narration, date=None, flag=None, payee=None, tags={}, links={}, postings=[], meta={ 'file_name': '', 'lineno': 0 }) result = Matcher().match_rule(tx, AMAZON_RULE) expected = { 'narration': { 'match-parameter': 'narration', 'match-value': narration, 'match-group': { 'payee': 'Amazon.com', 'order_id': 'MA4TS16T0' } } } self.assertEqual(result, expected)
def process_transaction(entry: Transaction, viewpoint: str) -> Optional[Transaction]: if viewpoint == 'everyone': return entry suffix = f':[{viewpoint}]' postings = [] relevant = False for posting in entry.postings: if posting.account.startswith('[Residuals]:'): if not posting.account.endswith(suffix): postings.append( posting._replace(units=amount.mul(posting.units, Decimal(-1)), )) else: if posting.account.endswith(suffix): postings.append( posting._replace(account=posting.account.rsplit(':', 1)[0], )) if posting.account.endswith(suffix): relevant = True if postings: return entry._replace( flag=entry.flag if relevant else 'T', postings=postings, )
def test_render_entries(example_ledger: FavaLedger, snapshot) -> None: entry1 = _get_entry(example_ledger, "Uncle Boons", "2016-04-09") entry2 = _get_entry(example_ledger, "BANK FEES", "2016-05-04") postings = [ Posting("Expenses:Food", A("10.00 USD"), None, None, None, None), ] transaction = Transaction( {}, date(2016, 1, 1), "*", "new payee", "narr", None, None, postings, ) entries = example_ledger.file.render_entries([entry1, entry2, transaction]) snapshot("\n".join(entries)) file_content = dedent( """\ 2016-04-09 * "Uncle Boons" "" #trip-new-york-2016 Liabilities:US:Chase:Slate -52.22 USD Expenses:Food:Restaurant 52.22 USD 2016-05-04 * "BANK FEES" "Monthly bank fee" Assets:US:BofA:Checking -4.00 USD Expenses:Financial:Fees 4.00 USD """ ) assert file_content == "\n".join( example_ledger.file.render_entries([entry1, entry2]) )
def parse(self): table = self.table rows = table.nrows for i in range(5, rows - 4): row = table.row_values(i) time = datetime.datetime(*xlrd.xldate_as_tuple( table.cell_value(rowx=i, colx=0), self.book.datemode)) print("Importing {} price = {} balance = {}".format( time, row[2], row[3])) meta = {} amount = float(row[1]) entry = Transaction(meta, date(time.year, time.month, time.day), '*', '余额宝', '余额宝', data.EMPTY_SET, data.EMPTY_SET, []) if not row[2] in incomes: amount = -amount if not self.deduplicate.find_duplicate(entry, amount, None, Account余额宝): print( "Unknown transaction for {}, check if Alipay transaction exists." .format(time)) self.deduplicate.apply_beans() return []
def _replace_transaction_properties(transaction: Transaction, changes: dict) -> Transaction: if 'links' in changes: links = changes['links'] if links is None: links = [] assert isinstance(links, list) and all( isinstance(x, str) for x in links) links = frozenset(links) else: links = transaction.links if 'tags' in changes: tags = changes['tags'] if tags is None: tags = [] assert isinstance(tags, list) and all(isinstance(x, str) for x in tags) tags = frozenset(tags) else: tags = transaction.tags narration = changes.get('narration', transaction.narration) payee = changes.get('payee', transaction.payee) if narration is None: if payee is not None: narration = payee payee = None else: narration = '' return transaction._replace(links=links, tags=tags, narration=narration, payee=payee)
def new_filtered_entries(tx, params, get_amounts, selected_postings, config): """ Beancount plugin: Dublicates all transaction's postings over time. Args: tx: A transaction instance. params: A parser options dict. get_amounts: A function, i.e. distribute_over_period. selected_postings: A list of postings. config: A configuration string in JSON format given in source file. Returns: An array of transaction entries. """ all_pairs = [] for _, new_account, params, posting in selected_postings: dates, amounts = get_amounts(params, tx.date, posting.units.number, config) all_pairs.append( (dates, amounts, posting, new_account) ) map_of_dates = {} for dates, amounts, posting, new_account in all_pairs: for i in range( min(len(dates), len(amounts)) ): if(not dates[i] in map_of_dates): map_of_dates[dates[i]] = [] amount = Amount(amounts[i], posting.units.currency) # Income/Expense to be spread map_of_dates[dates[i]].append(Posting(account=new_account, units=amount, cost=None, price=None, flag=posting.flag, meta=new_metadata(tx.meta['filename'], tx.meta['lineno']))) # Asset/Liability that buffers the difference map_of_dates[dates[i]].append(Posting(account=posting.account, units=mul(amount, D(-1)), cost=None, price=None, flag=posting.flag, meta=new_metadata(tx.meta['filename'], tx.meta['lineno']))) new_transactions = [] for i, (date, postings) in enumerate(sorted(map_of_dates.items())): if len(postings) > 0: e = Transaction( date=date, meta=tx.meta, flag=tx.flag, payee=tx.payee, narration=tx.narration + config['suffix']%(i+1, len(dates)), tags={config['tag']}, links=tx.links, postings=postings) new_transactions.append(e) return new_transactions
def _make_transaction(self, raw_txn: RawTransaction, link: bool, is_transfer: bool): amount = original_amount = amount_parsing.parse_amount( raw_txn[CSV_AMOUNT_TOTAL_KEY]) txn_type = raw_txn[CSV_TYPE_KEY] is_payment_txn = txn_type == 'Payment' or txn_type == 'Charge' if is_transfer and is_payment_txn: amount = -amount txn_time = parse_csv_date(raw_txn[CSV_DATETIME_KEY]) assets_posting = Posting( account=self.assets_account, units=amount, cost=None, price=None, flag=None, meta=collections.OrderedDict([ (VENMO_TRANSFER_KEY if is_transfer else VENMO_PAYMENT_KEY, raw_txn[CSV_ID_KEY]), ('date', txn_time.date()), (VENMO_TYPE_KEY, txn_type), ]), ) note = re.sub(r'\s+', ' ', raw_txn[CSV_NOTE_KEY]) payee = 'Venmo' if is_payment_txn: if original_amount.number > ZERO: payee = assets_posting.meta[VENMO_PAYER_KEY] = raw_txn[ CSV_FROM_KEY if txn_type == 'Payment' else CSV_TO_KEY] else: payee = assets_posting.meta[VENMO_PAYEE_KEY] = raw_txn[ CSV_TO_KEY if txn_type == 'Payment' else CSV_FROM_KEY] if note: assets_posting.meta[VENMO_DESCRIPTION_KEY] = note if is_transfer: assets_posting.meta[VENMO_ACCOUNT_DESCRIPTION_KEY] = raw_txn[ CSV_DESTINATION_KEY] or raw_txn[CSV_FUNDING_SOURCE_KEY] links = EMPTY_SET if link: links = frozenset(['venmo.%s' % raw_txn[CSV_ID_KEY]]) return Transaction( meta=None, date=txn_time.date(), flag=FLAG_OKAY, payee=payee, narration='Transfer' if is_transfer else note, tags=EMPTY_SET, links=links, postings=[ assets_posting, Posting( account=FIXME_ACCOUNT, units=-amount, cost=None, price=None, flag=None, meta=None, ), ], )
def add_payee_to_transaction(transaction: Transaction, payee: str, overwrite=False) -> Transaction: ''' Sets a transactions's payee. ''' if not transaction.payee or overwrite: transaction = transaction._replace(payee=payee) return transaction
def make_import_result(receipt: Any, receipt_directory: str, link_prefix: str) -> ImportResult: receipt_id = str(receipt['id']) if receipt['date']: date = datetime.datetime.strptime(receipt['date'], date_format).date() else: date = dateutil.parser.parse(receipt['submitted_at']).date() merchant = receipt['merchant'] note = receipt['note'] if note: payee = merchant narration = note else: payee = None narration = merchant if receipt['total']: amount = Amount( number=D(receipt['total']), currency=receipt['currency_code']) else: amount = Amount( number=ZERO, currency=receipt['currency_code']) postings = [ Posting( account=FIXME_ACCOUNT, units=amount, cost=None, meta=None, price=None, flag=None, ), Posting( account=FIXME_ACCOUNT, units=-amount, cost=None, meta=None, price=None, flag=None, ) ] return ImportResult( date=date, entries=[ Transaction( meta=collections.OrderedDict(), date=date, flag='*', payee=payee, narration=narration, links=frozenset([link_prefix + receipt_id]), tags=frozenset(), postings=postings, ), ], info=dict( type='image/jpeg', filename=os.path.realpath( os.path.join(receipt_directory, receipt_id + '.jpg')), ), )
def strip_flaky_meta(transaction: Transaction): transaction = transaction._replace( meta=metaset.discard(transaction.meta, 'filename')) transaction = transaction._replace( meta=metaset.discard(transaction.meta, 'lineno')) # new_postings = list(tx.postings) for j, _ in enumerate(transaction.postings): transaction.postings[j] = transaction.postings[j]._replace( meta=metaset.discard(transaction.postings[j].meta, 'filename')) transaction.postings[j] = transaction.postings[j]._replace( meta=metaset.discard(transaction.postings[j].meta, 'lineno')) transaction.postings[j] = transaction.postings[j]._replace( meta=metaset.discard(transaction.postings[j].meta, '__automatic__')) # transaction._replace(postings=new_postings) return transaction
def _add_suggestions_to_transaction(transaction: Transaction, suggestions: List[str], key='__suggestions__'): """ Adds a list of suggested accounts to a transaction under transaction.meta[key]. """ meta = transaction.meta meta[key] = json.dumps(suggestions) transaction = transaction._replace(meta=meta) return transaction
def make_import_result(purchase: Any, link_prefix: str, tz_info: Optional[datetime.tzinfo], html_path: str) -> ImportResult: purchase_id = str(purchase['id']) date = datetime.datetime.fromtimestamp(purchase['timestamp'] / 1000, tz_info).date() payment_processor = purchase['payment_processor'] merchant = purchase['merchant'] items = purchase['items'] payee = ' - '.join(x for x in [payment_processor, merchant] if x is not None) # type: Optional[str] narration = '; '.join(x for x in items) # type: Optional[str] if not narration: narration = payee payee = None if purchase['currency'] is None or purchase['units'] is None: pos_amount = neg_amount = Amount(D('0.00'), 'USD') else: pos_amount = Amount( round(D(purchase['units']), 2), purchase['currency']) neg_amount = -pos_amount postings = [ Posting( account=FIXME_ACCOUNT, units=pos_amount, cost=None, meta=None, price=None, flag=None, ), Posting( account=FIXME_ACCOUNT, units=neg_amount, cost=None, meta=None, price=None, flag=None, ) ] return ImportResult( date=date, entries=[ Transaction( meta=collections.OrderedDict(), date=date, flag='*', payee=payee, narration=narration, links=frozenset([link_prefix + purchase_id]), tags=frozenset(), postings=postings, ), ], info=dict( type='text/html', filename=os.path.realpath(html_path), ), )