@happyvertical/smrt-commerce 0.57.4 → 0.57.5
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/AGENTS.md +3 -0
- package/agents/historical-sales.md +18 -0
- package/agents/ordinary-accounting.md +72 -0
- package/dist/accounting-steps-internal.d.ts +49 -0
- package/dist/accounting-steps-internal.d.ts.map +1 -0
- package/dist/chunks/{ContractLineItemCollection-J4xtRtzE.js → ContractLineItemCollection-D5hBzrct.js} +4 -4
- package/dist/chunks/{ContractLineItemCollection-J4xtRtzE.js.map → ContractLineItemCollection-D5hBzrct.js.map} +1 -1
- package/dist/chunks/{DocumentTaxCollection-C42x0YLW.js → DocumentTaxCollection-C6cI5U89.js} +29 -19
- package/dist/chunks/DocumentTaxCollection-C6cI5U89.js.map +1 -0
- package/dist/chunks/{InvoiceCollection-BIcge9Wl.js → InvoiceCollection-BolwughC.js} +13 -11
- package/dist/chunks/InvoiceCollection-BolwughC.js.map +1 -0
- package/dist/chunks/{TaxCodeCollection-RWdVaheF.js → TaxCodeCollection-CW-tWjkU.js} +2 -2
- package/dist/chunks/{TaxCodeCollection-RWdVaheF.js.map → TaxCodeCollection-CW-tWjkU.js.map} +1 -1
- package/dist/chunks/{TaxComponentCollection-Dt2jeWiC.js → TaxComponentCollection-BiJ9aAe8.js} +3 -3
- package/dist/chunks/{TaxComponentCollection-Dt2jeWiC.js.map → TaxComponentCollection-BiJ9aAe8.js.map} +1 -1
- package/dist/chunks/{allocation-integrity-DJ7-LY1j.js → allocation-integrity-CH-i-hsY.js} +6 -6
- package/dist/chunks/{allocation-integrity-DJ7-LY1j.js.map → allocation-integrity-CH-i-hsY.js.map} +1 -1
- package/dist/chunks/{contract-money-DSRTMoXm.js → contract-money-BMsq4cpL.js} +6 -6
- package/dist/chunks/{contract-money-DSRTMoXm.js.map → contract-money-BMsq4cpL.js.map} +1 -1
- package/dist/chunks/{invoice-posting-BeTPw3h9.js → invoice-posting-IXic4UEP.js} +3 -3
- package/dist/chunks/{invoice-posting-BeTPw3h9.js.map → invoice-posting-IXic4UEP.js.map} +1 -1
- package/dist/chunks/{retention-accounting-Brdj9x-V.js → retention-accounting-c7kY7gFU.js} +504 -89
- package/dist/chunks/retention-accounting-c7kY7gFU.js.map +1 -0
- package/dist/chunks/retention-accounting-internal-DNxLLVvK.js +30 -0
- package/dist/chunks/retention-accounting-internal-DNxLLVvK.js.map +1 -0
- package/dist/chunks/{tenant-money-barrier-DoF6lR8h.js → tenant-money-barrier-DEVLrpaB.js} +3 -3
- package/dist/chunks/{tenant-money-barrier-DoF6lR8h.js.map → tenant-money-barrier-DEVLrpaB.js.map} +1 -1
- package/dist/document-money.d.ts.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +13 -13
- package/dist/index.js.map +1 -1
- package/dist/invoice-accounting-sync.d.ts.map +1 -1
- package/dist/invoices.d.ts +1 -0
- package/dist/invoices.d.ts.map +1 -1
- package/dist/invoices.js +2 -2
- package/dist/manifest.json +45 -9
- package/dist/models/Invoice.d.ts +2 -0
- package/dist/models/Invoice.d.ts.map +1 -1
- package/dist/models/Payment.d.ts +2 -0
- package/dist/models/Payment.d.ts.map +1 -1
- package/dist/models/PaymentAllocation.d.ts.map +1 -1
- package/dist/ordinary-accounting-internal.d.ts +3 -0
- package/dist/ordinary-accounting-internal.d.ts.map +1 -0
- package/dist/ordinary-accounting.d.ts +104 -0
- package/dist/ordinary-accounting.d.ts.map +1 -0
- package/dist/retention-accounting.d.ts +3 -24
- package/dist/retention-accounting.d.ts.map +1 -1
- package/dist/smrt-knowledge.json +14 -8
- package/package.json +16 -16
- package/dist/chunks/DocumentTaxCollection-C42x0YLW.js.map +0 -1
- package/dist/chunks/InvoiceCollection-BIcge9Wl.js.map +0 -1
- package/dist/chunks/retention-accounting-Brdj9x-V.js.map +0 -1
- package/dist/chunks/retention-accounting-internal-CBQRvd74.js +0 -17
- package/dist/chunks/retention-accounting-internal-CBQRvd74.js.map +0 -1
package/AGENTS.md
CHANGED
|
@@ -232,3 +232,6 @@ See [observed purchase prices](agents/observed-purchase-prices.md) for incomplet
|
|
|
232
232
|
original supplier prices, explicit unknown totals and reviewed completion.
|
|
233
233
|
See [received sales agreement money](agents/received-sales-contract-money.md) for
|
|
234
234
|
exact complete source Order/Agreement evidence without synthetic lines or tax recognition.
|
|
235
|
+
|
|
236
|
+
See [ordinary invoice accounting adjustments](agents/ordinary-accounting.md) for
|
|
237
|
+
non-retained provider credit applications, recorded refunds and durable recovery.
|
|
@@ -130,3 +130,21 @@ PostgreSQL scope/tenant checks. No100,000-row fixture, production writes or sche
|
|
|
130
130
|
changes were needed. Full logs remain external at the2026-10-16 MVP session's
|
|
131
131
|
`nonfinancial-import/scoped-sales-regression-base.log`, `scoped-sales-focused.log`
|
|
132
132
|
and `scoped-sales-postgres.log`. Native token usage was unavailable.
|
|
133
|
+
|
|
134
|
+
### Stable managed credit applications
|
|
135
|
+
|
|
136
|
+
Refreshing a source invoice's credit balance reserves each provider-managed
|
|
137
|
+
credit's exact retained and currently-due applications before allocating any
|
|
138
|
+
remaining capacity to unmanaged issued credits. A later same-day credit, a
|
|
139
|
+
lower UUID or a different database enumeration order cannot take an already
|
|
140
|
+
reserved application. This includes a zero-application credit whose excess is
|
|
141
|
+
refunded in cash. Unmanaged residual allocation follows issue date and ID order.
|
|
142
|
+
|
|
143
|
+
The reconciliation guard still rejects a missing/cancelled managed credit,
|
|
144
|
+
changed retained/application amounts or frozen applications exceeding the
|
|
145
|
+
source's remaining capacity. Ordinary refund saves and retries preserve the
|
|
146
|
+
provider allocation snapshot; they do not authorize an external reversal.
|
|
147
|
+
|
|
148
|
+
An issued unmanaged credit remains issued when residual application becomes zero,
|
|
149
|
+
even when no sent/viewed delivery timestamp was recorded. Repeated refreshes retain
|
|
150
|
+
its full credit effect; allocation changes do not cancel or return it to draft.
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# Ordinary invoice accounting adjustments (#4118)
|
|
2
|
+
|
|
3
|
+
Ordinary issued invoices use the same frozen money exporter as other canonical
|
|
4
|
+
Commerce invoices. Their provider credit applications and already-settled refund
|
|
5
|
+
recordings need durable ownership too; they do not have a retention agreement.
|
|
6
|
+
|
|
7
|
+
Use `syncOrdinaryInvoiceToAccounting` with the active tenant, canonical issued
|
|
8
|
+
invoice ID, provider, stable request ID, evidence reference, reviewed SDK
|
|
9
|
+
`QuickBooksOrdinaryReceivableMapping` (`kind: 'ordinary'`) and exact
|
|
10
|
+
`QuickBooksFrozenInvoiceMapping` (`invoiceMapping`). It saves both mappings before
|
|
11
|
+
calling `syncFrozenInvoiceToAccounting`. It does not fabricate quantities, tax,
|
|
12
|
+
retention accounts or an original provider invoice for historical imported facts.
|
|
13
|
+
The mapping supports explicit accrual/invoice-tax-point, verified home currency,
|
|
14
|
+
AR and optional bank accounts. The SDK validates real provider account/customer
|
|
15
|
+
metadata; ordinary mapping cannot withhold or release retention.
|
|
16
|
+
|
|
17
|
+
`readOrdinaryInvoiceAccountingState(invoice)` returns the immutable source and
|
|
18
|
+
reviewed mappings, nullable original external ID, pending/ready status, credits,
|
|
19
|
+
refunds and individual remote steps. On a lost original export response, retain
|
|
20
|
+
that state and use the existing `readFrozenAccountingSyncState` request/attempt
|
|
21
|
+
identity plus `reconcileOrdinaryBaseInvoice` and exact verified provider outcome.
|
|
22
|
+
Retry the original sync command afterwards. A fresh process can reconstruct the
|
|
23
|
+
request from the public state; it must not guess a mapping or query private JSON.
|
|
24
|
+
|
|
25
|
+
For an issued same-source credit, `loadOrdinaryCreditAccountingSnapshot` and
|
|
26
|
+
`toOrdinaryCreditAccountingInput` provide read-only exact review. Then call
|
|
27
|
+
`syncOrdinaryCreditToAccounting` with the credit ID/mapping, request/evidence and
|
|
28
|
+
explicit effective date. The command reserves the actual local applied amount,
|
|
29
|
+
exports the frozen CreditMemo and applies only that amount to the original remote
|
|
30
|
+
Invoice. Zero application leaves unapplied credit capacity; it creates no fake
|
|
31
|
+
zero payment. Once reserved, later same-date credits, UUID ordering and balance
|
|
32
|
+
refreshes cannot move the provider application. Generic direct credit export is
|
|
33
|
+
blocked for managed sources; the owning read helpers do not permit a write bypass.
|
|
34
|
+
|
|
35
|
+
`syncOrdinaryRefundToAccounting` requires an already-completed local refund
|
|
36
|
+
Payment, actual cash journal, one exact full allocation to a reconciled provider
|
|
37
|
+
credit, and effectiveOn equal to the local settlement date. It reserves remaining
|
|
38
|
+
unapplied credit capacity before recording the provider cash-refund journal and
|
|
39
|
+
its noncash CreditMemo application. These are accounting records of an existing
|
|
40
|
+
settlement, never a bank transfer. The Payment and its allocations cannot be
|
|
41
|
+
changed/deleted after reservation, including while an outcome is unknown.
|
|
42
|
+
|
|
43
|
+
Every remote write uses the shared recoverable step engine also used by retained
|
|
44
|
+
accounting. `reconcileOrdinaryAccountingStep` requires exact observed request and
|
|
45
|
+
attempt identity, authoritative readback/absence evidence and proof an interrupted
|
|
46
|
+
owner stopped. `reconcileOrdinaryCreditAccounting` resolves the frozen CreditMemo
|
|
47
|
+
export. Never reset unknown state on timeout or assume a request hash alone proves
|
|
48
|
+
remote idempotency. Resume the original operation after reconciliation; changed
|
|
49
|
+
request facts fail. Network operations reject caller-owned money transactions;
|
|
50
|
+
local reservations use the same tenant/source money locks and rollback boundary.
|
|
51
|
+
|
|
52
|
+
Ordinary state and refund bindings are command-owned fields on Invoice and Payment.
|
|
53
|
+
Run normal schema migration for these additive fields before using the capability.
|
|
54
|
+
Existing rows remain unbound; no automatic conversion of earlier app-only posted
|
|
55
|
+
history is performed. Retained APIs still require a real positive retention
|
|
56
|
+
agreement. No live provider certification is implied by controlled SDK fixtures.
|
|
57
|
+
|
|
58
|
+
Focused proof: `pnpm --filter @happyvertical/smrt-commerce exec vitest run
|
|
59
|
+
src/__tests__/ordinary-accounting.test.ts src/__tests__/retention-accounting.test.ts`.
|
|
60
|
+
The corresponding `ordinary-accounting.optional.test.ts` and retained suite run
|
|
61
|
+
through the standard managed PostgreSQL wrapper. The shared helper covers base,
|
|
62
|
+
credit and both refund-step lost responses; same-day ordering; changed retry;
|
|
63
|
+
source/tenant checks; reservation rollback; concurrent refund attempts; and direct
|
|
64
|
+
payment/allocation mutation refusal. The retained suite protects shared-engine
|
|
65
|
+
compatibility.
|
|
66
|
+
|
|
67
|
+
An existing generic frozen export can enter ordinary coordination only when the
|
|
68
|
+
reviewed mapping reconstructs its exact original creation request. This check
|
|
69
|
+
runs under the reservation lock, including unresolved exports; a mismatch leaves
|
|
70
|
+
ordinary state absent. Once ordinary authority is reserved, base export uses the
|
|
71
|
+
owning command so a competing generic export cannot replace that mapping between
|
|
72
|
+
reservation and provider I/O. Read-only frozen projections remain available.
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { AccountingProvider, QuickBooksReceivableReadback, QuickBooksReceivableRequest } from '@happyvertical/accounting';
|
|
2
|
+
import type { Invoice } from './models/Invoice.js';
|
|
3
|
+
interface ReconciliationEvidence {
|
|
4
|
+
outcome: 'found' | 'absent';
|
|
5
|
+
evidenceRef?: string;
|
|
6
|
+
externalId?: string;
|
|
7
|
+
recordedAt: string;
|
|
8
|
+
interruptedOwnerStopped: boolean;
|
|
9
|
+
}
|
|
10
|
+
export interface AccountingStep {
|
|
11
|
+
key: string;
|
|
12
|
+
status: 'in_flight' | 'unknown' | 'rejected' | 'confirmed';
|
|
13
|
+
attemptId: string;
|
|
14
|
+
request: QuickBooksReceivableRequest;
|
|
15
|
+
startedAt: string;
|
|
16
|
+
result?: QuickBooksReceivableReadback;
|
|
17
|
+
reconciliation?: ReconciliationEvidence;
|
|
18
|
+
priorAttempts?: {
|
|
19
|
+
attemptId: string;
|
|
20
|
+
status: string;
|
|
21
|
+
startedAt: string;
|
|
22
|
+
reconciliation?: ReconciliationEvidence;
|
|
23
|
+
}[];
|
|
24
|
+
}
|
|
25
|
+
export interface AccountingStepState {
|
|
26
|
+
steps: AccountingStep[];
|
|
27
|
+
}
|
|
28
|
+
export interface AccountingStepEngine<S extends AccountingStepState> {
|
|
29
|
+
locked<T>(operation: (invoice: Invoice, state: S | null) => Promise<T>): Promise<T>;
|
|
30
|
+
save(invoice: Invoice, state: S): Promise<void>;
|
|
31
|
+
provider: NonNullable<AccountingProvider['receivableTransfers']>;
|
|
32
|
+
}
|
|
33
|
+
export interface AccountingStepReconciliation {
|
|
34
|
+
stepKey: string;
|
|
35
|
+
requestId: string;
|
|
36
|
+
attemptId: string;
|
|
37
|
+
confirmInterruptedOwnerStopped?: true;
|
|
38
|
+
outcome: {
|
|
39
|
+
status: 'found';
|
|
40
|
+
externalId: string;
|
|
41
|
+
} | {
|
|
42
|
+
status: 'absent';
|
|
43
|
+
evidenceRef: string;
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
export declare function executeAccountingStep<S extends AccountingStepState>(engine: AccountingStepEngine<S>, key: string, make: (invoice: Invoice, state: S) => QuickBooksReceivableRequest): Promise<QuickBooksReceivableReadback>;
|
|
47
|
+
export declare function reconcileAccountingStep<S extends AccountingStepState>(engine: AccountingStepEngine<S>, input: AccountingStepReconciliation): Promise<S>;
|
|
48
|
+
export {};
|
|
49
|
+
//# sourceMappingURL=accounting-steps-internal.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"accounting-steps-internal.d.ts","sourceRoot":"","sources":["../src/accounting-steps-internal.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,kBAAkB,EAClB,4BAA4B,EAC5B,2BAA2B,EAC5B,MAAM,2BAA2B,CAAC;AACnC,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;AAGnD,UAAU,sBAAsB;IAC9B,OAAO,EAAE,OAAO,GAAG,QAAQ,CAAC;IAC5B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,uBAAuB,EAAE,OAAO,CAAC;CAClC;AACD,MAAM,WAAW,cAAc;IAC7B,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,WAAW,GAAG,SAAS,GAAG,UAAU,GAAG,WAAW,CAAC;IAC3D,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,2BAA2B,CAAC;IACrC,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,4BAA4B,CAAC;IACtC,cAAc,CAAC,EAAE,sBAAsB,CAAC;IACxC,aAAa,CAAC,EAAE;QACd,SAAS,EAAE,MAAM,CAAC;QAClB,MAAM,EAAE,MAAM,CAAC;QACf,SAAS,EAAE,MAAM,CAAC;QAClB,cAAc,CAAC,EAAE,sBAAsB,CAAC;KACzC,EAAE,CAAC;CACL;AAED,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,cAAc,EAAE,CAAC;CACzB;AACD,MAAM,WAAW,oBAAoB,CAAC,CAAC,SAAS,mBAAmB;IACjE,MAAM,CAAC,CAAC,EACN,SAAS,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,GAAG,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC,GAC3D,OAAO,CAAC,CAAC,CAAC,CAAC;IACd,IAAI,CAAC,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAChD,QAAQ,EAAE,WAAW,CAAC,kBAAkB,CAAC,qBAAqB,CAAC,CAAC,CAAC;CAClE;AACD,MAAM,WAAW,4BAA4B;IAC3C,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,8BAA8B,CAAC,EAAE,IAAI,CAAC;IACtC,OAAO,EACH;QAAE,MAAM,EAAE,OAAO,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE,GACvC;QAAE,MAAM,EAAE,QAAQ,CAAC;QAAC,WAAW,EAAE,MAAM,CAAA;KAAE,CAAC;CAC/C;AAWD,wBAAsB,qBAAqB,CAAC,CAAC,SAAS,mBAAmB,EACvE,MAAM,EAAE,oBAAoB,CAAC,CAAC,CAAC,EAC/B,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,KAAK,2BAA2B,GAChE,OAAO,CAAC,4BAA4B,CAAC,CA2EvC;AACD,wBAAsB,uBAAuB,CAAC,CAAC,SAAS,mBAAmB,EACzE,MAAM,EAAE,oBAAoB,CAAC,CAAC,CAAC,EAC/B,KAAK,EAAE,4BAA4B,GAClC,OAAO,CAAC,CAAC,CAAC,CA6CZ"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.js";
|
|
2
2
|
import { MoneyValidationError } from "../money.js";
|
|
3
3
|
import { a as assertCommerceMoneySchemaCurrent, f as DOCUMENT_MONEY_READ_REBIND, p as DOCUMENT_MONEY_WRITE } from "./detect-DAE-_NAk.js";
|
|
4
|
-
import { n as withTenantMoneyCollectionRead } from "./tenant-money-barrier-
|
|
4
|
+
import { n as withTenantMoneyCollectionRead } from "./tenant-money-barrier-DEVLrpaB.js";
|
|
5
5
|
import { SmrtCollection, SmrtObject, crossPackageRef, field, foreignKey, smrt } from "@happyvertical/smrt-core";
|
|
6
6
|
import { TenantScoped, getTenantId, queryGlobal, queryWithGlobals, tenantId } from "@happyvertical/smrt-tenancy";
|
|
7
7
|
//#region src/models/ContractLineItem.ts
|
|
@@ -116,7 +116,7 @@ var ContractLineItem = class extends SmrtObject {
|
|
|
116
116
|
return this;
|
|
117
117
|
}
|
|
118
118
|
async save() {
|
|
119
|
-
const { saveContractLineItem } = await import("./contract-money-
|
|
119
|
+
const { saveContractLineItem } = await import("./contract-money-BMsq4cpL.js");
|
|
120
120
|
return await saveContractLineItem(this);
|
|
121
121
|
}
|
|
122
122
|
/** @internal Contract aggregate persistence only. */
|
|
@@ -126,7 +126,7 @@ var ContractLineItem = class extends SmrtObject {
|
|
|
126
126
|
return await super.save();
|
|
127
127
|
}
|
|
128
128
|
async delete(options = {}) {
|
|
129
|
-
const { deleteContractLineItem } = await import("./contract-money-
|
|
129
|
+
const { deleteContractLineItem } = await import("./contract-money-BMsq4cpL.js");
|
|
130
130
|
await deleteContractLineItem(this, options);
|
|
131
131
|
}
|
|
132
132
|
/** @internal Contract aggregate persistence only. */
|
|
@@ -257,4 +257,4 @@ var ContractLineItemCollection = class extends SmrtCollection {
|
|
|
257
257
|
//#endregion
|
|
258
258
|
export { ContractLineItemCollection_exports as n, ContractLineItem as r, ContractLineItemCollection as t };
|
|
259
259
|
|
|
260
|
-
//# sourceMappingURL=ContractLineItemCollection-
|
|
260
|
+
//# sourceMappingURL=ContractLineItemCollection-D5hBzrct.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ContractLineItemCollection-J4xtRtzE.js","names":["field"],"sources":["../../src/models/ContractLineItem.ts","../../src/collections/ContractLineItemCollection.ts"],"sourcesContent":["/**\n * ContractLineItem model - individual items on a contract\n * @packageDocumentation\n */\n\nimport {\n crossPackageRef,\n field,\n foreignKey,\n SmrtObject,\n smrt,\n} from '@happyvertical/smrt-core';\nimport type { DatabaseInterface } from '@happyvertical/smrt-core/migrations';\nimport { TenantScoped, tenantId } from '@happyvertical/smrt-tenancy';\nimport {\n DOCUMENT_MONEY_READ_REBIND,\n DOCUMENT_MONEY_WRITE,\n type DocumentMoneyReadRebindCapability,\n type DocumentMoneyWriteCapability,\n} from '../document-money-internal.js';\nimport { MoneyValidationError } from '../money.js';\nimport type {\n ContractLineItemOptions,\n ContractTaxComponentSnapshot,\n} from '../types/index.js';\n\n/**\n * ContractLineItem represents a single line item on a contract.\n *\n * Line items contain the details of what is being sold/purchased,\n * including quantity, pricing, and optional subscription details.\n *\n * @example\n * ```typescript\n * const lineItem = await lineItems.create({\n * contractId: order.id,\n * description: 'Widget Pro',\n * quantity: 10,\n * unitPrice: 4999, // $49.99 in cents\n * taxRate: 0.08\n * });\n * ```\n */\n@TenantScoped({ mode: 'optional' })\n@smrt({\n tableStrategy: 'sti',\n api: { include: ['list', 'get', 'create', 'update', 'delete'] },\n mcp: { include: ['list', 'get'] },\n cli: { skipApiCheck: true },\n})\nexport class ContractLineItem extends SmrtObject { static __smrtPackage__ = \"@happyvertical/smrt-commerce\";\n /** @internal Collection read guard returns this snapshot on its root DB. */\n rebindMoneyRead(\n capability: DocumentMoneyReadRebindCapability,\n db: DatabaseInterface,\n ): this {\n if (capability !== DOCUMENT_MONEY_READ_REBIND)\n throw new Error(\n 'Contract line read rebinding requires the owning capability',\n );\n this._db = db;\n this.options.db = db;\n return this;\n }\n\n /**\n * Tenant ID for multi-tenant isolation\n * Nullable to support both tenant-scoped and global line items\n */\n @tenantId({ nullable: true })\n tenantId: string | null = null;\n\n /**\n * Parent contract\n */\n @field({ description: 'The order, quote or agreement this line belongs to.' })\n @foreignKey('Contract', { role: 'items' })\n contractId: string = '';\n\n /**\n * Item description\n */\n @field({ description: 'What is being sold or bought on this line.' })\n description: string = '';\n\n /**\n * Quantity ordered. Decimal on purpose — contracts price fractional\n * quantities (hours, weight, bandwidth), as do invoice line items.\n */\n @field({ description: 'How many.' })\n quantity: number = 1.0;\n\n /**\n * Unit price before discount, in **integer minor units** (cents, satoshis),\n * in the document's pricing mode: tax-inclusive when its `lineAmountType`\n * is `inclusive`.\n *\n * The `= 0` initializer is load-bearing: SMRT maps an integer literal to\n * INTEGER and a decimal literal to DECIMAL. Money is exact, so `$49.99` is\n * `4999` (#2401).\n */\n @field({\n description:\n \"The price of one, before discount; includes tax when the document's prices include tax.\",\n ui: { format: 'money' },\n })\n unitPrice: number = 0;\n\n /**\n * Discount amount (flat, not percentage), in integer minor units (#2401).\n */\n @field({ ui: { format: 'money' } })\n discount: number = 0;\n\n discountType: 'none' | 'percent' | 'amount' = 'none';\n\n @field({ type: 'decimal' })\n discountRate = '0.0';\n\n /**\n * Tax rate as decimal (e.g., 0.08 for 8%). A rate is inherently fractional,\n * so it stays DECIMAL — INTEGER would truncate every meaningful value.\n */\n @field({\n description:\n 'The stored fractional tax rate for this line (0.05 means 5%).',\n })\n taxRate: number = 0.0;\n\n taxMode: 'inherit' | 'override' = 'inherit';\n\n @foreignKey('TaxCode', { nullable: true })\n taxCodeId: string | null = null;\n\n taxCodeCode: string | null = null;\n\n taxCodeName: string | null = null;\n\n taxComponentsJson: string | null = null;\n\n get taxComponents(): ContractTaxComponentSnapshot[] {\n if (this.taxComponentsJson === null) return [];\n try {\n const value: unknown = JSON.parse(this.taxComponentsJson);\n if (!Array.isArray(value)) return [];\n return value.filter(\n (entry): entry is ContractTaxComponentSnapshot =>\n !!entry &&\n typeof entry === 'object' &&\n typeof (entry as ContractTaxComponentSnapshot).taxComponentId ===\n 'string' &&\n typeof (entry as ContractTaxComponentSnapshot).name === 'string' &&\n typeof (entry as ContractTaxComponentSnapshot).percent === 'string' &&\n Number.isSafeInteger(\n (entry as ContractTaxComponentSnapshot).taxMinor,\n ),\n );\n } catch {\n return [];\n }\n }\n\n set taxComponents(value: ContractTaxComponentSnapshot[]) {\n this.taxComponentsJson = JSON.stringify(value);\n }\n\n /** Exact sum of frozen component taxes, in integer minor units. */\n @field({ ui: { format: 'money' } })\n taxAmount = 0;\n\n /** Stable issue/freeze identity, independent of lifecycle updated_at. */\n moneySnapshotId = '';\n\n /**\n * Line amount in integer minor units: net of discount and before tax for\n * `exclusive` / `none` documents, and tax-inclusive for `inclusive` ones;\n * {@link taxAmount} holds the tax. Derived by the Contract money aggregate\n * on every save.\n */\n @field({\n description:\n \"The line amount: net of discount, and including tax only when the document's prices include tax.\",\n ui: { format: 'money' },\n })\n amount: number = 0;\n\n /**\n * Optional product reference (cross-package, plain string)\n */\n @field({\n description: 'The product this line is for, if it is one you list.',\n })\n @crossPackageRef('@happyvertical/smrt-products:Product')\n productId: string = '';\n\n /**\n * SKU or item code\n */\n sku: string = '';\n\n /**\n * Start date (for lease/subscription items)\n */\n startDate: Date | null = null;\n\n /**\n * End date (for lease/subscription items)\n */\n endDate: Date | null = null;\n\n /**\n * Billing period (for subscriptions: 'monthly', 'yearly', etc.)\n */\n billingPeriod: string = '';\n\n /**\n * Sort order within the contract\n */\n sortOrder: number = 0;\n\n constructor(options: ContractLineItemOptions = {}) {\n super(options);\n if (options.tenantId !== undefined) this.tenantId = options.tenantId;\n if (options.contractId !== undefined) this.contractId = options.contractId;\n if (options.description !== undefined)\n this.description = options.description;\n if (options.quantity !== undefined) this.quantity = options.quantity;\n if (options.unitPrice !== undefined) this.unitPrice = options.unitPrice;\n if (options.discount !== undefined) this.discount = options.discount;\n if (options.discountType !== undefined)\n this.discountType = options.discountType;\n if (options.discountRate !== undefined)\n this.discountRate = options.discountRate;\n if (options.taxRate !== undefined) this.taxRate = options.taxRate;\n if (options.taxMode !== undefined) this.taxMode = options.taxMode;\n if (options.taxCodeId !== undefined) this.taxCodeId = options.taxCodeId;\n if (options.taxCodeCode !== undefined)\n this.taxCodeCode = options.taxCodeCode;\n if (options.taxCodeName !== undefined)\n this.taxCodeName = options.taxCodeName;\n if (options.taxComponentsJson !== undefined)\n this.taxComponentsJson = options.taxComponentsJson;\n if (options.taxComponents !== undefined)\n this.taxComponents = structuredClone(options.taxComponents);\n if (options.taxAmount !== undefined) this.taxAmount = options.taxAmount;\n if (options.moneySnapshotId !== undefined)\n this.moneySnapshotId = options.moneySnapshotId;\n if (options.amount !== undefined) this.amount = options.amount;\n if (options.productId !== undefined) this.productId = options.productId;\n if (options.sku !== undefined) this.sku = options.sku;\n if (options.startDate !== undefined) this.startDate = options.startDate;\n if (options.endDate !== undefined) this.endDate = options.endDate;\n if (options.billingPeriod !== undefined)\n this.billingPeriod = options.billingPeriod;\n if (options.sortOrder !== undefined) this.sortOrder = options.sortOrder;\n }\n\n override async initialize(): Promise<this> {\n await super.initialize();\n // A row read from the database must not be old-format money data.\n if (this.isPersisted) {\n const { assertHydratedMoneyRowCurrent } = await import(\n '../upgrade/detect.js'\n );\n await assertHydratedMoneyRowCurrent(this);\n }\n return this;\n }\n\n override async save(): Promise<this> {\n const { saveContractLineItem } = await import('../contract-money.js');\n return (await saveContractLineItem(this)) as this;\n }\n\n /** @internal Contract aggregate persistence only. */\n async saveDerived(capability: DocumentMoneyWriteCapability): Promise<this> {\n if (capability !== DOCUMENT_MONEY_WRITE)\n throw new Error('Contract line money write capability is required');\n // Money is integer minor units: refuse a major-unit price before it can\n // reach the database (#2401), ahead of the aggregate recompute.\n for (const [field, value] of [\n ['unitPrice', this.unitPrice],\n ['discount', this.discount],\n ] as const)\n if (!Number.isSafeInteger(value))\n throw new MoneyValidationError(\n 'UNSAFE_MONEY',\n `Contract line ${field} ${value} must be a safe integer number of minor units`,\n );\n return (await super.save()) as this;\n }\n\n override async delete(\n options: { expectedUpdatedAt?: Date | string } = {},\n ): Promise<void> {\n const { deleteContractLineItem } = await import('../contract-money.js');\n await deleteContractLineItem(this, options);\n }\n\n /** @internal Contract aggregate persistence only. */\n async deleteDerived(\n capability: DocumentMoneyWriteCapability,\n options: { expectedUpdatedAt?: Date | string } = {},\n ): Promise<void> {\n if (capability !== DOCUMENT_MONEY_WRITE)\n throw new Error('Contract line money write capability is required');\n await super.delete(options);\n }\n\n /**\n * Check if this is a subscription/recurring item\n */\n isRecurring(): boolean {\n return !!this.billingPeriod && !!this.startDate;\n }\n\n /**\n * Check if subscription is active\n */\n isSubscriptionActive(): boolean {\n if (!this.isRecurring()) return false;\n if (!this.startDate) return false;\n\n const now = new Date();\n if (now < this.startDate) return false;\n if (this.endDate && now > this.endDate) return false;\n\n return true;\n }\n}\n\nexport default ContractLineItem;\n","/**\n * ContractLineItemCollection - Collection manager for ContractLineItem objects\n * @packageDocumentation\n */\n\nimport {\n SmrtCollection,\n type SmrtListOptions,\n type SmrtObject,\n type SmrtSelectedRow,\n type SmrtSelectField,\n type SmrtStiReadScope,\n type SmrtWhereClause,\n} from '@happyvertical/smrt-core';\nimport {\n getTenantId,\n queryGlobal,\n queryWithGlobals,\n} from '@happyvertical/smrt-tenancy';\nimport { DOCUMENT_MONEY_READ_REBIND } from '../document-money-internal.js';\nimport { ContractLineItem } from '../models/ContractLineItem.js';\nimport { withTenantMoneyCollectionRead } from '../tenant-money-barrier.js';\nimport { assertCommerceMoneySchemaCurrent } from '../upgrade/detect.js';\n\nexport class ContractLineItemCollection extends SmrtCollection<ContractLineItem> {\n static readonly _itemClass = ContractLineItem;\n\n private async withMoneyRead<T>(operation: () => Promise<T>): Promise<T> {\n const rootDb = this.db;\n const tenantId = getTenantId();\n if (!tenantId) {\n // Without a tenant, still refuse a database with old money storage.\n await assertCommerceMoneySchemaCurrent(rootDb);\n return operation();\n }\n const result = await withTenantMoneyCollectionRead(\n rootDb,\n tenantId,\n (boundDb) => this.withDatabase(boundDb, operation),\n );\n if (result instanceof ContractLineItem)\n result.rebindMoneyRead(DOCUMENT_MONEY_READ_REBIND, rootDb);\n else if (Array.isArray(result))\n for (const item of result)\n if (item instanceof ContractLineItem)\n item.rebindMoneyRead(DOCUMENT_MONEY_READ_REBIND, rootDb);\n return result;\n }\n\n override async get(\n filter: string | SmrtWhereClause<ContractLineItem>,\n options: Parameters<SmrtCollection<ContractLineItem>['get']>[1] = {},\n ): Promise<ContractLineItem | null> {\n return this.withMoneyRead(() => super.get(filter, options));\n }\n\n override async list<\n const Select extends readonly SmrtSelectField<ContractLineItem>[],\n >(\n options: Omit<SmrtListOptions<ContractLineItem>, 'select' | 'stiScope'> & {\n select: Select;\n include?: never;\n stiScope: SmrtStiReadScope;\n },\n ): Promise<Record<string, unknown>[]>;\n override async list<\n const Select extends readonly SmrtSelectField<ContractLineItem>[],\n >(\n options: Omit<SmrtListOptions<ContractLineItem>, 'select' | 'stiScope'> & {\n select: Select;\n include?: never;\n stiScope?: undefined;\n },\n ): Promise<SmrtSelectedRow<ContractLineItem, Select>[]>;\n override async list(\n options: Omit<SmrtListOptions<ContractLineItem>, 'select' | 'stiScope'> & {\n select?: undefined;\n stiScope: SmrtStiReadScope;\n },\n ): Promise<SmrtObject[]>;\n override async list(options?: undefined): Promise<ContractLineItem[]>;\n override async list(\n options: Omit<SmrtListOptions<ContractLineItem>, 'select' | 'stiScope'> & {\n select?: undefined;\n stiScope?: undefined;\n },\n ): Promise<ContractLineItem[]>;\n override async list(\n options:\n | (Omit<SmrtListOptions<ContractLineItem>, 'select'> & {\n select?: undefined;\n })\n | undefined,\n ): Promise<SmrtObject[]>;\n override async list(\n options: SmrtListOptions<ContractLineItem> | undefined,\n ): Promise<SmrtObject[] | Record<string, unknown>[]>;\n override async list(\n options: SmrtListOptions<ContractLineItem> = {},\n ): Promise<SmrtObject[] | Record<string, unknown>[]> {\n return this.withMoneyRead(() => super.list(options));\n }\n\n /**\n * Find contract line items by their parent contract.\n *\n * @param contractId - Contract ID\n * @returns Array of contract line items, sorted by sortOrder\n */\n async findByContract(contractId: string): Promise<ContractLineItem[]> {\n return await this.list({\n where: { contractId },\n orderBy: 'sortOrder ASC',\n });\n }\n\n // ============================================================================\n // Tenant Helper Methods\n // ============================================================================\n\n /**\n * Find all contract line items belonging to a specific tenant\n *\n * @param tenantId - Tenant ID\n * @returns Array of contract line items for the tenant\n */\n async findByTenant(tenantId: string): Promise<ContractLineItem[]> {\n return this.list({ where: { tenantId } });\n }\n\n /**\n * Find all global contract line items (not associated with any tenant).\n *\n * Routes through the shared tenant-global helper so it does not throw under\n * an active tenant context (an explicit `tenant_id IS NULL` filter would be\n * flagged as an isolation violation). (#1600)\n *\n * @returns Array of global contract line items\n */\n async findGlobal(): Promise<ContractLineItem[]> {\n return queryGlobal<ContractLineItem>(this);\n }\n\n /**\n * Find contract line items for a tenant including global line items.\n *\n * Fails closed if an active tenant context requests a different tenant's\n * rows; the admin/system path keeps the cross-tenant capability. (#1600)\n *\n * @param tenantId - Tenant ID\n * @returns Array of tenant-specific and global contract line items\n */\n async findWithGlobals(tenantId: string): Promise<ContractLineItem[]> {\n return queryWithGlobals<ContractLineItem>(\n this,\n tenantId,\n 'ContractLineItem.findWithGlobals',\n );\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ContractLineItemCollection-D5hBzrct.js","names":["field"],"sources":["../../src/models/ContractLineItem.ts","../../src/collections/ContractLineItemCollection.ts"],"sourcesContent":["/**\n * ContractLineItem model - individual items on a contract\n * @packageDocumentation\n */\n\nimport {\n crossPackageRef,\n field,\n foreignKey,\n SmrtObject,\n smrt,\n} from '@happyvertical/smrt-core';\nimport type { DatabaseInterface } from '@happyvertical/smrt-core/migrations';\nimport { TenantScoped, tenantId } from '@happyvertical/smrt-tenancy';\nimport {\n DOCUMENT_MONEY_READ_REBIND,\n DOCUMENT_MONEY_WRITE,\n type DocumentMoneyReadRebindCapability,\n type DocumentMoneyWriteCapability,\n} from '../document-money-internal.js';\nimport { MoneyValidationError } from '../money.js';\nimport type {\n ContractLineItemOptions,\n ContractTaxComponentSnapshot,\n} from '../types/index.js';\n\n/**\n * ContractLineItem represents a single line item on a contract.\n *\n * Line items contain the details of what is being sold/purchased,\n * including quantity, pricing, and optional subscription details.\n *\n * @example\n * ```typescript\n * const lineItem = await lineItems.create({\n * contractId: order.id,\n * description: 'Widget Pro',\n * quantity: 10,\n * unitPrice: 4999, // $49.99 in cents\n * taxRate: 0.08\n * });\n * ```\n */\n@TenantScoped({ mode: 'optional' })\n@smrt({\n tableStrategy: 'sti',\n api: { include: ['list', 'get', 'create', 'update', 'delete'] },\n mcp: { include: ['list', 'get'] },\n cli: { skipApiCheck: true },\n})\nexport class ContractLineItem extends SmrtObject { static __smrtPackage__ = \"@happyvertical/smrt-commerce\";\n /** @internal Collection read guard returns this snapshot on its root DB. */\n rebindMoneyRead(\n capability: DocumentMoneyReadRebindCapability,\n db: DatabaseInterface,\n ): this {\n if (capability !== DOCUMENT_MONEY_READ_REBIND)\n throw new Error(\n 'Contract line read rebinding requires the owning capability',\n );\n this._db = db;\n this.options.db = db;\n return this;\n }\n\n /**\n * Tenant ID for multi-tenant isolation\n * Nullable to support both tenant-scoped and global line items\n */\n @tenantId({ nullable: true })\n tenantId: string | null = null;\n\n /**\n * Parent contract\n */\n @field({ description: 'The order, quote or agreement this line belongs to.' })\n @foreignKey('Contract', { role: 'items' })\n contractId: string = '';\n\n /**\n * Item description\n */\n @field({ description: 'What is being sold or bought on this line.' })\n description: string = '';\n\n /**\n * Quantity ordered. Decimal on purpose — contracts price fractional\n * quantities (hours, weight, bandwidth), as do invoice line items.\n */\n @field({ description: 'How many.' })\n quantity: number = 1.0;\n\n /**\n * Unit price before discount, in **integer minor units** (cents, satoshis),\n * in the document's pricing mode: tax-inclusive when its `lineAmountType`\n * is `inclusive`.\n *\n * The `= 0` initializer is load-bearing: SMRT maps an integer literal to\n * INTEGER and a decimal literal to DECIMAL. Money is exact, so `$49.99` is\n * `4999` (#2401).\n */\n @field({\n description:\n \"The price of one, before discount; includes tax when the document's prices include tax.\",\n ui: { format: 'money' },\n })\n unitPrice: number = 0;\n\n /**\n * Discount amount (flat, not percentage), in integer minor units (#2401).\n */\n @field({ ui: { format: 'money' } })\n discount: number = 0;\n\n discountType: 'none' | 'percent' | 'amount' = 'none';\n\n @field({ type: 'decimal' })\n discountRate = '0.0';\n\n /**\n * Tax rate as decimal (e.g., 0.08 for 8%). A rate is inherently fractional,\n * so it stays DECIMAL — INTEGER would truncate every meaningful value.\n */\n @field({\n description:\n 'The stored fractional tax rate for this line (0.05 means 5%).',\n })\n taxRate: number = 0.0;\n\n taxMode: 'inherit' | 'override' = 'inherit';\n\n @foreignKey('TaxCode', { nullable: true })\n taxCodeId: string | null = null;\n\n taxCodeCode: string | null = null;\n\n taxCodeName: string | null = null;\n\n taxComponentsJson: string | null = null;\n\n get taxComponents(): ContractTaxComponentSnapshot[] {\n if (this.taxComponentsJson === null) return [];\n try {\n const value: unknown = JSON.parse(this.taxComponentsJson);\n if (!Array.isArray(value)) return [];\n return value.filter(\n (entry): entry is ContractTaxComponentSnapshot =>\n !!entry &&\n typeof entry === 'object' &&\n typeof (entry as ContractTaxComponentSnapshot).taxComponentId ===\n 'string' &&\n typeof (entry as ContractTaxComponentSnapshot).name === 'string' &&\n typeof (entry as ContractTaxComponentSnapshot).percent === 'string' &&\n Number.isSafeInteger(\n (entry as ContractTaxComponentSnapshot).taxMinor,\n ),\n );\n } catch {\n return [];\n }\n }\n\n set taxComponents(value: ContractTaxComponentSnapshot[]) {\n this.taxComponentsJson = JSON.stringify(value);\n }\n\n /** Exact sum of frozen component taxes, in integer minor units. */\n @field({ ui: { format: 'money' } })\n taxAmount = 0;\n\n /** Stable issue/freeze identity, independent of lifecycle updated_at. */\n moneySnapshotId = '';\n\n /**\n * Line amount in integer minor units: net of discount and before tax for\n * `exclusive` / `none` documents, and tax-inclusive for `inclusive` ones;\n * {@link taxAmount} holds the tax. Derived by the Contract money aggregate\n * on every save.\n */\n @field({\n description:\n \"The line amount: net of discount, and including tax only when the document's prices include tax.\",\n ui: { format: 'money' },\n })\n amount: number = 0;\n\n /**\n * Optional product reference (cross-package, plain string)\n */\n @field({\n description: 'The product this line is for, if it is one you list.',\n })\n @crossPackageRef('@happyvertical/smrt-products:Product')\n productId: string = '';\n\n /**\n * SKU or item code\n */\n sku: string = '';\n\n /**\n * Start date (for lease/subscription items)\n */\n startDate: Date | null = null;\n\n /**\n * End date (for lease/subscription items)\n */\n endDate: Date | null = null;\n\n /**\n * Billing period (for subscriptions: 'monthly', 'yearly', etc.)\n */\n billingPeriod: string = '';\n\n /**\n * Sort order within the contract\n */\n sortOrder: number = 0;\n\n constructor(options: ContractLineItemOptions = {}) {\n super(options);\n if (options.tenantId !== undefined) this.tenantId = options.tenantId;\n if (options.contractId !== undefined) this.contractId = options.contractId;\n if (options.description !== undefined)\n this.description = options.description;\n if (options.quantity !== undefined) this.quantity = options.quantity;\n if (options.unitPrice !== undefined) this.unitPrice = options.unitPrice;\n if (options.discount !== undefined) this.discount = options.discount;\n if (options.discountType !== undefined)\n this.discountType = options.discountType;\n if (options.discountRate !== undefined)\n this.discountRate = options.discountRate;\n if (options.taxRate !== undefined) this.taxRate = options.taxRate;\n if (options.taxMode !== undefined) this.taxMode = options.taxMode;\n if (options.taxCodeId !== undefined) this.taxCodeId = options.taxCodeId;\n if (options.taxCodeCode !== undefined)\n this.taxCodeCode = options.taxCodeCode;\n if (options.taxCodeName !== undefined)\n this.taxCodeName = options.taxCodeName;\n if (options.taxComponentsJson !== undefined)\n this.taxComponentsJson = options.taxComponentsJson;\n if (options.taxComponents !== undefined)\n this.taxComponents = structuredClone(options.taxComponents);\n if (options.taxAmount !== undefined) this.taxAmount = options.taxAmount;\n if (options.moneySnapshotId !== undefined)\n this.moneySnapshotId = options.moneySnapshotId;\n if (options.amount !== undefined) this.amount = options.amount;\n if (options.productId !== undefined) this.productId = options.productId;\n if (options.sku !== undefined) this.sku = options.sku;\n if (options.startDate !== undefined) this.startDate = options.startDate;\n if (options.endDate !== undefined) this.endDate = options.endDate;\n if (options.billingPeriod !== undefined)\n this.billingPeriod = options.billingPeriod;\n if (options.sortOrder !== undefined) this.sortOrder = options.sortOrder;\n }\n\n override async initialize(): Promise<this> {\n await super.initialize();\n // A row read from the database must not be old-format money data.\n if (this.isPersisted) {\n const { assertHydratedMoneyRowCurrent } = await import(\n '../upgrade/detect.js'\n );\n await assertHydratedMoneyRowCurrent(this);\n }\n return this;\n }\n\n override async save(): Promise<this> {\n const { saveContractLineItem } = await import('../contract-money.js');\n return (await saveContractLineItem(this)) as this;\n }\n\n /** @internal Contract aggregate persistence only. */\n async saveDerived(capability: DocumentMoneyWriteCapability): Promise<this> {\n if (capability !== DOCUMENT_MONEY_WRITE)\n throw new Error('Contract line money write capability is required');\n // Money is integer minor units: refuse a major-unit price before it can\n // reach the database (#2401), ahead of the aggregate recompute.\n for (const [field, value] of [\n ['unitPrice', this.unitPrice],\n ['discount', this.discount],\n ] as const)\n if (!Number.isSafeInteger(value))\n throw new MoneyValidationError(\n 'UNSAFE_MONEY',\n `Contract line ${field} ${value} must be a safe integer number of minor units`,\n );\n return (await super.save()) as this;\n }\n\n override async delete(\n options: { expectedUpdatedAt?: Date | string } = {},\n ): Promise<void> {\n const { deleteContractLineItem } = await import('../contract-money.js');\n await deleteContractLineItem(this, options);\n }\n\n /** @internal Contract aggregate persistence only. */\n async deleteDerived(\n capability: DocumentMoneyWriteCapability,\n options: { expectedUpdatedAt?: Date | string } = {},\n ): Promise<void> {\n if (capability !== DOCUMENT_MONEY_WRITE)\n throw new Error('Contract line money write capability is required');\n await super.delete(options);\n }\n\n /**\n * Check if this is a subscription/recurring item\n */\n isRecurring(): boolean {\n return !!this.billingPeriod && !!this.startDate;\n }\n\n /**\n * Check if subscription is active\n */\n isSubscriptionActive(): boolean {\n if (!this.isRecurring()) return false;\n if (!this.startDate) return false;\n\n const now = new Date();\n if (now < this.startDate) return false;\n if (this.endDate && now > this.endDate) return false;\n\n return true;\n }\n}\n\nexport default ContractLineItem;\n","/**\n * ContractLineItemCollection - Collection manager for ContractLineItem objects\n * @packageDocumentation\n */\n\nimport {\n SmrtCollection,\n type SmrtListOptions,\n type SmrtObject,\n type SmrtSelectedRow,\n type SmrtSelectField,\n type SmrtStiReadScope,\n type SmrtWhereClause,\n} from '@happyvertical/smrt-core';\nimport {\n getTenantId,\n queryGlobal,\n queryWithGlobals,\n} from '@happyvertical/smrt-tenancy';\nimport { DOCUMENT_MONEY_READ_REBIND } from '../document-money-internal.js';\nimport { ContractLineItem } from '../models/ContractLineItem.js';\nimport { withTenantMoneyCollectionRead } from '../tenant-money-barrier.js';\nimport { assertCommerceMoneySchemaCurrent } from '../upgrade/detect.js';\n\nexport class ContractLineItemCollection extends SmrtCollection<ContractLineItem> {\n static readonly _itemClass = ContractLineItem;\n\n private async withMoneyRead<T>(operation: () => Promise<T>): Promise<T> {\n const rootDb = this.db;\n const tenantId = getTenantId();\n if (!tenantId) {\n // Without a tenant, still refuse a database with old money storage.\n await assertCommerceMoneySchemaCurrent(rootDb);\n return operation();\n }\n const result = await withTenantMoneyCollectionRead(\n rootDb,\n tenantId,\n (boundDb) => this.withDatabase(boundDb, operation),\n );\n if (result instanceof ContractLineItem)\n result.rebindMoneyRead(DOCUMENT_MONEY_READ_REBIND, rootDb);\n else if (Array.isArray(result))\n for (const item of result)\n if (item instanceof ContractLineItem)\n item.rebindMoneyRead(DOCUMENT_MONEY_READ_REBIND, rootDb);\n return result;\n }\n\n override async get(\n filter: string | SmrtWhereClause<ContractLineItem>,\n options: Parameters<SmrtCollection<ContractLineItem>['get']>[1] = {},\n ): Promise<ContractLineItem | null> {\n return this.withMoneyRead(() => super.get(filter, options));\n }\n\n override async list<\n const Select extends readonly SmrtSelectField<ContractLineItem>[],\n >(\n options: Omit<SmrtListOptions<ContractLineItem>, 'select' | 'stiScope'> & {\n select: Select;\n include?: never;\n stiScope: SmrtStiReadScope;\n },\n ): Promise<Record<string, unknown>[]>;\n override async list<\n const Select extends readonly SmrtSelectField<ContractLineItem>[],\n >(\n options: Omit<SmrtListOptions<ContractLineItem>, 'select' | 'stiScope'> & {\n select: Select;\n include?: never;\n stiScope?: undefined;\n },\n ): Promise<SmrtSelectedRow<ContractLineItem, Select>[]>;\n override async list(\n options: Omit<SmrtListOptions<ContractLineItem>, 'select' | 'stiScope'> & {\n select?: undefined;\n stiScope: SmrtStiReadScope;\n },\n ): Promise<SmrtObject[]>;\n override async list(options?: undefined): Promise<ContractLineItem[]>;\n override async list(\n options: Omit<SmrtListOptions<ContractLineItem>, 'select' | 'stiScope'> & {\n select?: undefined;\n stiScope?: undefined;\n },\n ): Promise<ContractLineItem[]>;\n override async list(\n options:\n | (Omit<SmrtListOptions<ContractLineItem>, 'select'> & {\n select?: undefined;\n })\n | undefined,\n ): Promise<SmrtObject[]>;\n override async list(\n options: SmrtListOptions<ContractLineItem> | undefined,\n ): Promise<SmrtObject[] | Record<string, unknown>[]>;\n override async list(\n options: SmrtListOptions<ContractLineItem> = {},\n ): Promise<SmrtObject[] | Record<string, unknown>[]> {\n return this.withMoneyRead(() => super.list(options));\n }\n\n /**\n * Find contract line items by their parent contract.\n *\n * @param contractId - Contract ID\n * @returns Array of contract line items, sorted by sortOrder\n */\n async findByContract(contractId: string): Promise<ContractLineItem[]> {\n return await this.list({\n where: { contractId },\n orderBy: 'sortOrder ASC',\n });\n }\n\n // ============================================================================\n // Tenant Helper Methods\n // ============================================================================\n\n /**\n * Find all contract line items belonging to a specific tenant\n *\n * @param tenantId - Tenant ID\n * @returns Array of contract line items for the tenant\n */\n async findByTenant(tenantId: string): Promise<ContractLineItem[]> {\n return this.list({ where: { tenantId } });\n }\n\n /**\n * Find all global contract line items (not associated with any tenant).\n *\n * Routes through the shared tenant-global helper so it does not throw under\n * an active tenant context (an explicit `tenant_id IS NULL` filter would be\n * flagged as an isolation violation). (#1600)\n *\n * @returns Array of global contract line items\n */\n async findGlobal(): Promise<ContractLineItem[]> {\n return queryGlobal<ContractLineItem>(this);\n }\n\n /**\n * Find contract line items for a tenant including global line items.\n *\n * Fails closed if an active tenant context requests a different tenant's\n * rows; the admin/system path keeps the cross-tenant capability. (#1600)\n *\n * @param tenantId - Tenant ID\n * @returns Array of tenant-specific and global contract line items\n */\n async findWithGlobals(tenantId: string): Promise<ContractLineItem[]> {\n return queryWithGlobals<ContractLineItem>(\n this,\n tenantId,\n 'ContractLineItem.findWithGlobals',\n );\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AAkDO,IAAM,mBAAN,cAA+B,WAAW;;CAE/C,gBACE,YACA,IACM;EACN,IAAI,eAAe,4BACjB,MAAM,IAAI,MACR,6DACF;EACF,KAAK,MAAM;EACX,KAAK,QAAQ,KAAK;EAClB,OAAO;CACT;CAOA,WAA0B;CAO1B,aAAqB;CAMrB,cAAsB;CAOtB,WAAmB;CAgBnB,YAAoB;CAMpB,WAAmB;CAEnB,eAA8C;CAG9C,eAAe;CAUf,UAAkB;CAElB,UAAkC;CAGlC,YAA2B;CAE3B,cAA6B;CAE7B,cAA6B;CAE7B,oBAAmC;CAEnC,IAAI,gBAAgD;EAClD,IAAI,KAAK,sBAAsB,MAAM,OAAO,CAAC;EAC7C,IAAI;GACF,MAAM,QAAiB,KAAK,MAAM,KAAK,iBAAiB;GACxD,IAAI,CAAC,MAAM,QAAQ,KAAK,GAAG,OAAO,CAAC;GACnC,OAAO,MAAM,QACV,UACC,CAAC,CAAC,SACF,OAAO,UAAU,YACjB,OAAQ,MAAuC,mBAC7C,YACF,OAAQ,MAAuC,SAAS,YACxD,OAAQ,MAAuC,YAAY,YAC3D,OAAO,cACJ,MAAuC,QAC1C,CACJ;EACF,QAAQ;GACN,OAAO,CAAC;EACV;CACF;CAEA,IAAI,cAAc,OAAuC;EACvD,KAAK,oBAAoB,KAAK,UAAU,KAAK;CAC/C;CAIA,YAAY;;CAGZ,kBAAkB;CAalB,SAAiB;CASjB,YAAoB;;;;CAKpB,MAAc;;;;CAKd,YAAyB;;;;CAKzB,UAAuB;;;;CAKvB,gBAAwB;;;;CAKxB,YAAoB;CAEpB,YAAY,UAAmC,CAAC,GAAG;EACjD,MAAM,OAAO;EACb,IAAI,QAAQ,aAAa,KAAA,GAAW,KAAK,WAAW,QAAQ;EAC5D,IAAI,QAAQ,eAAe,KAAA,GAAW,KAAK,aAAa,QAAQ;EAChE,IAAI,QAAQ,gBAAgB,KAAA,GAC1B,KAAK,cAAc,QAAQ;EAC7B,IAAI,QAAQ,aAAa,KAAA,GAAW,KAAK,WAAW,QAAQ;EAC5D,IAAI,QAAQ,cAAc,KAAA,GAAW,KAAK,YAAY,QAAQ;EAC9D,IAAI,QAAQ,aAAa,KAAA,GAAW,KAAK,WAAW,QAAQ;EAC5D,IAAI,QAAQ,iBAAiB,KAAA,GAC3B,KAAK,eAAe,QAAQ;EAC9B,IAAI,QAAQ,iBAAiB,KAAA,GAC3B,KAAK,eAAe,QAAQ;EAC9B,IAAI,QAAQ,YAAY,KAAA,GAAW,KAAK,UAAU,QAAQ;EAC1D,IAAI,QAAQ,YAAY,KAAA,GAAW,KAAK,UAAU,QAAQ;EAC1D,IAAI,QAAQ,cAAc,KAAA,GAAW,KAAK,YAAY,QAAQ;EAC9D,IAAI,QAAQ,gBAAgB,KAAA,GAC1B,KAAK,cAAc,QAAQ;EAC7B,IAAI,QAAQ,gBAAgB,KAAA,GAC1B,KAAK,cAAc,QAAQ;EAC7B,IAAI,QAAQ,sBAAsB,KAAA,GAChC,KAAK,oBAAoB,QAAQ;EACnC,IAAI,QAAQ,kBAAkB,KAAA,GAC5B,KAAK,gBAAgB,gBAAgB,QAAQ,aAAa;EAC5D,IAAI,QAAQ,cAAc,KAAA,GAAW,KAAK,YAAY,QAAQ;EAC9D,IAAI,QAAQ,oBAAoB,KAAA,GAC9B,KAAK,kBAAkB,QAAQ;EACjC,IAAI,QAAQ,WAAW,KAAA,GAAW,KAAK,SAAS,QAAQ;EACxD,IAAI,QAAQ,cAAc,KAAA,GAAW,KAAK,YAAY,QAAQ;EAC9D,IAAI,QAAQ,QAAQ,KAAA,GAAW,KAAK,MAAM,QAAQ;EAClD,IAAI,QAAQ,cAAc,KAAA,GAAW,KAAK,YAAY,QAAQ;EAC9D,IAAI,QAAQ,YAAY,KAAA,GAAW,KAAK,UAAU,QAAQ;EAC1D,IAAI,QAAQ,kBAAkB,KAAA,GAC5B,KAAK,gBAAgB,QAAQ;EAC/B,IAAI,QAAQ,cAAc,KAAA,GAAW,KAAK,YAAY,QAAQ;CAChE;CAEA,MAAe,aAA4B;EACzC,MAAM,MAAM,WAAW;EAEvB,IAAI,KAAK,aAAa;GACpB,MAAM,EAAE,kCAAkC,MAAM,OAC9C,uBAAA,CAAA,MAAA,MAAA,EAAA,CAAA;GAEF,MAAM,8BAA8B,IAAI;EAC1C;EACA,OAAO;CACT;CAEA,MAAe,OAAsB;EACnC,MAAM,EAAE,yBAAyB,MAAM,OAAO;EAC9C,OAAQ,MAAM,qBAAqB,IAAI;CACzC;;CAGA,MAAM,YAAY,YAAyD;EACzE,IAAI,eAAe,sBACjB,MAAM,IAAI,MAAM,kDAAkD;EAGpE,KAAA,MAAW,CAACA,QAAO,UAAU,CAC3B,CAAC,aAAa,KAAK,SAAS,GAC5B,CAAC,YAAY,KAAK,QAAQ,CAC5B,GACE,IAAI,CAAC,OAAO,cAAc,KAAK,GAC7B,MAAM,IAAI,qBACR,gBACA,iBAAiBA,OAAK,GAAI,MAAK,8CACjC;EACJ,OAAQ,MAAM,MAAM,KAAK;CAC3B;CAEA,MAAe,OACb,UAAiD,CAAC,GACnC;EACf,MAAM,EAAE,2BAA2B,MAAM,OAAO;EAChD,MAAM,uBAAuB,MAAM,OAAO;CAC5C;;CAGA,MAAM,cACJ,YACA,UAAiD,CAAC,GACnC;EACf,IAAI,eAAe,sBACjB,MAAM,IAAI,MAAM,kDAAkD;EACpE,MAAM,MAAM,OAAO,OAAO;CAC5B;;;;CAKA,cAAuB;EACrB,OAAO,CAAC,CAAC,KAAK,iBAAiB,CAAC,CAAC,KAAK;CACxC;;;;CAKA,uBAAgC;EAC9B,IAAI,CAAC,KAAK,YAAY,GAAG,OAAO;EAChC,IAAI,CAAC,KAAK,WAAW,OAAO;EAE5B,MAAM,sBAAM,IAAI,KAAK;EACrB,IAAI,MAAM,KAAK,WAAW,OAAO;EACjC,IAAI,KAAK,WAAW,MAAM,KAAK,SAAS,OAAO;EAE/C,OAAO;CACT;AACF;AAvRmD,cAAtC,kBAA6C,mBAAkB,8BAAA;AAoB1E,gBAAA,CADC,SAAS,EAAE,UAAU,KAAK,CAAC,CAAA,GAnBjB,iBAoBX,WAAA,YAAA,CAAA;AAOA,gBAAA,CAFC,MAAM,EAAE,aAAa,sDAAsD,CAAC,GAC5E,WAAW,YAAY,EAAE,MAAM,QAAQ,CAAC,CAAA,GA1B9B,iBA2BX,WAAA,cAAA,CAAA;AAMA,gBAAA,CADC,MAAM,EAAE,aAAa,6CAA6C,CAAC,CAAA,GAhCzD,iBAiCX,WAAA,eAAA,CAAA;AAOA,gBAAA,CADC,MAAM,EAAE,aAAa,YAAY,CAAC,CAAA,GAvCxB,iBAwCX,WAAA,YAAA,CAAA;AAgBA,gBAAA,CALC,MAAM;CACL,aACE;CACF,IAAI,EAAE,QAAQ,QAAQ;AACxB,CAAC,CAAA,GAvDU,iBAwDX,WAAA,aAAA,CAAA;AAMA,gBAAA,CADC,MAAM,EAAE,IAAI,EAAE,QAAQ,QAAQ,EAAE,CAAC,CAAA,GA7DvB,iBA8DX,WAAA,YAAA,CAAA;AAKA,gBAAA,CADC,MAAM,EAAE,MAAM,UAAU,CAAC,CAAA,GAlEf,iBAmEX,WAAA,gBAAA,CAAA;AAUA,gBAAA,CAJC,MAAM,EACL,aACE,gEACJ,CAAC,CAAA,GA5EU,iBA6EX,WAAA,WAAA,CAAA;AAKA,gBAAA,CADC,WAAW,WAAW,EAAE,UAAU,KAAK,CAAC,CAAA,GAjF9B,iBAkFX,WAAA,aAAA,CAAA;AAoCA,gBAAA,CADC,MAAM,EAAE,IAAI,EAAE,QAAQ,QAAQ,EAAE,CAAC,CAAA,GArHvB,iBAsHX,WAAA,aAAA,CAAA;AAgBA,gBAAA,CALC,MAAM;CACL,aACE;CACF,IAAI,EAAE,QAAQ,QAAQ;AACxB,CAAC,CAAA,GArIU,iBAsIX,WAAA,UAAA,CAAA;AASA,gBAAA,CAJC,MAAM,EACL,aAAa,uDACf,CAAC,GACA,gBAAgB,sCAAsC,CAAA,GA9I5C,iBA+IX,WAAA,aAAA,CAAA;AA/IW,mBAAN,gBAAA,CAPN,aAAa,EAAE,MAAM,WAAW,CAAC,GACjC,KAAK;CACJ,eAAe;CACf,KAAK,EAAE,SAAS;EAAC;EAAQ;EAAO;EAAU;EAAU;CAAQ,EAAE;CAC9D,KAAK,EAAE,SAAS,CAAC,QAAQ,KAAK,EAAE;CAChC,KAAK,EAAE,cAAc,KAAK;AAC5B,CAAC,CAAA,GACY,gBAAA;;;;AC1BN,IAAM,6BAAN,cAAyC,eAAiC;CAC/E,OAAgB,aAAa;CAE7B,MAAc,cAAiB,WAAyC;EACtE,MAAM,SAAS,KAAK;EACpB,MAAM,WAAW,YAAY;EAC7B,IAAI,CAAC,UAAU;GAEb,MAAM,iCAAiC,MAAM;GAC7C,OAAO,UAAU;EACnB;EACA,MAAM,SAAS,MAAM,8BACnB,QACA,WACC,YAAY,KAAK,aAAa,SAAS,SAAS,CACnD;EACA,IAAI,kBAAkB,kBACpB,OAAO,gBAAgB,4BAA4B,MAAM;OAAA,IAClD,MAAM,QAAQ,MAAM;QAC3B,MAAW,QAAQ,QACjB,IAAI,gBAAgB,kBAClB,KAAK,gBAAgB,4BAA4B,MAAM;EAAA;EAC7D,OAAO;CACT;CAEA,MAAe,IACb,QACA,UAAkE,CAAC,GACjC;EAClC,OAAO,KAAK,oBAAoB,MAAM,IAAI,QAAQ,OAAO,CAAC;CAC5D;CA2CA,MAAe,KACb,UAA6C,CAAC,GACK;EACnD,OAAO,KAAK,oBAAoB,MAAM,KAAK,OAAO,CAAC;CACrD;;;;;;;CAQA,MAAM,eAAe,YAAiD;EACpE,OAAO,MAAM,KAAK,KAAK;GACrB,OAAO,EAAE,WAAW;GACpB,SAAS;EACX,CAAC;CACH;;;;;;;CAYA,MAAM,aAAa,UAA+C;EAChE,OAAO,KAAK,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,CAAC;CAC1C;;;;;;;;;;CAWA,MAAM,aAA0C;EAC9C,OAAO,YAA8B,IAAI;CAC3C;;;;;;;;;;CAWA,MAAM,gBAAgB,UAA+C;EACnE,OAAO,iBACL,MACA,UACA,kCACF;CACF;AACF"}
|
|
@@ -4,11 +4,11 @@ import { MoneyValidationError, assertIntegerMinorUnits, calculateMoneyDocument,
|
|
|
4
4
|
import { n as withHistoricalCommerceWrite, t as assertHistoricalCommerceWrite } from "./historical-commerce-internal-C9ButfuH.js";
|
|
5
5
|
import { t as allocateFrozenCredit } from "./credit-allocation-yspz0Lab.js";
|
|
6
6
|
import { _ as withDocumentMoneyTransaction, a as assertCommerceMoneySchemaCurrent, f as DOCUMENT_MONEY_READ_REBIND, g as withDocumentMoneyObjectTransaction, h as lockDocumentMoneyParents, p as DOCUMENT_MONEY_WRITE } from "./detect-DAE-_NAk.js";
|
|
7
|
-
import { s as parseInvoiceRetention, t as InvoiceCollection } from "./InvoiceCollection-
|
|
8
|
-
import { n as withTenantMoneyCollectionRead, r as withTenantMoneyRuntime, t as lockTenantMoneyRuntime } from "./tenant-money-barrier-
|
|
9
|
-
import { t as TaxCodeCollection } from "./TaxCodeCollection-
|
|
10
|
-
import { t as ContractLineItemCollection } from "./ContractLineItemCollection-
|
|
11
|
-
import { t as TaxComponentCollection } from "./TaxComponentCollection-
|
|
7
|
+
import { s as parseInvoiceRetention, t as InvoiceCollection } from "./InvoiceCollection-BolwughC.js";
|
|
8
|
+
import { n as withTenantMoneyCollectionRead, r as withTenantMoneyRuntime, t as lockTenantMoneyRuntime } from "./tenant-money-barrier-DEVLrpaB.js";
|
|
9
|
+
import { t as TaxCodeCollection } from "./TaxCodeCollection-CW-tWjkU.js";
|
|
10
|
+
import { t as ContractLineItemCollection } from "./ContractLineItemCollection-D5hBzrct.js";
|
|
11
|
+
import { t as TaxComponentCollection } from "./TaxComponentCollection-BiJ9aAe8.js";
|
|
12
12
|
import { SmrtCollection, SmrtObject, crossPackageRef, field, foreignKey, smrt } from "@happyvertical/smrt-core";
|
|
13
13
|
import { TenantScoped, getTenantId, queryGlobal, queryWithGlobals, tenantId, withTenant } from "@happyvertical/smrt-tenancy";
|
|
14
14
|
import { ISO_4217_MINOR_UNITS } from "@happyvertical/smrt-ui/currency";
|
|
@@ -1226,7 +1226,7 @@ var Contract = class extends SmrtObject {
|
|
|
1226
1226
|
* Calculate and update totals from line items
|
|
1227
1227
|
*/
|
|
1228
1228
|
async recalculateTotals() {
|
|
1229
|
-
const { recalculateContractTotals } = await import("./contract-money-
|
|
1229
|
+
const { recalculateContractTotals } = await import("./contract-money-BMsq4cpL.js");
|
|
1230
1230
|
await recalculateContractTotals(this);
|
|
1231
1231
|
}
|
|
1232
1232
|
/**
|
|
@@ -1256,12 +1256,12 @@ var Contract = class extends SmrtObject {
|
|
|
1256
1256
|
* write surface (#2401).
|
|
1257
1257
|
*/
|
|
1258
1258
|
async save() {
|
|
1259
|
-
const { saveContract } = await import("./contract-money-
|
|
1259
|
+
const { saveContract } = await import("./contract-money-BMsq4cpL.js");
|
|
1260
1260
|
return await saveContract(this);
|
|
1261
1261
|
}
|
|
1262
1262
|
/** Received purchase monetary history cannot be deleted. */
|
|
1263
1263
|
async delete(options = {}) {
|
|
1264
|
-
const { deleteContract } = await import("./contract-money-
|
|
1264
|
+
const { deleteContract } = await import("./contract-money-BMsq4cpL.js");
|
|
1265
1265
|
await deleteContract(this, options);
|
|
1266
1266
|
}
|
|
1267
1267
|
/** @internal Contract aggregate persistence only. */
|
|
@@ -2261,22 +2261,32 @@ async function refreshIssuedCreditBalance(db, source) {
|
|
|
2261
2261
|
const total = issued.reduce((sum, credit) => sum + credit.totalAmount, 0);
|
|
2262
2262
|
if (!Number.isSafeInteger(total) || total < 0) throw new MoneyValidationError("UNSAFE_MONEY", "Issued credit total is not a nonnegative safe integer");
|
|
2263
2263
|
let remainingSource = Math.max(0, source.totalAmount - source.amountPaid);
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
const
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
const
|
|
2270
|
-
|
|
2264
|
+
const managed = /* @__PURE__ */ new Map();
|
|
2265
|
+
if (source.retentionAccountingStateJson || source.ordinaryAccountingStateJson) {
|
|
2266
|
+
const providerState = JSON.parse(source.retentionAccountingStateJson || source.ordinaryAccountingStateJson);
|
|
2267
|
+
for (const entry of providerState.credits) {
|
|
2268
|
+
const credit = issued.find((candidate) => candidate.id === entry.creditId);
|
|
2269
|
+
const retainedMinor = source.retentionAccountingStateJson ? entry.retainedMinor : 0;
|
|
2270
|
+
const frozen = retainedMinor + entry.appliedMinor;
|
|
2271
|
+
if (!credit || managed.has(entry.creditId) || !Number.isSafeInteger(retainedMinor) || retainedMinor < 0 || !Number.isSafeInteger(entry.appliedMinor) || entry.appliedMinor < 0 || !Number.isSafeInteger(frozen) || frozen > credit.totalAmount || frozen !== credit.creditedAmount || frozen > remainingSource) throw new MoneyValidationError("ACCOUNTING_SYNC_RECONCILIATION_REQUIRED", "Provider-managed credit allocation cannot change without an explicit reversal contract");
|
|
2272
|
+
managed.set(entry.creditId, frozen);
|
|
2273
|
+
remainingSource -= frozen;
|
|
2271
2274
|
}
|
|
2275
|
+
}
|
|
2276
|
+
for (const credit of [...issued, ...credits.filter((entry) => !issued.includes(entry))]) {
|
|
2277
|
+
const included = issued.includes(credit);
|
|
2278
|
+
const frozen = managed.get(requireId(credit.id, "Credit invoice"));
|
|
2279
|
+
const applied = frozen ?? (included ? Math.min(credit.totalAmount, remainingSource) : 0);
|
|
2280
|
+
if (included && frozen === void 0) remainingSource -= applied;
|
|
2272
2281
|
if (credit.creditedAmount !== applied) {
|
|
2273
2282
|
credit.creditedAmount = applied;
|
|
2274
2283
|
credit.updatePaymentStatus(credit.amountPaid);
|
|
2284
|
+
if (included && credit.status === InvoiceStatus.DRAFT) credit.status = InvoiceStatus.SENT;
|
|
2275
2285
|
await credit.saveDerived(DOCUMENT_MONEY_WRITE);
|
|
2276
2286
|
}
|
|
2277
2287
|
}
|
|
2278
|
-
if (source.retentionAccountingStateJson && total !== source.creditedAmount) {
|
|
2279
|
-
const state = JSON.parse(source.retentionAccountingStateJson);
|
|
2288
|
+
if ((source.retentionAccountingStateJson || source.ordinaryAccountingStateJson) && total !== source.creditedAmount) {
|
|
2289
|
+
const state = JSON.parse(source.retentionAccountingStateJson || source.ordinaryAccountingStateJson);
|
|
2280
2290
|
if (state.status !== "ready" || state.steps.some((step) => step.status !== "confirmed")) throw new MoneyValidationError("ACCOUNTING_SYNC_RECONCILIATION_REQUIRED", "Provider retention operation must reconcile before changing source credits");
|
|
2281
2291
|
}
|
|
2282
2292
|
source.creditedAmount = total;
|
|
@@ -2806,7 +2816,7 @@ async function saveInvoice(input) {
|
|
|
2806
2816
|
const settings = await lockRuntimeSettings(tx, candidateTenantId);
|
|
2807
2817
|
const existing = invoice.id ? await findInvoice(tx, invoice.id) : null;
|
|
2808
2818
|
const tenantId = existing?.tenantId ?? candidateTenantId;
|
|
2809
|
-
if (invoice.retentionAccountingStateJson !== (existing?.retentionAccountingStateJson ?? "")) throw new MoneyValidationError("FROZEN_DOCUMENT", "Retention accounting state is managed by provider commands");
|
|
2819
|
+
if (invoice.retentionAccountingStateJson !== (existing?.retentionAccountingStateJson ?? "") || invoice.ordinaryAccountingStateJson !== (existing?.ordinaryAccountingStateJson ?? "")) throw new MoneyValidationError("FROZEN_DOCUMENT", "Retention accounting state is managed by provider commands");
|
|
2810
2820
|
if (invoice.moneyAuthority !== (existing?.moneyAuthority ?? "calculated") || invoice.historicalSalesMoneyJson !== (existing?.historicalSalesMoneyJson ?? "") || invoice.historicalSourceKeyHash !== (existing?.historicalSourceKeyHash ?? null)) throw new MoneyValidationError("FROZEN_DOCUMENT", "Historical source authority is managed only by historical sales commands");
|
|
2811
2821
|
if (existing?.moneyAuthority === "historical") getHistoricalSalesMoney(existing);
|
|
2812
2822
|
if (invoice.retentionStateJson !== (existing?.retentionStateJson ?? "")) throw new MoneyValidationError("FROZEN_DOCUMENT", "Retention state is managed by retention commands");
|
|
@@ -4382,4 +4392,4 @@ var DocumentTaxCollection = class extends SmrtCollection {
|
|
|
4382
4392
|
//#endregion
|
|
4383
4393
|
export { DEFAULT_PARTY_CONTACT_FIELD_NAMES as $, PurchaseContractCollection as A, PurchaseOrder as B, refreshIssuedCreditBalance as C, issueContractMilestoneInvoice as D, getContractBillingSchedule as E, Lease as F, isContractSnapshotReportEligible as G, SupplierQuote as H, LicenseSale as I, HistoricalSalesLine as J, Vendor as K, Order as L, Cart as M, Contract as N, ContractCollection as O, Estimate as P, Customer as Q, ProductionOrder as R, previewInvoiceMoney as S, approveContractBillingSchedule as T, WholesaleOrder as U, SalesContract as V, classifyContractPurchase as W, InvoiceLineItem as X, InvoiceLineItemCollection as Y, CustomerCollection as Z, historicalText as _, getReceivedSalesContractMoney as a, normalizePartyContacts as at, getInvoiceMoneyFacts as b, validateReceivedPurchaseMoneyFacts as c, validatePartyContactRows as ct, HistoricalSalesTaxClassificationRequired as d, PARTY_CONTACT_MAX_LENGTHS as et, listHistoricalSalesHeaderFacts as f, historicalCalendarDate as g, recordHistoricalSalesMoney as h, getReceivedPurchaseMoney as i, applyPartyContactIntent as it, Agreement as j, SalesContractCollection as k, VendorCollection as l, readHistoricalSalesLines as m, DocumentTaxCollection_exports as n, PartyContactPayloadError as nt, recordReceivedPurchaseMoney as o, readPartyContactRows as ot, getHistoricalSalesMoney as p, HistoricalSalesLineCollection as q, ReceivedPurchaseTaxClassificationRequired as r, PartyContactValidationError as rt, recordReceivedSalesContractMoney as s, savePartyContacts as st, DocumentTaxCollection as t, PartyContactPartyNotFoundError as tt, DocumentTax as u, validateHistoricalSalesMoneyFacts as v, allocateContractMilestones as w, lockAndReloadInvoice as x, document_money_exports as y, PurchaseContract as z };
|
|
4384
4394
|
|
|
4385
|
-
//# sourceMappingURL=DocumentTaxCollection-
|
|
4395
|
+
//# sourceMappingURL=DocumentTaxCollection-C6cI5U89.js.map
|