예제 #1
0
 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,
                            ),
                        ])
예제 #2
0
 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,
             ),
         ])
예제 #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)
예제 #4
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))
예제 #5
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])
예제 #6
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,
                ),
            ],
        )
예제 #7
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,
                    ),
                ],
            ),
        )
예제 #8
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
예제 #9
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')),
        ),
    )
예제 #10
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),
        ),
    )
예제 #11
0
    def extract(self,
                file: _FileMemo,
                existing_entries=None) -> List[Transaction]:
        payslip = file.convert(pdftotext)

        income_tax = Posting(account='Expenses:Tax:Income',
                             units=self._find_amount('PAYE Tax', payslip),
                             cost=None,
                             price=None,
                             flag=None,
                             meta=None)

        national_insurance = Posting(account='Expenses:Tax:NationalInsurance',
                                     units=self._find_amount(
                                         'National Insurance', payslip),
                                     cost=None,
                                     price=None,
                                     flag=None,
                                     meta=None)

        student_loan = Posting(account='Liabilities:StudentFinance',
                               units=self._find_amount('Student Loan',
                                                       payslip),
                               cost=None,
                               price=None,
                               flag=None,
                               meta=None) if self.student_loan else None

        net_pay = Posting(account=f'Assets:{self.asset}',
                          units=self._find_amount('Net Pay', payslip),
                          cost=None,
                          price=None,
                          flag=None,
                          meta=None)

        salary = Posting(account=f'Income:Salary:{self.employer}',
                         units=None,
                         cost=None,
                         price=None,
                         flag=None,
                         meta=None)

        postings: List[Posting] = list(
            filter(None, [
                income_tax, national_insurance, student_loan, net_pay, salary
            ]))

        txn = Transaction(meta=new_metadata(file.name, int(1)),
                          date=find_date(payslip),
                          flag=FLAG_OKAY,
                          payee=self.employer,
                          narration='Salary',
                          tags=EMPTY_SET,
                          links=EMPTY_SET,
                          postings=postings)

        return [txn]
예제 #12
0
 def _make_trade_journal_entry(self, t: TradeConfirmation):
     txn = Transaction(meta=collections.OrderedDict(),
                       date=t.settlement_date,
                       flag='*',
                       payee=self.payee,
                       narration='Sell',
                       tags=EMPTY_SET,
                       links=EMPTY_SET,
                       postings=[])
     txn.postings.append(
         Posting(
             account=self.get_stock_account(t),
             units=-t.quantity,
             cost=CostSpec(number_per=MISSING,
                           number_total=None,
                           currency=t.gross_amount.currency,
                           date=None,
                           label=None,
                           merge=False),
             price=t.share_price,
             meta={
                 POSTING_DATE_KEY: t.trade_date,
                 TRADE_REFERENCE_NUMBER_KEY: t.reference_number
             },
             flag=None,
         ))
     txn.postings.append(
         Posting(
             account=self.capital_gains_account + ':' + t.symbol,
             units=MISSING,
             meta=None,
             cost=None,
             price=None,
             flag=None,
         ))
     txn.postings.append(
         Posting(
             account=self.fees_account,
             units=t.fees,
             cost=None,
             meta={
                 POSTING_DATE_KEY: t.trade_date,
                 TRADE_REFERENCE_NUMBER_KEY: t.reference_number,
             },
             price=None,
             flag=None,
         ))
     txn.postings.append(
         Posting(
             account=self.asset_cash_account,
             units=t.net_amount,
             cost=None,
             meta=None,
             price=None,
             flag=None,
         ))
     return txn
예제 #13
0
 def test_merged_posting_is_cleared(self):
     postings = [
         Posting(account="a", units=Amount(D(10), "USD"), cost=None,
                 price=None, flag=None, meta=None),
         Posting(account="a", units=Amount(D(10), "USD"), cost=None,
                 price=None, flag=None, meta={"merge": True}),
     ]
     source = DescriptionBasedSource(lambda s: None)
     self.assertEqual(source.is_posting_cleared(postings[0]), False)
     self.assertEqual(source.is_posting_cleared(postings[1]), True)
예제 #14
0
def test_aggregate_holdings_list_with_price():
    holdings = [
        Posting('Assets', A('10 HOOL'), None, None, None, None),
        Posting('Assets', A('14 TEST'), Cost(D('10'), 'HOOL', None, None),
                D('12'), None, None),
    ]
    assert aggregate_holdings_list(holdings) == \
        Posting('Assets', Amount(D('24'), '*'),
                Cost(D('6.25'), 'HOOL', None, None),
                D('178') / 24, None, None)
예제 #15
0
 def get_postings(self) -> List[Posting]:
     postings = [
         Posting(
             account=self.get_primary_account(),
             units=-Amount(Decimal(str(self.quantity)),
                           currency=self.symbol),
             # TODO handle cost basis by parsing cost-basis lots CSV, so we don't end
             # up getting beancount errors due to ambiguity
             cost=CostSpec(
                 number_per=MISSING,
                 number_total=None,
                 currency=MISSING,
                 date=None,
                 label=None,
                 merge=None,
             ),
             price=Amount(self.price, currency="USD"),
             flag=None,
             meta=self.get_meta(),
         ),
         Posting(
             account=self.get_other_account(),
             units=self.amount,
             cost=None,
             price=None,
             flag=None,
             meta=self.get_meta(),
         ),
     ]
     if self.action != SchwabAction.SELL_TO_OPEN:
         # too early to realize gains/losses when opening a short position
         postings.append(
             Posting(
                 account=self.get_cap_gains_account(),
                 units=MISSING,
                 cost=None,
                 price=None,
                 flag=None,
                 meta={},
             ))
     fees = self.fees
     if fees is not None:
         postings.append(
             Posting(
                 account=self.fees_account,
                 units=Amount(self.fees, currency="USD"),
                 cost=None,
                 price=None,
                 flag=None,
                 meta={},
             ))
     return postings
예제 #16
0
 def get_postings(self) -> List[Posting]:
     postings = [
         Posting(
             account=self.get_primary_account(),
             units=Amount(Decimal(str(self.quantity)),
                          currency=self.symbol),
             cost=CostSpec(
                 number_per=self.price,
                 number_total=None,
                 currency="USD",
                 date=None,
                 label=None,
                 merge=None,
             ),
             price=None,
             flag=None,
             meta=self.get_meta(),
         ),
         Posting(
             account=self.get_other_account(),
             units=self.amount,
             cost=None,
             price=None,
             flag=None,
             meta=self.get_meta(),
         ),
     ]
     if self.action == SchwabAction.BUY_TO_CLOSE:
         # need to record gains when closing a short position
         postings.append(
             Posting(
                 account=self.get_cap_gains_account(),
                 units=MISSING,
                 cost=None,
                 price=None,
                 flag=None,
                 meta={},
             ))
     fees = self.fees
     if fees is not None:
         postings.append(
             Posting(
                 account=self.fees_account,
                 units=Amount(self.fees, currency="USD"),
                 cost=None,
                 price=None,
                 flag=None,
                 meta={},
             ))
     return postings
예제 #17
0
def test_insert_entry_align(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,
    )

    insert_entry(transaction, str(samplefile), [], 50)
    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
        """)
예제 #18
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")
예제 #19
0
def get_matchable_posting_for_merge(
        x: PendingEntry) -> matching.MatchablePosting:
    if x.posting.units.number < ZERO:
        if len(x.transaction.postings) == 2:
            for p in x.transaction.postings:
                if id(p) != id(x.posting):
                    posting = p
                    break
        else:
            posting = Posting(
                account=x.posting.account,
                units=-x.posting.units,
                cost=None,
                price=None,
                flag=None,
                meta=None)
        return matching.MatchablePosting(
            posting=posting,
            weight=matching.get_posting_weight(posting),
            source_postings=[
                p for p in x.transaction.postings if id(p) != id(x.posting)
            ],
        )
    return matching.MatchablePosting(
        posting=x.posting,
        weight=matching.get_posting_weight(x.posting),
        source_postings=[x.posting],
    )
예제 #20
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],
        )
예제 #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])
    )
예제 #22
0
파일: holdings.py 프로젝트: tichai/fava
def get_final_holdings(entries,
                       included_account_types=None,
                       price_map=None,
                       date=None):
    """Get a list of holdings by account (as Postings)."""

    simple_entries = [
        entry for entry in entries if (not isinstance(entry, Transaction)
                                       or entry.flag != flags.FLAG_UNREALIZED)
    ]

    root_account = realization.realize(simple_entries)

    holdings = []

    for real_account in sorted(list(realization.iter_children(root_account)),
                               key=lambda ra: ra.account):
        account_type = account_types.get_account_type(real_account.account)
        if (included_account_types
                and account_type not in included_account_types):
            continue
        for pos in real_account.balance:
            price = None
            if pos.cost and price_map:
                base_quote = (pos.units.currency, pos.cost.currency)
                _, price = prices.get_price(price_map, base_quote, date)
            holdings.append(
                Posting(real_account.account, pos.units, pos.cost, price, None,
                        None))

    return holdings
예제 #23
0
 def test_posting_merge_first_posting_does_nothing(self):
     postings = [
         Posting(account="a",
                 units=Amount(D(10), "USD"),
                 cost=None,
                 price=None,
                 flag=None,
                 meta={"merge": True}),
         Posting(account="a",
                 units=Amount(D(20), "USD"),
                 cost=None,
                 price=None,
                 flag=None,
                 meta=None),
     ]
     result = list(unbook.group_postings_by_meta(postings))
     assert len(result) == 2
예제 #24
0
def test_aggregate_holdings_by():
    assert aggregate_holdings_list([]) is None

    holdings = [
        Posting('Assets', A('10 HOOL'), None, None, None, None),
        Posting('Assets:Test', A('14 HOL'), None, None, None, None),
    ]
    assert aggregate_holdings_by(holdings, 'account') == holdings
    assert aggregate_holdings_by(holdings, 'currency') == holdings
    assert aggregate_holdings_by(holdings, 'invalid') == holdings

    holdings = [
        Posting('Assets', A('10 HOOL'), None, None, None, None),
        Posting('Assets', A('14 HOOL'), None, None, None, None),
    ]
    assert aggregate_holdings_by(holdings, 'account') == \
        [aggregate_holdings_list(holdings)]
예제 #25
0
def test_serialise_posting(pos, amount):
    pos = Posting('Assets:ETrade:Cash', *pos)
    json = {
        'account': 'Assets:ETrade:Cash',
        'amount': amount,
    }
    assert loads(dumps(serialise(pos))) == json
    assert deserialise_posting(json) == pos
예제 #26
0
def test_deserialise_posting(
    amount_cost_price: Tuple[Amount, Optional[CostSpec], Amount],
    amount_string: str,
) -> None:
    """Roundtrip is not possible here due to total price or calculation."""
    pos = Posting("Assets", *amount_cost_price, None, None)
    json = {"account": "Assets", "amount": amount_string}
    assert deserialise_posting(json) == pos
예제 #27
0
def test_serialise_posting(
    amount_cost_price: Tuple[Amount, Optional[CostSpec], Amount],
    amount_string: str,
) -> None:
    pos = Posting("Assets", *amount_cost_price, None, None)
    json = {"account": "Assets", "amount": amount_string}
    assert loads(dumps(serialise(pos))) == json
    assert deserialise_posting(json) == pos
예제 #28
0
 def add_posting(self, user, item_id):
     assert(user in self.get_users())
     account = "Assets:Receivable:{}".format(user)
     if self.transaction is None:
         self.transaction = Transaction(
             flag="*",
             payee="kitty",
             narration="cococount purchase",
             tags=None,
             postings=[Posting("Assets:Items", number.MISSING, None, None, None, {})],
             links=None,
             meta={},
             date=datetime.date.today())
     price = self.latest_price(item_id)
     self.transaction.postings.append(
             Posting(account, price.amount, None, None, None, {}))
     self.log.debug(format_entry(self.transaction))
예제 #29
0
파일: utils.py 프로젝트: SEIAROTg/autobean
def get_residual_postings(residual: Inventory, account_rounding: str):
    return [
        Posting(account=account_rounding,
                units=-position.units,
                cost=position.cost,
                price=None,
                flag=None,
                meta={}) for position in residual
    ]
예제 #30
0
파일: utils.py 프로젝트: SEIAROTg/autobean
def posting_distrib(posting: Posting, weight: Decimal, total_weight: Decimal):
    units = amount_distrib(posting.units, weight, total_weight)
    cost = posting.cost
    if isinstance(cost, CostSpec):
        cost = costspec_distrib(cost, weight, total_weight)
    return posting._replace(
        units=units,
        cost=cost,
    )