ledger: Add fund ledger report type.
See the FundLedgerODS docstring for details.
This commit is contained in:
parent
4188dc6a64
commit
55b347271c
2 changed files with 139 additions and 89 deletions
|
@ -46,6 +46,7 @@ from typing import (
|
||||||
Any,
|
Any,
|
||||||
Callable,
|
Callable,
|
||||||
Dict,
|
Dict,
|
||||||
|
Hashable,
|
||||||
Iterable,
|
Iterable,
|
||||||
Iterator,
|
Iterator,
|
||||||
List,
|
List,
|
||||||
|
@ -55,7 +56,9 @@ from typing import (
|
||||||
Set,
|
Set,
|
||||||
TextIO,
|
TextIO,
|
||||||
Tuple,
|
Tuple,
|
||||||
|
Type,
|
||||||
Union,
|
Union,
|
||||||
|
cast,
|
||||||
)
|
)
|
||||||
from ..beancount_types import (
|
from ..beancount_types import (
|
||||||
Transaction,
|
Transaction,
|
||||||
|
@ -64,6 +67,7 @@ from ..beancount_types import (
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
import odf.table # type:ignore[import]
|
import odf.table # type:ignore[import]
|
||||||
|
import odf.text # type:ignore[import]
|
||||||
|
|
||||||
from beancount.core import data as bc_data
|
from beancount.core import data as bc_data
|
||||||
from beancount.parser import printer as bc_printer
|
from beancount.parser import printer as bc_printer
|
||||||
|
@ -366,40 +370,6 @@ class LedgerODS(core.BaseODS[data.Posting, None]):
|
||||||
classification_cell,
|
classification_cell,
|
||||||
)
|
)
|
||||||
|
|
||||||
def write_balance_sheet(self) -> None:
|
|
||||||
self.use_sheet("Balance")
|
|
||||||
self.sheet.addElement(odf.table.TableColumn(stylename=self.column_style(3)))
|
|
||||||
self.sheet.addElement(odf.table.TableColumn(stylename=self.column_style(1.5)))
|
|
||||||
self.add_row(
|
|
||||||
self.string_cell("Account", stylename=self.style_bold),
|
|
||||||
self.string_cell("Balance", stylename=self.style_bold),
|
|
||||||
)
|
|
||||||
self.lock_first_row()
|
|
||||||
self.add_row()
|
|
||||||
self.add_row(self.string_cell(
|
|
||||||
f"Ledger From {self.date_range.start.isoformat()}"
|
|
||||||
f" To {self.date_range.stop.isoformat()}",
|
|
||||||
stylename=self.merge_styles(self.style_centertext, self.style_bold),
|
|
||||||
numbercolumnsspanned=2,
|
|
||||||
))
|
|
||||||
self.add_row()
|
|
||||||
for account, balance in core.account_balances(self.account_groups):
|
|
||||||
if account is core.OPENING_BALANCE_NAME:
|
|
||||||
text = f"Balance as of {self.date_range.start.isoformat()}"
|
|
||||||
style = self.merge_styles(self.style_bold, self.style_endtext)
|
|
||||||
elif account is core.ENDING_BALANCE_NAME:
|
|
||||||
text = f"Balance as of {self.date_range.stop.isoformat()}"
|
|
||||||
style = self.merge_styles(
|
|
||||||
self.style_bottomline, self.style_bold, self.style_endtext,
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
text = account
|
|
||||||
style = self.style_endtext
|
|
||||||
self.add_row(
|
|
||||||
self.string_cell(text, stylename=style),
|
|
||||||
self.balance_cell(-balance, stylename=style),
|
|
||||||
)
|
|
||||||
|
|
||||||
def _account_tally(self, account: data.Account) -> int:
|
def _account_tally(self, account: data.Account) -> int:
|
||||||
return len(self.account_groups[account])
|
return len(self.account_groups[account])
|
||||||
|
|
||||||
|
@ -430,7 +400,7 @@ class LedgerODS(core.BaseODS[data.Posting, None]):
|
||||||
))
|
))
|
||||||
for empty_acct in self.accounts.difference(self.account_groups):
|
for empty_acct in self.accounts.difference(self.account_groups):
|
||||||
self.account_groups[empty_acct] = related_cls()
|
self.account_groups[empty_acct] = related_cls()
|
||||||
self.write_balance_sheet()
|
self.start_spreadsheet()
|
||||||
tally_by_account_iter = (
|
tally_by_account_iter = (
|
||||||
(account, self._account_tally(account))
|
(account, self._account_tally(account))
|
||||||
for account in self.accounts
|
for account in self.accounts
|
||||||
|
@ -472,29 +442,97 @@ class LedgerODS(core.BaseODS[data.Posting, None]):
|
||||||
self.start_sheet(sheet_names[index])
|
self.start_sheet(sheet_names[index])
|
||||||
|
|
||||||
|
|
||||||
class TransactionFilter(enum.IntFlag):
|
class FundLedgerODS(LedgerODS):
|
||||||
ZERO = 1
|
"""Streamlined ledger report for a specific project fund
|
||||||
CREDIT = 2
|
|
||||||
DEBIT = 4
|
|
||||||
ALL = ZERO | CREDIT | DEBIT
|
|
||||||
|
|
||||||
@classmethod
|
This report is more appropriate to share with people who are interested in
|
||||||
def from_arg(cls, s: str) -> 'TransactionFilter':
|
the project fund. Differences from the main ledger report:
|
||||||
try:
|
|
||||||
return cls[s.upper()]
|
* It adds a cover sheet with a high level overview of the fund balance.
|
||||||
except KeyError:
|
* It only reports accounts that belong to funds.
|
||||||
raise ValueError(f"unknown transaction filter {s!r}")
|
* It does not use any links, since recipients likely won't have access to
|
||||||
|
be able to follow them. (It does include RT IDs as a reference point to
|
||||||
|
correspond about specific transactions.)
|
||||||
|
"""
|
||||||
|
ACCOUNT_COLUMNS = collections.OrderedDict([
|
||||||
|
('Income', ['program', 'rt-id', 'income-type', 'memo']),
|
||||||
|
('Expenses', ['program', 'rt-id', 'expense-type']),
|
||||||
|
('Equity', ['program', 'rt-id']),
|
||||||
|
('Assets:Receivable', ['program', 'rt-id']),
|
||||||
|
('Liabilities:Payable', ['program', 'rt-id']),
|
||||||
|
('Assets', ['rt-id']),
|
||||||
|
('Liabilities', ['rt-id']),
|
||||||
|
])
|
||||||
|
|
||||||
|
def multilink_cell(self, links: Iterable[core.LinkType], **attrs: Any) -> odf.table.TableCell:
|
||||||
|
source = super().multilink_cell(links, **attrs)
|
||||||
|
return self.multiline_cell(
|
||||||
|
''.join(child.data for child in link.childNodes)
|
||||||
|
for link in source.getElementsByType(odf.text.A)
|
||||||
|
)
|
||||||
|
|
||||||
|
def start_spreadsheet(self) -> None:
|
||||||
|
self.use_sheet("Balance")
|
||||||
|
self.sheet.addElement(odf.table.TableColumn(stylename=self.column_style(3)))
|
||||||
|
self.sheet.addElement(odf.table.TableColumn(stylename=self.column_style(1.5)))
|
||||||
|
self.add_row(
|
||||||
|
self.string_cell("Account", stylename=self.style_bold),
|
||||||
|
self.string_cell("Balance", stylename=self.style_bold),
|
||||||
|
)
|
||||||
|
self.lock_first_row()
|
||||||
|
self.add_row()
|
||||||
|
self.add_row(self.string_cell(
|
||||||
|
f"Ledger From {self.date_range.start.isoformat()}"
|
||||||
|
f" To {self.date_range.stop.isoformat()}",
|
||||||
|
stylename=self.merge_styles(self.style_centertext, self.style_bold),
|
||||||
|
numbercolumnsspanned=2,
|
||||||
|
))
|
||||||
|
self.add_row()
|
||||||
|
for account, balance in core.account_balances(self.account_groups):
|
||||||
|
if account is core.OPENING_BALANCE_NAME:
|
||||||
|
text = f"Balance as of {self.date_range.start.isoformat()}"
|
||||||
|
style = self.merge_styles(self.style_bold, self.style_endtext)
|
||||||
|
elif account is core.ENDING_BALANCE_NAME:
|
||||||
|
text = f"Balance as of {self.date_range.stop.isoformat()}"
|
||||||
|
style = self.merge_styles(
|
||||||
|
self.style_bottomline, self.style_bold, self.style_endtext,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
text = account
|
||||||
|
style = self.style_endtext
|
||||||
|
self.add_row(
|
||||||
|
self.string_cell(text, stylename=style),
|
||||||
|
self.balance_cell(-balance, stylename=style),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class ReportType(enum.IntFlag):
|
||||||
|
ZERO_TRANSACTIONS = enum.auto()
|
||||||
|
ZERO_TXNS = ZERO_TRANSACTIONS
|
||||||
|
CREDIT_TRANSACTIONS = enum.auto()
|
||||||
|
CREDIT_TXNS = CREDIT_TRANSACTIONS
|
||||||
|
DEBIT_TRANSACTIONS = enum.auto()
|
||||||
|
DEBIT_TXNS = DEBIT_TRANSACTIONS
|
||||||
|
ALL_TRANSACTIONS = ZERO_TRANSACTIONS | CREDIT_TRANSACTIONS | DEBIT_TRANSACTIONS
|
||||||
|
ALL_TXNS = ALL_TRANSACTIONS
|
||||||
|
FULL_LEDGER = enum.auto()
|
||||||
|
FUND_LEDGER = enum.auto()
|
||||||
|
PROJECT_LEDGER = FUND_LEDGER
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def post_flag(cls, post: data.Posting) -> int:
|
def post_flag(cls, post: data.Posting) -> int:
|
||||||
norm_func = core.normalize_amount_func(post.account)
|
norm_func = core.normalize_amount_func(post.account)
|
||||||
number = norm_func(post.units.number)
|
number = norm_func(post.units.number)
|
||||||
if not number:
|
if not number:
|
||||||
return cls.ZERO
|
return cls.ZERO_TRANSACTIONS
|
||||||
elif number > 0:
|
elif number > 0:
|
||||||
return cls.CREDIT
|
return cls.CREDIT_TRANSACTIONS
|
||||||
else:
|
else:
|
||||||
return cls.DEBIT
|
return cls.DEBIT_TRANSACTIONS
|
||||||
|
|
||||||
|
def _choices_sortkey(self) -> Hashable:
|
||||||
|
subtype, _, maintype = self.name.partition('_')
|
||||||
|
return (maintype, subtype)
|
||||||
|
|
||||||
|
|
||||||
class TransactionODS(LedgerODS):
|
class TransactionODS(LedgerODS):
|
||||||
|
@ -527,7 +565,7 @@ class TransactionODS(LedgerODS):
|
||||||
sheet_size: Optional[int]=None,
|
sheet_size: Optional[int]=None,
|
||||||
totals_with_entries: Optional[Sequence[str]]=None,
|
totals_with_entries: Optional[Sequence[str]]=None,
|
||||||
totals_without_entries: Optional[Sequence[str]]=None,
|
totals_without_entries: Optional[Sequence[str]]=None,
|
||||||
txn_filter: int=TransactionFilter.ALL,
|
txn_filter: int=ReportType.ALL_TRANSACTIONS,
|
||||||
) -> None:
|
) -> None:
|
||||||
super().__init__(
|
super().__init__(
|
||||||
start_date,
|
start_date,
|
||||||
|
@ -539,9 +577,9 @@ class TransactionODS(LedgerODS):
|
||||||
totals_without_entries,
|
totals_without_entries,
|
||||||
)
|
)
|
||||||
self.txn_filter = txn_filter
|
self.txn_filter = txn_filter
|
||||||
if self.txn_filter == TransactionFilter.CREDIT:
|
if self.txn_filter == ReportType.CREDIT_TRANSACTIONS:
|
||||||
self.report_name = "Receipts"
|
self.report_name = "Receipts"
|
||||||
elif self.txn_filter == TransactionFilter.DEBIT:
|
elif self.txn_filter == ReportType.DEBIT_TRANSACTIONS:
|
||||||
self.report_name = "Disbursements"
|
self.report_name = "Disbursements"
|
||||||
else:
|
else:
|
||||||
self.report_name = "Transactions"
|
self.report_name = "Transactions"
|
||||||
|
@ -551,18 +589,15 @@ class TransactionODS(LedgerODS):
|
||||||
for post in postings:
|
for post in postings:
|
||||||
txn = post.meta.txn
|
txn = post.meta.txn
|
||||||
if (txn is not last_txn
|
if (txn is not last_txn
|
||||||
and TransactionFilter.post_flag(post) & self.txn_filter):
|
and ReportType.post_flag(post) & self.txn_filter):
|
||||||
yield txn
|
yield txn
|
||||||
last_txn = txn
|
last_txn = txn
|
||||||
|
|
||||||
def metadata_columns_for(self, sheet_name: str) -> Sequence[str]:
|
def metadata_columns_for(self, sheet_name: str) -> Sequence[str]:
|
||||||
return self.METADATA_COLUMNS
|
return self.METADATA_COLUMNS
|
||||||
|
|
||||||
def write_balance_sheet(self) -> None:
|
|
||||||
return
|
|
||||||
|
|
||||||
def _report_section_balance(self, key: data.Account, date_key: str) -> None:
|
def _report_section_balance(self, key: data.Account, date_key: str) -> None:
|
||||||
if self.txn_filter == TransactionFilter.ALL:
|
if self.txn_filter == ReportType.ALL_TRANSACTIONS:
|
||||||
super()._report_section_balance(key, date_key)
|
super()._report_section_balance(key, date_key)
|
||||||
elif date_key == 'stop':
|
elif date_key == 'stop':
|
||||||
balance = core.Balance(
|
balance = core.Balance(
|
||||||
|
@ -638,7 +673,7 @@ class CashReportAction(argparse.Action):
|
||||||
values: Union[Sequence[Any], str, None]=None,
|
values: Union[Sequence[Any], str, None]=None,
|
||||||
option_string: Optional[str]=None,
|
option_string: Optional[str]=None,
|
||||||
) -> None:
|
) -> None:
|
||||||
namespace.txn_filter = self.const
|
namespace.report_type = self.const
|
||||||
if namespace.accounts is None:
|
if namespace.accounts is None:
|
||||||
namespace.accounts = []
|
namespace.accounts = []
|
||||||
namespace.accounts.append('Assets:PayPal')
|
namespace.accounts.append('Assets:PayPal')
|
||||||
|
@ -653,7 +688,7 @@ def parse_arguments(arglist: Optional[Sequence[str]]=None) -> argparse.Namespace
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--disbursements',
|
'--disbursements',
|
||||||
action=CashReportAction,
|
action=CashReportAction,
|
||||||
const=TransactionFilter.DEBIT,
|
const=ReportType.DEBIT_TRANSACTIONS,
|
||||||
nargs=0,
|
nargs=0,
|
||||||
help="""Shortcut to set all the necessary options to generate a cash
|
help="""Shortcut to set all the necessary options to generate a cash
|
||||||
disbursements report.
|
disbursements report.
|
||||||
|
@ -661,7 +696,7 @@ disbursements report.
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--receipts',
|
'--receipts',
|
||||||
action=CashReportAction,
|
action=CashReportAction,
|
||||||
const=TransactionFilter.CREDIT,
|
const=ReportType.CREDIT_TRANSACTIONS,
|
||||||
nargs=0,
|
nargs=0,
|
||||||
help="""Shortcut to set all the necessary options to generate a cash
|
help="""Shortcut to set all the necessary options to generate a cash
|
||||||
receipts report.
|
receipts report.
|
||||||
|
@ -683,15 +718,23 @@ The default is one year ago.
|
||||||
The default is a year after the start date, or 30 days from today if the start
|
The default is a year after the start date, or 30 days from today if the start
|
||||||
date was also not specified.
|
date was also not specified.
|
||||||
""")
|
""")
|
||||||
parser.add_argument(
|
report_type = cliutil.EnumArgument(ReportType)
|
||||||
'--transactions', '-t',
|
report_type_default = ReportType.FULL_LEDGER
|
||||||
dest='txn_filter',
|
report_type_arg = parser.add_argument(
|
||||||
|
'--report-type', '-t',
|
||||||
metavar='TYPE',
|
metavar='TYPE',
|
||||||
type=TransactionFilter.from_arg,
|
type=report_type.enum_type,
|
||||||
help="""Report whole transactions rather than individual postings.
|
default=report_type_default,
|
||||||
The type argument selects which type of transactions to report. Choices are
|
help=f"""The type of report to generate. Choices are
|
||||||
credit, debit, or all.
|
{report_type.choices_str()}. Default is {report_type_default.name.lower()!r}.
|
||||||
""")
|
""")
|
||||||
|
# --transactions got merged into --report-type; this is backwards compatibility.
|
||||||
|
parser.add_argument(
|
||||||
|
'--transactions',
|
||||||
|
dest=report_type_arg.dest,
|
||||||
|
type=cast(Callable, report_type_arg.type),
|
||||||
|
help=argparse.SUPPRESS,
|
||||||
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--account', '-a',
|
'--account', '-a',
|
||||||
dest='accounts',
|
dest='accounts',
|
||||||
|
@ -814,18 +857,10 @@ def main(arglist: Optional[Sequence[str]]=None,
|
||||||
rt_wrapper = config.rt_wrapper()
|
rt_wrapper = config.rt_wrapper()
|
||||||
if rt_wrapper is None:
|
if rt_wrapper is None:
|
||||||
logger.warning("could not initialize RT client; spreadsheet links will be broken")
|
logger.warning("could not initialize RT client; spreadsheet links will be broken")
|
||||||
|
report: LedgerODS
|
||||||
|
report_cls: Type[LedgerODS]
|
||||||
try:
|
try:
|
||||||
if args.txn_filter is None:
|
if args.report_type & ReportType.ALL_TRANSACTIONS:
|
||||||
report = LedgerODS(
|
|
||||||
args.start_date,
|
|
||||||
args.stop_date,
|
|
||||||
args.accounts,
|
|
||||||
rt_wrapper,
|
|
||||||
args.sheet_size,
|
|
||||||
args.show_totals,
|
|
||||||
args.add_totals,
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
report = TransactionODS(
|
report = TransactionODS(
|
||||||
args.start_date,
|
args.start_date,
|
||||||
args.stop_date,
|
args.stop_date,
|
||||||
|
@ -834,7 +869,21 @@ def main(arglist: Optional[Sequence[str]]=None,
|
||||||
args.sheet_size,
|
args.sheet_size,
|
||||||
args.show_totals,
|
args.show_totals,
|
||||||
args.add_totals,
|
args.add_totals,
|
||||||
args.txn_filter,
|
args.report_type,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
if args.report_type is ReportType.FUND_LEDGER:
|
||||||
|
report_cls = FundLedgerODS
|
||||||
|
else:
|
||||||
|
report_cls = LedgerODS
|
||||||
|
report = report_cls(
|
||||||
|
args.start_date,
|
||||||
|
args.stop_date,
|
||||||
|
args.accounts,
|
||||||
|
rt_wrapper,
|
||||||
|
args.sheet_size,
|
||||||
|
args.show_totals,
|
||||||
|
args.add_totals,
|
||||||
)
|
)
|
||||||
except ValueError as error:
|
except ValueError as error:
|
||||||
logger.error("%s: %r", *error.args)
|
logger.error("%s: %r", *error.args)
|
||||||
|
|
|
@ -32,7 +32,6 @@ Acct = data.Account
|
||||||
|
|
||||||
_ledger_load = bc_loader.load_file(testutil.test_path('books/ledger.beancount'))
|
_ledger_load = bc_loader.load_file(testutil.test_path('books/ledger.beancount'))
|
||||||
DEFAULT_REPORT_SHEETS = [
|
DEFAULT_REPORT_SHEETS = [
|
||||||
'Balance',
|
|
||||||
'Income',
|
'Income',
|
||||||
'Expenses:Payroll',
|
'Expenses:Payroll',
|
||||||
'Expenses',
|
'Expenses',
|
||||||
|
@ -44,8 +43,9 @@ DEFAULT_REPORT_SHEETS = [
|
||||||
'Liabilities',
|
'Liabilities',
|
||||||
]
|
]
|
||||||
PROJECT_REPORT_SHEETS = [
|
PROJECT_REPORT_SHEETS = [
|
||||||
*DEFAULT_REPORT_SHEETS[:2],
|
'Balance',
|
||||||
*DEFAULT_REPORT_SHEETS[3:6],
|
'Income',
|
||||||
|
*DEFAULT_REPORT_SHEETS[2:5],
|
||||||
'Assets:Prepaid',
|
'Assets:Prepaid',
|
||||||
'Liabilities:UnearnedIncome',
|
'Liabilities:UnearnedIncome',
|
||||||
'Liabilities:Payable',
|
'Liabilities:Payable',
|
||||||
|
@ -60,7 +60,7 @@ STOP_DATE = datetime.date(2020, 3, 1)
|
||||||
REPORT_KWARGS = [
|
REPORT_KWARGS = [
|
||||||
{'report_class': ledger.LedgerODS},
|
{'report_class': ledger.LedgerODS},
|
||||||
*({'report_class': ledger.TransactionODS, 'txn_filter': flags}
|
*({'report_class': ledger.TransactionODS, 'txn_filter': flags}
|
||||||
for flags in ledger.TransactionFilter),
|
for flags in ledger.ReportType if flags & ledger.ReportType.ALL_TRANSACTIONS),
|
||||||
]
|
]
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
|
@ -189,7 +189,7 @@ class ExpectedPostings(core.RelatedPostings):
|
||||||
period_bal = core.MutableBalance()
|
period_bal = core.MutableBalance()
|
||||||
rows = self.find_section(ods, account)
|
rows = self.find_section(ods, account)
|
||||||
if (expect_totals
|
if (expect_totals
|
||||||
and txn_filter == ledger.TransactionFilter.ALL
|
and txn_filter == ledger.ReportType.ALL_TRANSACTIONS
|
||||||
and account.is_under('Assets', 'Liabilities')):
|
and account.is_under('Assets', 'Liabilities')):
|
||||||
opening_row = testutil.ODSCell.from_row(next(rows))
|
opening_row = testutil.ODSCell.from_row(next(rows))
|
||||||
assert opening_row[0].value == start_date
|
assert opening_row[0].value == start_date
|
||||||
|
@ -198,7 +198,7 @@ class ExpectedPostings(core.RelatedPostings):
|
||||||
last_txn = None
|
last_txn = None
|
||||||
for post in expect_posts:
|
for post in expect_posts:
|
||||||
txn = post.meta.txn
|
txn = post.meta.txn
|
||||||
post_flag = ledger.TransactionFilter.post_flag(post)
|
post_flag = ledger.ReportType.post_flag(post)
|
||||||
if txn is last_txn or (not txn_filter & post_flag):
|
if txn is last_txn or (not txn_filter & post_flag):
|
||||||
continue
|
continue
|
||||||
last_txn = txn
|
last_txn = txn
|
||||||
|
@ -462,7 +462,7 @@ def test_main_account_limit(ledger_entries, acct_arg):
|
||||||
assert not errors.getvalue()
|
assert not errors.getvalue()
|
||||||
assert retcode == 0
|
assert retcode == 0
|
||||||
ods = odf.opendocument.load(output)
|
ods = odf.opendocument.load(output)
|
||||||
assert get_sheet_names(ods) == ['Balance', 'Liabilities']
|
assert get_sheet_names(ods) == ['Liabilities']
|
||||||
postings = data.Posting.from_entries(ledger_entries)
|
postings = data.Posting.from_entries(ledger_entries)
|
||||||
for account, expected in ExpectedPostings.group_by_account(postings):
|
for account, expected in ExpectedPostings.group_by_account(postings):
|
||||||
if account == 'Liabilities:UnearnedIncome':
|
if account == 'Liabilities:UnearnedIncome':
|
||||||
|
@ -485,7 +485,7 @@ def test_main_account_classification_splits_hierarchy(ledger_entries):
|
||||||
assert not errors.getvalue()
|
assert not errors.getvalue()
|
||||||
assert retcode == 0
|
assert retcode == 0
|
||||||
ods = odf.opendocument.load(output)
|
ods = odf.opendocument.load(output)
|
||||||
assert get_sheet_names(ods) == ['Balance', 'Assets']
|
assert get_sheet_names(ods) == ['Assets']
|
||||||
postings = data.Posting.from_entries(ledger_entries)
|
postings = data.Posting.from_entries(ledger_entries)
|
||||||
for account, expected in ExpectedPostings.group_by_account(postings):
|
for account, expected in ExpectedPostings.group_by_account(postings):
|
||||||
should_find = (account == 'Assets:Checking' or account == 'Assets:PayPal')
|
should_find = (account == 'Assets:Checking' or account == 'Assets:PayPal')
|
||||||
|
@ -509,6 +509,7 @@ def test_main_project_report(ledger_entries, project, start_date, stop_date):
|
||||||
retcode, output, errors = run_main([
|
retcode, output, errors = run_main([
|
||||||
f'--begin={start_date.isoformat()}',
|
f'--begin={start_date.isoformat()}',
|
||||||
f'--end={stop_date.isoformat()}',
|
f'--end={stop_date.isoformat()}',
|
||||||
|
'--report-type=fund_ledger',
|
||||||
project,
|
project,
|
||||||
])
|
])
|
||||||
assert not errors.getvalue()
|
assert not errors.getvalue()
|
||||||
|
@ -528,9 +529,9 @@ def test_main_project_report(ledger_entries, project, start_date, stop_date):
|
||||||
])
|
])
|
||||||
def test_main_cash_report(ledger_entries, flag):
|
def test_main_cash_report(ledger_entries, flag):
|
||||||
if flag == '--receipts':
|
if flag == '--receipts':
|
||||||
txn_filter = ledger.TransactionFilter.CREDIT
|
txn_filter = ledger.ReportType.CREDIT_TRANSACTIONS
|
||||||
else:
|
else:
|
||||||
txn_filter = ledger.TransactionFilter.DEBIT
|
txn_filter = ledger.ReportType.DEBIT_TRANSACTIONS
|
||||||
retcode, output, errors = run_main([
|
retcode, output, errors = run_main([
|
||||||
flag,
|
flag,
|
||||||
'-b', START_DATE.isoformat(),
|
'-b', START_DATE.isoformat(),
|
||||||
|
|
Loading…
Reference in a new issue