Files
household-bot/packages/application/src/payment-guidance.ts

77 lines
2.7 KiB
TypeScript

import { BillingPeriod, Money, Temporal } from '@household/domain'
import type {
HouseholdBillingSettingsRecord,
HouseholdPaymentBalanceAdjustmentPolicy
} from '@household/ports'
import type { FinanceDashboardMemberLine } from './finance-command-service'
export interface MemberPaymentGuidance {
kind: 'rent' | 'utilities'
adjustmentPolicy: HouseholdPaymentBalanceAdjustmentPolicy
baseAmount: Money
purchaseOffset: Money
proposalAmount: Money
totalRemaining: Money
reminderDate: string
dueDate: string
paymentWindowOpen: boolean
paymentDue: boolean
}
function cycleDate(period: string, day: number): Temporal.PlainDate {
const billingPeriod = BillingPeriod.fromString(period)
const [yearRaw, monthRaw] = billingPeriod.toString().split('-')
const year = Number(yearRaw)
const month = Number(monthRaw)
const yearMonth = new Temporal.PlainYearMonth(year, month)
const boundedDay = Math.min(Math.max(day, 1), yearMonth.daysInMonth)
return new Temporal.PlainDate(year, month, boundedDay)
}
function adjustmentApplies(
policy: HouseholdPaymentBalanceAdjustmentPolicy,
kind: 'rent' | 'utilities'
): boolean {
return (policy === 'utilities' && kind === 'utilities') || (policy === 'rent' && kind === 'rent')
}
export function buildMemberPaymentGuidance(input: {
kind: 'rent' | 'utilities'
period: string
memberLine: FinanceDashboardMemberLine
settings: HouseholdBillingSettingsRecord
referenceInstant?: Temporal.Instant
}): MemberPaymentGuidance {
const policy = input.settings.paymentBalanceAdjustmentPolicy ?? 'utilities'
const baseAmount =
input.kind === 'rent' ? input.memberLine.rentShare : input.memberLine.utilityShare
const purchaseOffset = input.memberLine.purchaseOffset
const proposalAmount = adjustmentApplies(policy, input.kind)
? baseAmount.add(purchaseOffset)
: baseAmount
const reminderDay =
input.kind === 'rent' ? input.settings.rentWarningDay : input.settings.utilitiesReminderDay
const dueDay = input.kind === 'rent' ? input.settings.rentDueDay : input.settings.utilitiesDueDay
const reminderDate = cycleDate(input.period, reminderDay)
const dueDate = cycleDate(input.period, dueDay)
const localDate = (input.referenceInstant ?? Temporal.Now.instant())
.toZonedDateTimeISO(input.settings.timezone)
.toPlainDate()
return {
kind: input.kind,
adjustmentPolicy: policy,
baseAmount,
purchaseOffset,
proposalAmount,
totalRemaining: input.memberLine.remaining,
reminderDate: reminderDate.toString(),
dueDate: dueDate.toString(),
paymentWindowOpen: Temporal.PlainDate.compare(localDate, reminderDate) >= 0,
paymentDue: Temporal.PlainDate.compare(localDate, dueDate) >= 0
}
}