@classytic/primitives 0.27.0 → 0.29.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +122 -0
- package/dist/approval.d.mts +26 -2
- package/dist/approval.mjs +26 -3
- package/dist/bank-transaction.d.mts +1 -1
- package/dist/canonical.d.mts +6 -1
- package/dist/canonical.mjs +10 -4
- package/dist/currency.d.mts +1 -1
- package/dist/environment.d.mts +30 -1
- package/dist/environment.mjs +72 -1
- package/dist/events.d.mts +39 -1
- package/dist/events.mjs +169 -1
- package/dist/idempotency.d.mts +4 -0
- package/dist/idempotency.mjs +4 -0
- package/dist/item-stock.d.mts +43 -0
- package/dist/item-stock.mjs +67 -0
- package/dist/legal-form.d.mts +12 -0
- package/dist/legal-form.mjs +27 -0
- package/dist/line-settlement.d.mts +70 -0
- package/dist/line-settlement.mjs +79 -0
- package/dist/memory-outbox.mjs +2 -1
- package/dist/monetization.d.mts +92 -6
- package/dist/monetization.mjs +68 -5
- package/dist/money-BBOARE5f.mjs +204 -0
- package/dist/{money-CfAvfNiM.d.mts → money-C9iLWiWR.d.mts} +21 -1
- package/dist/money.d.mts +2 -2
- package/dist/money.mjs +3 -177
- package/dist/outbox.d.mts +20 -3
- package/dist/payment-events.d.mts +7 -1
- package/dist/payment-gateway.d.mts +1 -1
- package/dist/period.d.mts +22 -1
- package/dist/period.mjs +30 -1
- package/dist/phone.d.mts +13 -1
- package/dist/phone.mjs +21 -1
- package/dist/proration.d.mts +12 -2
- package/dist/proration.mjs +24 -1
- package/dist/signed-token.d.mts +54 -0
- package/dist/signed-token.mjs +139 -0
- package/dist/unit-cost-rate.d.mts +1 -1
- package/dist/unit-cost-rate.mjs +1 -1
- package/package.json +75 -1
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import { CurrencyMismatchError, currencyCode, isCurrencyCode, minorUnitFactor } from "./currency.mjs";
|
|
2
|
+
//#region src/money/cash-destination.ts
|
|
3
|
+
/**
|
|
4
|
+
* WHERE received cash landed, as an INTENT — never a chart-of-accounts code.
|
|
5
|
+
*
|
|
6
|
+
* The one vocabulary a request body, a domain event, a client SDK and a
|
|
7
|
+
* settlement dialog all speak. The accounting tier maps an intent to whatever
|
|
8
|
+
* account the active chart assigns, so a chart renumber cannot reach any of
|
|
9
|
+
* them. A literal code in a client once named a bank account the chart had
|
|
10
|
+
* already renumbered, and every settlement it sent was refused.
|
|
11
|
+
*
|
|
12
|
+
* `cash` — the business's bank / current account, the default for a courier
|
|
13
|
+
* remittance, a card payout or a transfer.
|
|
14
|
+
* `petty_cash` — cash in hand, for an in-person handover at the till.
|
|
15
|
+
*/
|
|
16
|
+
const CASH_DESTINATIONS = ["cash", "petty_cash"];
|
|
17
|
+
function isCashDestination(value) {
|
|
18
|
+
return typeof value === "string" && CASH_DESTINATIONS.includes(value);
|
|
19
|
+
}
|
|
20
|
+
/** Human label for a destination — the ONE place the wording lives. */
|
|
21
|
+
function cashDestinationLabel(destination) {
|
|
22
|
+
switch (destination) {
|
|
23
|
+
case "cash": return "Bank account";
|
|
24
|
+
case "petty_cash": return "Cash in hand";
|
|
25
|
+
default: throw new Error(`unknown cash destination ${String(destination)}`);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
//#endregion
|
|
29
|
+
//#region src/money/money.ts
|
|
30
|
+
/**
|
|
31
|
+
* Cast a raw number to {@link MinorUnits}. Pass the currency as the second
|
|
32
|
+
* argument to fix the phantom type. With no second arg, `MinorUnits<string>`
|
|
33
|
+
* — useful for generic SDK boundaries that don't know the currency yet.
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* MinorUnits(489900, 'BDT') // typed MinorUnits<'BDT'>
|
|
37
|
+
* MinorUnits(199, 'USD') // typed MinorUnits<'USD'>
|
|
38
|
+
*/
|
|
39
|
+
function MinorUnits(amount, _currency) {
|
|
40
|
+
return amount;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Construct {@link Money} from an integer minor-unit amount.
|
|
44
|
+
*
|
|
45
|
+
* @throws TypeError if `amount` is not a finite integer.
|
|
46
|
+
* @throws InvalidCurrencyCodeError if `currency` is not ISO 4217 format.
|
|
47
|
+
*/
|
|
48
|
+
function money(amount, currency) {
|
|
49
|
+
if (!Number.isFinite(amount) || !Number.isInteger(amount)) throw new TypeError(`Money.amount must be a finite integer in minor units, got ${amount}`);
|
|
50
|
+
return {
|
|
51
|
+
amount,
|
|
52
|
+
currency: currencyCode(currency)
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Construct {@link Money} from a major-unit number (e.g. dollars, taka) by
|
|
57
|
+
* scaling to the currency's minor unit and rounding half-away-from-zero.
|
|
58
|
+
*
|
|
59
|
+
* Uses `toPrecision(15)` to strip the trailing-bit noise that appears in
|
|
60
|
+
* multiply-then-round (e.g. `0.1 + 0.2 = 0.30000000000000004`). This is
|
|
61
|
+
* enough for the vast majority of decimals encountered in real-world pricing.
|
|
62
|
+
*
|
|
63
|
+
* **Precision caveat.** A handful of decimals cannot be represented exactly
|
|
64
|
+
* in IEEE 754 and will round to the IEEE-closest integer rather than the
|
|
65
|
+
* mathematically-expected one — e.g. `1.005` is stored as `1.00499999…` and
|
|
66
|
+
* rounds to `100` minor units, not `101`. If you need decimal-exact rounding,
|
|
67
|
+
* construct money from a string or use integer minor units directly via
|
|
68
|
+
* {@link money}.
|
|
69
|
+
*
|
|
70
|
+
* @example
|
|
71
|
+
* fromMajor(19.99, 'USD') // { amount: 1999, currency: 'USD' }
|
|
72
|
+
* fromMajor(0.1 + 0.2, 'USD') // { amount: 30, currency: 'USD' }
|
|
73
|
+
* fromMajor(1500, 'JPY') // { amount: 1500, currency: 'JPY' }
|
|
74
|
+
*/
|
|
75
|
+
function fromMajor(major, currency) {
|
|
76
|
+
if (!Number.isFinite(major)) throw new TypeError(`fromMajor received non-finite value: ${major}`);
|
|
77
|
+
const raw = major * minorUnitFactor(currency);
|
|
78
|
+
const cleaned = Number(raw.toPrecision(15));
|
|
79
|
+
return {
|
|
80
|
+
amount: Math.round(Math.abs(cleaned)) * (cleaned < 0 ? -1 : 1),
|
|
81
|
+
currency: currencyCode(currency)
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
/** Convert {@link Money} back to its major-unit floating-point representation. */
|
|
85
|
+
function toMajor(m) {
|
|
86
|
+
return m.amount / minorUnitFactor(m.currency);
|
|
87
|
+
}
|
|
88
|
+
function assertSameCurrency(a, b) {
|
|
89
|
+
if (a.currency !== b.currency) throw new CurrencyMismatchError(a.currency, b.currency);
|
|
90
|
+
}
|
|
91
|
+
function addMoney(a, b) {
|
|
92
|
+
assertSameCurrency(a, b);
|
|
93
|
+
return {
|
|
94
|
+
amount: a.amount + b.amount,
|
|
95
|
+
currency: a.currency
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
function subtractMoney(a, b) {
|
|
99
|
+
assertSameCurrency(a, b);
|
|
100
|
+
return {
|
|
101
|
+
amount: a.amount - b.amount,
|
|
102
|
+
currency: a.currency
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Multiply money by a scalar. The result is rounded to an integer number of
|
|
107
|
+
* minor units using half-away-from-zero rounding.
|
|
108
|
+
*/
|
|
109
|
+
function multiplyMoney(m, scalar) {
|
|
110
|
+
if (!Number.isFinite(scalar)) throw new TypeError(`multiplyMoney received non-finite scalar: ${scalar}`);
|
|
111
|
+
const raw = m.amount * scalar;
|
|
112
|
+
return {
|
|
113
|
+
amount: Math.round(Math.abs(raw)) * (raw < 0 ? -1 : 1),
|
|
114
|
+
currency: m.currency
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
/** Sum a list of {@link Money}. Returns zero in the given currency for empty input. */
|
|
118
|
+
function sumMoney(values, currency) {
|
|
119
|
+
return values.reduce((acc, v) => addMoney(acc, v), {
|
|
120
|
+
amount: 0,
|
|
121
|
+
currency: currencyCode(currency)
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
/** Strict equality (amount and currency both match). */
|
|
125
|
+
function equalsMoney(a, b) {
|
|
126
|
+
return a.amount === b.amount && a.currency === b.currency;
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Ordering predicate. Returns -1, 0, or 1. Throws on currency mismatch — use
|
|
130
|
+
* {@link equalsMoney} for a total equality check across currencies.
|
|
131
|
+
*/
|
|
132
|
+
function compareMoney(a, b) {
|
|
133
|
+
assertSameCurrency(a, b);
|
|
134
|
+
if (a.amount < b.amount) return -1;
|
|
135
|
+
if (a.amount > b.amount) return 1;
|
|
136
|
+
return 0;
|
|
137
|
+
}
|
|
138
|
+
function isZeroMoney(m) {
|
|
139
|
+
return m.amount === 0;
|
|
140
|
+
}
|
|
141
|
+
function isPositiveMoney(m) {
|
|
142
|
+
return m.amount > 0;
|
|
143
|
+
}
|
|
144
|
+
function isNegativeMoney(m) {
|
|
145
|
+
return m.amount < 0;
|
|
146
|
+
}
|
|
147
|
+
function negateMoney(m) {
|
|
148
|
+
return {
|
|
149
|
+
amount: m.amount === 0 ? 0 : -m.amount,
|
|
150
|
+
currency: m.currency
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
function absMoney(m) {
|
|
154
|
+
return m.amount < 0 ? negateMoney(m) : m;
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Decimal major-unit number → integer minor units by decimal exponent.
|
|
158
|
+
* Rounding: raw IEEE `Math.round` — half-up toward +∞ (so `-10.5 → -10`),
|
|
159
|
+
* documented above. Throws when the result leaves the safe-integer range
|
|
160
|
+
* (also catches `NaN`/`Infinity` inputs).
|
|
161
|
+
*
|
|
162
|
+
* @example
|
|
163
|
+
* majorToMinorUnits(10.5) // 1050
|
|
164
|
+
* majorToMinorUnits(1000, 0) // 1000 (JPY-style)
|
|
165
|
+
* majorToMinorUnits(1.005) // 100 — IEEE, see note above
|
|
166
|
+
*/
|
|
167
|
+
function majorToMinorUnits(major, minorUnitDecimals = 2) {
|
|
168
|
+
const factor = 10 ** minorUnitDecimals;
|
|
169
|
+
const minor = Math.round(major * factor);
|
|
170
|
+
if (!Number.isSafeInteger(minor)) throw new RangeError(`Amount ${major} exceeds safe integer limit when converted to minor units. Max safe amount: ${Number.MAX_SAFE_INTEGER / factor}`);
|
|
171
|
+
return minor;
|
|
172
|
+
}
|
|
173
|
+
/** Integer minor units → decimal major-unit number by decimal exponent: `1050 → 10.5`. */
|
|
174
|
+
function minorUnitsToMajor(minor, minorUnitDecimals = 2) {
|
|
175
|
+
return minor / 10 ** minorUnitDecimals;
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Percentage of an integer minor-unit amount → integer minor units.
|
|
179
|
+
* EXACT multiply-then-round: `Math.round((minor × ratePercent) / 100)` —
|
|
180
|
+
* one rounding point, half-up toward +∞. Handles genuinely fractional rates
|
|
181
|
+
* (QST 9.975 %) without precision loss: `percentOfMinor(20000, 9.975) === 1995`.
|
|
182
|
+
*
|
|
183
|
+
* Hosts whose domain guarantees at-most-2-decimal rates MAY snap the rate to
|
|
184
|
+
* integer basis points BEFORE calling (be-prod's `#shared/money` seam does) —
|
|
185
|
+
* that is host policy layered on top, not part of this primitive.
|
|
186
|
+
*/
|
|
187
|
+
function percentOfMinor(minor, ratePercent) {
|
|
188
|
+
if (!Number.isFinite(minor) || !Number.isFinite(ratePercent)) throw new TypeError(`percentOfMinor received non-finite input: minor=${minor}, ratePercent=${ratePercent}`);
|
|
189
|
+
return Math.round(minor * ratePercent / 100);
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Integer minor units → plain decimal string (no currency symbol, no locale):
|
|
193
|
+
* `formatMinorUnits(10550) → "105.50"`, `formatMinorUnits(1000, 0) → "1000"`.
|
|
194
|
+
* For localized currency strings use `Intl.NumberFormat` at the display layer.
|
|
195
|
+
*/
|
|
196
|
+
function formatMinorUnits(minor, minorUnitDecimals = 2) {
|
|
197
|
+
return minorUnitsToMajor(minor, minorUnitDecimals).toFixed(minorUnitDecimals);
|
|
198
|
+
}
|
|
199
|
+
/** Structural type guard. */
|
|
200
|
+
function isMoney(value) {
|
|
201
|
+
return typeof value === "object" && value !== null && typeof value.amount === "number" && Number.isInteger(value.amount) && isCurrencyCode(value.currency);
|
|
202
|
+
}
|
|
203
|
+
//#endregion
|
|
204
|
+
export { isCashDestination as C, cashDestinationLabel as S, percentOfMinor as _, equalsMoney as a, toMajor as b, isMoney as c, isZeroMoney as d, majorToMinorUnits as f, negateMoney as g, multiplyMoney as h, compareMoney as i, isNegativeMoney as l, money as m, absMoney as n, formatMinorUnits as o, minorUnitsToMajor as p, addMoney as r, fromMajor as s, MinorUnits as t, isPositiveMoney as u, subtractMoney as v, CASH_DESTINATIONS as x, sumMoney as y };
|
|
@@ -196,6 +196,26 @@ declare function reverseWithSnapshot(base: Money, fx: FxSnapshot, options?: {
|
|
|
196
196
|
*/
|
|
197
197
|
declare function isFxSnapshot(value: unknown): value is FxSnapshot;
|
|
198
198
|
//#endregion
|
|
199
|
+
//#region src/money/cash-destination.d.ts
|
|
200
|
+
/**
|
|
201
|
+
* WHERE received cash landed, as an INTENT — never a chart-of-accounts code.
|
|
202
|
+
*
|
|
203
|
+
* The one vocabulary a request body, a domain event, a client SDK and a
|
|
204
|
+
* settlement dialog all speak. The accounting tier maps an intent to whatever
|
|
205
|
+
* account the active chart assigns, so a chart renumber cannot reach any of
|
|
206
|
+
* them. A literal code in a client once named a bank account the chart had
|
|
207
|
+
* already renumbered, and every settlement it sent was refused.
|
|
208
|
+
*
|
|
209
|
+
* `cash` — the business's bank / current account, the default for a courier
|
|
210
|
+
* remittance, a card payout or a transfer.
|
|
211
|
+
* `petty_cash` — cash in hand, for an in-person handover at the till.
|
|
212
|
+
*/
|
|
213
|
+
declare const CASH_DESTINATIONS: readonly ["cash", "petty_cash"];
|
|
214
|
+
type CashDestination = (typeof CASH_DESTINATIONS)[number];
|
|
215
|
+
declare function isCashDestination(value: unknown): value is CashDestination;
|
|
216
|
+
/** Human label for a destination — the ONE place the wording lives. */
|
|
217
|
+
declare function cashDestinationLabel(destination: CashDestination): string;
|
|
218
|
+
//#endregion
|
|
199
219
|
//#region src/money/money.d.ts
|
|
200
220
|
/**
|
|
201
221
|
* Wire-format monetary amount: integer minor units, branded with a phantom
|
|
@@ -343,4 +363,4 @@ declare function formatMinorUnits(minor: number, minorUnitDecimals?: number): st
|
|
|
343
363
|
/** Structural type guard. */
|
|
344
364
|
declare function isMoney(value: unknown): value is Money;
|
|
345
365
|
//#endregion
|
|
346
|
-
export {
|
|
366
|
+
export { FxRounding as A, reverseWithSnapshot as B, CashDestination as C, CURRENCY_PATTERN as D, CURRENCIES as E, currencyCode as F, isCurrencyCode as I, isFxSnapshot as L, InvalidCurrencyCodeError as M, MINOR_UNIT_FACTOR as N, CurrencyCode as O, convertWithSnapshot as P, isKnownCurrency as R, CASH_DESTINATIONS as S, isCashDestination as T, toCurrencyCode as V, negateMoney as _, compareMoney as a, sumMoney as b, fromMajor as c, isPositiveMoney as d, isZeroMoney as f, multiplyMoney as g, money as h, addMoney as i, FxSnapshot as j, CurrencyMismatchError as k, isMoney as l, minorUnitsToMajor as m, Money as n, equalsMoney as o, majorToMinorUnits as p, absMoney as r, formatMinorUnits as s, MinorUnits as t, isNegativeMoney as u, percentOfMinor as v, cashDestinationLabel as w, toMajor as x, subtractMoney as y, minorUnitFactor as z };
|
package/dist/money.d.mts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { T as
|
|
2
|
-
export { CurrencyMismatchError, MinorUnits, Money, absMoney, addMoney, compareMoney, equalsMoney, formatMinorUnits, fromMajor, isMoney, isNegativeMoney, isPositiveMoney, isZeroMoney, majorToMinorUnits, minorUnitsToMajor, money, multiplyMoney, negateMoney, percentOfMinor, subtractMoney, sumMoney, toMajor };
|
|
1
|
+
import { C as CashDestination, S as CASH_DESTINATIONS, T as isCashDestination, _ as negateMoney, a as compareMoney, b as sumMoney, c as fromMajor, d as isPositiveMoney, f as isZeroMoney, g as multiplyMoney, h as money, i as addMoney, k as CurrencyMismatchError, l as isMoney, m as minorUnitsToMajor, n as Money, o as equalsMoney, p as majorToMinorUnits, r as absMoney, s as formatMinorUnits, t as MinorUnits, u as isNegativeMoney, v as percentOfMinor, w as cashDestinationLabel, x as toMajor, y as subtractMoney } from "./money-C9iLWiWR.mjs";
|
|
2
|
+
export { CASH_DESTINATIONS, type CashDestination, CurrencyMismatchError, MinorUnits, Money, absMoney, addMoney, cashDestinationLabel, compareMoney, equalsMoney, formatMinorUnits, fromMajor, isCashDestination, isMoney, isNegativeMoney, isPositiveMoney, isZeroMoney, majorToMinorUnits, minorUnitsToMajor, money, multiplyMoney, negateMoney, percentOfMinor, subtractMoney, sumMoney, toMajor };
|
package/dist/money.mjs
CHANGED
|
@@ -1,177 +1,3 @@
|
|
|
1
|
-
import { CurrencyMismatchError
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
* Cast a raw number to {@link MinorUnits}. Pass the currency as the second
|
|
5
|
-
* argument to fix the phantom type. With no second arg, `MinorUnits<string>`
|
|
6
|
-
* — useful for generic SDK boundaries that don't know the currency yet.
|
|
7
|
-
*
|
|
8
|
-
* @example
|
|
9
|
-
* MinorUnits(489900, 'BDT') // typed MinorUnits<'BDT'>
|
|
10
|
-
* MinorUnits(199, 'USD') // typed MinorUnits<'USD'>
|
|
11
|
-
*/
|
|
12
|
-
function MinorUnits(amount, _currency) {
|
|
13
|
-
return amount;
|
|
14
|
-
}
|
|
15
|
-
/**
|
|
16
|
-
* Construct {@link Money} from an integer minor-unit amount.
|
|
17
|
-
*
|
|
18
|
-
* @throws TypeError if `amount` is not a finite integer.
|
|
19
|
-
* @throws InvalidCurrencyCodeError if `currency` is not ISO 4217 format.
|
|
20
|
-
*/
|
|
21
|
-
function money(amount, currency) {
|
|
22
|
-
if (!Number.isFinite(amount) || !Number.isInteger(amount)) throw new TypeError(`Money.amount must be a finite integer in minor units, got ${amount}`);
|
|
23
|
-
return {
|
|
24
|
-
amount,
|
|
25
|
-
currency: currencyCode(currency)
|
|
26
|
-
};
|
|
27
|
-
}
|
|
28
|
-
/**
|
|
29
|
-
* Construct {@link Money} from a major-unit number (e.g. dollars, taka) by
|
|
30
|
-
* scaling to the currency's minor unit and rounding half-away-from-zero.
|
|
31
|
-
*
|
|
32
|
-
* Uses `toPrecision(15)` to strip the trailing-bit noise that appears in
|
|
33
|
-
* multiply-then-round (e.g. `0.1 + 0.2 = 0.30000000000000004`). This is
|
|
34
|
-
* enough for the vast majority of decimals encountered in real-world pricing.
|
|
35
|
-
*
|
|
36
|
-
* **Precision caveat.** A handful of decimals cannot be represented exactly
|
|
37
|
-
* in IEEE 754 and will round to the IEEE-closest integer rather than the
|
|
38
|
-
* mathematically-expected one — e.g. `1.005` is stored as `1.00499999…` and
|
|
39
|
-
* rounds to `100` minor units, not `101`. If you need decimal-exact rounding,
|
|
40
|
-
* construct money from a string or use integer minor units directly via
|
|
41
|
-
* {@link money}.
|
|
42
|
-
*
|
|
43
|
-
* @example
|
|
44
|
-
* fromMajor(19.99, 'USD') // { amount: 1999, currency: 'USD' }
|
|
45
|
-
* fromMajor(0.1 + 0.2, 'USD') // { amount: 30, currency: 'USD' }
|
|
46
|
-
* fromMajor(1500, 'JPY') // { amount: 1500, currency: 'JPY' }
|
|
47
|
-
*/
|
|
48
|
-
function fromMajor(major, currency) {
|
|
49
|
-
if (!Number.isFinite(major)) throw new TypeError(`fromMajor received non-finite value: ${major}`);
|
|
50
|
-
const raw = major * minorUnitFactor(currency);
|
|
51
|
-
const cleaned = Number(raw.toPrecision(15));
|
|
52
|
-
return {
|
|
53
|
-
amount: Math.round(Math.abs(cleaned)) * (cleaned < 0 ? -1 : 1),
|
|
54
|
-
currency: currencyCode(currency)
|
|
55
|
-
};
|
|
56
|
-
}
|
|
57
|
-
/** Convert {@link Money} back to its major-unit floating-point representation. */
|
|
58
|
-
function toMajor(m) {
|
|
59
|
-
return m.amount / minorUnitFactor(m.currency);
|
|
60
|
-
}
|
|
61
|
-
function assertSameCurrency(a, b) {
|
|
62
|
-
if (a.currency !== b.currency) throw new CurrencyMismatchError(a.currency, b.currency);
|
|
63
|
-
}
|
|
64
|
-
function addMoney(a, b) {
|
|
65
|
-
assertSameCurrency(a, b);
|
|
66
|
-
return {
|
|
67
|
-
amount: a.amount + b.amount,
|
|
68
|
-
currency: a.currency
|
|
69
|
-
};
|
|
70
|
-
}
|
|
71
|
-
function subtractMoney(a, b) {
|
|
72
|
-
assertSameCurrency(a, b);
|
|
73
|
-
return {
|
|
74
|
-
amount: a.amount - b.amount,
|
|
75
|
-
currency: a.currency
|
|
76
|
-
};
|
|
77
|
-
}
|
|
78
|
-
/**
|
|
79
|
-
* Multiply money by a scalar. The result is rounded to an integer number of
|
|
80
|
-
* minor units using half-away-from-zero rounding.
|
|
81
|
-
*/
|
|
82
|
-
function multiplyMoney(m, scalar) {
|
|
83
|
-
if (!Number.isFinite(scalar)) throw new TypeError(`multiplyMoney received non-finite scalar: ${scalar}`);
|
|
84
|
-
const raw = m.amount * scalar;
|
|
85
|
-
return {
|
|
86
|
-
amount: Math.round(Math.abs(raw)) * (raw < 0 ? -1 : 1),
|
|
87
|
-
currency: m.currency
|
|
88
|
-
};
|
|
89
|
-
}
|
|
90
|
-
/** Sum a list of {@link Money}. Returns zero in the given currency for empty input. */
|
|
91
|
-
function sumMoney(values, currency) {
|
|
92
|
-
return values.reduce((acc, v) => addMoney(acc, v), {
|
|
93
|
-
amount: 0,
|
|
94
|
-
currency: currencyCode(currency)
|
|
95
|
-
});
|
|
96
|
-
}
|
|
97
|
-
/** Strict equality (amount and currency both match). */
|
|
98
|
-
function equalsMoney(a, b) {
|
|
99
|
-
return a.amount === b.amount && a.currency === b.currency;
|
|
100
|
-
}
|
|
101
|
-
/**
|
|
102
|
-
* Ordering predicate. Returns -1, 0, or 1. Throws on currency mismatch — use
|
|
103
|
-
* {@link equalsMoney} for a total equality check across currencies.
|
|
104
|
-
*/
|
|
105
|
-
function compareMoney(a, b) {
|
|
106
|
-
assertSameCurrency(a, b);
|
|
107
|
-
if (a.amount < b.amount) return -1;
|
|
108
|
-
if (a.amount > b.amount) return 1;
|
|
109
|
-
return 0;
|
|
110
|
-
}
|
|
111
|
-
function isZeroMoney(m) {
|
|
112
|
-
return m.amount === 0;
|
|
113
|
-
}
|
|
114
|
-
function isPositiveMoney(m) {
|
|
115
|
-
return m.amount > 0;
|
|
116
|
-
}
|
|
117
|
-
function isNegativeMoney(m) {
|
|
118
|
-
return m.amount < 0;
|
|
119
|
-
}
|
|
120
|
-
function negateMoney(m) {
|
|
121
|
-
return {
|
|
122
|
-
amount: m.amount === 0 ? 0 : -m.amount,
|
|
123
|
-
currency: m.currency
|
|
124
|
-
};
|
|
125
|
-
}
|
|
126
|
-
function absMoney(m) {
|
|
127
|
-
return m.amount < 0 ? negateMoney(m) : m;
|
|
128
|
-
}
|
|
129
|
-
/**
|
|
130
|
-
* Decimal major-unit number → integer minor units by decimal exponent.
|
|
131
|
-
* Rounding: raw IEEE `Math.round` — half-up toward +∞ (so `-10.5 → -10`),
|
|
132
|
-
* documented above. Throws when the result leaves the safe-integer range
|
|
133
|
-
* (also catches `NaN`/`Infinity` inputs).
|
|
134
|
-
*
|
|
135
|
-
* @example
|
|
136
|
-
* majorToMinorUnits(10.5) // 1050
|
|
137
|
-
* majorToMinorUnits(1000, 0) // 1000 (JPY-style)
|
|
138
|
-
* majorToMinorUnits(1.005) // 100 — IEEE, see note above
|
|
139
|
-
*/
|
|
140
|
-
function majorToMinorUnits(major, minorUnitDecimals = 2) {
|
|
141
|
-
const factor = 10 ** minorUnitDecimals;
|
|
142
|
-
const minor = Math.round(major * factor);
|
|
143
|
-
if (!Number.isSafeInteger(minor)) throw new RangeError(`Amount ${major} exceeds safe integer limit when converted to minor units. Max safe amount: ${Number.MAX_SAFE_INTEGER / factor}`);
|
|
144
|
-
return minor;
|
|
145
|
-
}
|
|
146
|
-
/** Integer minor units → decimal major-unit number by decimal exponent: `1050 → 10.5`. */
|
|
147
|
-
function minorUnitsToMajor(minor, minorUnitDecimals = 2) {
|
|
148
|
-
return minor / 10 ** minorUnitDecimals;
|
|
149
|
-
}
|
|
150
|
-
/**
|
|
151
|
-
* Percentage of an integer minor-unit amount → integer minor units.
|
|
152
|
-
* EXACT multiply-then-round: `Math.round((minor × ratePercent) / 100)` —
|
|
153
|
-
* one rounding point, half-up toward +∞. Handles genuinely fractional rates
|
|
154
|
-
* (QST 9.975 %) without precision loss: `percentOfMinor(20000, 9.975) === 1995`.
|
|
155
|
-
*
|
|
156
|
-
* Hosts whose domain guarantees at-most-2-decimal rates MAY snap the rate to
|
|
157
|
-
* integer basis points BEFORE calling (be-prod's `#shared/money` seam does) —
|
|
158
|
-
* that is host policy layered on top, not part of this primitive.
|
|
159
|
-
*/
|
|
160
|
-
function percentOfMinor(minor, ratePercent) {
|
|
161
|
-
if (!Number.isFinite(minor) || !Number.isFinite(ratePercent)) throw new TypeError(`percentOfMinor received non-finite input: minor=${minor}, ratePercent=${ratePercent}`);
|
|
162
|
-
return Math.round(minor * ratePercent / 100);
|
|
163
|
-
}
|
|
164
|
-
/**
|
|
165
|
-
* Integer minor units → plain decimal string (no currency symbol, no locale):
|
|
166
|
-
* `formatMinorUnits(10550) → "105.50"`, `formatMinorUnits(1000, 0) → "1000"`.
|
|
167
|
-
* For localized currency strings use `Intl.NumberFormat` at the display layer.
|
|
168
|
-
*/
|
|
169
|
-
function formatMinorUnits(minor, minorUnitDecimals = 2) {
|
|
170
|
-
return minorUnitsToMajor(minor, minorUnitDecimals).toFixed(minorUnitDecimals);
|
|
171
|
-
}
|
|
172
|
-
/** Structural type guard. */
|
|
173
|
-
function isMoney(value) {
|
|
174
|
-
return typeof value === "object" && value !== null && typeof value.amount === "number" && Number.isInteger(value.amount) && isCurrencyCode(value.currency);
|
|
175
|
-
}
|
|
176
|
-
//#endregion
|
|
177
|
-
export { CurrencyMismatchError, MinorUnits, absMoney, addMoney, compareMoney, equalsMoney, formatMinorUnits, fromMajor, isMoney, isNegativeMoney, isPositiveMoney, isZeroMoney, majorToMinorUnits, minorUnitsToMajor, money, multiplyMoney, negateMoney, percentOfMinor, subtractMoney, sumMoney, toMajor };
|
|
1
|
+
import { CurrencyMismatchError } from "./currency.mjs";
|
|
2
|
+
import { C as isCashDestination, S as cashDestinationLabel, _ as percentOfMinor, a as equalsMoney, b as toMajor, c as isMoney, d as isZeroMoney, f as majorToMinorUnits, g as negateMoney, h as multiplyMoney, i as compareMoney, l as isNegativeMoney, m as money, n as absMoney, o as formatMinorUnits, p as minorUnitsToMajor, r as addMoney, s as fromMajor, t as MinorUnits, u as isPositiveMoney, v as subtractMoney, x as CASH_DESTINATIONS, y as sumMoney } from "./money-BBOARE5f.mjs";
|
|
3
|
+
export { CASH_DESTINATIONS, CurrencyMismatchError, MinorUnits, absMoney, addMoney, cashDestinationLabel, compareMoney, equalsMoney, formatMinorUnits, fromMajor, isCashDestination, isMoney, isNegativeMoney, isPositiveMoney, isZeroMoney, majorToMinorUnits, minorUnitsToMajor, money, multiplyMoney, negateMoney, percentOfMinor, subtractMoney, sumMoney, toMajor };
|
package/dist/outbox.d.mts
CHANGED
|
@@ -46,6 +46,20 @@ interface OutboxClaimOptions {
|
|
|
46
46
|
interface OutboxClaimedEvent {
|
|
47
47
|
readonly event: DomainEvent;
|
|
48
48
|
readonly fencingToken: number;
|
|
49
|
+
/**
|
|
50
|
+
* DURABLE delivery-attempt count for this event, INCLUDING the claim that
|
|
51
|
+
* returned it (1 on the first claim). Optional: stores that persist a
|
|
52
|
+
* per-row counter report it; stores that don't leave it absent.
|
|
53
|
+
*
|
|
54
|
+
* Why it exists: a relay can only count failures in its own memory, and
|
|
55
|
+
* that count is wrong in exactly the cases a failure policy exists for.
|
|
56
|
+
* A poison event that crashes the relay restarts at 1 on every boot, so
|
|
57
|
+
* `attempts >= 5 → deadLetter` never fires; and across N relays sharing a
|
|
58
|
+
* store, each counts its own attempts, so "5" means 5×N. The store already
|
|
59
|
+
* increments a counter atomically inside the claim — reporting it here is
|
|
60
|
+
* what lets the policy see the true number.
|
|
61
|
+
*/
|
|
62
|
+
readonly attempts?: number;
|
|
49
63
|
}
|
|
50
64
|
/** Options for {@link OutboxStore.acknowledge}. */
|
|
51
65
|
interface OutboxAcknowledgeOptions {
|
|
@@ -81,9 +95,12 @@ interface OutboxFailureContext {
|
|
|
81
95
|
/** The error returned by the transport / handler. */
|
|
82
96
|
readonly error: Error;
|
|
83
97
|
/**
|
|
84
|
-
* Attempt count including this failure (1 on the first fail).
|
|
85
|
-
*
|
|
86
|
-
*
|
|
98
|
+
* Attempt count including this failure (1 on the first fail).
|
|
99
|
+
*
|
|
100
|
+
* DURABLE when the store reports {@link OutboxClaimedEvent.attempts} from a
|
|
101
|
+
* fenced claim — survives relay restarts and is shared by every relay on the
|
|
102
|
+
* store. Otherwise the relay's in-process count: accurate within one relay
|
|
103
|
+
* process, reset on restart. Relays take the larger of the two.
|
|
87
104
|
*/
|
|
88
105
|
readonly attempts: number;
|
|
89
106
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { n as Money } from "./money-
|
|
1
|
+
import { n as Money } from "./money-C9iLWiWR.mjs";
|
|
2
2
|
import { PaymentMethodKind } from "./payment-method-kind.mjs";
|
|
3
3
|
import { ProviderUnknownCause } from "./payment-gateway.mjs";
|
|
4
4
|
//#region src/events/payment-events.d.ts
|
|
@@ -119,6 +119,9 @@ interface PaymentRefundedPayload {
|
|
|
119
119
|
refundId: string;
|
|
120
120
|
providerRef?: string;
|
|
121
121
|
providerCode: string;
|
|
122
|
+
/** How the original payment moved — money goes back the way it came, so this places the refund too. */
|
|
123
|
+
methodKind: PaymentMethodKind;
|
|
124
|
+
methodCode?: string;
|
|
122
125
|
/** Amount refunded in this event — may be less than the original payment. */
|
|
123
126
|
refundedAmount: Money;
|
|
124
127
|
/** Original payment total — present so consumers can compute remaining refundable without a Payment lookup. */
|
|
@@ -142,6 +145,9 @@ interface PaymentReversedPayload {
|
|
|
142
145
|
paymentId: string;
|
|
143
146
|
providerRef?: string;
|
|
144
147
|
providerCode: string;
|
|
148
|
+
/** How the payment being unwound moved. */
|
|
149
|
+
methodKind: PaymentMethodKind;
|
|
150
|
+
methodCode?: string;
|
|
145
151
|
/** Amount being unwound — equals the original payment for full reversal, less for partial. */
|
|
146
152
|
reversedAmount: Money;
|
|
147
153
|
/** Original payment total — present so consumers can detect partial vs full without a Payment lookup. */
|
package/dist/period.d.mts
CHANGED
|
@@ -123,6 +123,27 @@ declare function resolveMonth(year: number, month: number, timezone: string): Da
|
|
|
123
123
|
declare function resolveQuarter(year: number, quarter: 1 | 2 | 3 | 4, timezone: string): DateRange;
|
|
124
124
|
/** Half-open `[start, end)` range covering one local calendar year. */
|
|
125
125
|
declare function resolveYear(year: number, timezone: string): DateRange;
|
|
126
|
+
/** A fiscal year: the calendar year it starts in, its label, and its half-open local range. */
|
|
127
|
+
interface FiscalYear {
|
|
128
|
+
startYear: number;
|
|
129
|
+
/** `2026-27` for a year starting mid-calendar-year; `2026` for one starting in January. */
|
|
130
|
+
label: string;
|
|
131
|
+
range: DateRange;
|
|
132
|
+
}
|
|
133
|
+
/** Half-open range of the fiscal year starting on the 1st of `startMonth` of `startYear`, local to `timezone`. */
|
|
134
|
+
declare function resolveFiscalYear(startYear: number, opts: {
|
|
135
|
+
startMonth: number;
|
|
136
|
+
timezone: string;
|
|
137
|
+
}): DateRange;
|
|
138
|
+
/**
|
|
139
|
+
* The fiscal year `date` falls in — 1 July–30 June (Bangladesh, Australia), 1 April–31 March (India),
|
|
140
|
+
* 1 January (most) — decided on the LOCAL calendar day in `timezone`, so the last evening of June in Dhaka
|
|
141
|
+
* is still the old year.
|
|
142
|
+
*/
|
|
143
|
+
declare function fiscalYearOf(date: Date, opts: {
|
|
144
|
+
startMonth: number;
|
|
145
|
+
timezone: string;
|
|
146
|
+
}): FiscalYear;
|
|
126
147
|
/**
|
|
127
148
|
* Half-open range spanning two local dates, INCLUSIVE of both days — what an
|
|
128
149
|
* operator means by "1st to 31st". The half-open end is therefore the start of
|
|
@@ -209,4 +230,4 @@ declare function parsePeriod(label: string, timezone?: string): Period;
|
|
|
209
230
|
*/
|
|
210
231
|
declare function formatPeriod(period: Period): string;
|
|
211
232
|
//#endregion
|
|
212
|
-
export { DateRange, DateRangeError, Period, PeriodError, PeriodErrorCode, PeriodGranularity, dayStart, formatPeriod, granularityOf, inclusiveEnd, isDateRange, isWithin, parsePeriod, periodOf, periodTimeZone, rangeDurationMs, rangesOverlap, resolveDateSpan, resolveDay, resolveMonth, resolvePeriod, resolveQuarter, resolveYear, shiftPeriod };
|
|
233
|
+
export { DateRange, DateRangeError, FiscalYear, Period, PeriodError, PeriodErrorCode, PeriodGranularity, dayStart, fiscalYearOf, formatPeriod, granularityOf, inclusiveEnd, isDateRange, isWithin, parsePeriod, periodOf, periodTimeZone, rangeDurationMs, rangesOverlap, resolveDateSpan, resolveDay, resolveFiscalYear, resolveMonth, resolvePeriod, resolveQuarter, resolveYear, shiftPeriod };
|
package/dist/period.mjs
CHANGED
|
@@ -181,6 +181,35 @@ function resolveYear(year, timezone) {
|
|
|
181
181
|
end: dayStart(`${y + 1}-01-01`, zone)
|
|
182
182
|
};
|
|
183
183
|
}
|
|
184
|
+
/** Half-open range of the fiscal year starting on the 1st of `startMonth` of `startYear`, local to `timezone`. */
|
|
185
|
+
function resolveFiscalYear(startYear, opts) {
|
|
186
|
+
const zone = assertZone(opts.timezone);
|
|
187
|
+
const y = assertYear(startYear);
|
|
188
|
+
const m = assertMonth(opts.startMonth);
|
|
189
|
+
return {
|
|
190
|
+
start: dayStart(`${y}-${pad2(m)}-01`, zone),
|
|
191
|
+
end: dayStart(`${y + 1}-${pad2(m)}-01`, zone)
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* The fiscal year `date` falls in — 1 July–30 June (Bangladesh, Australia), 1 April–31 March (India),
|
|
196
|
+
* 1 January (most) — decided on the LOCAL calendar day in `timezone`, so the last evening of June in Dhaka
|
|
197
|
+
* is still the old year.
|
|
198
|
+
*/
|
|
199
|
+
function fiscalYearOf(date, opts) {
|
|
200
|
+
const zone = assertZone(opts.timezone);
|
|
201
|
+
const m = assertMonth(opts.startMonth);
|
|
202
|
+
const [y, mm] = String(civilDateOf(date, zone)).split("-").map(Number);
|
|
203
|
+
const startYear = mm >= m ? y : y - 1;
|
|
204
|
+
return {
|
|
205
|
+
startYear,
|
|
206
|
+
label: m === 1 ? String(startYear) : `${startYear}-${pad2((startYear + 1) % 100)}`,
|
|
207
|
+
range: resolveFiscalYear(startYear, {
|
|
208
|
+
startMonth: m,
|
|
209
|
+
timezone: zone
|
|
210
|
+
})
|
|
211
|
+
};
|
|
212
|
+
}
|
|
184
213
|
/**
|
|
185
214
|
* Half-open range spanning two local dates, INCLUSIVE of both days — what an
|
|
186
215
|
* operator means by "1st to 31st". The half-open end is therefore the start of
|
|
@@ -388,4 +417,4 @@ function formatPeriod(period) {
|
|
|
388
417
|
throw new PeriodError("INVALID_LABEL", "An ad-hoc Period (start + end) has no canonical label.");
|
|
389
418
|
}
|
|
390
419
|
//#endregion
|
|
391
|
-
export { DateRangeError, PeriodError, dayStart, formatPeriod, granularityOf, inclusiveEnd, isDateRange, isWithin, parsePeriod, periodOf, periodTimeZone, rangeDurationMs, rangesOverlap, resolveDateSpan, resolveDay, resolveMonth, resolvePeriod, resolveQuarter, resolveYear, shiftPeriod };
|
|
420
|
+
export { DateRangeError, PeriodError, dayStart, fiscalYearOf, formatPeriod, granularityOf, inclusiveEnd, isDateRange, isWithin, parsePeriod, periodOf, periodTimeZone, rangeDurationMs, rangesOverlap, resolveDateSpan, resolveDay, resolveFiscalYear, resolveMonth, resolvePeriod, resolveQuarter, resolveYear, shiftPeriod };
|
package/dist/phone.d.mts
CHANGED
|
@@ -40,7 +40,19 @@ declare function formatPhone(phone: PhoneNumber): string;
|
|
|
40
40
|
* @example formatNational(parsePhone('+14155550182').value!) → '+1 415 555 0182'
|
|
41
41
|
*/
|
|
42
42
|
declare function formatNational(phone: PhoneNumber): string;
|
|
43
|
+
/**
|
|
44
|
+
* Prefix of a stored PLACEHOLDER phone: a record whose `phone` is required and
|
|
45
|
+
* unique but was created before a number was known gets `pending_<id>`. It means
|
|
46
|
+
* "no number collected" — never a number to show, dial, dedupe or search on.
|
|
47
|
+
*/
|
|
48
|
+
declare const PLACEHOLDER_PHONE_PREFIX = "pending_";
|
|
49
|
+
/** Mint the placeholder for a phone-less record — unique per `id`. */
|
|
50
|
+
declare function placeholderPhone(id: string): string;
|
|
51
|
+
/** True for a stored placeholder (see {@link PLACEHOLDER_PHONE_PREFIX}); whitespace-tolerant. */
|
|
52
|
+
declare function isPlaceholderPhone(value: unknown): boolean;
|
|
53
|
+
/** A stored phone worth showing: trimmed, or `null` for empty or a placeholder. */
|
|
54
|
+
declare function shownPhone(value: unknown): string | null;
|
|
43
55
|
/** Type guard. */
|
|
44
56
|
declare function isPhoneNumber(value: unknown): value is PhoneNumber;
|
|
45
57
|
//#endregion
|
|
46
|
-
export { PhoneError, PhoneErrorCode, PhoneNumber, formatNational, formatPhone, isPhoneNumber, parsePhone };
|
|
58
|
+
export { PLACEHOLDER_PHONE_PREFIX, PhoneError, PhoneErrorCode, PhoneNumber, formatNational, formatPhone, isPhoneNumber, isPlaceholderPhone, parsePhone, placeholderPhone, shownPhone };
|
package/dist/phone.mjs
CHANGED
|
@@ -302,9 +302,29 @@ function formatNational(phone) {
|
|
|
302
302
|
const groups = phone.nationalNumber.match(/.{1,3}/g) ?? [];
|
|
303
303
|
return `+${phone.callingCode} ${groups.join(" ")}`.trim();
|
|
304
304
|
}
|
|
305
|
+
/**
|
|
306
|
+
* Prefix of a stored PLACEHOLDER phone: a record whose `phone` is required and
|
|
307
|
+
* unique but was created before a number was known gets `pending_<id>`. It means
|
|
308
|
+
* "no number collected" — never a number to show, dial, dedupe or search on.
|
|
309
|
+
*/
|
|
310
|
+
const PLACEHOLDER_PHONE_PREFIX = "pending_";
|
|
311
|
+
/** Mint the placeholder for a phone-less record — unique per `id`. */
|
|
312
|
+
function placeholderPhone(id) {
|
|
313
|
+
return `${PLACEHOLDER_PHONE_PREFIX}${id}`;
|
|
314
|
+
}
|
|
315
|
+
/** True for a stored placeholder (see {@link PLACEHOLDER_PHONE_PREFIX}); whitespace-tolerant. */
|
|
316
|
+
function isPlaceholderPhone(value) {
|
|
317
|
+
return typeof value === "string" && value.trim().startsWith("pending_");
|
|
318
|
+
}
|
|
319
|
+
/** A stored phone worth showing: trimmed, or `null` for empty or a placeholder. */
|
|
320
|
+
function shownPhone(value) {
|
|
321
|
+
if (typeof value !== "string") return null;
|
|
322
|
+
const v = value.trim();
|
|
323
|
+
return v && !isPlaceholderPhone(v) ? v : null;
|
|
324
|
+
}
|
|
305
325
|
/** Type guard. */
|
|
306
326
|
function isPhoneNumber(value) {
|
|
307
327
|
return typeof value === "object" && value !== null && typeof value.e164 === "string" && typeof value.callingCode === "string" && typeof value.nationalNumber === "string" && value.e164.startsWith("+");
|
|
308
328
|
}
|
|
309
329
|
//#endregion
|
|
310
|
-
export { PhoneError, formatNational, formatPhone, isPhoneNumber, parsePhone };
|
|
330
|
+
export { PLACEHOLDER_PHONE_PREFIX, PhoneError, formatNational, formatPhone, isPhoneNumber, isPlaceholderPhone, parsePhone, placeholderPhone, shownPhone };
|
package/dist/proration.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { n as Money } from "./money-
|
|
1
|
+
import { n as Money } from "./money-C9iLWiWR.mjs";
|
|
2
2
|
//#region src/money/proration.d.ts
|
|
3
3
|
/**
|
|
4
4
|
* How to count days within a period.
|
|
@@ -62,6 +62,16 @@ declare function splitByPeriodFraction(amount: Money, fraction: PeriodFraction):
|
|
|
62
62
|
readonly consumed: Money;
|
|
63
63
|
readonly remaining: Money;
|
|
64
64
|
};
|
|
65
|
+
/**
|
|
66
|
+
* The share of `total` carried by `part` of `whole`, in integer minor units — the VAT on a partial
|
|
67
|
+
* refund, the tax on one tender of a split sale. Largest-remainder rounding, so the share and its
|
|
68
|
+
* complement sum to `total` exactly; `part === whole` returns `total` untouched.
|
|
69
|
+
*
|
|
70
|
+
* @throws ProrationError('INVALID_FRACTION') when an input is not an integer, `whole` is not
|
|
71
|
+
* positive, or `part` lies outside `[0, whole]` — a refund larger than its payment is a defect
|
|
72
|
+
* upstream, and clamping it would book a plausible number.
|
|
73
|
+
*/
|
|
74
|
+
declare function apportion(total: number, part: number, whole: number): number;
|
|
65
75
|
/**
|
|
66
76
|
* Allocate the `fraction`-weighted part of a `Money` amount, drift-free — the
|
|
67
77
|
* complement (`1 - fraction`) is discarded but computed so rounding is exact
|
|
@@ -72,4 +82,4 @@ declare function splitByPeriodFraction(amount: Money, fraction: PeriodFraction):
|
|
|
72
82
|
*/
|
|
73
83
|
declare function allocateMoneyByFraction(amount: Money, fraction: number): Money;
|
|
74
84
|
//#endregion
|
|
75
|
-
export { PeriodFraction, PeriodProgressInput, ProrationError, ProrationErrorCode, ProrationGranularity, allocateMoneyByFraction, periodProgress, splitByPeriodFraction };
|
|
85
|
+
export { PeriodFraction, PeriodProgressInput, ProrationError, ProrationErrorCode, ProrationGranularity, allocateMoneyByFraction, apportion, periodProgress, splitByPeriodFraction };
|