def test_extract_(tmpdir):
    csv_path = path.join(tmpdir, "微信支付账单(20200830-20200906).csv")
    with open(csv_path, "w", encoding="utf-8", newline="") as f:
        f.write("\n" * 16)
        writer = csv.writer(f)
        writer.writerow(
            "交易时间,交易类型,交易对方,商品,收/支,金额(元),支付方式,当前状态,交易单号,商户单号,备注".split(","))
        writer.writerow([
            "2020-09-06 23:19:24",
            "零钱充值",
            "招商银行(1111)",
            "/",
            "/",
            "¥1.00",
            "招商银行(1111)",
            "充值完成",
            233,
            "/",
            "/",
        ])
    file = cache._FileMemo(csv_path)
    importer: WechatImporter = get_importer("examples/wechat.import")
    entries = importer.extract(file)
    assert len(entries) == 1
    txn = entries[0]
    assert [x.account == importer.account
            for x in txn.postings] == [True, False]
    assert [x.units for x in txn.postings] == [
        Amount(Decimal(1), "CNY"),
        Amount(Decimal(-1), "CNY"),
    ]
    assert txn.postings[1].account == "Assets:Bank:CMB:C1111"
示例#2
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))
示例#3
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')),
        ),
    )
示例#4
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),
        ),
    )
示例#5
0
def test_emits_closing_balance_directive(tmp_file):
    tmp_file.write(
        _format(
            '''
            "Kreditkarte:";"{card_number} Kreditkarte";

            "Von:";"01.01.2018";
            "Bis:";"31.01.2018";
            "Saldo:";"5000.01 EUR";
            "Datum:";"30.01.2018";

            {header};
            "Ja";"15.01.2018";"15.01.2018";"REWE Filiale Muenchen";"-10,80";"";
            ''',  # NOQA
            dict(
                card_number=Constants.card_number.value,
                header=Constants.header.value,
            ),
        )
    )

    importer = CreditImporter(
        Constants.card_number.value, 'Assets:DKB:Credit', file_encoding='utf-8'
    )

    with open(str(tmp_file.realpath())) as fd:
        transactions = importer.extract(fd)

    assert len(transactions) == 2
    assert isinstance(transactions[1], Balance)
    assert transactions[1].date == datetime.date(2018, 1, 31)
    assert transactions[1].amount == Amount(Decimal('5000.01'), currency='EUR')
示例#6
0
def test_extract_no_transactions(tmp_file):
    importer = CreditImporter(Constants.card_number.value, 'Assets:DKB:Credit')

    tmp_file.write(
        _format(
            '''
            "Kreditkarte:";"{card_number} Kreditkarte";

            "Von:";"01.01.2018";
            "Bis:";"31.01.2018";
            "Saldo:";"5000.01 EUR";
            "Datum:";"30.01.2018";

            {header};
            ''',
            dict(
                card_number=Constants.card_number.value,
                header=Constants.header.value,
            ),
        )
    )

    with open(str(tmp_file.realpath())) as fd:
        transactions = importer.extract(fd)

    assert len(transactions) == 1
    assert isinstance(transactions[0], Balance)
    assert transactions[0].date == datetime.date(2018, 1, 31)
    assert transactions[0].amount == Amount(Decimal('5000.01'), currency='EUR')
示例#7
0
def test_mismatching_dates_in_meta(tmp_file):
    tmp_file.write_text(
        _format(
            '''
            "Kontonummer:";"{iban} / Girokonto";

            "Von:";"01.01.2018";
            "Bis:";"31.01.2018";
            "Kontostand vom 31.01.2019:";"5.000,01 EUR";

            {header};
            "16.01.2018";"16.01.2018";"Lastschrift";"REWE Filialen Voll";"REWE SAGT DANKE.";"DE00000000000000000000";"AAAAAAAA";"-15,37";"000000000000000000    ";"0000000000000000000000";"";
            ''',  # NOQA
            dict(iban=IBAN, header=HEADER),
        )
    )

    importer = ECImporter(IBAN, 'Assets:DKB:EC', file_encoding='utf-8')

    with tmp_file.open() as fd:
        directives = importer.extract(fd)

    assert len(directives) == 2
    assert isinstance(directives[1], Balance)
    assert directives[1].date == datetime.date(2019, 2, 1)
    assert directives[1].amount == Amount(Decimal('5000.01'), currency='EUR')
示例#8
0
def test_tagessaldo_with_empty_balance_does_not_crash(tmp_file):
    tmp_file.write_text(
        _format(
            '''
            "Kontonummer:";"{iban} / Girokonto";

            "Von:";"01.01.2018";
            "Bis:";"31.01.2018";
            "Kontostand vom 31.01.2018:";"5.000,01 EUR";

            {header};
            "20.01.2018";"";"";"";"Tagessaldo";"";"";"";
            ''',
            dict(iban=IBAN, header=HEADER),
        )
    )

    importer = ECImporter(IBAN, 'Assets:DKB:EC', file_encoding='utf-8')

    with tmp_file.open() as fd:
        directives = importer.extract(fd)

    assert len(directives) == 1
    assert isinstance(directives[0], Balance)
    assert directives[0].date == datetime.date(2018, 2, 1)
    assert directives[0].amount == Amount(Decimal('5000.01'), currency='EUR')
示例#9
0
def test_extract_no_transactions(tmp_file):
    importer = ECImporter(IBAN, 'Assets:DKB:EC')

    tmp_file.write_text(
        _format(
            '''
            "Kontonummer:";"{iban} / Girokonto";

            "Von:";"01.01.2018";
            "Bis:";"31.01.2018";
            "Kontostand vom 31.01.2018:";"5.000,01 EUR";

            {header};
            ''',
            dict(iban=IBAN, header=HEADER),
        )
    )

    with tmp_file.open() as fd:
        directives = importer.extract(fd)

    assert len(directives) == 1
    assert isinstance(directives[0], Balance)
    assert directives[0].date == datetime.date(2018, 2, 1)
    assert directives[0].amount == Amount(Decimal('5000.01'), currency='EUR')
示例#10
0
    def test_tagessaldo_emits_balance_directive(self):
        with open(self.filename, 'wb') as fd:
            fd.write(
                _format(
                    '''
                "Kontonummer:";"{iban} / Girokonto";

                "Von:";"01.01.2018";
                "Bis:";"31.01.2018";
                "Kontostand vom 31.01.2017:";"5.000,01 EUR";

                {header};
                "20.01.2018";"";"";"";"Tagessaldo";"";"";"2.500,01";
            ''', dict(iban=self.iban, header=HEADER)))  # NOQA
        importer = ECImporter(self.iban,
                              'Assets:DKB:EC',
                              file_encoding='utf-8')

        with open(self.filename) as fd:
            transactions = importer.extract(fd)

        self.assertEqual(len(transactions), 1)
        self.assertTrue(isinstance(transactions[0], Balance))
        self.assertEqual(transactions[0].date, datetime.date(2018, 1, 20))
        self.assertEqual(transactions[0].amount,
                         Amount(Decimal('2500.01'), currency='EUR'))
示例#11
0
    def test_extract_sets_timestamps(self):
        with open(self.filename, 'wb') as fd:
            fd.write(_format('''
                "Kontonummer:";"{iban} / Girokonto";

                "Von:";"01.01.2018";
                "Bis:";"31.01.2018";
                "Kontostand vom 31.01.2018:";"5.000,01 EUR";

                {header};
                "16.01.2018";"16.01.2018";"Lastschrift";"REWE Filialen Voll";"REWE SAGT DANKE.";"DE00000000000000000000";"AAAAAAAA";"-15,37";"000000000000000000    ";"0000000000000000000000";"";
            ''', dict(iban=self.iban, header=HEADER)))  # NOQA

        importer = ECImporter(self.iban, 'Assets:DKB:EC',
                              file_encoding='utf-8')

        self.assertFalse(importer._date_from)
        self.assertFalse(importer._date_to)
        self.assertFalse(importer._balance)

        with open(self.filename) as fd:
            transactions = importer.extract(fd)

        self.assertTrue(transactions)
        self.assertEqual(importer._date_from, datetime.date(2018, 1, 1))
        self.assertEqual(importer._date_to, datetime.date(2018, 1, 31))
        self.assertEqual(importer._balance,
                         Amount(Decimal('5000.01'), currency='EUR'))
示例#12
0
def test_tagessaldo_with_empty_balance_does_not_crash(tmp_file):
    tmp_file.write(
        _format(
            '''
            "Kontonummer:";"{iban} / Girokonto";

            "Von:";"01.01.2018";
            "Bis:";"31.01.2018";
            "Kontostand vom 31.01.2018:";"5.000,01 EUR";

            {header};
            "20.01.2018";"";"";"";"Tagessaldo";"";"";"";
            ''',
            dict(iban=Constants.iban.value, header=Constants.header.value),
        ))

    importer = ECImporter(Constants.iban.value,
                          'Assets:DKB:EC',
                          file_encoding='utf-8')

    with open(str(tmp_file.realpath())) as fd:
        transactions = importer.extract(fd)

    assert len(transactions) == 1
    assert isinstance(transactions[0], Balance)
    assert transactions[0].date == datetime.date(2018, 2, 1)
    assert transactions[0].amount == Amount(Decimal('5000.01'), currency='EUR')
示例#13
0
    def test_extract_no_transactions(self):
        importer = CreditImporter(self.card_number, 'Assets:DKB:Credit')

        with open(self.filename, 'wb') as fd:
            fd.write(
                _format(
                    '''
                    "Kreditkarte:";"{card_number} Kreditkarte";

                    "Von:";"01.01.2018";
                    "Bis:";"31.01.2018";
                    "Saldo:";"5000.01 EUR";
                    "Datum:";"30.01.2018";

                    {header};
                    ''',
                    dict(card_number=self.card_number, header=HEADER),
                ))

        with open(self.filename) as fd:
            transactions = importer.extract(fd)

        self.assertEqual(len(transactions), 1)
        self.assertTrue(isinstance(transactions[0], Balance))
        self.assertEqual(transactions[0].date, datetime.date(2018, 1, 31))
        self.assertEqual(transactions[0].amount,
                         Amount(Decimal('5000.01'), currency='EUR'))
示例#14
0
def test_emits_closing_balance_directive(tmp_file):
    tmp_file.write_text(
        _format(
            '''
            "Kreditkarte:";"{card_number} Kreditkarte";

            "Von:";"01.01.2018";
            "Bis:";"31.01.2018";
            "Saldo:";"5000.01 EUR";
            "Datum:";"30.01.2018";

            {header};
            "Ja";"15.01.2018";"15.01.2018";"REWE Filiale Muenchen";"-10,80";"";
            ''',  # NOQA
            dict(card_number=CARD_NUMBER, header=HEADER),
        ))

    importer = CreditImporter(CARD_NUMBER,
                              'Assets:DKB:Credit',
                              file_encoding='utf-8')

    with tmp_file.open() as fd:
        directives = importer.extract(fd)

    assert len(directives) == 2
    assert isinstance(directives[1], Balance)
    assert directives[1].date == datetime.date(2018, 1, 31)
    assert directives[1].amount == Amount(Decimal('5000.01'), currency='EUR')
示例#15
0
def test_extract_no_transactions(tmp_file):
    importer = CreditImporter(CARD_NUMBER, 'Assets:DKB:Credit')

    tmp_file.write_text(
        _format(
            '''
            "Kreditkarte:";"{card_number} Kreditkarte";

            "Von:";"01.01.2018";
            "Bis:";"31.01.2018";
            "Saldo:";"5000.01 EUR";
            "Datum:";"30.01.2018";

            {header};
            ''',
            dict(card_number=CARD_NUMBER, header=HEADER),
        ))

    with tmp_file.open() as fd:
        directives = importer.extract(fd)

    assert len(directives) == 1
    assert isinstance(directives[0], Balance)
    assert directives[0].date == datetime.date(2018, 1, 31)
    assert directives[0].amount == Amount(Decimal('5000.01'), currency='EUR')
示例#16
0
def test_extract_sets_timestamps(tmp_file):
    tmp_file.write(
        _format(
            '''
            "Kontonummer:";"{iban} / Girokonto";

            "Von:";"01.01.2018";
            "Bis:";"31.01.2018";
            "Kontostand vom 31.01.2018:";"5.000,01 EUR";

            {header};
            "16.01.2018";"16.01.2018";"Lastschrift";"REWE Filialen Voll";"REWE SAGT DANKE.";"DE00000000000000000000";"AAAAAAAA";"-15,37";"000000000000000000    ";"0000000000000000000000";"";
            ''',  # NOQA
            dict(iban=Constants.iban.value, header=Constants.header.value),
        ))

    importer = ECImporter(Constants.iban.value,
                          'Assets:DKB:EC',
                          file_encoding='utf-8')

    assert not importer._date_from
    assert not importer._date_to
    assert not importer._balance_amount
    assert not importer._balance_date

    with open(str(tmp_file.realpath())) as fd:
        transactions = importer.extract(fd)

    assert transactions
    assert importer._date_from == datetime.date(2018, 1, 1)
    assert importer._date_to == datetime.date(2018, 1, 31)
    assert importer._balance_amount == Amount(Decimal('5000.01'),
                                              currency='EUR')
    assert importer._balance_date == datetime.date(2018, 2, 1)
示例#17
0
    def test_emits_closing_balance_directive(self):
        with open(self.filename, 'wb') as fd:
            fd.write(
                _format(
                    '''
                    "Kreditkarte:";"{card_number} Kreditkarte";

                    "Von:";"01.01.2018";
                    "Bis:";"31.01.2018";
                    "Saldo:";"5000.01 EUR";
                    "Datum:";"30.01.2018";

                    {header};
                    "Ja";"15.01.2018";"15.01.2018";"REWE Filiale Muenchen";"-10,80";"";
                    ''',  # NOQA
                    dict(card_number=self.card_number, header=HEADER),
                ))
        importer = CreditImporter(self.card_number,
                                  'Assets:DKB:Credit',
                                  file_encoding='utf-8')

        with open(self.filename) as fd:
            transactions = importer.extract(fd)

        self.assertEqual(len(transactions), 2)
        self.assertTrue(isinstance(transactions[1], Balance))
        self.assertEqual(transactions[1].date, datetime.date(2018, 1, 31))
        self.assertEqual(transactions[1].amount,
                         Amount(Decimal('5000.01'), currency='EUR'))
示例#18
0
def test_mismatching_dates_in_meta(tmp_file):
    tmp_file.write(
        _format(
            '''
            "Kontonummer:";"{iban} / Girokonto";

            "Von:";"01.01.2018";
            "Bis:";"31.01.2018";
            "Kontostand vom 31.01.2019:";"5.000,01 EUR";

            {header};
            "16.01.2018";"16.01.2018";"Lastschrift";"REWE Filialen Voll";"REWE SAGT DANKE.";"DE00000000000000000000";"AAAAAAAA";"-15,37";"000000000000000000    ";"0000000000000000000000";"";
            ''',  # NOQA
            dict(iban=Constants.iban.value, header=Constants.header.value),
        ))

    importer = ECImporter(Constants.iban.value,
                          'Assets:DKB:EC',
                          file_encoding='utf-8')

    with open(str(tmp_file.realpath())) as fd:
        transactions = importer.extract(fd)

    assert len(transactions) == 2
    assert isinstance(transactions[1], Balance)
    assert transactions[1].date == datetime.date(2019, 2, 1)
    assert transactions[1].amount == Amount(Decimal('5000.01'), currency='EUR')
示例#19
0
 def add_posting(section, row_name, value):
     account_pattern = account_pattern_for_row_name(row_name, section)
     txn.postings.append(
         Posting(
             account=account_pattern.format(year=year),
             units=Amount(currency=currency, number=value),
             cost=None,
             meta={config.desc_key: '%s: %s' % (section, row_name)},
             price=None,
             flag=None,
         ))
示例#20
0
def unbook_postings(postings: List[Posting]) -> Posting:
    """Unbooks a list of postings back into a single posting.

    The combined units are computed, the cost and price are left unspecified.
    """
    if len(postings) == 1:
        return postings[0]
    number = sum((posting.units.number for posting in postings), ZERO)
    return postings[0]._replace(units=Amount(
        number=number, currency=postings[0].units.currency),
                                cost=CostSpec(number_per=None,
                                              number_total=None,
                                              currency=None,
                                              date=None,
                                              label=None,
                                              merge=None))
示例#21
0
def test_get_features():
    date = datetime.date.min
    amount = Amount.from_string('3 USD')
    assert training.get_features(
        training.PredictionInput(date=date,
                                 amount=amount,
                                 source_account='Assets:Checking',
                                 key_value_pairs={
                                     'a': 'hello',
                                     'b': 'foo bar'
                                 })) == {
                                     'account:Assets:Checking': True,
                                     'a:hello': True,
                                     'b:foo': True,
                                     'b:bar': True,
                                     'b:foo bar': True
                                 }
示例#22
0
    def test_extract_no_transactions(self):
        importer = ECImporter(self.iban, 'Assets:DKB:EC')

        with open(self.filename, 'wb') as fd:
            fd.write(_format('''
                "Kontonummer:";"{iban} / Girokonto";

                "Von:";"01.01.2018";
                "Bis:";"31.01.2018";
                "Kontostand vom 31.01.2018:";"5.000,01 EUR";

                {header};
            ''', dict(iban=self.iban, header=HEADER)))

        with open(self.filename) as fd:
            transactions = importer.extract(fd)

        self.assertEqual(len(transactions), 1)
        self.assertTrue(isinstance(transactions[0], Balance))
        self.assertEqual(transactions[0].date, datetime.date(2018, 1, 31))
        self.assertEqual(transactions[0].amount,
                         Amount(Decimal('5000.01'), currency='EUR'))
示例#23
0
    def test_emits_closing_balance_directive(self):
        with open(self.filename, 'wb') as fd:
            fd.write(_format('''
                "Kontonummer:";"{iban} / Girokonto";

                "Von:";"01.01.2018";
                "Bis:";"31.01.2018";
                "Kontostand vom 31.01.2017:";"5.000,01 EUR";

                {header};
                "16.01.2018";"16.01.2018";"Lastschrift";"REWE Filialen Voll";"REWE SAGT DANKE.";"DE00000000000000000000";"AAAAAAAA";"-15,37";"000000000000000000    ";"0000000000000000000000";"";
            ''', dict(iban=self.iban, header=HEADER)))  # NOQA
        importer = ECImporter(self.iban, 'Assets:DKB:EC',
                              file_encoding='utf-8')

        with open(self.filename) as fd:
            transactions = importer.extract(fd)

        self.assertEqual(len(transactions), 2)
        self.assertTrue(isinstance(transactions[1], Balance))
        self.assertEqual(transactions[1].date, datetime.date(2018, 1, 31))
        self.assertEqual(transactions[1].amount,
                         Amount(Decimal('5000.01'), currency='EUR'))
示例#24
0
def test_extract_no_transactions(tmp_file):
    importer = ECImporter(Constants.iban.value, 'Assets:DKB:EC')

    tmp_file.write(
        _format(
            '''
            "Kontonummer:";"{iban} / Girokonto";

            "Von:";"01.01.2018";
            "Bis:";"31.01.2018";
            "Kontostand vom 31.01.2018:";"5.000,01 EUR";

            {header};
            ''',
            dict(iban=Constants.iban.value, header=Constants.header.value),
        ))

    with open(str(tmp_file.realpath())) as fd:
        transactions = importer.extract(fd)

    assert len(transactions) == 1
    assert isinstance(transactions[0], Balance)
    assert transactions[0].date == datetime.date(2018, 2, 1)
    assert transactions[0].amount == Amount(Decimal('5000.01'), currency='EUR')
    def parse(self):
        d = self.soup
        transactions = []
        last_account = ''
        date_string = d.text.split('出单日:')[1].split('日期范围')[0].strip()
        balance_date = date(int(date_string[0:4]), int(
            date_string[5:7]), int(date_string[8:10]))
        balances = d.select('[style="busi-cunkuan1.tab3.display"] .table1 tr')
        for balance in balances:
            tds = balance.select('td.dspts')
            if len(tds) == 0 or len(tds) < 3:
                continue
            account = tds[0].text.strip()
            account = last_account if account == '' else account
            last_account = account
            balance_account = get_account_by_name('ICBC_' + account)
            currency = self.change_currency(tds[3].text.strip())
            price = str(tds[5].text.strip().replace(',', ''))
            entry = Balance(
                account=balance_account,
                amount=Amount(Decimal(price), currency),
                meta={},
                tolerance='',
                diff_amount=Amount(Decimal('0'), currency),
                date=balance_date
            )
            transactions.append(entry)

        bands = d.select('[style="busi-other_detail.tab3.display"] .table1 tr')

        for band in bands:
            tds = band.select('td.dspts')
            if len(tds) == 0:
                continue
            trade_date = tds[10].text.strip()
            if trade_date == '':
                continue
            time = date(int(trade_date[0:4]), int(
                trade_date[4:6]), int(trade_date[6:8]))
            description = tds[6].text.strip()
            trade_currency = self.change_currency(tds[3].text.strip())
            trade_price = tds[7].text.strip()
            account = tds[0].text.strip()
            account = last_account if account == '' else account
            last_account = account
            print("Importing {} at {}".format(description, time))
            trade_account = get_account_by_name('ICBC_' + account)
            flag = "*"
            amount = float(trade_price.replace(',', ''))
            if account == "Unknown":
                flag = "!"
            meta = {}
            meta = data.new_metadata(
                'beancount/core/testing.beancount',
                12345,
                meta
            )
            entry = Transaction(
                meta,
                time,
                flag,
                description,
                None,
                data.EMPTY_SET,
                data.EMPTY_SET, []
            )
            data.create_simple_posting(
                entry, trade_account, trade_price, trade_currency)
            data.create_simple_posting(entry, AccountUnknown, None, None)
            if not self.deduplicate.find_duplicate(entry, -amount, None, account):
                transactions.append(entry)

        self.deduplicate.apply_beans()
        return transactions
示例#26
0
def make_takeout_import_result(purchase: Any, purchase_id: str,
                               link_prefix: str,
                               ignored_transaction_merchants_pattern: str,
                               tz_info: Optional[datetime.tzinfo],
                               html_path: str) -> Optional[ImportResult]:
    if ('creationTime' not in purchase or 'transactionMerchant' not in purchase
            or 'name' not in purchase['transactionMerchant']
            or 'usecSinceEpochUtc' not in purchase['creationTime']):
        # May be a reservation rather than a purchase
        return None
    date = datetime.datetime.fromtimestamp(
        int(purchase['creationTime']['usecSinceEpochUtc']) / 1000000,
        tz_info).date()
    payment_processor = purchase['transactionMerchant']['name']
    if (payment_processor is not None and re.fullmatch(
            ignored_transaction_merchants_pattern, payment_processor)):
        return None
    unique_merchants = set()
    merchant = None  # type: Optional[str]
    item_names = []
    for line_item in purchase['lineItem']:
        if 'provider' in line_item:
            merchant = line_item['provider']['name']
            unique_merchants.add(merchant)
        if 'purchase' not in line_item:
            continue
        line_item_purchase = line_item['purchase']
        if 'productInfo' in line_item_purchase:
            product_info = line_item_purchase['productInfo']
            text = product_info['name']
            if 'description' in line_item:
                text += '; '
                text += product_info['description']
            item_names.append(text)
    if len(unique_merchants) != 1:
        merchant = None
    inventory = SimpleInventory()
    for priceline in purchase.get('priceline', []):
        inventory += parse_amount_from_priceline(priceline['amount'])
    payee = ' - '.join(x for x in [payment_processor, merchant]
                       if x is not None)  # type: Optional[str]
    narration = '; '.join(x for x in item_names)  # type: Optional[str]
    if not narration:
        narration = payee
        payee = None
    postings = []
    if len(inventory) == 0:
        inventory['USD'] = ZERO
    for currency, units in inventory.items():
        pos_amount = Amount(round(units, 2), currency)
        neg_amount = -pos_amount
        postings.append(
            Posting(
                account=FIXME_ACCOUNT,
                units=pos_amount,
                cost=None,
                meta=None,
                price=None,
                flag=None,
            ))
        postings.append(
            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),
        ),
    )
示例#27
0
def parse_amount_from_priceline(x: Any):
    return Amount(D(x['amountMicros']) / 1000000, x['currencyCode']['code'])
示例#28
0
    def parse(self):
        d = self.soup
        transactions = []
        # balance = d.select('#fixBand16')[0].text.replace('RMB', '').strip()
        date_range = d.select('#fixBand6 div font')[0].text.strip()
        transaction_date = dateparser.parse(
            date_range.split('-')[1].split('(')[0])
        transaction_date = date(transaction_date.year, transaction_date.month,
                                transaction_date.day)
        self.date = transaction_date
        balance = '-' + \
            d.select('#fixBand7 div font')[0].text.replace(
                '¥', '').replace(',', '').strip()
        entry = Balance(account=Account招商,
                        amount=Amount(Decimal(balance), 'CNY'),
                        meta={},
                        tolerance='',
                        diff_amount=Amount(Decimal('0'), 'CNY'),
                        date=self.date)
        transactions.append(entry)

        # bands = d.select('#fixBand29 #loopBand2>table>tbody>tr')
        bands = d.select("#fixBand29 #loopBand2 > table >tr")
        for band in bands:
            tds = band.select('td #fixBand15 table table td')
            if len(tds) == 0:
                continue

            # trade_date = tds[1].text.strip()
            # if trade_date == '':
            trade_date = tds[2].text.strip()
            time = self.get_date(trade_date)
            if '支付宝' in tds[3].text.strip():
                full_descriptions = tds[3].text.strip().split('-')
                payee = full_descriptions[0]
                description = '-'.join(full_descriptions[1:])
            elif '还款' in tds[3].text.strip():
                payee = "还款"
                description = tds[3].text.strip()
            else:
                full_descriptions = tds[3].text.strip().split()
                payee = full_descriptions[0]
                description = '-'.join(full_descriptions[1:])

            trade_currency = self.change_currency(tds[6].text.strip())
            trade_price = tds[7].text.replace('\xa0', '').strip()
            real_currency = 'CNY'
            real_price = tds[4].text.replace('¥', '').replace('\xa0',
                                                              '').strip()
            print("Importing {} at {}".format(description, time))
            account = get_account_by_guess(payee, description, time)
            flag = "*"
            amount = float(real_price.replace(',', ''))
            if account == "Unknown":
                flag = "!"
            meta = {}
            meta = data.new_metadata('beancount/core/testing.beancount', 12345,
                                     meta)
            entry = Transaction(meta, time, flag, payee, description,
                                data.EMPTY_SET, data.EMPTY_SET, [])

            if real_currency == trade_currency:
                data.create_simple_posting(entry, account, trade_price,
                                           trade_currency)
            else:
                trade_amount = Amount(Decimal(trade_price), trade_currency)
                real_amount = Amount(
                    Decimal(abs(round(float(real_price), 2))) /
                    Decimal(abs(round(float(trade_price), 2))), real_currency)
                posting = Posting(account, trade_amount, None, real_amount,
                                  None, None)
                entry.postings.append(posting)

            data.create_simple_posting(entry, Account招商, None, None)
            if not self.deduplicate.find_duplicate(entry, -amount, None,
                                                   Account招商):
                transactions.append(entry)

        self.deduplicate.apply_beans()
        return transactions
示例#29
0
def evensplit(entries, options_map):
    errors = []
    for entry in filter_txns(entries):
        new_postings_map = ddict(lambda: (D(), {}))
        special_postings = []

        split_others = set(
            posting.meta.get('split_others') for posting in entry.postings
            if posting.meta)
        split_others.discard(None)

        if split_others:
            split_others = list(split_others)
            try:
                split_others, = split_others
            except ValueError:
                pass
            for i, posting in enumerate(entry.postings):
                if posting.meta is None:
                    posting = posting._replace(meta={})
                if 'split_others' not in posting.meta and 'split' not in posting.meta:
                    posting.meta['split'] = copy(split_others)
                entry.postings[i] = posting

        for i, posting in enumerate(entry.postings):
            if posting.meta and 'split' in posting.meta:
                assert posting.cost is None

                save_meta = deepcopy(posting.meta)
                targets = save_meta.pop('split')

                if is_account(targets):
                    # special case for single split to account (because halfcents)
                    assert posting.account != "Assets:Receivables"
                    amount = adiv(posting.units, D(2))
                    posting = posting._replace(units=amount)
                    newacct = targets
                    add_posting(new_postings_map, newacct, amount, save_meta)
                else:
                    if not isinstance(targets, list):
                        targets = [targets]
                    ntargets = len(targets)
                    if posting.account != "Assets:Receivables":
                        ntargets += 1
                    split_amount = posting.units.number / ntargets
                    split_amount = round(split_amount, 2)
                    amount = posting.units._replace(number=split_amount)

                    if posting.account != "Assets:Receivables":
                        remainder = posting.units.number - split_amount * len(
                            targets)
                        remainder = posting.units._replace(number=remainder)
                        posting = posting._replace(units=remainder)
                    else:
                        posting = None

                    for target in targets:
                        if is_account(target):
                            newacct = target
                            newamount = amount
                            newcost = None
                            add_posting(new_postings_map, newacct, newamount,
                                        save_meta)
                        else:
                            newacct = "Assets:Receivables"
                            newamount = Amount(D(1), "REIMB")
                            newcost = Cost(amount.number, amount.currency,
                                           entry.date, target)
                            special_postings.append(
                                Posting(newacct, newamount, newcost, None,
                                        None, save_meta))

                entry.postings[i] = posting

        entry.postings[:] = list(
            filter(lambda x: x is not None, entry.postings))
        for (acct, currency), (amt, meta) in new_postings_map.items():
            entry.postings.append(
                Posting(acct, Amount(amt, currency), None, None, None, meta))
        entry.postings.extend(special_postings)
    return entries, errors
def parse_cmb(filename):
    account = "Liabilities:CreditCard:CMB"
    transactions = []
    with open(filename, "rb") as f:
        file_bytes = f.read()
        parsed_eml = eml_parser.eml_parser.decode_email_b(file_bytes, include_raw_body=True)
        #  print(parsed_eml)
        content = parsed_eml["body"][0]["content"]
        soup = BeautifulSoup(content, "html.parser")
        print(soup)
        # balance according to bill amount
        date_range = soup.select("#fixBand38 div font")[0].text.strip()
        transaction_date = dateparser.parse(
            date_range.split('-')[1].split('(')[0])
        transaction_date = date(transaction_date.year,
                                transaction_date.month, transaction_date.day)
        start_date = dateparser.parse(date_range.split('-')[0])
        start_date = date(start_date.year,
                          start_date.month,
                          start_date.day)
        balance = '-' + \
            soup.select('#fixBand40 div font')[0].text.replace(
                '¥', '').replace(',', '').strip()

        entry = Balance(
            account=account,
            amount=Amount(Decimal(balance), 'CNY'),
            meta={},
            tolerance='',
            diff_amount=Amount(Decimal('0'), 'CNY'),
            date=transaction_date
        )
        transactions.append(entry)

        #  bands = soup.select('#fixBand29 #loopBand2>table>tbody>tr')
        bands = soup.select("#fixBand29 #loopBand2>table>tr")
        for band in bands:
            tds = band.select('td #fixBand15 table table td')
            if len(tds) == 0:
                continue
            trade_date = tds[1].text.strip()
            if trade_date == '':
                trade_date = tds[2].text.strip()
            time = get_date(start_date, trade_date)
            full_descriptions = tds[3].text.strip().split('-')
            payee = full_descriptions[0]
            description = '-'.join(full_descriptions[1:])
            trade_currency = get_currency(tds[6].text.strip())
            trade_price = tds[7].text.replace('\xa0', '').strip()
            real_currency = 'CNY'
            real_price = tds[4].text.replace(
                '¥', '').replace('\xa0', '').strip()
            print("Importing {} - {} at {}".format(payee, description, time))

            category = get_category(description, payee)
            if (payee == "自动还款" or payee == "掌上生活还款"):
                description = payee
                category = "Assets:DepositCard:CMB9843"
            flag = "*"
            amount = float(real_price.replace(',', ''))
            meta = {}
            entry = Transaction(meta, time, flag, payee,
                                description, data.EMPTY_SET, data.EMPTY_SET, [])

            if real_currency == trade_currency:
                data.create_simple_posting(
                    entry, category, trade_price, trade_currency)
            else:
                trade_amount = Amount(Decimal(trade_price), trade_currency)
                real_amount = Amount(Decimal(abs(round(float(
                    real_price), 2))) / Decimal(abs(round(float(trade_price), 2))), real_currency)
                posting = Posting(category, trade_amount,
                                    None, real_amount, None, None)
                entry.postings.append(posting)

            data.create_simple_posting(entry, account, None, None)
            transactions.append(entry)
        return transactions