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:
2026-03-13 07:41:31 +04:00
parent ba99460a34
commit 588174fa52
5 changed files with 354 additions and 40 deletions

View File

@@ -15,6 +15,8 @@ import type {
import {
BillingCycleId,
BillingPeriod,
DomainError,
DOMAIN_ERROR_CODE,
MemberId,
Money,
PurchaseEntryId,
@@ -867,6 +869,27 @@ export function createFinanceCommandService(
)
const currency = parseCurrency(currencyArg, settings.settlementCurrency)
const amount = Money.fromMajor(amountArg, currency)
if (split?.mode === 'custom_amounts') {
if (split.participants.some((p) => p.shareAmountMajor === undefined)) {
throw new DomainError(
DOMAIN_ERROR_CODE.INVALID_SETTLEMENT_INPUT,
'Purchase custom split must include explicit share amounts for every participant'
)
}
const totalMinor = split.participants.reduce(
(sum, p) => sum + Money.fromMajor(p.shareAmountMajor!, currency).amountMinor,
0n
)
if (totalMinor !== amount.amountMinor) {
throw new DomainError(
DOMAIN_ERROR_CODE.INVALID_SETTLEMENT_INPUT,
'Purchase custom split must add up to the full amount'
)
}
}
const updated = await repository.updateParsedPurchase({
purchaseId,
amountMinor: amount.amountMinor,