balance_sheet: Support arbitrary date ranges.
This lets you do year-over-year comparisons of smaller ranges of time, like a quarter.
This commit is contained in:
parent
9e7df0b020
commit
a0ff9e6834
2 changed files with 33 additions and 19 deletions
|
@ -71,9 +71,11 @@ class Fund(enum.IntFlag):
|
||||||
class Period(enum.IntFlag):
|
class Period(enum.IntFlag):
|
||||||
OPENING = enum.auto()
|
OPENING = enum.auto()
|
||||||
PRIOR = enum.auto()
|
PRIOR = enum.auto()
|
||||||
|
MIDDLE = enum.auto()
|
||||||
PERIOD = enum.auto()
|
PERIOD = enum.auto()
|
||||||
BEFORE_PERIOD = OPENING | PRIOR
|
THRU_PRIOR = OPENING | PRIOR
|
||||||
ANY = OPENING | PRIOR | PERIOD
|
THRU_MIDDLE = THRU_PRIOR | MIDDLE
|
||||||
|
ANY = THRU_MIDDLE | PERIOD
|
||||||
|
|
||||||
|
|
||||||
class BalanceKey(NamedTuple):
|
class BalanceKey(NamedTuple):
|
||||||
|
@ -92,24 +94,35 @@ class Balances:
|
||||||
fund_key: str='project',
|
fund_key: str='project',
|
||||||
unrestricted_fund_value: str='Conservancy',
|
unrestricted_fund_value: str='Conservancy',
|
||||||
) -> None:
|
) -> None:
|
||||||
self.prior_range = ranges.DateRange(
|
year_diff = (stop_date - start_date).days // 365
|
||||||
cliutil.diff_year(start_date, -1),
|
if year_diff == 0:
|
||||||
cliutil.diff_year(stop_date, -1),
|
self.prior_range = ranges.DateRange(
|
||||||
)
|
cliutil.diff_year(start_date, -1),
|
||||||
assert self.prior_range.stop <= start_date
|
cliutil.diff_year(stop_date, -1),
|
||||||
|
)
|
||||||
|
self.period_desc = "Period"
|
||||||
|
else:
|
||||||
|
self.prior_range = ranges.DateRange(
|
||||||
|
cliutil.diff_year(start_date, -year_diff),
|
||||||
|
start_date,
|
||||||
|
)
|
||||||
|
self.period_desc = f"Year{'s' if year_diff > 1 else ''}"
|
||||||
|
self.middle_range = ranges.DateRange(self.prior_range.stop, start_date)
|
||||||
self.period_range = ranges.DateRange(start_date, stop_date)
|
self.period_range = ranges.DateRange(start_date, stop_date)
|
||||||
self.balances: Mapping[BalanceKey, core.MutableBalance] \
|
self.balances: Mapping[BalanceKey, core.MutableBalance] \
|
||||||
= collections.defaultdict(core.MutableBalance)
|
= collections.defaultdict(core.MutableBalance)
|
||||||
for post in postings:
|
for post in postings:
|
||||||
post_date = post.meta.date
|
post_date = post.meta.date
|
||||||
if post_date in self.period_range:
|
if post_date >= stop_date:
|
||||||
|
continue
|
||||||
|
elif post_date in self.period_range:
|
||||||
period = Period.PERIOD
|
period = Period.PERIOD
|
||||||
|
elif post_date in self.middle_range:
|
||||||
|
period = Period.MIDDLE
|
||||||
elif post_date in self.prior_range:
|
elif post_date in self.prior_range:
|
||||||
period = Period.PRIOR
|
period = Period.PRIOR
|
||||||
elif post_date < self.prior_range.start:
|
|
||||||
period = Period.OPENING
|
|
||||||
else:
|
else:
|
||||||
continue
|
period = Period.OPENING
|
||||||
if post.account == 'Expenses:CurrencyConversion':
|
if post.account == 'Expenses:CurrencyConversion':
|
||||||
account = data.Account('Income:CurrencyConversion')
|
account = data.Account('Income:CurrencyConversion')
|
||||||
else:
|
else:
|
||||||
|
@ -214,6 +227,7 @@ class Report(core.BaseODS[Sequence[None], None]):
|
||||||
) -> None:
|
) -> None:
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.balances = balances
|
self.balances = balances
|
||||||
|
self.period_desc = balances.period_desc
|
||||||
self.date_fmt = date_fmt
|
self.date_fmt = date_fmt
|
||||||
one_day = datetime.timedelta(days=1)
|
one_day = datetime.timedelta(days=1)
|
||||||
date = balances.period_range.stop - one_day
|
date = balances.period_range.stop - one_day
|
||||||
|
@ -359,7 +373,7 @@ class Report(core.BaseODS[Sequence[None], None]):
|
||||||
self.start_sheet("Financial Position")
|
self.start_sheet("Financial Position")
|
||||||
balance_kwargs: Sequence[KWArgs] = [
|
balance_kwargs: Sequence[KWArgs] = [
|
||||||
{'period': Period.ANY},
|
{'period': Period.ANY},
|
||||||
{'period': Period.BEFORE_PERIOD},
|
{'period': Period.THRU_PRIOR},
|
||||||
]
|
]
|
||||||
|
|
||||||
asset_totals = self.write_classifications_by_account('Assets', balance_kwargs)
|
asset_totals = self.write_classifications_by_account('Assets', balance_kwargs)
|
||||||
|
@ -400,7 +414,7 @@ class Report(core.BaseODS[Sequence[None], None]):
|
||||||
"Activities",
|
"Activities",
|
||||||
["Without Donor", "Restrictions"],
|
["Without Donor", "Restrictions"],
|
||||||
["With Donor", "Restrictions"],
|
["With Donor", "Restrictions"],
|
||||||
totals_prefix=["Total Year Ended"],
|
totals_prefix=[f"Total {self.period_desc} Ended"],
|
||||||
)
|
)
|
||||||
bal_kwargs: Sequence[Dict[str, Any]] = [
|
bal_kwargs: Sequence[Dict[str, Any]] = [
|
||||||
{'period': Period.PERIOD, 'fund': Fund.UNRESTRICTED},
|
{'period': Period.PERIOD, 'fund': Fund.UNRESTRICTED},
|
||||||
|
@ -482,7 +496,7 @@ class Report(core.BaseODS[Sequence[None], None]):
|
||||||
|
|
||||||
for kwargs in bal_kwargs:
|
for kwargs in bal_kwargs:
|
||||||
if kwargs['period'] is Period.PERIOD:
|
if kwargs['period'] is Period.PERIOD:
|
||||||
kwargs['period'] = Period.BEFORE_PERIOD
|
kwargs['period'] = Period.THRU_MIDDLE
|
||||||
else:
|
else:
|
||||||
kwargs['period'] = Period.OPENING
|
kwargs['period'] = Period.OPENING
|
||||||
equity_totals = [
|
equity_totals = [
|
||||||
|
@ -502,7 +516,7 @@ class Report(core.BaseODS[Sequence[None], None]):
|
||||||
["Program", "Services"],
|
["Program", "Services"],
|
||||||
["Management and", "Administrative"],
|
["Management and", "Administrative"],
|
||||||
["Fundraising"],
|
["Fundraising"],
|
||||||
totals_prefix=["Total Year Ended"],
|
totals_prefix=[f"Total {self.period_desc} Ended"],
|
||||||
)
|
)
|
||||||
totals = self.write_classifications_by_account('Expenses', [
|
totals = self.write_classifications_by_account('Expenses', [
|
||||||
{'period': Period.PERIOD, 'post_type': 'program'},
|
{'period': Period.PERIOD, 'post_type': 'program'},
|
||||||
|
@ -558,7 +572,7 @@ class Report(core.BaseODS[Sequence[None], None]):
|
||||||
self.write_totals_row("Net Increase in Cash", period_totals)
|
self.write_totals_row("Net Increase in Cash", period_totals)
|
||||||
begin_totals = [
|
begin_totals = [
|
||||||
self.balances.total(classification=self.C_CASH, period=period)
|
self.balances.total(classification=self.C_CASH, period=period)
|
||||||
for period in [Period.BEFORE_PERIOD, Period.OPENING]
|
for period in [Period.THRU_MIDDLE, Period.OPENING]
|
||||||
]
|
]
|
||||||
self.write_totals_row("Beginning Cash", begin_totals)
|
self.write_totals_row("Beginning Cash", begin_totals)
|
||||||
self.write_totals_row(
|
self.write_totals_row(
|
||||||
|
@ -572,8 +586,8 @@ class Report(core.BaseODS[Sequence[None], None]):
|
||||||
"Trial Balances",
|
"Trial Balances",
|
||||||
["Account Name"],
|
["Account Name"],
|
||||||
["Classification"],
|
["Classification"],
|
||||||
["Balance Ending", self.opening_name],
|
["Balance Beginning", self.balances.period_range.start.strftime(self.date_fmt)],
|
||||||
totals_prefix=["Change During", "Year Ending"],
|
totals_prefix=["Change During", f"{self.period_desc} Ending"],
|
||||||
title_fmt="Chart of Accounts with DRAFT {sheet_name}",
|
title_fmt="Chart of Accounts with DRAFT {sheet_name}",
|
||||||
)
|
)
|
||||||
# Widen text columns
|
# Widen text columns
|
||||||
|
|
2
setup.py
2
setup.py
|
@ -5,7 +5,7 @@ from setuptools import setup
|
||||||
setup(
|
setup(
|
||||||
name='conservancy_beancount',
|
name='conservancy_beancount',
|
||||||
description="Plugin, library, and reports for reading Conservancy's books",
|
description="Plugin, library, and reports for reading Conservancy's books",
|
||||||
version='1.8.3',
|
version='1.8.4',
|
||||||
author='Software Freedom Conservancy',
|
author='Software Freedom Conservancy',
|
||||||
author_email='info@sfconservancy.org',
|
author_email='info@sfconservancy.org',
|
||||||
license='GNU AGPLv3+',
|
license='GNU AGPLv3+',
|
||||||
|
|
Loading…
Reference in a new issue