finance.accruals-prepayments@1.0.0
impl/python.py
2,069 bytes · the Python implementation · view raw
Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.
from typing import List, Sequence
from .dates_days_between import days_between ← from dates.days-between ^1.0.0 · built alongside by fune
from .dates_overlap import overlap_days ← from dates.overlap ^1.0.0 · built alongside by fune
from .finance_accruals_prepayments_types import AccountingPeriod, PeriodAllocation
from .money_allocate import allocate ← from money.allocate ^1.0.0 · built alongside by fune
from .money_amount import Money, money ← from money.amount ^1.0.0 · built alongside by fune
def allocate_to_periods(
amount: Money, service_start: str, service_end: str, periods: Sequence[AccountingPeriod]
) -> List[PeriodAllocation]:
"""Split an invoice across accounting periods by days of service.
money.allocate does the split with each period's days as the weight, so the
shares add back to the invoice exactly. A negative amount is split as its
positive and negated, so a credit note mirrors its invoice period by period.
"""
service_days = days_between(service_start, service_end) + 1
if service_days < 1:
raise ValueError("service ends %s, before it starts %s" % (service_end, service_start))
if len(periods) == 0:
raise ValueError("at least one accounting period is needed")
days: List[int] = []
for index, period in enumerate(periods):
if days_between(period.start, period.end) < 0:
raise ValueError("period %s ends before it starts" % (period.label,))
if index > 0 and days_between(periods[index - 1].end, period.start) < 1:
raise ValueError("period %s overlaps or precedes the period before it" % (period.label,))
days.append(overlap_days(service_start, service_end, period.start, period.end, True))
covered = sum(days)
if covered != service_days:
raise ValueError("the accounting periods cover %d of the %d days of service" % (covered, service_days))
negative = amount.minor < 0
shares = allocate(money(-amount.minor if negative else amount.minor, amount.currency), days)
return [
PeriodAllocation(
label=period.label,
days=days[index],
amount=money(-shares[index].minor, amount.currency) if negative else shares[index],
)
for index, period in enumerate(periods)
]