def parse(self): content = self.content f = StringIO(content) reader = DictReaderStrip(f, delimiter=',') transactions = [] for row in reader: print("Importing {} at {}".format(row['商品'], row['交易时间'])) meta = {} time = dateparser.parse(row['交易时间']) meta['wechat_trade_no'] = row['交易单号'] meta['trade_time'] = row['交易时间'] meta['timestamp'] = str(time.timestamp()).replace('.0', '') account = get_account_by_guess(row['交易对方'], row['商品'], time) # flag = "*" amount_string = row['金额(元)'].replace('¥', '') amount = float(amount_string) if row['商户单号'] != '/': meta['shop_trade_no'] = row['商户单号'] if row['备注'] != '/': meta['note'] = row['备注'] meta = data.new_metadata( 'beancount/core/testing.beancount', 12345, meta ) entry = Transaction( meta, date(time.year, time.month, time.day), '*', row['交易对方'], row['商品'], data.EMPTY_SET, data.EMPTY_SET, [] ) status = row['当前状态'] if status == '支付成功' or status == '朋友已收钱' or status == '已全额退款' or '已退款' in status: if '转入零钱通' in row['交易类型']: entry = entry._replace(payee='') entry = entry._replace(narration='转入零钱通') data.create_simple_posting( entry, Account零钱通, amount_string, 'CNY') else: if '微信红包' in row['交易类型']: account = Account支出红包 if entry.narration == '/': entry = entry._replace(narration=row['交易类型']) else: account = get_account_by_guess( row['交易对方'], row['商品'], time) # if account == "Unknown": # entry = replace_flag(entry, '!') if status == '已全额退款': amount_string = '-' + amount_string data.create_simple_posting( entry, account, amount_string, 'CNY') data.create_simple_posting( entry, accounts[row['支付方式']], None, None) elif row['当前状态'] == '已存入零钱': if '微信红包' in row['交易类型']: if entry.narration == '/': entry = entry._replace(narration=row['交易类型']) data.create_simple_posting(entry, Account收入红包, None, 'CNY') else: income = get_income_account_by_guess( row['交易对方'], row['商品'], time) if income == 'Income:Unknown': entry = replace_flag(entry, '!') data.create_simple_posting(entry, income, None, 'CNY') data.create_simple_posting( entry, Account余额, amount_string, 'CNY') else: print('Unknown row', row) #b = printer.format_entry(entry) # print(b) if not self.deduplicate.find_duplicate(entry, amount, 'wechat_trade_no'): transactions.append(entry) self.deduplicate.apply_beans() return transactions
def parse(self): content = self.content f = StringIO(content) reader = DictReaderStrip(f, delimiter=',') transactions = [] for row in reader: print("Importing {} at {}".format(row['商品名称'], row['最近修改时间'])) meta = {} time = dateparser.parse(row['最近修改时间']) meta['alipay_trade_no'] = row['交易号'] meta['trade_time'] = row['最近修改时间'] meta['timestamp'] = str(time.timestamp()).replace('.0', '') account = get_account_by_guess(row['交易对方'], row['商品名称'], time) flag = "*" amount = float(row['金额(元)']) if account == "Unknown": flag = "!" if row['备注'] != '': meta['note'] = row['备注'] if row['商家订单号'] != '': meta['shop_trade_no'] = row['商家订单号'] meta = data.new_metadata( 'beancount/core/testing.beancount', 12345, meta ) entry = Transaction( meta, date(time.year, time.month, time.day), flag, row['交易对方'], row['商品名称'], data.EMPTY_SET, data.EMPTY_SET, [] ) data.create_simple_posting(entry, account, row['金额(元)'], 'CNY') if row['资金状态'] == '已支出': data.create_simple_posting(entry, Account支付宝, None, None) amount = -amount elif row['资金状态'] == '资金转移': data.create_simple_posting(entry, Account支付宝, None, None) elif row['资金状态'] == '已收入': income = get_income_account_by_guess(row['交易对方'], row['商品名称'], time) if income == 'Income:Unknown': entry = entry._replace(flag = '!') data.create_simple_posting(entry, income, None, None) else: print('Unknown status') print(row) if (row['服务费(元)'] != '0.00'): data.create_simple_posting(entry, 'Expenses:Fee', row['服务费(元)'], 'CNY') #b = printer.format_entry(entry) #print(b) if not self.deduplicate.find_duplicate(entry, amount): transactions.append(entry) self.deduplicate.apply_beans() return transactions
def test_insert_entry_transaction(tmp_path) -> None: file_content = dedent("""\ 2016-02-26 * "Uncle Boons" "Eating out alone" Liabilities:US:Chase:Slate -24.84 USD Expenses:Food:Restaurant 24.84 USD """) samplefile = tmp_path / "example.beancount" samplefile.write_text(file_content) postings = [ Posting( "Liabilities:US:Chase:Slate", A("-10.00 USD"), None, None, None, None, ), Posting("Expenses:Food", A("10.00 USD"), None, None, None, None), ] transaction = Transaction( {}, date(2016, 1, 1), "*", "new payee", "narr", None, None, postings, ) # Test insertion without "insert-entry" options. insert_entry(transaction, str(samplefile), [], 61) assert samplefile.read_text("utf-8") == dedent("""\ 2016-02-26 * "Uncle Boons" "Eating out alone" Liabilities:US:Chase:Slate -24.84 USD Expenses:Food:Restaurant 24.84 USD 2016-01-01 * "new payee" "narr" Liabilities:US:Chase:Slate -10.00 USD Expenses:Food 10.00 USD """) # Verify that InsertEntryOptions with dates greater or equal than the # transaction dates are ignored. options = [ InsertEntryOption( date(2015, 1, 1), re.compile(".*:Food"), str(samplefile), 1, ), InsertEntryOption( date(2015, 1, 2), re.compile(".*:FOOO"), str(samplefile), 1, ), InsertEntryOption( date(2017, 1, 1), re.compile(".*:Food"), str(samplefile), 6, ), ] new_options = insert_entry(transaction._replace(narration="narr1"), str(samplefile), options, 61) assert new_options[0].lineno == 5 assert new_options[1].lineno == 5 assert new_options[2].lineno == 10 assert samplefile.read_text("utf-8") == dedent("""\ 2016-01-01 * "new payee" "narr1" Liabilities:US:Chase:Slate -10.00 USD Expenses:Food 10.00 USD 2016-02-26 * "Uncle Boons" "Eating out alone" Liabilities:US:Chase:Slate -24.84 USD Expenses:Food:Restaurant 24.84 USD 2016-01-01 * "new payee" "narr" Liabilities:US:Chase:Slate -10.00 USD Expenses:Food 10.00 USD """) # Verify that previous postings are matched against InsertEntryOptions when # the last posting doesn't match. options = [ InsertEntryOption( date(2015, 1, 1), re.compile(".*:Slate"), str(samplefile), 5, ), InsertEntryOption( date(2015, 1, 2), re.compile(".*:FOOO"), str(samplefile), 1, ), ] transaction = transaction._replace(narration="narr2") new_options = insert_entry(transaction, str(samplefile), options, 61) assert new_options[0].lineno == 9 assert new_options[1].lineno == 1 assert samplefile.read_text("utf-8") == dedent("""\ 2016-01-01 * "new payee" "narr1" Liabilities:US:Chase:Slate -10.00 USD Expenses:Food 10.00 USD 2016-01-01 * "new payee" "narr2" Liabilities:US:Chase:Slate -10.00 USD Expenses:Food 10.00 USD 2016-02-26 * "Uncle Boons" "Eating out alone" Liabilities:US:Chase:Slate -24.84 USD Expenses:Food:Restaurant 24.84 USD 2016-01-01 * "new payee" "narr" Liabilities:US:Chase:Slate -10.00 USD Expenses:Food 10.00 USD """) # Verify that preference is given to InsertEntryOptions with later dates in # case several of them match a posting. options = [ InsertEntryOption( date(2015, 1, 1), re.compile(".*:Food"), str(samplefile), 5, ), InsertEntryOption( date(2015, 1, 2), re.compile(".*:Food"), str(samplefile), 1, ), ] transaction = transaction._replace(narration="narr3") insert_entry(transaction, str(samplefile), options, 61) assert samplefile.read_text("utf-8") == dedent("""\ 2016-01-01 * "new payee" "narr3" Liabilities:US:Chase:Slate -10.00 USD Expenses:Food 10.00 USD 2016-01-01 * "new payee" "narr1" Liabilities:US:Chase:Slate -10.00 USD Expenses:Food 10.00 USD 2016-01-01 * "new payee" "narr2" Liabilities:US:Chase:Slate -10.00 USD Expenses:Food 10.00 USD 2016-02-26 * "Uncle Boons" "Eating out alone" Liabilities:US:Chase:Slate -24.84 USD Expenses:Food:Restaurant 24.84 USD 2016-01-01 * "new payee" "narr" Liabilities:US:Chase:Slate -10.00 USD Expenses:Food 10.00 USD """)
def modify_entry( self, entry: data.Transaction, match_values: dict, flag_to_done=True): """takes an Entry and a dict of values we parsed from the Entry """ # Pre-process match_value? for sr in self.set_rules: if sr.directive == 'transaction': # Should Possible Eval the Value? if sr.meta_key: # Meta Key is inserted meta = dict(entry.meta) meta[sr.meta_key] = sr.value entry = entry._replace(meta=meta) else: value = sr.value if sr.parameter in ('links', 'tags'): current:set = getattr(entry, sr.parameter, set()) or set() current.add(value) value = current entry = entry._replace(**{sr.parameter: value}) elif sr.directive == 'posting': postings = [] for posting in entry.postings: if posting.flag == '!': if sr.meta_key: meta = dict(posting.meta) meta[sr.meta_key] = sr.value posting = posting._replace( meta=meta ) elif sr.value is not None: posting = posting._replace(**{ sr.parameter: sr.value }) else: logger.warning(f"Invalid directive to Set {sr.parameter} to {sr.value} on {posting} for {entry}") logger.warning(f"{self}") logger.debug(f"New POSTING: {posting}") postings.append(posting) entry = entry._replace(postings=postings) for field, value in match_values.items(): # We allow <payee> and <meta_tagname> in match-groups if field == "payee" and not entry.payee: entry = entry._replace(payee=value.title()) # Propercase if field.startswith('meta_'): meta_name = field[5:] entry.meta[meta_name] = value if flag_to_done: # Set all ! -> * postings = [] for posting in entry.postings: if posting.flag == '!': posting = posting._replace(flag='') postings.append(posting) entry = entry._replace(postings=postings, flag='*') return entry