mirror of
https://github.com/whekin/household-bot.git
synced 2026-04-01 05:54:03 +00:00
Support rebalance and validate purchase splits
Add ParticipantShare type and client-side rebalance/validation helpers for purchase splits (rebalancePurchaseSplit, recalculatePercentages, calculateRemainingToAllocate, validatePurchaseDraft). Integrate rebalancing and validation into the miniapp ledger UI (auto-adjust shares, percentage<>amount syncing, remaining/error display, prefill participants on new purchase, disable save when invalid). On the backend, tighten input validation in finance-command-service for custom_amounts (require explicit shares and sum match) and make settlement-engine more lenient when reading legacy/malformed custom splits (ignore missing shares, accept explicit subset). Also add a test ensuring dashboard generation doesn't 500 on legacy malformed purchases.
This commit is contained in:
@@ -259,19 +259,18 @@ export function calculateMonthlySettlement(input: SettlementInput): SettlementRe
|
||||
payer.purchasePaid = payer.purchasePaid.add(purchase.amount)
|
||||
|
||||
const participants = purchaseParticipantMembers(activeMembers, purchase)
|
||||
const explicitShareAmounts = purchase.participants?.map(
|
||||
(participant) => participant.shareAmount
|
||||
)
|
||||
|
||||
if (explicitShareAmounts && explicitShareAmounts.some((amount) => amount !== undefined)) {
|
||||
if (explicitShareAmounts.some((amount) => amount === undefined)) {
|
||||
throw new DomainError(
|
||||
DOMAIN_ERROR_CODE.INVALID_SETTLEMENT_INPUT,
|
||||
`Purchase custom split must include explicit share amounts for every participant: ${purchase.purchaseId.toString()}`
|
||||
)
|
||||
}
|
||||
// Identify participants with explicit share amounts (lenient read path for legacy data)
|
||||
const explicitShares =
|
||||
purchase.participants
|
||||
?.filter((p) => p.shareAmount !== undefined)
|
||||
.map((p) => ({
|
||||
memberId: p.memberId,
|
||||
shareAmount: p.shareAmount!
|
||||
})) ?? []
|
||||
|
||||
const shares = explicitShareAmounts as readonly Money[]
|
||||
if (explicitShares.length > 0) {
|
||||
const shares = explicitShares.map((p) => p.shareAmount)
|
||||
const shareTotal = sumMoney(shares, currency)
|
||||
if (!shareTotal.equals(purchase.amount)) {
|
||||
throw new DomainError(
|
||||
@@ -280,15 +279,13 @@ export function calculateMonthlySettlement(input: SettlementInput): SettlementRe
|
||||
)
|
||||
}
|
||||
|
||||
for (const [index, member] of participants.entries()) {
|
||||
const state = membersById.get(member.memberId.toString())
|
||||
for (const participant of explicitShares) {
|
||||
const state = membersById.get(participant.memberId.toString())
|
||||
if (!state) {
|
||||
continue
|
||||
}
|
||||
|
||||
state.purchaseSharedCost = state.purchaseSharedCost.add(
|
||||
shares[index] ?? Money.zero(currency)
|
||||
)
|
||||
state.purchaseSharedCost = state.purchaseSharedCost.add(participant.shareAmount)
|
||||
}
|
||||
|
||||
continue
|
||||
|
||||
Reference in New Issue
Block a user