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_types import RoundingMode from .money_amount import Money, money from .time_round_to_increment import round_to_increment 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))