platform-types 1.10.853 → 1.10.854

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/dist/account.d.ts CHANGED
@@ -188,6 +188,12 @@ declare global {
188
188
  * payment): the obligation whose refund the debit books. Absent on every other row.
189
189
  */
190
190
  refundObligationId?: string;
191
+ /**
192
+ * On a link-credit reversal row (SK `LINKREV#{reversedAccountId}`, `paymentRef*` naming the linked
193
+ * payment): the ledger row it reverses — the payment's credit, or that credit's FX adjustment.
194
+ * Absent on every other row.
195
+ */
196
+ reversesAccountId?: string;
191
197
  }
192
198
  /**
193
199
  * `POST /account` — a new customer-ledger movement. Mirrors the request side of
@@ -0,0 +1,125 @@
1
+ /**
2
+ * The cuenta-corriente allocator — which customer debits are still open, derived
3
+ * on read from `ACCOUNT#{storeId}` rows, in INTEGER MINOR UNITS. One
4
+ * implementation shared by the api, the operator app and the storefront, so the
5
+ * three agree on which debit a payment can still settle.
6
+ *
7
+ * No allocation entity is stored: a credit carries no pointer to the debit it
8
+ * paid, so every reader re-derives it, and two readers that derive it
9
+ * differently tell the customer two different things. The rules:
10
+ *
11
+ * 1. **Row order is `createdAt` ascending, NEVER `dated`.** A backdated row is
12
+ * allocated where it was written. Ties: a debit before a credit (an order's
13
+ * debit and its auto-linked payment can share a millisecond), then
14
+ * `accountId`, so the result never depends on the order DynamoDB returned.
15
+ * 2. **A credit stamped with an `orderId` serves that order's open debits
16
+ * first**, oldest first, and only debits already on the ledger — it cannot
17
+ * reach a debit of that order written after it.
18
+ * 3. **Everything else applies oldest-first**: an unstamped credit, and the rest
19
+ * of a stamped one once its order is paid (or has no debit yet).
20
+ * 4. **Credit with no open debit to land on waits unapplied** and is drained by
21
+ * the next debit, so a deposit taken before the order is booked still pays
22
+ * it. What is never drained is reported as `unappliedMinor`.
23
+ *
24
+ * Rows it reads, and how:
25
+ *
26
+ * - A `deleted` row, or one whose `createdAt` is past `asOf`, is skipped.
27
+ * - A synthesised opening row (`ACC000001`, `ACC000001-<suffix>` — the "Saldo
28
+ * anterior" a statement leads with) is a movement of its `balance`: positive
29
+ * is an orderless debit, negative an unstamped credit, and any `orderId` on
30
+ * it is ignored. A `balance` on any OTHER row is a projected running total
31
+ * and is ignored.
32
+ * - A row whose `debit` is above zero is a debit (its `credit` is then not
33
+ * read); otherwise a row whose `credit` is above zero at the ledger scale is
34
+ * a credit; anything else moves nothing. A stored `BALANCE-` checkpoint
35
+ * carries neither, so it moves nothing — the ledger must be read from its
36
+ * first row instead, because FIFO over a ledger with a missing debit is
37
+ * wrong.
38
+ * - A refund-in-transit debit (`RIT#…`) is an ordinary debit stamped with its
39
+ * order. It is reported like any other item; a consumer offering debits to
40
+ * pay excludes it by its `accountId` if it must.
41
+ *
42
+ * ⚠️ **ONE ledger bucket — `ledgerCurrency` — never one per row label.** Every
43
+ * writer stores `debit`/`credit` already converted into the store's ledger
44
+ * denomination; a legacy manual row's `currency` labels what the operator
45
+ * TYPED, so bucketing by it files pesos as dollars. Opening rows follow the
46
+ * same rule, a legacy `ACC000001-usd` seed included. Every item therefore
47
+ * carries `ledgerCurrency`.
48
+ *
49
+ * ⚠️ **Exact, with no display tolerance.** An item is `paid` only at zero minor
50
+ * units. A reader that rounds to fewer decimals than the ledger scale (a store
51
+ * pricing in whole units on a two-decimal ledger) can call "paid" what this
52
+ * calls `partial`; that tolerance is the reader's to apply, on top of these
53
+ * figures. Amounts are quantised once per row — half away from zero after
54
+ * normalising the scaled value to 15 significant digits, the api ledger's own
55
+ * rule — and never summed as floats. A missing or non-finite amount is zero.
56
+ *
57
+ * Pure: no clock, no I/O, input rows never mutated.
58
+ */
59
+ /** An item's allocation state, as a code — the reader owns the label. */
60
+ export declare const ACCOUNT_ITEM_STATUSES: readonly ["open", "partial", "paid"];
61
+ export type AccountItemStatus = (typeof ACCOUNT_ITEM_STATUSES)[number];
62
+ /** The fields the allocator reads — a stored row and either wire projection all satisfy it. */
63
+ export type AllocatableAccountRow = Pick<Account, 'accountId' | 'dated'> & Partial<Pick<Account, 'createdAt' | 'orderId' | 'dueDate' | 'details' | 'debit' | 'credit' | 'balance' | 'deleted'>>;
64
+ export interface AccountAllocationOptions {
65
+ /** The store's ledger denomination (`store.config.displayCurrency` as a catalogId). Lowercased on output. */
66
+ ledgerCurrency: string;
67
+ /** Decimal places of `ledgerCurrency`'s minor unit, an integer 0–4. Defaults to 2. */
68
+ ledgerScale?: number;
69
+ /** Unix ms; rows written after it are left out. Omitted, every row counts. */
70
+ asOf?: number;
71
+ }
72
+ /** One debit row after allocation. */
73
+ export interface AllocatedAccountItem {
74
+ /** The debit row's `Account.accountId`. */
75
+ accountId: string;
76
+ /** Absent for orderless debt — an empty `orderId` included, which allocation treats as orderless too. */
77
+ orderId?: string;
78
+ /** `AccountLedgerAllocation.currency`. */
79
+ currency: string;
80
+ /** `YYYYMMDD` ledger day the debit is dated — what an aging read buckets. */
81
+ dated: number;
82
+ /** `YYYYMMDD` due day stamped on the row. Absent means unspecified — never inferred from `dated`. */
83
+ dueDate?: number;
84
+ /** The row's `Account.details`, verbatim when the row carries the key. */
85
+ details?: string;
86
+ /** The row's `createdAt` — the axis allocation ran on. */
87
+ createdAt: number;
88
+ /** The debit, in minor units of `currency`. */
89
+ originalMinor: number;
90
+ /** How much of it credits have covered. */
91
+ paidMinor: number;
92
+ /** `originalMinor - paidMinor`, never negative. */
93
+ remainingMinor: number;
94
+ status: AccountItemStatus;
95
+ }
96
+ /** Every debit of one order, summed. */
97
+ export interface AllocatedAccountOrder {
98
+ originalMinor: number;
99
+ paidMinor: number;
100
+ remainingMinor: number;
101
+ status: AccountItemStatus;
102
+ }
103
+ export interface AccountLedgerAllocation {
104
+ /** The ledger bucket every amount here is in: `ledgerCurrency`, lowercased. */
105
+ currency: string;
106
+ /** Decimal places of one minor unit of `currency`. */
107
+ scale: number;
108
+ /** Every debit row, in allocation order, paid ones included. */
109
+ items: AllocatedAccountItem[];
110
+ /** The items carrying an `orderId`, summed per order. */
111
+ orders: Record<string, AllocatedAccountOrder>;
112
+ /** Credit no debit absorbed — owed to the customer. Never negative. */
113
+ unappliedMinor: number;
114
+ /** Debits minus credits: positive is owed by the customer. Equals the open items' total minus `unappliedMinor`. */
115
+ saldoMinor: number;
116
+ }
117
+ /**
118
+ * Allocate a customer's ledger. Pass the WHOLE ledger — every movement from the
119
+ * first, or a synthesised opening row standing in for what precedes the rows —
120
+ * since a debit missing from the input leaves a later credit landing on the
121
+ * wrong one. Throws a `RangeError` on a scale outside 0–4.
122
+ */
123
+ export declare const allocateAccountLedger: (rows: readonly AllocatableAccountRow[], options: AccountAllocationOptions) => AccountLedgerAllocation;
124
+ /** The items of an allocation that still owe something, in allocation order. */
125
+ export declare const openAccountItems: (allocation: AccountLedgerAllocation) => AllocatedAccountItem[];
@@ -0,0 +1,193 @@
1
+ /**
2
+ * The cuenta-corriente allocator — which customer debits are still open, derived
3
+ * on read from `ACCOUNT#{storeId}` rows, in INTEGER MINOR UNITS. One
4
+ * implementation shared by the api, the operator app and the storefront, so the
5
+ * three agree on which debit a payment can still settle.
6
+ *
7
+ * No allocation entity is stored: a credit carries no pointer to the debit it
8
+ * paid, so every reader re-derives it, and two readers that derive it
9
+ * differently tell the customer two different things. The rules:
10
+ *
11
+ * 1. **Row order is `createdAt` ascending, NEVER `dated`.** A backdated row is
12
+ * allocated where it was written. Ties: a debit before a credit (an order's
13
+ * debit and its auto-linked payment can share a millisecond), then
14
+ * `accountId`, so the result never depends on the order DynamoDB returned.
15
+ * 2. **A credit stamped with an `orderId` serves that order's open debits
16
+ * first**, oldest first, and only debits already on the ledger — it cannot
17
+ * reach a debit of that order written after it.
18
+ * 3. **Everything else applies oldest-first**: an unstamped credit, and the rest
19
+ * of a stamped one once its order is paid (or has no debit yet).
20
+ * 4. **Credit with no open debit to land on waits unapplied** and is drained by
21
+ * the next debit, so a deposit taken before the order is booked still pays
22
+ * it. What is never drained is reported as `unappliedMinor`.
23
+ *
24
+ * Rows it reads, and how:
25
+ *
26
+ * - A `deleted` row, or one whose `createdAt` is past `asOf`, is skipped.
27
+ * - A synthesised opening row (`ACC000001`, `ACC000001-<suffix>` — the "Saldo
28
+ * anterior" a statement leads with) is a movement of its `balance`: positive
29
+ * is an orderless debit, negative an unstamped credit, and any `orderId` on
30
+ * it is ignored. A `balance` on any OTHER row is a projected running total
31
+ * and is ignored.
32
+ * - A row whose `debit` is above zero is a debit (its `credit` is then not
33
+ * read); otherwise a row whose `credit` is above zero at the ledger scale is
34
+ * a credit; anything else moves nothing. A stored `BALANCE-` checkpoint
35
+ * carries neither, so it moves nothing — the ledger must be read from its
36
+ * first row instead, because FIFO over a ledger with a missing debit is
37
+ * wrong.
38
+ * - A refund-in-transit debit (`RIT#…`) is an ordinary debit stamped with its
39
+ * order. It is reported like any other item; a consumer offering debits to
40
+ * pay excludes it by its `accountId` if it must.
41
+ *
42
+ * ⚠️ **ONE ledger bucket — `ledgerCurrency` — never one per row label.** Every
43
+ * writer stores `debit`/`credit` already converted into the store's ledger
44
+ * denomination; a legacy manual row's `currency` labels what the operator
45
+ * TYPED, so bucketing by it files pesos as dollars. Opening rows follow the
46
+ * same rule, a legacy `ACC000001-usd` seed included. Every item therefore
47
+ * carries `ledgerCurrency`.
48
+ *
49
+ * ⚠️ **Exact, with no display tolerance.** An item is `paid` only at zero minor
50
+ * units. A reader that rounds to fewer decimals than the ledger scale (a store
51
+ * pricing in whole units on a two-decimal ledger) can call "paid" what this
52
+ * calls `partial`; that tolerance is the reader's to apply, on top of these
53
+ * figures. Amounts are quantised once per row — half away from zero after
54
+ * normalising the scaled value to 15 significant digits, the api ledger's own
55
+ * rule — and never summed as floats. A missing or non-finite amount is zero.
56
+ *
57
+ * Pure: no clock, no I/O, input rows never mutated.
58
+ */
59
+ /** An item's allocation state, as a code — the reader owns the label. */
60
+ export const ACCOUNT_ITEM_STATUSES = ['open', 'partial', 'paid'];
61
+ const OPENING_ROW_ID = 'ACC000001';
62
+ const DEFAULT_SCALE = 2;
63
+ const MAX_SCALE = 4;
64
+ const isOpeningRow = (accountId) => accountId === OPENING_ROW_ID || accountId.startsWith(`${OPENING_ROW_ID}-`);
65
+ const finiteOrZero = (value) => (typeof value === 'number' && Number.isFinite(value) ? value : 0);
66
+ const toMinorUnits = (amount, scale) => {
67
+ const rounded = Math.round(Number(Math.abs(amount * 10 ** scale).toPrecision(15)));
68
+ // Never `-0`.
69
+ return amount < 0 && rounded !== 0 ? -rounded : rounded;
70
+ };
71
+ const statusOf = (originalMinor, paidMinor) => {
72
+ if (originalMinor - paidMinor <= 0)
73
+ return 'paid';
74
+ return paidMinor > 0 ? 'partial' : 'open';
75
+ };
76
+ const toMove = (row, scale) => {
77
+ const createdAt = finiteOrZero(row.createdAt);
78
+ if (isOpeningRow(row.accountId)) {
79
+ const opening = toMinorUnits(finiteOrZero(row.balance), scale);
80
+ return {
81
+ row,
82
+ createdAt,
83
+ isDebit: opening > 0,
84
+ debitMinor: Math.max(opening, 0),
85
+ creditMinor: Math.max(-opening, 0),
86
+ };
87
+ }
88
+ const debit = finiteOrZero(row.debit);
89
+ return {
90
+ row,
91
+ createdAt,
92
+ // The RAW amount decides the kind, as the operator app does: a debit too small to reach one
93
+ // minor unit is still a (zero) debit, and its `credit` is still not read.
94
+ isDebit: debit > 0,
95
+ debitMinor: toMinorUnits(debit, scale),
96
+ creditMinor: toMinorUnits(finiteOrZero(row.credit), scale),
97
+ ...(row.orderId ? { orderId: row.orderId } : {}),
98
+ };
99
+ };
100
+ const compareMoves = (a, b) => a.createdAt - b.createdAt || Number(b.isDebit) - Number(a.isDebit) || a.row.accountId.localeCompare(b.row.accountId);
101
+ /** Serves `debits` in order; returns the credit left over. */
102
+ const applyFifo = (debits, creditMinor) => {
103
+ let remaining = creditMinor;
104
+ for (const debit of debits) {
105
+ if (remaining <= 0)
106
+ break;
107
+ const open = debit.totalMinor - debit.paidMinor;
108
+ if (open <= 0)
109
+ continue;
110
+ const applied = Math.min(open, remaining);
111
+ debit.paidMinor += applied;
112
+ remaining -= applied;
113
+ }
114
+ return remaining;
115
+ };
116
+ const toItem = (tracker, currency) => {
117
+ const { row } = tracker;
118
+ return {
119
+ accountId: row.accountId,
120
+ ...(tracker.orderId ? { orderId: tracker.orderId } : {}),
121
+ currency,
122
+ dated: row.dated,
123
+ ...(row.dueDate === undefined ? {} : { dueDate: row.dueDate }),
124
+ ...(row.details === undefined ? {} : { details: row.details }),
125
+ createdAt: tracker.createdAt,
126
+ originalMinor: tracker.totalMinor,
127
+ paidMinor: tracker.paidMinor,
128
+ remainingMinor: Math.max(tracker.totalMinor - tracker.paidMinor, 0),
129
+ status: statusOf(tracker.totalMinor, tracker.paidMinor),
130
+ };
131
+ };
132
+ const summariseOrders = (items) => {
133
+ const orders = {};
134
+ for (const item of items) {
135
+ if (!item.orderId)
136
+ continue;
137
+ const originalMinor = (orders[item.orderId]?.originalMinor ?? 0) + item.originalMinor;
138
+ const paidMinor = (orders[item.orderId]?.paidMinor ?? 0) + item.paidMinor;
139
+ orders[item.orderId] = {
140
+ originalMinor,
141
+ paidMinor,
142
+ remainingMinor: Math.max(originalMinor - paidMinor, 0),
143
+ status: statusOf(originalMinor, paidMinor),
144
+ };
145
+ }
146
+ return orders;
147
+ };
148
+ /**
149
+ * Allocate a customer's ledger. Pass the WHOLE ledger — every movement from the
150
+ * first, or a synthesised opening row standing in for what precedes the rows —
151
+ * since a debit missing from the input leaves a later credit landing on the
152
+ * wrong one. Throws a `RangeError` on a scale outside 0–4.
153
+ */
154
+ export const allocateAccountLedger = (rows, options) => {
155
+ const scale = options.ledgerScale ?? DEFAULT_SCALE;
156
+ if (!Number.isInteger(scale) || scale < 0 || scale > MAX_SCALE) {
157
+ throw new RangeError(`Ledger scale must be an integer in 0-${MAX_SCALE}, got ${scale}`);
158
+ }
159
+ const currency = options.ledgerCurrency.toLowerCase();
160
+ const { asOf } = options;
161
+ const moves = rows
162
+ .filter((row) => !row.deleted && (asOf === undefined || finiteOrZero(row.createdAt) <= asOf))
163
+ .map((row) => toMove(row, scale))
164
+ .sort(compareMoves);
165
+ const debits = [];
166
+ let unappliedMinor = 0;
167
+ let saldoMinor = 0;
168
+ for (const move of moves) {
169
+ if (move.isDebit) {
170
+ saldoMinor += move.debitMinor;
171
+ debits.push({
172
+ row: move.row,
173
+ createdAt: move.createdAt,
174
+ ...(move.orderId ? { orderId: move.orderId } : {}),
175
+ totalMinor: move.debitMinor,
176
+ paidMinor: 0,
177
+ });
178
+ unappliedMinor = applyFifo(debits, unappliedMinor);
179
+ continue;
180
+ }
181
+ if (move.creditMinor <= 0)
182
+ continue;
183
+ saldoMinor -= move.creditMinor;
184
+ const spare = move.orderId
185
+ ? applyFifo(debits.filter((debit) => debit.orderId === move.orderId), move.creditMinor)
186
+ : move.creditMinor;
187
+ unappliedMinor = applyFifo(debits, unappliedMinor + spare);
188
+ }
189
+ const items = debits.map((tracker) => toItem(tracker, currency));
190
+ return { currency, scale, items, orders: summariseOrders(items), unappliedMinor, saldoMinor };
191
+ };
192
+ /** The items of an allocation that still owe something, in allocation order. */
193
+ export const openAccountItems = (allocation) => allocation.items.filter((item) => item.remainingMinor > 0);
@@ -0,0 +1,198 @@
1
+ "use strict";
2
+ /**
3
+ * The cuenta-corriente allocator — which customer debits are still open, derived
4
+ * on read from `ACCOUNT#{storeId}` rows, in INTEGER MINOR UNITS. One
5
+ * implementation shared by the api, the operator app and the storefront, so the
6
+ * three agree on which debit a payment can still settle.
7
+ *
8
+ * No allocation entity is stored: a credit carries no pointer to the debit it
9
+ * paid, so every reader re-derives it, and two readers that derive it
10
+ * differently tell the customer two different things. The rules:
11
+ *
12
+ * 1. **Row order is `createdAt` ascending, NEVER `dated`.** A backdated row is
13
+ * allocated where it was written. Ties: a debit before a credit (an order's
14
+ * debit and its auto-linked payment can share a millisecond), then
15
+ * `accountId`, so the result never depends on the order DynamoDB returned.
16
+ * 2. **A credit stamped with an `orderId` serves that order's open debits
17
+ * first**, oldest first, and only debits already on the ledger — it cannot
18
+ * reach a debit of that order written after it.
19
+ * 3. **Everything else applies oldest-first**: an unstamped credit, and the rest
20
+ * of a stamped one once its order is paid (or has no debit yet).
21
+ * 4. **Credit with no open debit to land on waits unapplied** and is drained by
22
+ * the next debit, so a deposit taken before the order is booked still pays
23
+ * it. What is never drained is reported as `unappliedMinor`.
24
+ *
25
+ * Rows it reads, and how:
26
+ *
27
+ * - A `deleted` row, or one whose `createdAt` is past `asOf`, is skipped.
28
+ * - A synthesised opening row (`ACC000001`, `ACC000001-<suffix>` — the "Saldo
29
+ * anterior" a statement leads with) is a movement of its `balance`: positive
30
+ * is an orderless debit, negative an unstamped credit, and any `orderId` on
31
+ * it is ignored. A `balance` on any OTHER row is a projected running total
32
+ * and is ignored.
33
+ * - A row whose `debit` is above zero is a debit (its `credit` is then not
34
+ * read); otherwise a row whose `credit` is above zero at the ledger scale is
35
+ * a credit; anything else moves nothing. A stored `BALANCE-` checkpoint
36
+ * carries neither, so it moves nothing — the ledger must be read from its
37
+ * first row instead, because FIFO over a ledger with a missing debit is
38
+ * wrong.
39
+ * - A refund-in-transit debit (`RIT#…`) is an ordinary debit stamped with its
40
+ * order. It is reported like any other item; a consumer offering debits to
41
+ * pay excludes it by its `accountId` if it must.
42
+ *
43
+ * ⚠️ **ONE ledger bucket — `ledgerCurrency` — never one per row label.** Every
44
+ * writer stores `debit`/`credit` already converted into the store's ledger
45
+ * denomination; a legacy manual row's `currency` labels what the operator
46
+ * TYPED, so bucketing by it files pesos as dollars. Opening rows follow the
47
+ * same rule, a legacy `ACC000001-usd` seed included. Every item therefore
48
+ * carries `ledgerCurrency`.
49
+ *
50
+ * ⚠️ **Exact, with no display tolerance.** An item is `paid` only at zero minor
51
+ * units. A reader that rounds to fewer decimals than the ledger scale (a store
52
+ * pricing in whole units on a two-decimal ledger) can call "paid" what this
53
+ * calls `partial`; that tolerance is the reader's to apply, on top of these
54
+ * figures. Amounts are quantised once per row — half away from zero after
55
+ * normalising the scaled value to 15 significant digits, the api ledger's own
56
+ * rule — and never summed as floats. A missing or non-finite amount is zero.
57
+ *
58
+ * Pure: no clock, no I/O, input rows never mutated.
59
+ */
60
+ Object.defineProperty(exports, "__esModule", { value: true });
61
+ exports.openAccountItems = exports.allocateAccountLedger = exports.ACCOUNT_ITEM_STATUSES = void 0;
62
+ /** An item's allocation state, as a code — the reader owns the label. */
63
+ exports.ACCOUNT_ITEM_STATUSES = ['open', 'partial', 'paid'];
64
+ const OPENING_ROW_ID = 'ACC000001';
65
+ const DEFAULT_SCALE = 2;
66
+ const MAX_SCALE = 4;
67
+ const isOpeningRow = (accountId) => accountId === OPENING_ROW_ID || accountId.startsWith(`${OPENING_ROW_ID}-`);
68
+ const finiteOrZero = (value) => (typeof value === 'number' && Number.isFinite(value) ? value : 0);
69
+ const toMinorUnits = (amount, scale) => {
70
+ const rounded = Math.round(Number(Math.abs(amount * 10 ** scale).toPrecision(15)));
71
+ // Never `-0`.
72
+ return amount < 0 && rounded !== 0 ? -rounded : rounded;
73
+ };
74
+ const statusOf = (originalMinor, paidMinor) => {
75
+ if (originalMinor - paidMinor <= 0)
76
+ return 'paid';
77
+ return paidMinor > 0 ? 'partial' : 'open';
78
+ };
79
+ const toMove = (row, scale) => {
80
+ const createdAt = finiteOrZero(row.createdAt);
81
+ if (isOpeningRow(row.accountId)) {
82
+ const opening = toMinorUnits(finiteOrZero(row.balance), scale);
83
+ return {
84
+ row,
85
+ createdAt,
86
+ isDebit: opening > 0,
87
+ debitMinor: Math.max(opening, 0),
88
+ creditMinor: Math.max(-opening, 0),
89
+ };
90
+ }
91
+ const debit = finiteOrZero(row.debit);
92
+ return {
93
+ row,
94
+ createdAt,
95
+ // The RAW amount decides the kind, as the operator app does: a debit too small to reach one
96
+ // minor unit is still a (zero) debit, and its `credit` is still not read.
97
+ isDebit: debit > 0,
98
+ debitMinor: toMinorUnits(debit, scale),
99
+ creditMinor: toMinorUnits(finiteOrZero(row.credit), scale),
100
+ ...(row.orderId ? { orderId: row.orderId } : {}),
101
+ };
102
+ };
103
+ const compareMoves = (a, b) => a.createdAt - b.createdAt || Number(b.isDebit) - Number(a.isDebit) || a.row.accountId.localeCompare(b.row.accountId);
104
+ /** Serves `debits` in order; returns the credit left over. */
105
+ const applyFifo = (debits, creditMinor) => {
106
+ let remaining = creditMinor;
107
+ for (const debit of debits) {
108
+ if (remaining <= 0)
109
+ break;
110
+ const open = debit.totalMinor - debit.paidMinor;
111
+ if (open <= 0)
112
+ continue;
113
+ const applied = Math.min(open, remaining);
114
+ debit.paidMinor += applied;
115
+ remaining -= applied;
116
+ }
117
+ return remaining;
118
+ };
119
+ const toItem = (tracker, currency) => {
120
+ const { row } = tracker;
121
+ return {
122
+ accountId: row.accountId,
123
+ ...(tracker.orderId ? { orderId: tracker.orderId } : {}),
124
+ currency,
125
+ dated: row.dated,
126
+ ...(row.dueDate === undefined ? {} : { dueDate: row.dueDate }),
127
+ ...(row.details === undefined ? {} : { details: row.details }),
128
+ createdAt: tracker.createdAt,
129
+ originalMinor: tracker.totalMinor,
130
+ paidMinor: tracker.paidMinor,
131
+ remainingMinor: Math.max(tracker.totalMinor - tracker.paidMinor, 0),
132
+ status: statusOf(tracker.totalMinor, tracker.paidMinor),
133
+ };
134
+ };
135
+ const summariseOrders = (items) => {
136
+ const orders = {};
137
+ for (const item of items) {
138
+ if (!item.orderId)
139
+ continue;
140
+ const originalMinor = (orders[item.orderId]?.originalMinor ?? 0) + item.originalMinor;
141
+ const paidMinor = (orders[item.orderId]?.paidMinor ?? 0) + item.paidMinor;
142
+ orders[item.orderId] = {
143
+ originalMinor,
144
+ paidMinor,
145
+ remainingMinor: Math.max(originalMinor - paidMinor, 0),
146
+ status: statusOf(originalMinor, paidMinor),
147
+ };
148
+ }
149
+ return orders;
150
+ };
151
+ /**
152
+ * Allocate a customer's ledger. Pass the WHOLE ledger — every movement from the
153
+ * first, or a synthesised opening row standing in for what precedes the rows —
154
+ * since a debit missing from the input leaves a later credit landing on the
155
+ * wrong one. Throws a `RangeError` on a scale outside 0–4.
156
+ */
157
+ const allocateAccountLedger = (rows, options) => {
158
+ const scale = options.ledgerScale ?? DEFAULT_SCALE;
159
+ if (!Number.isInteger(scale) || scale < 0 || scale > MAX_SCALE) {
160
+ throw new RangeError(`Ledger scale must be an integer in 0-${MAX_SCALE}, got ${scale}`);
161
+ }
162
+ const currency = options.ledgerCurrency.toLowerCase();
163
+ const { asOf } = options;
164
+ const moves = rows
165
+ .filter((row) => !row.deleted && (asOf === undefined || finiteOrZero(row.createdAt) <= asOf))
166
+ .map((row) => toMove(row, scale))
167
+ .sort(compareMoves);
168
+ const debits = [];
169
+ let unappliedMinor = 0;
170
+ let saldoMinor = 0;
171
+ for (const move of moves) {
172
+ if (move.isDebit) {
173
+ saldoMinor += move.debitMinor;
174
+ debits.push({
175
+ row: move.row,
176
+ createdAt: move.createdAt,
177
+ ...(move.orderId ? { orderId: move.orderId } : {}),
178
+ totalMinor: move.debitMinor,
179
+ paidMinor: 0,
180
+ });
181
+ unappliedMinor = applyFifo(debits, unappliedMinor);
182
+ continue;
183
+ }
184
+ if (move.creditMinor <= 0)
185
+ continue;
186
+ saldoMinor -= move.creditMinor;
187
+ const spare = move.orderId
188
+ ? applyFifo(debits.filter((debit) => debit.orderId === move.orderId), move.creditMinor)
189
+ : move.creditMinor;
190
+ unappliedMinor = applyFifo(debits, unappliedMinor + spare);
191
+ }
192
+ const items = debits.map((tracker) => toItem(tracker, currency));
193
+ return { currency, scale, items, orders: summariseOrders(items), unappliedMinor, saldoMinor };
194
+ };
195
+ exports.allocateAccountLedger = allocateAccountLedger;
196
+ /** The items of an allocation that still owe something, in allocation order. */
197
+ const openAccountItems = (allocation) => allocation.items.filter((item) => item.remainingMinor > 0);
198
+ exports.openAccountItems = openAccountItems;
package/dist/cjs/index.js CHANGED
@@ -23,6 +23,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
23
23
  // the published ESM build keeps the extension Node's resolver demands — bare
24
24
  // `export * from "./account"` would throw ERR_MODULE_NOT_FOUND at import time.
25
25
  __exportStar(require("./account.js"), exports);
26
+ __exportStar(require("./accountAllocation.js"), exports);
26
27
  __exportStar(require("./afip.js"), exports);
27
28
  __exportStar(require("./ai.js"), exports);
28
29
  __exportStar(require("./api.js"), exports);
@@ -1,2 +1,46 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PAYMENT_CREDIT_REVERSAL_LOCK_REASONS = exports.PAYMENT_CREDIT_REVERSAL_ERROR_CODES = void 0;
4
+ /**
5
+ * The credit reversal's refusals (`PaymentCreditReversalRequest`). Each writes nothing.
6
+ *
7
+ * - `ADMIN_REQUIRED` · 403 — the token is below the ADMIN tier (an `ACCOUNTANT` is `403 ROLE_FORBIDDEN`, earlier).
8
+ * - `INVALID_INPUT` · 400 — the path's `source` is not `mp`, `stripe` or `mp_movement`.
9
+ * - `VALIDATION_FAILED` · 400 — the body is not `{ mode, reason }` or the reason is refused.
10
+ * - `PAYMENT_NOT_FOUND` · 404 — the store has no such payment.
11
+ * - `LINK_LOCKED` · 409 — `reason` is one of {@link PAYMENT_CREDIT_REVERSAL_LOCK_REASONS}.
12
+ * - `CUSTOMER_MERGED` · 409 — the credit's owner is mid-merge (or the payment carries a historical
13
+ * merged-refund flag); `data.mergedInto` names the survivor.
14
+ * - `CUSTOMER_ANONYMIZED` · 409 — a legacy credit naming no customer, on an erased customer.
15
+ * - `CUSTOMER_NOT_FOUND` · 404 — the customer whose balance the credit moved is gone.
16
+ */
17
+ exports.PAYMENT_CREDIT_REVERSAL_ERROR_CODES = [
18
+ 'ADMIN_REQUIRED',
19
+ 'INVALID_INPUT',
20
+ 'VALIDATION_FAILED',
21
+ 'PAYMENT_NOT_FOUND',
22
+ 'LINK_LOCKED',
23
+ 'CUSTOMER_MERGED',
24
+ 'CUSTOMER_ANONYMIZED',
25
+ 'CUSTOMER_NOT_FOUND',
26
+ ];
27
+ /**
28
+ * Every `reason` of the credit reversal's `409 LINK_LOCKED`:
29
+ *
30
+ * - `credit-reversed` — this credit was already reversed (`reversalAccountId` names the row): a second
31
+ * reversal of the same credit, a replay included.
32
+ * - `not-credited` — the payment's `accountId` names no credit of its own (never credited, or a pointer to
33
+ * a row this payment did not write), or the payment names no customer (un-linked by hand).
34
+ * - `refund-obligation` — a refund obligation already debited part of this credit as in transit
35
+ * (`refundObligationId`); reversing it all would debit that share twice.
36
+ * - `edited-ledger-pointer` — the payment's `fxAdjustmentAccountId` names a row that is not its own FX
37
+ * adjustment for the credit's customer, so the delta to reverse cannot be named.
38
+ * - `concurrent-link` — the payment or its ledger rows kept changing while the reversal ran; retry.
39
+ */
40
+ exports.PAYMENT_CREDIT_REVERSAL_LOCK_REASONS = [
41
+ 'credit-reversed',
42
+ 'not-credited',
43
+ 'refund-obligation',
44
+ 'edited-ledger-pointer',
45
+ 'concurrent-link',
46
+ ];
@@ -26,6 +26,10 @@ const storefrontDesignRegistry_ts_1 = require("./storefrontDesignRegistry.js");
26
26
  * parameters.
27
27
  */
28
28
  exports.STOREFRONT_WEB_LITERAL_KEYS = Object.freeze([
29
+ 'ADDRESSES_IS_DEFAULT',
30
+ 'ADDRESSES_LIMIT_REACHED',
31
+ 'ADDRESSES_NOT_FOUND',
32
+ 'ADDRESSES_WRITE_CONTENDED',
29
33
  'ALERT_INCLUDED_VAT',
30
34
  'APPOINTMENTS_BOOKED_DESC',
31
35
  'APPOINTMENTS_BOOKED_TITLE',
@@ -323,9 +327,11 @@ exports.STOREFRONT_WEB_LITERAL_KEYS = Object.freeze([
323
327
  'BASKET_LINE_LIMIT_REACHED',
324
328
  'BASKET_LINE_NOT_LOADED',
325
329
  'BASKET_LINE_NOT_OFFERED',
330
+ 'BASKET_LINE_NO_PRICE',
326
331
  'BASKET_LINE_OUT_OF_STOCK',
327
332
  'BASKET_LINE_STOCK_SHORT_HIDDEN',
328
333
  'BASKET_LINE_UNAVAILABLE',
334
+ 'BASKET_LINE_WITHDRAWN',
329
335
  'BASKET_LOADING',
330
336
  'BASKET_MIN_BUY_MISSING',
331
337
  'BASKET_NEXT_BUTTON',
@@ -356,8 +362,28 @@ exports.STOREFRONT_WEB_LITERAL_KEYS = Object.freeze([
356
362
  'BASKET_UNDO_ACTION',
357
363
  'BASKET_UPDATED_CONFIRMATION',
358
364
  'BASKET_VIEW_BASKET_ACTION',
365
+ 'BRANDS_DIRECTORY_EMPTY',
359
366
  'BRANDS_ERR_LOAD',
367
+ 'BRANDS_FILTER_ALL',
368
+ 'BRANDS_JUMP_LABEL',
369
+ 'BRANDS_LETTERS_LABEL',
370
+ 'BRANDS_LETTER_OTHER',
371
+ 'BRANDS_LETTER_OTHER_LABEL',
372
+ 'BRANDS_SEARCH_CLEAR',
373
+ 'BRANDS_SEARCH_LABEL',
374
+ 'BRANDS_SHOW_MORE',
360
375
  'BRANDS_TITLE',
376
+ 'BRAND_BREADCRUMB_LABEL',
377
+ 'BRAND_DESCRIPTION_MORE',
378
+ 'CATEGORY_DIRECTORY_EMPTY',
379
+ 'CATEGORY_DIRECTORY_EMPTY_ACTION',
380
+ 'CATEGORY_DIRECTORY_FAILED',
381
+ 'CATEGORY_DIRECTORY_JUMP_LABEL',
382
+ 'CATEGORY_MORE_ROOTS_TITLE',
383
+ 'CATEGORY_SHOW_LESS',
384
+ 'CATEGORY_SIBLINGS_LABEL',
385
+ 'CATEGORY_SUBCATEGORIES_LABEL',
386
+ 'CATEGORY_VIEW_ALL',
361
387
  'CHECKOUT_ACCESS_PENDING',
362
388
  'CHECKOUT_ACCESS_REJECTED',
363
389
  'CHECKOUT_ACCOUNT_DISABLED',
@@ -426,7 +452,6 @@ exports.STOREFRONT_WEB_LITERAL_KEYS = Object.freeze([
426
452
  'CHECKOUT_DNI_REQUIRED_MESSAGE',
427
453
  'CHECKOUT_DNI_REQUIRED_MESSAGE_NO_THRESHOLD',
428
454
  'CHECKOUT_EDIT_BASKET',
429
- 'CHECKOUT_EMAIL_VERIFICATION_REQUIRED',
430
455
  'CHECKOUT_EMPTY_BASKET',
431
456
  'CHECKOUT_FX_NOTICE_PAYING',
432
457
  'CHECKOUT_FX_NOTICE_PREFERRED',
@@ -541,11 +566,13 @@ exports.STOREFRONT_WEB_LITERAL_KEYS = Object.freeze([
541
566
  'DATA_RIGHTS_COLLECTION_CONTACTS',
542
567
  'DATA_RIGHTS_COLLECTION_FAVORITES',
543
568
  'DATA_RIGHTS_COLLECTION_INVOICES',
569
+ 'DATA_RIGHTS_COLLECTION_LOYALTY',
544
570
  'DATA_RIGHTS_COLLECTION_NOTES',
545
571
  'DATA_RIGHTS_COLLECTION_ORDERS',
546
572
  'DATA_RIGHTS_COLLECTION_PAYMENTS',
547
573
  'DATA_RIGHTS_COLLECTION_QUOTE_REQUESTS',
548
574
  'DATA_RIGHTS_COLLECTION_RECENT_PURCHASES',
575
+ 'DATA_RIGHTS_COLLECTION_SERVICE_ORDERS',
549
576
  'DATA_RIGHTS_COLLECTION_WHOLESALE_REQUESTS',
550
577
  'DATA_RIGHTS_DIALOG_ACK',
551
578
  'DATA_RIGHTS_DIALOG_CONFIRM',
@@ -626,6 +653,11 @@ exports.STOREFRONT_WEB_LITERAL_KEYS = Object.freeze([
626
653
  'FAVORITES_TOGGLE_ARIA',
627
654
  'FAVORITES_TOGGLE_NAME',
628
655
  'FAVORITES_UNAVAILABLE',
656
+ 'FOOTER_DEFAULT_ACCOUNT_TITLE',
657
+ 'FOOTER_DEFAULT_CONTACT',
658
+ 'FOOTER_DEFAULT_HELP_TITLE',
659
+ 'FOOTER_DEFAULT_SHOP_TITLE',
660
+ 'FOOTER_LEGAL_NAV_ARIA',
629
661
  'FOOTER_PRIVACY_POLICY',
630
662
  'FORMS_FIELD_DNI_LABEL',
631
663
  'FORMS_FIELD_PHONE_LABEL',
@@ -672,7 +704,6 @@ exports.STOREFRONT_WEB_LITERAL_KEYS = Object.freeze([
672
704
  'FORMS_ZOD_VALUE_OPTIONS',
673
705
  'FORM_FIX_ERRORS',
674
706
  'GLOBALS_APPOINTMENTS',
675
- 'GLOBALS_APP_VERSION',
676
707
  'GLOBALS_BASKET',
677
708
  'GLOBALS_BRANDS',
678
709
  'GLOBALS_CATEGORIES',
@@ -744,6 +775,9 @@ exports.STOREFRONT_WEB_LITERAL_KEYS = Object.freeze([
744
775
  'HEADER_SEARCH_INPUT',
745
776
  'HEADER_SEARCH_LABEL',
746
777
  'HEADER_SEARCH_RECENT',
778
+ 'HEADER_SEARCH_RECENT_CLEAR',
779
+ 'HEADER_SEARCH_RECENT_CLEAR_ARIA',
780
+ 'HEADER_SEARCH_RECENT_REMOVE',
747
781
  'HEADER_SEARCH_SUGGESTIONS',
748
782
  'HEADER_SEARCH_SUGGESTION_BARCODE',
749
783
  'HEADER_SEARCH_SUGGESTION_SKU',
@@ -889,7 +923,9 @@ exports.STOREFRONT_WEB_LITERAL_KEYS = Object.freeze([
889
923
  'MAINTENANCE_REDIRECT_TO_BASKET',
890
924
  'MAINTENANCE_SUPPORT_HINT_EMAIL',
891
925
  'MAINTENANCE_SURVEY_DISABLED',
926
+ 'MAINTENANCE_WHATSAPP_OPT_IN_DISABLED',
892
927
  'MAINTENANCE_WHOLESALE_REQUEST_DISABLED',
928
+ 'MAINTENANCE_WITHDRAWAL_DISABLED',
893
929
  'MARKETING_ACTIVATE_ALL',
894
930
  'MARKETING_ACTIVE_COUNT',
895
931
  'MARKETING_CHANNEL_ADS',
@@ -898,12 +934,6 @@ exports.STOREFRONT_WEB_LITERAL_KEYS = Object.freeze([
898
934
  'MARKETING_CHANNEL_EMAIL',
899
935
  'MARKETING_CHANNEL_EMAIL_DESC',
900
936
  'MARKETING_CHANNEL_EMAIL_EXAMPLE',
901
- 'MARKETING_CHANNEL_SMS',
902
- 'MARKETING_CHANNEL_SMS_DESC',
903
- 'MARKETING_CHANNEL_SMS_EXAMPLE',
904
- 'MARKETING_CHANNEL_WHATSAPP',
905
- 'MARKETING_CHANNEL_WHATSAPP_DESC',
906
- 'MARKETING_CHANNEL_WHATSAPP_EXAMPLE',
907
937
  'MARKETING_DEACTIVATE_ALL',
908
938
  'MARKETING_ERROR_MESSAGE',
909
939
  'MARKETING_EXAMPLE_PREFIX',
@@ -923,6 +953,7 @@ exports.STOREFRONT_WEB_LITERAL_KEYS = Object.freeze([
923
953
  'MOBILE_BUTTONS_BASKET',
924
954
  'MOBILE_BUTTONS_FAVORITES',
925
955
  'MOBILE_BUTTONS_INVOICES',
956
+ 'MOBILE_BUTTONS_NAV_ARIA',
926
957
  'MOBILE_BUTTONS_ORDERS',
927
958
  'NETWORK_OFFLINE',
928
959
  'NETWORK_REQUEST_FAILED',
@@ -951,6 +982,10 @@ exports.STOREFRONT_WEB_LITERAL_KEYS = Object.freeze([
951
982
  'NOTIFICATION_PREFS_SUBTITLE',
952
983
  'NOTIFICATION_PREFS_TITLE',
953
984
  'NOTIFICATION_PREFS_UNAVAILABLE_RETRY',
985
+ 'NOTIFICATION_PREFS_WHATSAPP_ARIA',
986
+ 'NOTIFICATION_PREFS_WHATSAPP_DESCRIPTION',
987
+ 'NOTIFICATION_PREFS_WHATSAPP_LABEL',
988
+ 'NOTIFICATION_PREFS_WHATSAPP_NOT_SAVED',
954
989
  'NOT_FOUND_BACK_HOME',
955
990
  'NOT_FOUND_PAGE_BODY',
956
991
  'NOT_FOUND_PAGE_TITLE',
@@ -1179,6 +1214,7 @@ exports.STOREFRONT_WEB_LITERAL_KEYS = Object.freeze([
1179
1214
  'PREVIEW_UNAVAILABLE_ACTION',
1180
1215
  'PREVIEW_VARIANTS',
1181
1216
  'PREVIEW_WAITING',
1217
+ 'PRICING_NET_PLUS_IVA',
1182
1218
  'PRICING_TIER_CHIP',
1183
1219
  'PRICING_TIER_FROM',
1184
1220
  'PRICING_TIER_UNITS',
@@ -1189,10 +1225,14 @@ exports.STOREFRONT_WEB_LITERAL_KEYS = Object.freeze([
1189
1225
  'PRODUCTS_CLEAR_FILTERS',
1190
1226
  'PRODUCTS_FILTERS_LABEL',
1191
1227
  'PRODUCTS_FILTER_ALL_BRANDS',
1228
+ 'PRODUCTS_FILTER_ALL_CATEGORIES',
1192
1229
  'PRODUCTS_FILTER_ALL_PRODUCTS',
1193
1230
  'PRODUCTS_FILTER_ALL_TAGS',
1194
1231
  'PRODUCTS_FILTER_AVAILABILITY',
1195
1232
  'PRODUCTS_FILTER_BRAND',
1233
+ 'PRODUCTS_FILTER_BRAND_UNAVAILABLE',
1234
+ 'PRODUCTS_FILTER_CATEGORY',
1235
+ 'PRODUCTS_FILTER_CATEGORY_UNAVAILABLE',
1196
1236
  'PRODUCTS_FILTER_IN_OFFER',
1197
1237
  'PRODUCTS_FILTER_IN_STOCK',
1198
1238
  'PRODUCTS_FILTER_TAG',
@@ -1203,10 +1243,14 @@ exports.STOREFRONT_WEB_LITERAL_KEYS = Object.freeze([
1203
1243
  'PRODUCTS_NO_INCOMES',
1204
1244
  'PRODUCTS_NO_MATCHES',
1205
1245
  'PRODUCTS_NO_RESULTS',
1246
+ 'PRODUCTS_PICK_SUBCATEGORY',
1206
1247
  'PRODUCTS_SEARCH_TITLE',
1207
- 'PRODUCTS_SELECT_CATEGORY',
1208
1248
  'PRODUCTS_SORT_LABEL',
1249
+ 'PRODUCTS_SORT_NAME_ASC',
1250
+ 'PRODUCTS_SORT_NAME_DESC',
1209
1251
  'PRODUCTS_SORT_NEWEST',
1252
+ 'PRODUCTS_SORT_PRICE_ASC',
1253
+ 'PRODUCTS_SORT_PRICE_DESC',
1210
1254
  'PRODUCTS_SORT_RELEVANCE',
1211
1255
  'PRODUCTS_TAG_NOT_FOUND',
1212
1256
  'PRODUCTS_TAG_TITLE',
@@ -1250,6 +1294,8 @@ exports.STOREFRONT_WEB_LITERAL_KEYS = Object.freeze([
1250
1294
  'PRODUCT_INFO_TITLE',
1251
1295
  'PRODUCT_LOAD_FAILED',
1252
1296
  'PRODUCT_PAGE_TITLE',
1297
+ 'PRODUCT_PRICE_ON_REQUEST',
1298
+ 'PRODUCT_PRICE_ON_REQUEST_HINT',
1253
1299
  'PRODUCT_PRICE_PER_UNIT',
1254
1300
  'PRODUCT_PROMOTIONS_HINT',
1255
1301
  'PRODUCT_PROMOTIONS_TITLE',
@@ -1401,6 +1447,7 @@ exports.STOREFRONT_WEB_LITERAL_KEYS = Object.freeze([
1401
1447
  'QUOTE_REQUEST_SIGNED_IN_HINT',
1402
1448
  'QUOTE_REQUEST_SUBMIT',
1403
1449
  'QUOTE_REQUEST_SUCCESS',
1450
+ 'REORDER_SHELF_TITLE',
1404
1451
  'SALE_BADGE_OFFER',
1405
1452
  'SALE_CHIP_ALL',
1406
1453
  'SALE_DISCOUNT_PERCENT',
@@ -1489,6 +1536,14 @@ exports.STOREFRONT_WEB_LITERAL_KEYS = Object.freeze([
1489
1536
  'SURVEY_TITLE',
1490
1537
  'SYNC_ALERT_ERROR_MESSAGE',
1491
1538
  'SYNC_ALERT_OFFLINE_MESSAGE',
1539
+ 'WHATSAPP_OPT_IN_ERROR',
1540
+ 'WHATSAPP_OPT_IN_HELP_GUEST',
1541
+ 'WHATSAPP_OPT_IN_HELP_SIGNED_IN',
1542
+ 'WHATSAPP_OPT_IN_OFFER_ACTION',
1543
+ 'WHATSAPP_OPT_IN_OFFER_TEXT',
1544
+ 'WHATSAPP_OPT_IN_PROFILE_LINK',
1545
+ 'WHATSAPP_OPT_IN_SUCCESS',
1546
+ 'WHATSAPP_OPT_IN_UNAVAILABLE',
1492
1547
  'WHOLESALE_REQUEST_ACTIVITY_LABEL',
1493
1548
  'WHOLESALE_REQUEST_ALREADY_PENDING',
1494
1549
  'WHOLESALE_REQUEST_APPROVED',
@@ -731,6 +731,13 @@ declare global {
731
731
  * `balance` deltas. Those are arithmetic (`ADD balance :add`) precisely so
732
732
  * concurrent writers compose; replacing this array wholesale loses updates
733
733
  * the arithmetic next to it would have kept.
734
+ *
735
+ * ⚠️ **No wire carries it.** Every boundary that spreads a raw CUSTOMER row
736
+ * (the session responses, the operator point read, the write echo) strips
737
+ * it with `openDebitsOverflow`; only the server's own report reads it. A
738
+ * client showing a customer's open debits derives them from the ledger with
739
+ * `allocateAccountLedger` — this summary ignores order stamps and is capped,
740
+ * so the two can disagree.
734
741
  */
735
742
  openDebits?: OpenDebit[];
736
743
  /**
@@ -740,6 +747,7 @@ declare global {
740
747
  * ⚠️ Absence means "not overflowed", never "unknown" — but a reader must
741
748
  * still not treat a present-and-`false` value as proof the array is
742
749
  * complete on a row written before this field existed. Forward-only.
750
+ * Stripped from every wire with `openDebits`.
743
751
  */
744
752
  openDebitsOverflow?: boolean;
745
753
  /**
package/dist/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  export * from './account.ts';
2
+ export * from './accountAllocation.ts';
2
3
  export * from './afip.ts';
3
4
  export * from './ai.ts';
4
5
  export * from './api.ts';
package/dist/index.js CHANGED
@@ -7,6 +7,7 @@
7
7
  // the published ESM build keeps the extension Node's resolver demands — bare
8
8
  // `export * from "./account"` would throw ERR_MODULE_NOT_FOUND at import time.
9
9
  export * from "./account.js";
10
+ export * from "./accountAllocation.js";
10
11
  export * from "./afip.js";
11
12
  export * from "./ai.js";
12
13
  export * from "./api.js";
package/dist/payment.d.ts CHANGED
@@ -199,5 +199,77 @@ declare global {
199
199
  customers: CustomerCandidate[];
200
200
  orders: OrderCandidate[];
201
201
  }
202
+ /**
203
+ * `POST /payments/{source}/{paymentId}/link` with `mode: 'reverse-credit'` — ADMIN tier only
204
+ * (`ADMIN`, `SUPERVISOR`, `MANAGER`; anyone else `403 ADMIN_REQUIRED` — an `ACCOUNTANT` token never reaches
205
+ * the route: the API's accountant gate answers it `403 ROLE_FORBIDDEN` first), refused under impersonation
206
+ * (`payment.creditReverse`). Writes an `ACCOUNT#` row reversing the ledger credit the payment's link
207
+ * booked — on any day, whoever linked it (an operator, the MercadoLibre auto-link) — and moves the
208
+ * customer's balance back in the same transaction. The amount is the stored credit's (and its FX
209
+ * sibling's), never the caller's: the body carries no amount, account or customer. The link itself
210
+ * (customer, order, `Order.linkedPayments`) stays as it is.
211
+ */
212
+ interface PaymentCreditReversalRequest {
213
+ mode: 'reverse-credit';
214
+ /**
215
+ * Required: 10–500 characters after trimming, refused when it carries an email, phone, DNI, CUIT/CUIL,
216
+ * CBU, card number or token in a recognisable shape (`400 VALIDATION_FAILED`). Stored on the payment's
217
+ * per-source row and projected onto no wire (not the response, not `GET /payments/received`); not stored
218
+ * at all when the customer was erased. The audit row records its presence only.
219
+ */
220
+ reason: string;
221
+ }
222
+ /** One committed reversal: what was reversed, on whose balance, by how much. */
223
+ interface PaymentCreditReversal {
224
+ source: PaymentReceivedSource;
225
+ paymentId: string;
226
+ /** The link credit's `accountId`, and the reversal row's (`LINKREV#{creditAccountId}`). */
227
+ creditAccountId: string;
228
+ reversalAccountId: string;
229
+ /** Present when the link also booked an FX adjustment: that row, and the row reversing it. */
230
+ fxAdjustmentAccountId?: string;
231
+ fxReversalAccountId?: string;
232
+ /** Whose balance moved — the survivor, when the payment's customer was merged into another since. */
233
+ customerId: string;
234
+ /** What the customer's balance rose by, in the ledger `currency` (the credit plus its FX sibling's delta). */
235
+ amount: number;
236
+ currency: string;
237
+ reversedAt: number;
238
+ }
239
+ /** `data` of a successful credit reversal. */
240
+ interface PaymentCreditReversalResponse {
241
+ reversal: PaymentCreditReversal;
242
+ }
243
+ /** Every `error` the credit reversal answers itself — see {@link PAYMENT_CREDIT_REVERSAL_ERROR_CODES}. */
244
+ type PaymentCreditReversalErrorCode = (typeof PAYMENT_CREDIT_REVERSAL_ERROR_CODES)[number];
245
+ /** Every `reason` of the credit reversal's `409 LINK_LOCKED` — see {@link PAYMENT_CREDIT_REVERSAL_LOCK_REASONS}. */
246
+ type PaymentCreditReversalLockReason = (typeof PAYMENT_CREDIT_REVERSAL_LOCK_REASONS)[number];
202
247
  }
203
- export {};
248
+ /**
249
+ * The credit reversal's refusals (`PaymentCreditReversalRequest`). Each writes nothing.
250
+ *
251
+ * - `ADMIN_REQUIRED` · 403 — the token is below the ADMIN tier (an `ACCOUNTANT` is `403 ROLE_FORBIDDEN`, earlier).
252
+ * - `INVALID_INPUT` · 400 — the path's `source` is not `mp`, `stripe` or `mp_movement`.
253
+ * - `VALIDATION_FAILED` · 400 — the body is not `{ mode, reason }` or the reason is refused.
254
+ * - `PAYMENT_NOT_FOUND` · 404 — the store has no such payment.
255
+ * - `LINK_LOCKED` · 409 — `reason` is one of {@link PAYMENT_CREDIT_REVERSAL_LOCK_REASONS}.
256
+ * - `CUSTOMER_MERGED` · 409 — the credit's owner is mid-merge (or the payment carries a historical
257
+ * merged-refund flag); `data.mergedInto` names the survivor.
258
+ * - `CUSTOMER_ANONYMIZED` · 409 — a legacy credit naming no customer, on an erased customer.
259
+ * - `CUSTOMER_NOT_FOUND` · 404 — the customer whose balance the credit moved is gone.
260
+ */
261
+ export declare const PAYMENT_CREDIT_REVERSAL_ERROR_CODES: readonly ["ADMIN_REQUIRED", "INVALID_INPUT", "VALIDATION_FAILED", "PAYMENT_NOT_FOUND", "LINK_LOCKED", "CUSTOMER_MERGED", "CUSTOMER_ANONYMIZED", "CUSTOMER_NOT_FOUND"];
262
+ /**
263
+ * Every `reason` of the credit reversal's `409 LINK_LOCKED`:
264
+ *
265
+ * - `credit-reversed` — this credit was already reversed (`reversalAccountId` names the row): a second
266
+ * reversal of the same credit, a replay included.
267
+ * - `not-credited` — the payment's `accountId` names no credit of its own (never credited, or a pointer to
268
+ * a row this payment did not write), or the payment names no customer (un-linked by hand).
269
+ * - `refund-obligation` — a refund obligation already debited part of this credit as in transit
270
+ * (`refundObligationId`); reversing it all would debit that share twice.
271
+ * - `edited-ledger-pointer` — the payment's `fxAdjustmentAccountId` names a row that is not its own FX
272
+ * adjustment for the credit's customer, so the delta to reverse cannot be named.
273
+ * - `concurrent-link` — the payment or its ledger rows kept changing while the reversal ran; retry.
274
+ */
275
+ export declare const PAYMENT_CREDIT_REVERSAL_LOCK_REASONS: readonly ["credit-reversed", "not-credited", "refund-obligation", "edited-ledger-pointer", "concurrent-link"];
package/dist/payment.js CHANGED
@@ -1 +1,43 @@
1
- export {};
1
+ /**
2
+ * The credit reversal's refusals (`PaymentCreditReversalRequest`). Each writes nothing.
3
+ *
4
+ * - `ADMIN_REQUIRED` · 403 — the token is below the ADMIN tier (an `ACCOUNTANT` is `403 ROLE_FORBIDDEN`, earlier).
5
+ * - `INVALID_INPUT` · 400 — the path's `source` is not `mp`, `stripe` or `mp_movement`.
6
+ * - `VALIDATION_FAILED` · 400 — the body is not `{ mode, reason }` or the reason is refused.
7
+ * - `PAYMENT_NOT_FOUND` · 404 — the store has no such payment.
8
+ * - `LINK_LOCKED` · 409 — `reason` is one of {@link PAYMENT_CREDIT_REVERSAL_LOCK_REASONS}.
9
+ * - `CUSTOMER_MERGED` · 409 — the credit's owner is mid-merge (or the payment carries a historical
10
+ * merged-refund flag); `data.mergedInto` names the survivor.
11
+ * - `CUSTOMER_ANONYMIZED` · 409 — a legacy credit naming no customer, on an erased customer.
12
+ * - `CUSTOMER_NOT_FOUND` · 404 — the customer whose balance the credit moved is gone.
13
+ */
14
+ export const PAYMENT_CREDIT_REVERSAL_ERROR_CODES = [
15
+ 'ADMIN_REQUIRED',
16
+ 'INVALID_INPUT',
17
+ 'VALIDATION_FAILED',
18
+ 'PAYMENT_NOT_FOUND',
19
+ 'LINK_LOCKED',
20
+ 'CUSTOMER_MERGED',
21
+ 'CUSTOMER_ANONYMIZED',
22
+ 'CUSTOMER_NOT_FOUND',
23
+ ];
24
+ /**
25
+ * Every `reason` of the credit reversal's `409 LINK_LOCKED`:
26
+ *
27
+ * - `credit-reversed` — this credit was already reversed (`reversalAccountId` names the row): a second
28
+ * reversal of the same credit, a replay included.
29
+ * - `not-credited` — the payment's `accountId` names no credit of its own (never credited, or a pointer to
30
+ * a row this payment did not write), or the payment names no customer (un-linked by hand).
31
+ * - `refund-obligation` — a refund obligation already debited part of this credit as in transit
32
+ * (`refundObligationId`); reversing it all would debit that share twice.
33
+ * - `edited-ledger-pointer` — the payment's `fxAdjustmentAccountId` names a row that is not its own FX
34
+ * adjustment for the credit's customer, so the delta to reverse cannot be named.
35
+ * - `concurrent-link` — the payment or its ledger rows kept changing while the reversal ran; retry.
36
+ */
37
+ export const PAYMENT_CREDIT_REVERSAL_LOCK_REASONS = [
38
+ 'credit-reversed',
39
+ 'not-credited',
40
+ 'refund-obligation',
41
+ 'edited-ledger-pointer',
42
+ 'concurrent-link',
43
+ ];
package/dist/print.d.ts CHANGED
@@ -565,6 +565,12 @@ declare global {
565
565
  /** ms epoch of the LATEST transition. Plain assignment, moves every write — the recency value to display. */
566
566
  lastTransitionAt: number;
567
567
  useCase?: PrintUseCase;
568
+ /**
569
+ * The automatic-print event (`Store.config.autoPrint`) that minted this job — stamped once, on the
570
+ * job's first `queued` write by the automatic-print consumer, and never cleared. ABSENT means a
571
+ * manual print (or a job written before this field existed — forward-only, never backfilled).
572
+ */
573
+ autoPrintEvent?: keyof StoreAutoPrintConfig;
568
574
  agentId?: string;
569
575
  printerId?: string;
570
576
  orderId?: string;
package/dist/product.d.ts CHANGED
@@ -59,6 +59,13 @@ declare global {
59
59
  * `netOfNationalTaxes` when the product is rated.
60
60
  */
61
61
  prices?: PublicPriceSlot[];
62
+ /**
63
+ * The product's GTIN, derived from its barcodes (never sent): the first one
64
+ * typed `EAN8`, `UPC`, `EAN13` or `GTIN14` that passes the GS1 check digit and
65
+ * is not restricted circulation (02/20–29, store-generated). Absent when none
66
+ * qualifies, and on a partial frame that did not carry the barcodes.
67
+ */
68
+ gtin?: string;
62
69
  };
63
70
  /** Anonymous Web GET /products lookup; the tenant comes from the public store context. */
64
71
  interface PublicProductsByIdsInput {
@@ -23,6 +23,10 @@ import { STOREFRONT_DESIGN_LITERAL_MAX_CODE_POINTS } from "./storefrontDesignReg
23
23
  * parameters.
24
24
  */
25
25
  export const STOREFRONT_WEB_LITERAL_KEYS = Object.freeze([
26
+ 'ADDRESSES_IS_DEFAULT',
27
+ 'ADDRESSES_LIMIT_REACHED',
28
+ 'ADDRESSES_NOT_FOUND',
29
+ 'ADDRESSES_WRITE_CONTENDED',
26
30
  'ALERT_INCLUDED_VAT',
27
31
  'APPOINTMENTS_BOOKED_DESC',
28
32
  'APPOINTMENTS_BOOKED_TITLE',
@@ -320,9 +324,11 @@ export const STOREFRONT_WEB_LITERAL_KEYS = Object.freeze([
320
324
  'BASKET_LINE_LIMIT_REACHED',
321
325
  'BASKET_LINE_NOT_LOADED',
322
326
  'BASKET_LINE_NOT_OFFERED',
327
+ 'BASKET_LINE_NO_PRICE',
323
328
  'BASKET_LINE_OUT_OF_STOCK',
324
329
  'BASKET_LINE_STOCK_SHORT_HIDDEN',
325
330
  'BASKET_LINE_UNAVAILABLE',
331
+ 'BASKET_LINE_WITHDRAWN',
326
332
  'BASKET_LOADING',
327
333
  'BASKET_MIN_BUY_MISSING',
328
334
  'BASKET_NEXT_BUTTON',
@@ -353,8 +359,28 @@ export const STOREFRONT_WEB_LITERAL_KEYS = Object.freeze([
353
359
  'BASKET_UNDO_ACTION',
354
360
  'BASKET_UPDATED_CONFIRMATION',
355
361
  'BASKET_VIEW_BASKET_ACTION',
362
+ 'BRANDS_DIRECTORY_EMPTY',
356
363
  'BRANDS_ERR_LOAD',
364
+ 'BRANDS_FILTER_ALL',
365
+ 'BRANDS_JUMP_LABEL',
366
+ 'BRANDS_LETTERS_LABEL',
367
+ 'BRANDS_LETTER_OTHER',
368
+ 'BRANDS_LETTER_OTHER_LABEL',
369
+ 'BRANDS_SEARCH_CLEAR',
370
+ 'BRANDS_SEARCH_LABEL',
371
+ 'BRANDS_SHOW_MORE',
357
372
  'BRANDS_TITLE',
373
+ 'BRAND_BREADCRUMB_LABEL',
374
+ 'BRAND_DESCRIPTION_MORE',
375
+ 'CATEGORY_DIRECTORY_EMPTY',
376
+ 'CATEGORY_DIRECTORY_EMPTY_ACTION',
377
+ 'CATEGORY_DIRECTORY_FAILED',
378
+ 'CATEGORY_DIRECTORY_JUMP_LABEL',
379
+ 'CATEGORY_MORE_ROOTS_TITLE',
380
+ 'CATEGORY_SHOW_LESS',
381
+ 'CATEGORY_SIBLINGS_LABEL',
382
+ 'CATEGORY_SUBCATEGORIES_LABEL',
383
+ 'CATEGORY_VIEW_ALL',
358
384
  'CHECKOUT_ACCESS_PENDING',
359
385
  'CHECKOUT_ACCESS_REJECTED',
360
386
  'CHECKOUT_ACCOUNT_DISABLED',
@@ -423,7 +449,6 @@ export const STOREFRONT_WEB_LITERAL_KEYS = Object.freeze([
423
449
  'CHECKOUT_DNI_REQUIRED_MESSAGE',
424
450
  'CHECKOUT_DNI_REQUIRED_MESSAGE_NO_THRESHOLD',
425
451
  'CHECKOUT_EDIT_BASKET',
426
- 'CHECKOUT_EMAIL_VERIFICATION_REQUIRED',
427
452
  'CHECKOUT_EMPTY_BASKET',
428
453
  'CHECKOUT_FX_NOTICE_PAYING',
429
454
  'CHECKOUT_FX_NOTICE_PREFERRED',
@@ -538,11 +563,13 @@ export const STOREFRONT_WEB_LITERAL_KEYS = Object.freeze([
538
563
  'DATA_RIGHTS_COLLECTION_CONTACTS',
539
564
  'DATA_RIGHTS_COLLECTION_FAVORITES',
540
565
  'DATA_RIGHTS_COLLECTION_INVOICES',
566
+ 'DATA_RIGHTS_COLLECTION_LOYALTY',
541
567
  'DATA_RIGHTS_COLLECTION_NOTES',
542
568
  'DATA_RIGHTS_COLLECTION_ORDERS',
543
569
  'DATA_RIGHTS_COLLECTION_PAYMENTS',
544
570
  'DATA_RIGHTS_COLLECTION_QUOTE_REQUESTS',
545
571
  'DATA_RIGHTS_COLLECTION_RECENT_PURCHASES',
572
+ 'DATA_RIGHTS_COLLECTION_SERVICE_ORDERS',
546
573
  'DATA_RIGHTS_COLLECTION_WHOLESALE_REQUESTS',
547
574
  'DATA_RIGHTS_DIALOG_ACK',
548
575
  'DATA_RIGHTS_DIALOG_CONFIRM',
@@ -623,6 +650,11 @@ export const STOREFRONT_WEB_LITERAL_KEYS = Object.freeze([
623
650
  'FAVORITES_TOGGLE_ARIA',
624
651
  'FAVORITES_TOGGLE_NAME',
625
652
  'FAVORITES_UNAVAILABLE',
653
+ 'FOOTER_DEFAULT_ACCOUNT_TITLE',
654
+ 'FOOTER_DEFAULT_CONTACT',
655
+ 'FOOTER_DEFAULT_HELP_TITLE',
656
+ 'FOOTER_DEFAULT_SHOP_TITLE',
657
+ 'FOOTER_LEGAL_NAV_ARIA',
626
658
  'FOOTER_PRIVACY_POLICY',
627
659
  'FORMS_FIELD_DNI_LABEL',
628
660
  'FORMS_FIELD_PHONE_LABEL',
@@ -669,7 +701,6 @@ export const STOREFRONT_WEB_LITERAL_KEYS = Object.freeze([
669
701
  'FORMS_ZOD_VALUE_OPTIONS',
670
702
  'FORM_FIX_ERRORS',
671
703
  'GLOBALS_APPOINTMENTS',
672
- 'GLOBALS_APP_VERSION',
673
704
  'GLOBALS_BASKET',
674
705
  'GLOBALS_BRANDS',
675
706
  'GLOBALS_CATEGORIES',
@@ -741,6 +772,9 @@ export const STOREFRONT_WEB_LITERAL_KEYS = Object.freeze([
741
772
  'HEADER_SEARCH_INPUT',
742
773
  'HEADER_SEARCH_LABEL',
743
774
  'HEADER_SEARCH_RECENT',
775
+ 'HEADER_SEARCH_RECENT_CLEAR',
776
+ 'HEADER_SEARCH_RECENT_CLEAR_ARIA',
777
+ 'HEADER_SEARCH_RECENT_REMOVE',
744
778
  'HEADER_SEARCH_SUGGESTIONS',
745
779
  'HEADER_SEARCH_SUGGESTION_BARCODE',
746
780
  'HEADER_SEARCH_SUGGESTION_SKU',
@@ -886,7 +920,9 @@ export const STOREFRONT_WEB_LITERAL_KEYS = Object.freeze([
886
920
  'MAINTENANCE_REDIRECT_TO_BASKET',
887
921
  'MAINTENANCE_SUPPORT_HINT_EMAIL',
888
922
  'MAINTENANCE_SURVEY_DISABLED',
923
+ 'MAINTENANCE_WHATSAPP_OPT_IN_DISABLED',
889
924
  'MAINTENANCE_WHOLESALE_REQUEST_DISABLED',
925
+ 'MAINTENANCE_WITHDRAWAL_DISABLED',
890
926
  'MARKETING_ACTIVATE_ALL',
891
927
  'MARKETING_ACTIVE_COUNT',
892
928
  'MARKETING_CHANNEL_ADS',
@@ -895,12 +931,6 @@ export const STOREFRONT_WEB_LITERAL_KEYS = Object.freeze([
895
931
  'MARKETING_CHANNEL_EMAIL',
896
932
  'MARKETING_CHANNEL_EMAIL_DESC',
897
933
  'MARKETING_CHANNEL_EMAIL_EXAMPLE',
898
- 'MARKETING_CHANNEL_SMS',
899
- 'MARKETING_CHANNEL_SMS_DESC',
900
- 'MARKETING_CHANNEL_SMS_EXAMPLE',
901
- 'MARKETING_CHANNEL_WHATSAPP',
902
- 'MARKETING_CHANNEL_WHATSAPP_DESC',
903
- 'MARKETING_CHANNEL_WHATSAPP_EXAMPLE',
904
934
  'MARKETING_DEACTIVATE_ALL',
905
935
  'MARKETING_ERROR_MESSAGE',
906
936
  'MARKETING_EXAMPLE_PREFIX',
@@ -920,6 +950,7 @@ export const STOREFRONT_WEB_LITERAL_KEYS = Object.freeze([
920
950
  'MOBILE_BUTTONS_BASKET',
921
951
  'MOBILE_BUTTONS_FAVORITES',
922
952
  'MOBILE_BUTTONS_INVOICES',
953
+ 'MOBILE_BUTTONS_NAV_ARIA',
923
954
  'MOBILE_BUTTONS_ORDERS',
924
955
  'NETWORK_OFFLINE',
925
956
  'NETWORK_REQUEST_FAILED',
@@ -948,6 +979,10 @@ export const STOREFRONT_WEB_LITERAL_KEYS = Object.freeze([
948
979
  'NOTIFICATION_PREFS_SUBTITLE',
949
980
  'NOTIFICATION_PREFS_TITLE',
950
981
  'NOTIFICATION_PREFS_UNAVAILABLE_RETRY',
982
+ 'NOTIFICATION_PREFS_WHATSAPP_ARIA',
983
+ 'NOTIFICATION_PREFS_WHATSAPP_DESCRIPTION',
984
+ 'NOTIFICATION_PREFS_WHATSAPP_LABEL',
985
+ 'NOTIFICATION_PREFS_WHATSAPP_NOT_SAVED',
951
986
  'NOT_FOUND_BACK_HOME',
952
987
  'NOT_FOUND_PAGE_BODY',
953
988
  'NOT_FOUND_PAGE_TITLE',
@@ -1176,6 +1211,7 @@ export const STOREFRONT_WEB_LITERAL_KEYS = Object.freeze([
1176
1211
  'PREVIEW_UNAVAILABLE_ACTION',
1177
1212
  'PREVIEW_VARIANTS',
1178
1213
  'PREVIEW_WAITING',
1214
+ 'PRICING_NET_PLUS_IVA',
1179
1215
  'PRICING_TIER_CHIP',
1180
1216
  'PRICING_TIER_FROM',
1181
1217
  'PRICING_TIER_UNITS',
@@ -1186,10 +1222,14 @@ export const STOREFRONT_WEB_LITERAL_KEYS = Object.freeze([
1186
1222
  'PRODUCTS_CLEAR_FILTERS',
1187
1223
  'PRODUCTS_FILTERS_LABEL',
1188
1224
  'PRODUCTS_FILTER_ALL_BRANDS',
1225
+ 'PRODUCTS_FILTER_ALL_CATEGORIES',
1189
1226
  'PRODUCTS_FILTER_ALL_PRODUCTS',
1190
1227
  'PRODUCTS_FILTER_ALL_TAGS',
1191
1228
  'PRODUCTS_FILTER_AVAILABILITY',
1192
1229
  'PRODUCTS_FILTER_BRAND',
1230
+ 'PRODUCTS_FILTER_BRAND_UNAVAILABLE',
1231
+ 'PRODUCTS_FILTER_CATEGORY',
1232
+ 'PRODUCTS_FILTER_CATEGORY_UNAVAILABLE',
1193
1233
  'PRODUCTS_FILTER_IN_OFFER',
1194
1234
  'PRODUCTS_FILTER_IN_STOCK',
1195
1235
  'PRODUCTS_FILTER_TAG',
@@ -1200,10 +1240,14 @@ export const STOREFRONT_WEB_LITERAL_KEYS = Object.freeze([
1200
1240
  'PRODUCTS_NO_INCOMES',
1201
1241
  'PRODUCTS_NO_MATCHES',
1202
1242
  'PRODUCTS_NO_RESULTS',
1243
+ 'PRODUCTS_PICK_SUBCATEGORY',
1203
1244
  'PRODUCTS_SEARCH_TITLE',
1204
- 'PRODUCTS_SELECT_CATEGORY',
1205
1245
  'PRODUCTS_SORT_LABEL',
1246
+ 'PRODUCTS_SORT_NAME_ASC',
1247
+ 'PRODUCTS_SORT_NAME_DESC',
1206
1248
  'PRODUCTS_SORT_NEWEST',
1249
+ 'PRODUCTS_SORT_PRICE_ASC',
1250
+ 'PRODUCTS_SORT_PRICE_DESC',
1207
1251
  'PRODUCTS_SORT_RELEVANCE',
1208
1252
  'PRODUCTS_TAG_NOT_FOUND',
1209
1253
  'PRODUCTS_TAG_TITLE',
@@ -1247,6 +1291,8 @@ export const STOREFRONT_WEB_LITERAL_KEYS = Object.freeze([
1247
1291
  'PRODUCT_INFO_TITLE',
1248
1292
  'PRODUCT_LOAD_FAILED',
1249
1293
  'PRODUCT_PAGE_TITLE',
1294
+ 'PRODUCT_PRICE_ON_REQUEST',
1295
+ 'PRODUCT_PRICE_ON_REQUEST_HINT',
1250
1296
  'PRODUCT_PRICE_PER_UNIT',
1251
1297
  'PRODUCT_PROMOTIONS_HINT',
1252
1298
  'PRODUCT_PROMOTIONS_TITLE',
@@ -1398,6 +1444,7 @@ export const STOREFRONT_WEB_LITERAL_KEYS = Object.freeze([
1398
1444
  'QUOTE_REQUEST_SIGNED_IN_HINT',
1399
1445
  'QUOTE_REQUEST_SUBMIT',
1400
1446
  'QUOTE_REQUEST_SUCCESS',
1447
+ 'REORDER_SHELF_TITLE',
1401
1448
  'SALE_BADGE_OFFER',
1402
1449
  'SALE_CHIP_ALL',
1403
1450
  'SALE_DISCOUNT_PERCENT',
@@ -1486,6 +1533,14 @@ export const STOREFRONT_WEB_LITERAL_KEYS = Object.freeze([
1486
1533
  'SURVEY_TITLE',
1487
1534
  'SYNC_ALERT_ERROR_MESSAGE',
1488
1535
  'SYNC_ALERT_OFFLINE_MESSAGE',
1536
+ 'WHATSAPP_OPT_IN_ERROR',
1537
+ 'WHATSAPP_OPT_IN_HELP_GUEST',
1538
+ 'WHATSAPP_OPT_IN_HELP_SIGNED_IN',
1539
+ 'WHATSAPP_OPT_IN_OFFER_ACTION',
1540
+ 'WHATSAPP_OPT_IN_OFFER_TEXT',
1541
+ 'WHATSAPP_OPT_IN_PROFILE_LINK',
1542
+ 'WHATSAPP_OPT_IN_SUCCESS',
1543
+ 'WHATSAPP_OPT_IN_UNAVAILABLE',
1489
1544
  'WHOLESALE_REQUEST_ACTIVITY_LABEL',
1490
1545
  'WHOLESALE_REQUEST_ALREADY_PENDING',
1491
1546
  'WHOLESALE_REQUEST_APPROVED',
@@ -939,6 +939,21 @@ declare global {
939
939
  payment_id: string;
940
940
  source: 'mp' | 'stripe' | 'mp_movement';
941
941
  }
942
+ /**
943
+ * ADMIN-tier staff reversed a linked payment's ledger credit (`POST /payments/{source}/{paymentId}/link`
944
+ * `{ mode: 'reverse-credit' }`). `user_id` is the acting operator; `customer_id` the customer whose
945
+ * balance rose by `amount` (in the ledger `currency`). Ids and figures only — never the operator's reason.
946
+ * BE-emitted, after the commit.
947
+ */
948
+ interface PaymentCreditReversedEvent extends UserActivityEventBase {
949
+ event: 'Payment Credit Reversed';
950
+ payment_id: string;
951
+ source: PaymentReceivedSource;
952
+ customer_id: string;
953
+ reversal_account_id: string;
954
+ amount: number;
955
+ currency: string;
956
+ }
942
957
  interface PaymentLinkageUpdatedEvent extends UserActivityEventBase {
943
958
  event: 'Payment Linkage Updated';
944
959
  payment_id: string;
@@ -1400,7 +1415,7 @@ declare global {
1400
1415
  event: 'Store Network Removed';
1401
1416
  network_id: string;
1402
1417
  }
1403
- type UserActivityEvent = CustomerConsentImportedEvent | CustomerConsentImportCompletedEvent | UserLoggedInEvent | UserLoggedOutEvent | UserPasswordChangedEvent | UserSuspendedEvent | TwoFactorEnrolledEvent | TwoFactorDisabledEvent | TwoFactorResetEvent | TwoFactorRecoveryCodesGeneratedEvent | PasskeyRegisteredEvent | PasskeyRevokedEvent | CustomerPasskeysRevokedEvent | StoreSettingsUpdatedEvent | PlanChangedEvent | InvoiceCreatedEvent | OrderCreatedEvent | OrderCancelledEvent | OrderEditedEvent | OrderReturnedEvent | ReturnLineRegradedEvent | ProductPriceChangedEvent | CustomerCreatedEvent | CustomerEditedEvent | CustomerRecordServedEvent | CustomerCreditCheckPerformedEvent | CustomersMergedEvent | CustomerMergeCancelledEvent | CashDrawerOpenedEvent | CashDrawerClosedEvent | TenantImpersonatedEvent | SecretRotatedEvent | UserCreatedEvent | UserUpdatedEvent | ProductCreatedEvent | ProductUpdatedEvent | StockIncomeCreatedEvent | StockAdjustedEvent | CategoryCreatedEvent | CategoryUpdatedEvent | BrandCreatedEvent | BrandUpdatedEvent | AttributeCreatedEvent | AttributeUpdatedEvent | SupplierCreatedEvent | SupplierUpdatedEvent | SupplierInvoiceCreatedEvent | SupplierInvoiceMatchOverriddenEvent | SupplierClaimRaisedEvent | SupplierClaimStatusChangedEvent | SupplierAccountCreatedEvent | SupplierAccountUpdatedEvent | AccountCreatedEvent | AccountDeletedEvent | CustomerBalanceReprojectedEvent | BasketUpdatedEvent | BasketDiscountGrantedEvent | BasketDeletedEvent | CashDrawerMovementEvent | CashDrawerReconciledEvent | PaymentCreatedEvent | PaymentLinkedEvent | PaymentUnlinkedEvent | PaymentLinkageUpdatedEvent | CreditLimitOverriddenEvent | AppointmentSelfBookingBlockOverriddenEvent | AppointmentSelfBookingBlockLiftedEvent | NotificationReadEvent | LogDeletedEvent | PlanCreatedEvent | StoreMaintenanceToggledEvent | PlatformMaintenanceToggledEvent | MaintenanceBroadcastSentEvent | TenantCreatedEvent | LiteralUpdatedEvent | SupportTicketCreatedEvent | SupportTicketUpdatedEvent | AuditTrailViewedEvent | ReportViewedEvent | CustomerPiiViewedEvent | CashDrawerUiOpenedEvent | CashDrawerUiClosedEvent | ExportInitiatedEvent | ImpersonationUiStartedEvent | ImpersonationUiEndedEvent | PaymentViewedEvent | InvoiceViewedEvent | CustomerDetailViewedEvent | SupplierAccountViewedEvent | SearchPerformedEvent | ActionDeniedEvent | TwoFactorChallengeShownEvent | TwoFactorCodeValidationFailedEvent | TwoFactorEnrollmentStartedEvent | TwoFactorRecoveryCodesRevealedEvent | TwoFactorResetInitiatedEvent | IntegrationTokenRefreshedEvent | WaitlistConvertedEvent | PlatformConfigUpdatedEvent | MlChannelStatusChangedEvent | IntegrationConnectionChangedEvent | PrintRuleCreatedEvent | PrintRuleEditedEvent | PrintRuleDeletedEvent | WebhookCreatedEvent | WebhookUpdatedEvent | WebhookDeletedEvent | PrinterActiveToggledEvent | PrinterRawFormatsUpdatedEvent | PrinterProfileUpdatedEvent | PlanTemplateUpdatedEvent | SupplierAccountDeletedEvent | CustomerPasswordResetInitiatedEvent | UserSessionsRevokedEvent | UserAnonymizedEvent | UserPasswordResetInitiatedByOperatorEvent | AgentCommandDispatchedEvent | PrintJobCancelledEvent | PrintAgentPairingCodeMintedEvent | PrintAgentPairedEvent | PrintAgentRevokedEvent | RmaRequestedEvent | RmaTransitionedEvent | RmaTrackingUpdatedEvent | ClockedInEvent | ClockedOutEvent | AttendanceCorrectedEvent | StoreNetworkRegisteredEvent | StoreNetworkRemovedEvent;
1418
+ type UserActivityEvent = CustomerConsentImportedEvent | CustomerConsentImportCompletedEvent | UserLoggedInEvent | UserLoggedOutEvent | UserPasswordChangedEvent | UserSuspendedEvent | TwoFactorEnrolledEvent | TwoFactorDisabledEvent | TwoFactorResetEvent | TwoFactorRecoveryCodesGeneratedEvent | PasskeyRegisteredEvent | PasskeyRevokedEvent | CustomerPasskeysRevokedEvent | StoreSettingsUpdatedEvent | PlanChangedEvent | InvoiceCreatedEvent | OrderCreatedEvent | OrderCancelledEvent | OrderEditedEvent | OrderReturnedEvent | ReturnLineRegradedEvent | ProductPriceChangedEvent | CustomerCreatedEvent | CustomerEditedEvent | CustomerRecordServedEvent | CustomerCreditCheckPerformedEvent | CustomersMergedEvent | CustomerMergeCancelledEvent | CashDrawerOpenedEvent | CashDrawerClosedEvent | TenantImpersonatedEvent | SecretRotatedEvent | UserCreatedEvent | UserUpdatedEvent | ProductCreatedEvent | ProductUpdatedEvent | StockIncomeCreatedEvent | StockAdjustedEvent | CategoryCreatedEvent | CategoryUpdatedEvent | BrandCreatedEvent | BrandUpdatedEvent | AttributeCreatedEvent | AttributeUpdatedEvent | SupplierCreatedEvent | SupplierUpdatedEvent | SupplierInvoiceCreatedEvent | SupplierInvoiceMatchOverriddenEvent | SupplierClaimRaisedEvent | SupplierClaimStatusChangedEvent | SupplierAccountCreatedEvent | SupplierAccountUpdatedEvent | AccountCreatedEvent | AccountDeletedEvent | CustomerBalanceReprojectedEvent | BasketUpdatedEvent | BasketDiscountGrantedEvent | BasketDeletedEvent | CashDrawerMovementEvent | CashDrawerReconciledEvent | PaymentCreatedEvent | PaymentLinkedEvent | PaymentUnlinkedEvent | PaymentCreditReversedEvent | PaymentLinkageUpdatedEvent | CreditLimitOverriddenEvent | AppointmentSelfBookingBlockOverriddenEvent | AppointmentSelfBookingBlockLiftedEvent | NotificationReadEvent | LogDeletedEvent | PlanCreatedEvent | StoreMaintenanceToggledEvent | PlatformMaintenanceToggledEvent | MaintenanceBroadcastSentEvent | TenantCreatedEvent | LiteralUpdatedEvent | SupportTicketCreatedEvent | SupportTicketUpdatedEvent | AuditTrailViewedEvent | ReportViewedEvent | CustomerPiiViewedEvent | CashDrawerUiOpenedEvent | CashDrawerUiClosedEvent | ExportInitiatedEvent | ImpersonationUiStartedEvent | ImpersonationUiEndedEvent | PaymentViewedEvent | InvoiceViewedEvent | CustomerDetailViewedEvent | SupplierAccountViewedEvent | SearchPerformedEvent | ActionDeniedEvent | TwoFactorChallengeShownEvent | TwoFactorCodeValidationFailedEvent | TwoFactorEnrollmentStartedEvent | TwoFactorRecoveryCodesRevealedEvent | TwoFactorResetInitiatedEvent | IntegrationTokenRefreshedEvent | WaitlistConvertedEvent | PlatformConfigUpdatedEvent | MlChannelStatusChangedEvent | IntegrationConnectionChangedEvent | PrintRuleCreatedEvent | PrintRuleEditedEvent | PrintRuleDeletedEvent | WebhookCreatedEvent | WebhookUpdatedEvent | WebhookDeletedEvent | PrinterActiveToggledEvent | PrinterRawFormatsUpdatedEvent | PrinterProfileUpdatedEvent | PlanTemplateUpdatedEvent | SupplierAccountDeletedEvent | CustomerPasswordResetInitiatedEvent | UserSessionsRevokedEvent | UserAnonymizedEvent | UserPasswordResetInitiatedByOperatorEvent | AgentCommandDispatchedEvent | PrintJobCancelledEvent | PrintAgentPairingCodeMintedEvent | PrintAgentPairedEvent | PrintAgentRevokedEvent | RmaRequestedEvent | RmaTransitionedEvent | RmaTrackingUpdatedEvent | ClockedInEvent | ClockedOutEvent | AttendanceCorrectedEvent | StoreNetworkRegisteredEvent | StoreNetworkRemovedEvent;
1404
1419
  }
1405
1420
  /**
1406
1421
  * Canonical whitelist of UI-only `UserActivityEvent` variant names. Imported by
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "platform-types",
3
- "version": "1.10.853",
3
+ "version": "1.10.854",
4
4
  "description": "Shared wire and entity contracts for the platform.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",