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 Dict, List, Sequence
from .legal_time_billing_types import FeeEarnerRate, FeeEarnerTotal, TimeBill, TimeBillLine, TimeEntry
from .math_round_div import round_div ← from math.round-div ^1.0.0 · built alongside by fune
from .math_round_div_types import RoundingMode
from .money_amount import Money, money ← from money.amount ^1.0.0 · built alongside by fune
from .time_round_to_increment import round_to_increment ← from time.round-to-increment ^1.0.0 · built alongside by fune
def time_billing(entries: Sequence[TimeEntry], rates: Sequence[FeeEarnerRate], unit_minutes: int, mode: RoundingMode) -> TimeBill:
"""Price time entries in whole billing units at each fee earner's hourly rate."""
if len(rates) == 0:
raise ValueError("rates must not be empty")
currency = rates[0].hourly_rate.currency
card: Dict[str, Money] = {}
for rate in rates:
if rate.fee_earner in card:
raise ValueError('duplicate rate for fee earner "%s"' % (rate.fee_earner,))
if rate.hourly_rate.currency != currency:
raise ValueError("currency mismatch: %s and %s" % (currency, rate.hourly_rate.currency))
if rate.hourly_rate.minor < 0:
raise ValueError("hourly rates must not be negative, received %s" % (rate.hourly_rate.minor,))
card[rate.fee_earner] = rate.hourly_rate
lines: List[TimeBillLine] = []
order: List[str] = []
totals: Dict[str, List[int]] = {}
units = 0
billed_minutes = 0
total = 0
for entry in entries:
hourly_rate = card.get(entry.fee_earner)
if hourly_rate is None:
raise ValueError('no rate for fee earner "%s"' % (entry.fee_earner,))
# Rounded per entry: seven 1-minute emails are seven units, not two.
billed = round_to_increment(entry.minutes, unit_minutes, mode)
line_units = billed // unit_minutes
fee = round_div(billed * hourly_rate.minor, 60, "half-up")
lines.append(
TimeBillLine(
fee_earner=entry.fee_earner,
minutes=entry.minutes,
billed_minutes=billed,
units=line_units,
hourly_rate=hourly_rate,
fee=money(fee, currency),
)
)
if entry.fee_earner not in totals:
totals[entry.fee_earner] = [0, 0]
order.append(entry.fee_earner)
totals[entry.fee_earner][0] += line_units
totals[entry.fee_earner][1] += fee
units += line_units
billed_minutes += billed
total += fee
by_fee_earner = [FeeEarnerTotal(fee_earner=c, units=totals[c][0], fee=money(totals[c][1], currency)) for c in order]
return TimeBill(lines=lines, by_fee_earner=by_fee_earner, units=units, billed_minutes=billed_minutes, total=money(total, currency))