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
Example #2
0
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
Example #3
0
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)
Example #4
0
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)]
Example #5
0
 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")
Example #6
0
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
Example #7
0
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,
    }
Example #8
0
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'})
Example #9
0
    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
Example #10
0
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
Example #11
0
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
Example #12
0
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,
             ),
         ])
Example #15
0
    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,
                    ),
                ],
            ),
        )
Example #16
0
    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))
Example #17
0
    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],
        )
Example #18
0
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])
Example #19
0
 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)
Example #20
0
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,
        )
Example #21
0
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])
    )
Example #22
0
    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 []
Example #23
0
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)
Example #24
0
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
Example #25
0
    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
Example #27
0
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')),
        ),
    )
Example #28
0
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
Example #30
0
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),
        ),
    )