@happyvertical/smrt-commerce 0.57.4 → 0.57.6
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
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.js";
|
|
2
|
-
import { Y as InvoiceLineItemCollection, t as DocumentTaxCollection, x as lockAndReloadInvoice } from "./DocumentTaxCollection-
|
|
2
|
+
import { Y as InvoiceLineItemCollection, t as DocumentTaxCollection, x as lockAndReloadInvoice } from "./DocumentTaxCollection-C6cI5U89.js";
|
|
3
3
|
import { d as PaymentStatus, s as InvoiceStatus, u as PaymentMethod } from "./types-Bbm0Xflc.js";
|
|
4
4
|
import { o as minorToMajorUnits } from "./units-8ElbjVZK.js";
|
|
5
|
-
import { n as withRetentionAccountingAuthority, t as hasRetentionAccountingAuthority } from "./retention-accounting-internal-
|
|
6
|
-
import { i as claimAllocationParent, n as allocationTotal, r as assertAllocationParent, t as allocationOwner } from "./allocation-integrity-
|
|
5
|
+
import { i as withOrdinaryAccountingAuthority, n as withRetentionAccountingAuthority, r as hasOrdinaryAccountingAuthority, t as hasRetentionAccountingAuthority } from "./retention-accounting-internal-DNxLLVvK.js";
|
|
6
|
+
import { i as claimAllocationParent, n as allocationTotal, r as assertAllocationParent, t as allocationOwner } from "./allocation-integrity-CH-i-hsY.js";
|
|
7
7
|
import { n as customerFundsState, t as assertCustomerFundsWrite } from "./customer-funds-internal-BQzoJXK5.js";
|
|
8
8
|
import { MoneyValidationError, assertIntegerMinorUnits, calculateMoneyDocument } from "../money.js";
|
|
9
9
|
import { t as HistoricalCustomerFundsAllocationCollection } from "./HistoricalCustomerFundsCollections-B5x8gTW3.js";
|
|
10
10
|
import { n as calculateAvailableRefund } from "./credit-allocation-yspz0Lab.js";
|
|
11
11
|
import { _ as withDocumentMoneyTransaction, m as isMoneyTransaction, p as DOCUMENT_MONEY_WRITE } from "./detect-DAE-_NAk.js";
|
|
12
|
-
import { c as validateRetentionEvidence, o as calculateInvoicePayable, s as parseInvoiceRetention, t as InvoiceCollection } from "./InvoiceCollection-
|
|
13
|
-
import { t as lockTenantMoneyRuntime } from "./tenant-money-barrier-
|
|
12
|
+
import { c as validateRetentionEvidence, o as calculateInvoicePayable, s as parseInvoiceRetention, t as InvoiceCollection } from "./InvoiceCollection-BolwughC.js";
|
|
13
|
+
import { t as lockTenantMoneyRuntime } from "./tenant-money-barrier-DEVLrpaB.js";
|
|
14
14
|
import { SmrtCollection, SmrtObject, crossPackageRef, field, foreignKey, isPostgresDatabase, smrt } from "@happyvertical/smrt-core";
|
|
15
15
|
import { TenantScoped, getTenantId, queryGlobal, queryWithGlobals, tenantId } from "@happyvertical/smrt-tenancy";
|
|
16
16
|
import { createHash, randomUUID } from "node:crypto";
|
|
@@ -50,6 +50,7 @@ var Payment = class extends SmrtObject {
|
|
|
50
50
|
kind = "payment";
|
|
51
51
|
customerFundsJson = "";
|
|
52
52
|
retentionAccountingBindingJson = "";
|
|
53
|
+
ordinaryAccountingBindingJson = "";
|
|
53
54
|
tenantId = null;
|
|
54
55
|
contractId = null;
|
|
55
56
|
customerId = null;
|
|
@@ -212,6 +213,8 @@ var Payment = class extends SmrtObject {
|
|
|
212
213
|
]);
|
|
213
214
|
const priorRow = await this.loadPersistedRow();
|
|
214
215
|
if (this.customerFundsJson || priorRow?.customer_funds_json) assertCustomerFundsWrite(this);
|
|
216
|
+
const ordinaryBinding = this.ordinaryAccountingBindingJson || String(priorRow?.ordinary_accounting_binding_json ?? "");
|
|
217
|
+
if (ordinaryBinding && !hasOrdinaryAccountingAuthority(this.tenantId, JSON.parse(ordinaryBinding).invoiceId)) throw new Error("Provider-recorded refund is immutable; use an explicit reversal workflow");
|
|
215
218
|
const retainedBinding = this.retentionAccountingBindingJson || String(priorRow?.retention_accounting_binding_json ?? "");
|
|
216
219
|
if (retainedBinding && !hasRetentionAccountingAuthority(this.tenantId, JSON.parse(retainedBinding).invoiceId)) throw new Error("Provider-recorded refund is immutable; use an explicit reversal workflow");
|
|
217
220
|
const prior = priorRow && priorRow.status != null ? priorRow.status : loadedPaymentStatus.get(this);
|
|
@@ -231,7 +234,7 @@ var Payment = class extends SmrtObject {
|
|
|
231
234
|
/** Managed receipts and refunds retain immutable cash provenance. */
|
|
232
235
|
async delete(options = {}) {
|
|
233
236
|
const persisted = await this.loadPersistedRow();
|
|
234
|
-
if (this.retentionAccountingBindingJson || persisted?.retention_accounting_binding_json) throw new Error("Provider-recorded refund history cannot be deleted");
|
|
237
|
+
if (this.retentionAccountingBindingJson || persisted?.retention_accounting_binding_json || this.ordinaryAccountingBindingJson || persisted?.ordinary_accounting_binding_json) throw new Error("Provider-recorded refund history cannot be deleted");
|
|
235
238
|
if (this.customerFundsJson || persisted?.customer_funds_json) throw new Error("Customer funds cash history cannot be deleted");
|
|
236
239
|
return super.delete(options);
|
|
237
240
|
}
|
|
@@ -509,6 +512,7 @@ var Payment = class extends SmrtObject {
|
|
|
509
512
|
__publicField$1(Payment, "__smrtPackage__", "@happyvertical/smrt-commerce");
|
|
510
513
|
__decorateClass$1([field({ type: "text" })], Payment.prototype, "customerFundsJson", 2);
|
|
511
514
|
__decorateClass$1([field({ sensitive: true })], Payment.prototype, "retentionAccountingBindingJson", 2);
|
|
515
|
+
__decorateClass$1([field({ sensitive: true })], Payment.prototype, "ordinaryAccountingBindingJson", 2);
|
|
512
516
|
__decorateClass$1([tenantId({ nullable: true })], Payment.prototype, "tenantId", 2);
|
|
513
517
|
__decorateClass$1([field({ description: "The order or agreement this payment is for, if any." }), foreignKey("SalesContract", { validateSubtype: true })], Payment.prototype, "contractId", 2);
|
|
514
518
|
__decorateClass$1([field({ description: "The customer who paid." }), foreignKey("Customer")], Payment.prototype, "customerId", 2);
|
|
@@ -809,7 +813,7 @@ var PaymentAllocation = class extends SmrtObject {
|
|
|
809
813
|
if (!this.paymentId || !this.invoiceId) throw new Error("PaymentAllocation requires payment and invoice");
|
|
810
814
|
return this.withTransaction(async () => {
|
|
811
815
|
const { PaymentCollection } = await Promise.resolve().then(() => PaymentCollection_exports);
|
|
812
|
-
const { InvoiceCollection } = await import("./InvoiceCollection-
|
|
816
|
+
const { InvoiceCollection } = await import("./InvoiceCollection-BolwughC.js").then((n) => n.n);
|
|
813
817
|
const { PaymentAllocationCollection } = await Promise.resolve().then(() => PaymentAllocationCollection_exports);
|
|
814
818
|
const payments = await PaymentCollection.create({ db: this.db });
|
|
815
819
|
const invoices = await InvoiceCollection.create({ db: this.db });
|
|
@@ -818,7 +822,7 @@ var PaymentAllocation = class extends SmrtObject {
|
|
|
818
822
|
if (prior && (prior.paymentId !== this.paymentId || prior.invoiceId !== this.invoiceId || prior.tenantId !== allocationOwner(this.tenantId))) throw new Error("PaymentAllocation parent identity is immutable");
|
|
819
823
|
const payment = await payments.get({ id: this.paymentId }, { cache: false });
|
|
820
824
|
if (!payment) throw new Error("PaymentAllocation referenced Payment does not exist");
|
|
821
|
-
if (payment.retentionAccountingBindingJson) throw new Error("Provider-recorded refund allocation is immutable");
|
|
825
|
+
if (payment.retentionAccountingBindingJson || payment.ordinaryAccountingBindingJson) throw new Error("Provider-recorded refund allocation is immutable");
|
|
822
826
|
if (customerFundsState(payment.customerFundsJson)) assertCustomerFundsWrite(this);
|
|
823
827
|
if (!payment.updated_at) throw new Error("Allocation parent has no persisted revision");
|
|
824
828
|
await payment.claimRevision(payment.updated_at);
|
|
@@ -882,7 +886,7 @@ var PaymentAllocation = class extends SmrtObject {
|
|
|
882
886
|
if (!payment.updated_at) throw new Error("Allocation parent has no persisted revision");
|
|
883
887
|
await payment.claimRevision(payment.updated_at);
|
|
884
888
|
}
|
|
885
|
-
if (payment?.retentionAccountingBindingJson) throw new Error("Provider-recorded refund allocation cannot be deleted");
|
|
889
|
+
if (payment?.retentionAccountingBindingJson || payment?.ordinaryAccountingBindingJson) throw new Error("Provider-recorded refund allocation cannot be deleted");
|
|
886
890
|
if (payment?.customerFundsJson) assertCustomerFundsWrite(this);
|
|
887
891
|
return super.delete(options);
|
|
888
892
|
});
|
|
@@ -916,7 +920,7 @@ var PaymentAllocationCollection = class PaymentAllocationCollection extends Smrt
|
|
|
916
920
|
async allocatePayment(input) {
|
|
917
921
|
if (!input.allocations.length || new Set(input.allocations.map((item) => item.id)).size !== input.allocations.length) throw new Error("Allocation batch requires distinct stable allocation ids");
|
|
918
922
|
const { PaymentCollection } = await Promise.resolve().then(() => PaymentCollection_exports);
|
|
919
|
-
const { InvoiceCollection } = await import("./InvoiceCollection-
|
|
923
|
+
const { InvoiceCollection } = await import("./InvoiceCollection-BolwughC.js").then((n) => n.n);
|
|
920
924
|
const { InvoiceStatus } = await import("./types-Bbm0Xflc.js").then((n) => n.m);
|
|
921
925
|
const payment = await (await PaymentCollection.create({ db: this.db })).get({ id: input.paymentId }, { cache: false });
|
|
922
926
|
if (!payment) throw new Error("Payment does not exist");
|
|
@@ -959,7 +963,7 @@ var PaymentAllocationCollection = class PaymentAllocationCollection extends Smrt
|
|
|
959
963
|
const allocation = await this.get({ id }, { cache: false });
|
|
960
964
|
if (!allocation) return;
|
|
961
965
|
const { PaymentCollection } = await Promise.resolve().then(() => PaymentCollection_exports);
|
|
962
|
-
const { InvoiceCollection } = await import("./InvoiceCollection-
|
|
966
|
+
const { InvoiceCollection } = await import("./InvoiceCollection-BolwughC.js").then((n) => n.n);
|
|
963
967
|
const payment = await (await PaymentCollection.create({ db: this.db })).get({ id: allocation.paymentId }, { cache: false });
|
|
964
968
|
if (!payment) throw new Error("Allocation payment does not exist");
|
|
965
969
|
await payment.withTransaction(async () => {
|
|
@@ -1347,6 +1351,7 @@ async function loadSnapshot(db, tenantId, invoice) {
|
|
|
1347
1351
|
const source = await (await InvoiceCollection.create({ db })).get({ id: invoice.creditedInvoiceId }, { cache: false });
|
|
1348
1352
|
if (!source || source.tenantId !== tenantId || !source.externalId) throw new MoneyValidationError("INVALID_MONEY_INPUT", "Frozen credit note requires the source invoice external ID");
|
|
1349
1353
|
if (source.retentionAccountingStateJson && !hasRetentionAccountingAuthority(source.tenantId, source.id)) unsupported("Provider-retained credit requires the retained-first accounting adjustment command");
|
|
1354
|
+
if (source.ordinaryAccountingStateJson && !hasOrdinaryAccountingAuthority(source.tenantId, source.id)) unsupported("Provider ordinary credit requires its owning accounting adjustment command");
|
|
1350
1355
|
const sourceState = parseSyncState(source.accountingSyncStateJson);
|
|
1351
1356
|
if (sourceState?.status !== "synced" || sourceState.externalId !== source.externalId) throw new MoneyValidationError("ACCOUNTING_SYNC_RECONCILIATION_REQUIRED", "Frozen credit note requires a reconciled source invoice accounting identity");
|
|
1352
1357
|
creditedInvoiceExternalId = source.externalId;
|
|
@@ -1457,6 +1462,7 @@ async function prepareSync(input) {
|
|
|
1457
1462
|
return withDocumentMoneyTransaction(input.db, async (tx) => {
|
|
1458
1463
|
await lockTenantMoneyRuntime(tx, input.tenantId);
|
|
1459
1464
|
const { invoice } = await lockAndReloadInvoice(tx, input.invoiceId, input.tenantId);
|
|
1465
|
+
if (invoice.ordinaryAccountingStateJson && !hasOrdinaryAccountingAuthority(invoice.tenantId, invoice.id)) unsupported("Provider ordinary invoice requires its owning accounting command");
|
|
1460
1466
|
const snapshot = await loadSnapshot(tx, input.tenantId, invoice);
|
|
1461
1467
|
const sdkInput = toSdkInput(snapshot, input.mapping);
|
|
1462
1468
|
const prior = parseSyncState(invoice.accountingSyncStateJson);
|
|
@@ -1729,6 +1735,481 @@ function calculateInvoiceMinorLine(input) {
|
|
|
1729
1735
|
};
|
|
1730
1736
|
}
|
|
1731
1737
|
//#endregion
|
|
1738
|
+
//#region src/accounting-steps-internal.ts
|
|
1739
|
+
function reject$2(message) {
|
|
1740
|
+
throw new MoneyValidationError("ACCOUNTING_SYNC_RECONCILIATION_REQUIRED", message);
|
|
1741
|
+
}
|
|
1742
|
+
function text$2(value, label) {
|
|
1743
|
+
if (typeof value !== "string" || !value || value.trim() !== value) reject$2(`${label} is required`);
|
|
1744
|
+
}
|
|
1745
|
+
async function executeAccountingStep(engine, key, make) {
|
|
1746
|
+
const prepared = await engine.locked(async (invoice, state) => {
|
|
1747
|
+
if (!state) reject$2("Retention provider contract is missing");
|
|
1748
|
+
const expected = make(invoice, state), prior = state.steps.find((s) => s.key === key);
|
|
1749
|
+
if (prior && prior.request.payloadHash !== expected.payloadHash) reject$2("Retention operation identity reused with changed facts");
|
|
1750
|
+
if (prior?.status === "confirmed") {
|
|
1751
|
+
if (!prior.result) reject$2("Confirmed retention step has no readback");
|
|
1752
|
+
return { complete: prior.result };
|
|
1753
|
+
}
|
|
1754
|
+
if (prior && prior.status !== "rejected") reject$2("Retention operation has an unresolved remote owner");
|
|
1755
|
+
if (state.steps.some((s) => s.key !== key && s.status !== "confirmed")) reject$2("Another retention operation requires reconciliation");
|
|
1756
|
+
const step2 = {
|
|
1757
|
+
key,
|
|
1758
|
+
status: "in_flight",
|
|
1759
|
+
attemptId: randomUUID(),
|
|
1760
|
+
request: expected,
|
|
1761
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1762
|
+
priorAttempts: prior ? [...prior.priorAttempts ?? [], {
|
|
1763
|
+
attemptId: prior.attemptId,
|
|
1764
|
+
status: prior.status,
|
|
1765
|
+
startedAt: prior.startedAt,
|
|
1766
|
+
reconciliation: prior.reconciliation
|
|
1767
|
+
}] : []
|
|
1768
|
+
};
|
|
1769
|
+
if (prior) state.steps[state.steps.indexOf(prior)] = step2;
|
|
1770
|
+
else state.steps.push(step2);
|
|
1771
|
+
await engine.save(invoice, state);
|
|
1772
|
+
return { step: step2 };
|
|
1773
|
+
});
|
|
1774
|
+
if ("complete" in prepared && prepared.complete) return prepared.complete;
|
|
1775
|
+
if (!prepared.step) reject$2("Prepared retention step is missing");
|
|
1776
|
+
const step = prepared.step;
|
|
1777
|
+
let result;
|
|
1778
|
+
try {
|
|
1779
|
+
result = await engine.provider.execute(step.request);
|
|
1780
|
+
} catch (error) {
|
|
1781
|
+
await engine.locked(async (invoice, state) => {
|
|
1782
|
+
const current = state?.steps.find((s) => s.key === key);
|
|
1783
|
+
if (!state || current?.attemptId !== step.attemptId || current.status !== "in_flight") reject$2("Retention step ownership changed after remote failure");
|
|
1784
|
+
current.status = error?.outcome === "rejected" ? "rejected" : "unknown";
|
|
1785
|
+
await engine.save(invoice, state);
|
|
1786
|
+
});
|
|
1787
|
+
throw error;
|
|
1788
|
+
}
|
|
1789
|
+
return engine.locked(async (invoice, state) => {
|
|
1790
|
+
const current = state?.steps.find((s) => s.key === key);
|
|
1791
|
+
if (!state || current?.attemptId !== step.attemptId || current.status !== "in_flight" || result.payloadHash !== step.request.payloadHash) reject$2("Retention step ownership or readback changed after write");
|
|
1792
|
+
current.status = "confirmed";
|
|
1793
|
+
current.result = structuredClone(result);
|
|
1794
|
+
await engine.save(invoice, state);
|
|
1795
|
+
return result;
|
|
1796
|
+
});
|
|
1797
|
+
}
|
|
1798
|
+
async function reconcileAccountingStep(engine, input) {
|
|
1799
|
+
const step = await engine.locked(async (_invoice, state) => {
|
|
1800
|
+
const found = state?.steps.find((s) => s.key === input.stepKey);
|
|
1801
|
+
if (!found || found.request.requestId !== input.requestId || found.attemptId !== input.attemptId || !["in_flight", "unknown"].includes(found.status)) reject$2("Observed retention reconciliation owner differs");
|
|
1802
|
+
if (found.status === "in_flight" && !input.confirmInterruptedOwnerStopped) reject$2("Interrupted retention owner must be proven stopped");
|
|
1803
|
+
return structuredClone(found);
|
|
1804
|
+
});
|
|
1805
|
+
const result = input.outcome.status === "found" ? await engine.provider.read(step.request, input.outcome.externalId) : null;
|
|
1806
|
+
if (input.outcome.status === "absent") text$2(input.outcome.evidenceRef, "Authoritative provider absence evidence");
|
|
1807
|
+
return engine.locked(async (invoice, state) => {
|
|
1808
|
+
const current = state?.steps.find((s) => s.key === input.stepKey);
|
|
1809
|
+
if (!state || current?.attemptId !== step.attemptId || current.status !== step.status) reject$2("Retention reconciliation owner changed");
|
|
1810
|
+
current.reconciliation = {
|
|
1811
|
+
outcome: input.outcome.status,
|
|
1812
|
+
recordedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1813
|
+
interruptedOwnerStopped: input.confirmInterruptedOwnerStopped === true,
|
|
1814
|
+
...input.outcome.status === "found" ? { externalId: input.outcome.externalId } : { evidenceRef: input.outcome.evidenceRef }
|
|
1815
|
+
};
|
|
1816
|
+
if (result) {
|
|
1817
|
+
if (result.payloadHash !== step.request.payloadHash) reject$2("Retention reconciliation readback differs");
|
|
1818
|
+
current.result = result;
|
|
1819
|
+
current.status = "confirmed";
|
|
1820
|
+
} else current.status = "rejected";
|
|
1821
|
+
await engine.save(invoice, state);
|
|
1822
|
+
return state;
|
|
1823
|
+
});
|
|
1824
|
+
}
|
|
1825
|
+
//#endregion
|
|
1826
|
+
//#region src/ordinary-accounting.ts
|
|
1827
|
+
var hash$1 = (value) => createHash("sha256").update(JSON.stringify(value)).digest("hex");
|
|
1828
|
+
function reject$1(message) {
|
|
1829
|
+
throw new MoneyValidationError("ACCOUNTING_SYNC_RECONCILIATION_REQUIRED", message);
|
|
1830
|
+
}
|
|
1831
|
+
function text$1(value, label) {
|
|
1832
|
+
if (typeof value !== "string" || !value || value.trim() !== value) reject$1(`${label} is required`);
|
|
1833
|
+
}
|
|
1834
|
+
function date$1(value, label) {
|
|
1835
|
+
if (!/^\d{4}-\d{2}-\d{2}$/.test(value) || !Number.isFinite(Date.parse(value)) || new Date(value).toISOString().slice(0, 10) !== value) reject$1(`${label} is invalid`);
|
|
1836
|
+
}
|
|
1837
|
+
function principal$1(input) {
|
|
1838
|
+
if (!input.tenantId || getTenantId() !== input.tenantId) reject$1("Ordinary accounting requires its active tenant");
|
|
1839
|
+
if (isMoneyTransaction(input.db)) reject$1("Provider writes cannot run inside a caller-owned money transaction");
|
|
1840
|
+
if (input.provider.type !== "quickbooks" || !input.provider.receivableTransfers) reject$1("Provider lacks reviewed receivable operations");
|
|
1841
|
+
}
|
|
1842
|
+
function syncedIdentity(invoice, mapping) {
|
|
1843
|
+
const sync = readFrozenAccountingSyncState(invoice);
|
|
1844
|
+
if (!sync || sync.status !== "synced" || sync.documentKind !== "invoice" || sync.provider !== "quickbooks" || sync.moneySnapshotId !== invoice.moneySnapshotId || sync.realmId !== mapping.realmId || sync.environment !== mapping.environment || invoice.externalProvider !== "quickbooks" || !invoice.externalId) reject$1("Ordinary accounting requires its reconciled frozen invoice export identity");
|
|
1845
|
+
}
|
|
1846
|
+
function readOrdinaryInvoiceAccountingState(invoice) {
|
|
1847
|
+
if (!invoice.ordinaryAccountingStateJson) return null;
|
|
1848
|
+
let state;
|
|
1849
|
+
try {
|
|
1850
|
+
state = JSON.parse(invoice.ordinaryAccountingStateJson);
|
|
1851
|
+
} catch {
|
|
1852
|
+
return reject$1("Malformed ordinary accounting state");
|
|
1853
|
+
}
|
|
1854
|
+
if (!state || !state.mapping || state.version !== 1 || state.kind !== "ordinary" || state.mapping.kind !== "ordinary" || state.invoiceId !== invoice.id || state.moneySnapshotId !== invoice.moneySnapshotId || state.externalInvoiceId !== null && state.externalInvoiceId !== invoice.externalId || state.currency !== invoice.currency || state.grossMinor !== invoice.totalAmount || state.mapping.customerRef !== invoice.customerExternalId || !Array.isArray(state.steps) || !Array.isArray(state.credits) || !Array.isArray(state.refunds) || invoice.retentionStateJson || invoice.retentionAccountingStateJson || invoice.moneyAuthority !== "calculated") reject$1("Ordinary provider source identity changed");
|
|
1855
|
+
if (state.externalInvoiceId !== null) syncedIdentity(invoice, state.mapping);
|
|
1856
|
+
return state;
|
|
1857
|
+
}
|
|
1858
|
+
async function locked$1(input, operation) {
|
|
1859
|
+
return withDocumentMoneyTransaction(input.db, async (tx) => {
|
|
1860
|
+
await lockTenantMoneyRuntime(tx, input.tenantId);
|
|
1861
|
+
const { invoice } = await lockAndReloadInvoice(tx, input.invoiceId, input.tenantId);
|
|
1862
|
+
return operation(invoice, readOrdinaryInvoiceAccountingState(invoice));
|
|
1863
|
+
});
|
|
1864
|
+
}
|
|
1865
|
+
async function save$1(invoice, state) {
|
|
1866
|
+
invoice.ordinaryAccountingStateJson = JSON.stringify(state);
|
|
1867
|
+
await invoice.saveDerived(DOCUMENT_MONEY_WRITE);
|
|
1868
|
+
}
|
|
1869
|
+
function refreshStatus$1(state) {
|
|
1870
|
+
state.status = state.steps.every((s) => s.status === "confirmed") && state.credits.every((c) => c.committed) && state.refunds.every((r) => r.committed) ? "ready" : "pending";
|
|
1871
|
+
}
|
|
1872
|
+
function requireReady$1(state) {
|
|
1873
|
+
if (!state || state.status !== "ready" || state.steps.some((s) => s.status !== "confirmed")) reject$1("Ordinary provider operations require reconciliation");
|
|
1874
|
+
return state;
|
|
1875
|
+
}
|
|
1876
|
+
function requestId$1(state, key) {
|
|
1877
|
+
return `smrt_ord_${hash$1([
|
|
1878
|
+
state.invoiceId,
|
|
1879
|
+
state.moneySnapshotId,
|
|
1880
|
+
state.mapping.realmId,
|
|
1881
|
+
state.mapping.environment,
|
|
1882
|
+
key
|
|
1883
|
+
]).slice(0, 40)}`;
|
|
1884
|
+
}
|
|
1885
|
+
function executeStep$1(input, key, make) {
|
|
1886
|
+
return executeAccountingStep({
|
|
1887
|
+
locked: (operation) => locked$1(input, operation),
|
|
1888
|
+
save: save$1,
|
|
1889
|
+
provider: input.provider.receivableTransfers
|
|
1890
|
+
}, key, make);
|
|
1891
|
+
}
|
|
1892
|
+
function validateOrdinarySource(invoice, input) {
|
|
1893
|
+
if (invoice.kind !== "invoice" || !invoice.moneySnapshotId || invoice.retentionStateJson || invoice.retentionAccountingStateJson || invoice.moneyAuthority !== "calculated" || [
|
|
1894
|
+
InvoiceStatus.DRAFT,
|
|
1895
|
+
InvoiceStatus.CANCELLED,
|
|
1896
|
+
InvoiceStatus.WRITTEN_OFF
|
|
1897
|
+
].includes(invoice.status) || invoice.collectionMethod || invoice.providerTaxAmount || invoice.currency !== input.mapping.currency || invoice.customerExternalId !== input.mapping.customerRef || input.invoiceMapping.realmId !== input.mapping.realmId || input.invoiceMapping.environment !== input.mapping.environment) reject$1("Ordinary accounting requires its issued, nonretained canonical frozen source");
|
|
1898
|
+
}
|
|
1899
|
+
async function syncOrdinaryInvoiceToAccounting(input) {
|
|
1900
|
+
principal$1(input);
|
|
1901
|
+
text$1(input.requestId, "Ordinary request");
|
|
1902
|
+
text$1(input.evidenceRef, "Ordinary evidence");
|
|
1903
|
+
if (input.mapping.kind !== "ordinary") reject$1("Ordinary mapping is required");
|
|
1904
|
+
await locked$1(input, async (invoice) => {
|
|
1905
|
+
validateOrdinarySource(invoice, input);
|
|
1906
|
+
await toFrozenInvoiceAccountingInput({
|
|
1907
|
+
db: invoice.db,
|
|
1908
|
+
tenantId: input.tenantId,
|
|
1909
|
+
invoiceId: input.invoiceId,
|
|
1910
|
+
mapping: input.invoiceMapping
|
|
1911
|
+
});
|
|
1912
|
+
});
|
|
1913
|
+
await input.provider.receivableTransfers.verifyMapping(input.mapping);
|
|
1914
|
+
await locked$1(input, async (invoice, state) => {
|
|
1915
|
+
validateOrdinarySource(invoice, input);
|
|
1916
|
+
const prior = readFrozenAccountingSyncState(invoice);
|
|
1917
|
+
if (prior) {
|
|
1918
|
+
const request = prepareQuickBooksInvoiceRequest({
|
|
1919
|
+
...await toFrozenInvoiceAccountingInput({
|
|
1920
|
+
db: invoice.db,
|
|
1921
|
+
tenantId: input.tenantId,
|
|
1922
|
+
invoiceId: input.invoiceId,
|
|
1923
|
+
mapping: input.invoiceMapping
|
|
1924
|
+
}),
|
|
1925
|
+
externalId: void 0
|
|
1926
|
+
}, {
|
|
1927
|
+
requestId: prior.requestId,
|
|
1928
|
+
realmId: input.invoiceMapping.realmId,
|
|
1929
|
+
environment: input.invoiceMapping.environment
|
|
1930
|
+
});
|
|
1931
|
+
if (prior.provider !== "quickbooks" || prior.documentKind !== "invoice" || prior.moneySnapshotId !== invoice.moneySnapshotId || prior.realmId !== request.realmId || prior.environment !== request.environment || prior.request.payloadHash !== request.payloadHash) reject$1("Ordinary invoice mapping differs from its frozen export request");
|
|
1932
|
+
}
|
|
1933
|
+
if (state) {
|
|
1934
|
+
if (hash$1([
|
|
1935
|
+
state.requestId,
|
|
1936
|
+
state.evidenceRef,
|
|
1937
|
+
state.mapping,
|
|
1938
|
+
state.invoiceMapping
|
|
1939
|
+
]) !== hash$1([
|
|
1940
|
+
input.requestId,
|
|
1941
|
+
input.evidenceRef,
|
|
1942
|
+
input.mapping,
|
|
1943
|
+
input.invoiceMapping
|
|
1944
|
+
])) reject$1("Ordinary accounting initialization changed");
|
|
1945
|
+
return;
|
|
1946
|
+
}
|
|
1947
|
+
await save$1(invoice, {
|
|
1948
|
+
version: 1,
|
|
1949
|
+
kind: "ordinary",
|
|
1950
|
+
invoiceId: input.invoiceId,
|
|
1951
|
+
moneySnapshotId: invoice.moneySnapshotId,
|
|
1952
|
+
externalInvoiceId: null,
|
|
1953
|
+
invoiceMapping: structuredClone(input.invoiceMapping),
|
|
1954
|
+
requestId: input.requestId,
|
|
1955
|
+
evidenceRef: input.evidenceRef,
|
|
1956
|
+
currency: invoice.currency,
|
|
1957
|
+
grossMinor: invoice.totalAmount,
|
|
1958
|
+
mapping: structuredClone(input.mapping),
|
|
1959
|
+
status: "pending",
|
|
1960
|
+
steps: [],
|
|
1961
|
+
credits: [],
|
|
1962
|
+
refunds: []
|
|
1963
|
+
});
|
|
1964
|
+
});
|
|
1965
|
+
const result = await withOrdinaryAccountingAuthority(input.tenantId, input.invoiceId, () => syncFrozenInvoiceToAccounting({
|
|
1966
|
+
...input,
|
|
1967
|
+
mapping: input.invoiceMapping
|
|
1968
|
+
}));
|
|
1969
|
+
return locked$1(input, async (invoice, state) => {
|
|
1970
|
+
if (!state) reject$1("Ordinary provider reservation disappeared");
|
|
1971
|
+
syncedIdentity(invoice, state.mapping);
|
|
1972
|
+
state.externalInvoiceId = result.externalId;
|
|
1973
|
+
refreshStatus$1(state);
|
|
1974
|
+
await save$1(invoice, state);
|
|
1975
|
+
return state;
|
|
1976
|
+
});
|
|
1977
|
+
}
|
|
1978
|
+
async function reconcileOrdinaryBaseInvoice(input) {
|
|
1979
|
+
principal$1(input);
|
|
1980
|
+
await locked$1(input, async (_invoice, state) => {
|
|
1981
|
+
if (!state || hash$1(state.invoiceMapping) !== hash$1(input.mapping)) reject$1("Ordinary base mapping differs");
|
|
1982
|
+
});
|
|
1983
|
+
return reconcileFrozenInvoiceAccountingSync(input);
|
|
1984
|
+
}
|
|
1985
|
+
async function reconcileOrdinaryAccountingStep(input) {
|
|
1986
|
+
principal$1(input);
|
|
1987
|
+
return reconcileAccountingStep({
|
|
1988
|
+
locked: (operation) => locked$1(input, operation),
|
|
1989
|
+
save: save$1,
|
|
1990
|
+
provider: input.provider.receivableTransfers
|
|
1991
|
+
}, input);
|
|
1992
|
+
}
|
|
1993
|
+
async function syncOrdinaryCreditToAccounting(input) {
|
|
1994
|
+
principal$1(input);
|
|
1995
|
+
text$1(input.requestId, "Credit adjustment request");
|
|
1996
|
+
text$1(input.evidenceRef, "Credit adjustment evidence");
|
|
1997
|
+
date$1(input.effectiveOn, "Credit adjustment date");
|
|
1998
|
+
const reserved = await locked$1(input, async (invoice, state) => {
|
|
1999
|
+
if (!state) reject$1("Ordinary provider contract is missing");
|
|
2000
|
+
const credit = await (await InvoiceCollection.create({ db: invoice.db })).get({ id: input.creditInvoiceId }, { cache: false });
|
|
2001
|
+
if (!credit || credit.tenantId !== input.tenantId || credit.customerId !== invoice.customerId || credit.creditedInvoiceId !== invoice.id || credit.kind !== "credit_note" || !credit.moneySnapshotId || [InvoiceStatus.DRAFT, InvoiceStatus.CANCELLED].includes(credit.status) || credit.currency !== state.currency || credit.customerExternalId !== state.mapping.customerRef) reject$1("Credit adjustment requires its issued same-customer source credit");
|
|
2002
|
+
if (input.creditMapping.realmId !== state.mapping.realmId || input.creditMapping.environment !== state.mapping.environment) reject$1("Credit adjustment provider scope differs");
|
|
2003
|
+
const prior = state.credits.find((c) => c.creditId === input.creditInvoiceId || c.requestId === input.requestId);
|
|
2004
|
+
if (prior) {
|
|
2005
|
+
if (prior.creditId !== input.creditInvoiceId || prior.requestId !== input.requestId || prior.moneySnapshotId !== credit.moneySnapshotId || prior.grossMinor !== credit.totalAmount || prior.evidenceRef !== input.evidenceRef || prior.effectiveOn !== input.effectiveOn || hash$1(prior.mapping) !== hash$1(input.creditMapping)) reject$1("Credit adjustment identity reused with changed facts");
|
|
2006
|
+
return structuredClone(prior);
|
|
2007
|
+
}
|
|
2008
|
+
requireReady$1(state);
|
|
2009
|
+
if (state.credits.reduce((sum, c) => sum + c.grossMinor, 0) + credit.totalAmount > invoice.creditedAmount) reject$1("Provider credit exceeds issued source credit capacity");
|
|
2010
|
+
const next = {
|
|
2011
|
+
creditId: input.creditInvoiceId,
|
|
2012
|
+
moneySnapshotId: credit.moneySnapshotId,
|
|
2013
|
+
requestId: input.requestId,
|
|
2014
|
+
evidenceRef: input.evidenceRef,
|
|
2015
|
+
effectiveOn: input.effectiveOn,
|
|
2016
|
+
grossMinor: credit.totalAmount,
|
|
2017
|
+
appliedMinor: credit.creditedAmount,
|
|
2018
|
+
mapping: structuredClone(input.creditMapping),
|
|
2019
|
+
committed: false
|
|
2020
|
+
};
|
|
2021
|
+
state.credits.push(next);
|
|
2022
|
+
state.status = "pending";
|
|
2023
|
+
await save$1(invoice, state);
|
|
2024
|
+
return next;
|
|
2025
|
+
});
|
|
2026
|
+
const creditResult = await withOrdinaryAccountingAuthority(input.tenantId, input.invoiceId, () => syncFrozenInvoiceToAccounting({
|
|
2027
|
+
db: input.db,
|
|
2028
|
+
tenantId: input.tenantId,
|
|
2029
|
+
invoiceId: input.creditInvoiceId,
|
|
2030
|
+
provider: input.provider,
|
|
2031
|
+
mapping: input.creditMapping
|
|
2032
|
+
}));
|
|
2033
|
+
const prefix = `credit:${input.requestId}`;
|
|
2034
|
+
const creditSource = (state) => ({
|
|
2035
|
+
type: "CreditMemo",
|
|
2036
|
+
id: creditResult.externalId,
|
|
2037
|
+
customerRef: state.mapping.customerRef,
|
|
2038
|
+
evidenceRef: reserved.moneySnapshotId
|
|
2039
|
+
});
|
|
2040
|
+
if (reserved.appliedMinor > 0) await executeStep$1(input, `${prefix}:due-application`, (invoice, state) => prepareQuickBooksReceivableRequest({
|
|
2041
|
+
kind: "application",
|
|
2042
|
+
mapping: state.mapping,
|
|
2043
|
+
source: creditSource(state),
|
|
2044
|
+
requestId: requestId$1(state, `${prefix}:due-application`),
|
|
2045
|
+
effectiveOn: reserved.effectiveOn,
|
|
2046
|
+
evidenceRef: reserved.evidenceRef,
|
|
2047
|
+
amountMinor: reserved.appliedMinor,
|
|
2048
|
+
debit: {
|
|
2049
|
+
type: "Invoice",
|
|
2050
|
+
id: invoice.externalId
|
|
2051
|
+
},
|
|
2052
|
+
credit: {
|
|
2053
|
+
type: "CreditMemo",
|
|
2054
|
+
id: creditResult.externalId
|
|
2055
|
+
}
|
|
2056
|
+
}));
|
|
2057
|
+
return locked$1(input, async (invoice, state) => {
|
|
2058
|
+
if (!state) reject$1("Ordinary provider state is missing");
|
|
2059
|
+
const credit = state.credits.find((c) => c.creditId === input.creditInvoiceId);
|
|
2060
|
+
if (!credit) reject$1("Credit reservation disappeared");
|
|
2061
|
+
credit.committed = true;
|
|
2062
|
+
refreshStatus$1(state);
|
|
2063
|
+
await save$1(invoice, state);
|
|
2064
|
+
return state;
|
|
2065
|
+
});
|
|
2066
|
+
}
|
|
2067
|
+
async function syncOrdinaryRefundToAccounting(input) {
|
|
2068
|
+
principal$1(input);
|
|
2069
|
+
text$1(input.requestId, "Refund recording request");
|
|
2070
|
+
text$1(input.evidenceRef, "Refund settlement evidence");
|
|
2071
|
+
date$1(input.effectiveOn, "Refund effective date");
|
|
2072
|
+
const reservation = await locked$1(input, async (invoice, state) => {
|
|
2073
|
+
if (!state) reject$1("Ordinary provider contract is missing");
|
|
2074
|
+
const credit = state.credits.find((c) => c.creditId === input.creditInvoiceId && c.committed);
|
|
2075
|
+
if (!credit) reject$1("Refund requires its reconciled provider credit");
|
|
2076
|
+
const payment = await (await PaymentCollection.create({ db: invoice.db })).get({ id: input.paymentId }, { cache: false });
|
|
2077
|
+
const creditInvoice = await (await InvoiceCollection.create({ db: invoice.db })).get({ id: input.creditInvoiceId }, { cache: false });
|
|
2078
|
+
if (!payment || !creditInvoice || payment.tenantId !== input.tenantId || creditInvoice.tenantId !== input.tenantId || creditInvoice.customerId !== invoice.customerId || creditInvoice.currency !== state.currency || creditInvoice.creditedInvoiceId !== invoice.id || payment.customerId !== invoice.customerId || payment.currency !== state.currency || payment.kind !== "refund" || payment.status !== "completed" || !payment.journalId || !payment.paidAt || new Date(payment.paidAt).toISOString().slice(0, 10) !== input.effectiveOn || !creditInvoice.externalId || creditInvoice.moneySnapshotId !== credit.moneySnapshotId) reject$1("Provider refund requires its completed same-customer local cash journal and source credit");
|
|
2079
|
+
const allocations = await (await PaymentAllocationCollection.create({ db: invoice.db })).findByPayment(input.paymentId);
|
|
2080
|
+
if (allocations.length !== 1 || allocations[0].invoiceId !== input.creditInvoiceId || allocations[0].amount !== payment.amount) reject$1("Refund recording requires one exact full source-credit allocation");
|
|
2081
|
+
const fingerprint = hash$1([
|
|
2082
|
+
payment.id,
|
|
2083
|
+
payment.customerId,
|
|
2084
|
+
payment.currency,
|
|
2085
|
+
payment.amount,
|
|
2086
|
+
payment.journalId,
|
|
2087
|
+
new Date(payment.paidAt).toISOString(),
|
|
2088
|
+
allocations[0].id,
|
|
2089
|
+
allocations[0].amount,
|
|
2090
|
+
credit.moneySnapshotId
|
|
2091
|
+
]);
|
|
2092
|
+
const next = {
|
|
2093
|
+
paymentId: input.paymentId,
|
|
2094
|
+
creditId: input.creditInvoiceId,
|
|
2095
|
+
requestId: input.requestId,
|
|
2096
|
+
evidenceRef: input.evidenceRef,
|
|
2097
|
+
effectiveOn: input.effectiveOn,
|
|
2098
|
+
amountMinor: payment.amount,
|
|
2099
|
+
paymentFingerprint: fingerprint,
|
|
2100
|
+
committed: false
|
|
2101
|
+
};
|
|
2102
|
+
const prior = state.refunds.find((r) => r.paymentId === input.paymentId || r.requestId === input.requestId);
|
|
2103
|
+
if (prior) {
|
|
2104
|
+
if (hash$1({
|
|
2105
|
+
...prior,
|
|
2106
|
+
committed: false
|
|
2107
|
+
}) !== hash$1(next)) reject$1("Refund recording identity reused with changed facts");
|
|
2108
|
+
return {
|
|
2109
|
+
...prior,
|
|
2110
|
+
externalCreditId: creditInvoice.externalId
|
|
2111
|
+
};
|
|
2112
|
+
}
|
|
2113
|
+
requireReady$1(state);
|
|
2114
|
+
if (!state.mapping.cashAccountRef) reject$1("Refund provider bank account must be explicitly reviewed");
|
|
2115
|
+
if (state.refunds.filter((r) => r.creditId === input.creditInvoiceId).reduce((sum, r) => sum + r.amountMinor, 0) + payment.amount > credit.grossMinor - credit.appliedMinor) reject$1("Refund exceeds unapplied provider credit capacity");
|
|
2116
|
+
if (payment.ordinaryAccountingBindingJson || payment.retentionAccountingBindingJson) reject$1("Refund is already bound to a provider operation");
|
|
2117
|
+
payment.ordinaryAccountingBindingJson = JSON.stringify({
|
|
2118
|
+
invoiceId: input.invoiceId,
|
|
2119
|
+
tenantId: input.tenantId,
|
|
2120
|
+
requestId: input.requestId,
|
|
2121
|
+
paymentFingerprint: fingerprint
|
|
2122
|
+
});
|
|
2123
|
+
await withOrdinaryAccountingAuthority(input.tenantId, input.invoiceId, () => payment.save());
|
|
2124
|
+
state.refunds.push(next);
|
|
2125
|
+
state.status = "pending";
|
|
2126
|
+
await save$1(invoice, state);
|
|
2127
|
+
return {
|
|
2128
|
+
...next,
|
|
2129
|
+
externalCreditId: creditInvoice.externalId
|
|
2130
|
+
};
|
|
2131
|
+
});
|
|
2132
|
+
const prefix = `refund:${input.requestId}`;
|
|
2133
|
+
const creditSource = (state) => ({
|
|
2134
|
+
type: "CreditMemo",
|
|
2135
|
+
id: reservation.externalCreditId,
|
|
2136
|
+
customerRef: state.mapping.customerRef,
|
|
2137
|
+
evidenceRef: reservation.paymentFingerprint
|
|
2138
|
+
});
|
|
2139
|
+
const journal = await executeStep$1(input, prefix, (_invoice, state) => prepareQuickBooksReceivableRequest({
|
|
2140
|
+
kind: "cash-refund",
|
|
2141
|
+
mapping: state.mapping,
|
|
2142
|
+
source: creditSource(state),
|
|
2143
|
+
requestId: requestId$1(state, prefix),
|
|
2144
|
+
effectiveOn: reservation.effectiveOn,
|
|
2145
|
+
evidenceRef: reservation.evidenceRef,
|
|
2146
|
+
amountMinor: reservation.amountMinor
|
|
2147
|
+
}));
|
|
2148
|
+
await executeStep$1(input, `${prefix}:application`, (_invoice, state) => prepareQuickBooksReceivableRequest({
|
|
2149
|
+
kind: "application",
|
|
2150
|
+
mapping: state.mapping,
|
|
2151
|
+
source: creditSource(state),
|
|
2152
|
+
requestId: requestId$1(state, `${prefix}:application`),
|
|
2153
|
+
effectiveOn: reservation.effectiveOn,
|
|
2154
|
+
evidenceRef: reservation.evidenceRef,
|
|
2155
|
+
amountMinor: reservation.amountMinor,
|
|
2156
|
+
debit: {
|
|
2157
|
+
type: "JournalEntry",
|
|
2158
|
+
id: journal.externalId
|
|
2159
|
+
},
|
|
2160
|
+
credit: {
|
|
2161
|
+
type: "CreditMemo",
|
|
2162
|
+
id: reservation.externalCreditId
|
|
2163
|
+
}
|
|
2164
|
+
}));
|
|
2165
|
+
return locked$1(input, async (invoice, state) => {
|
|
2166
|
+
if (!state) reject$1("Ordinary provider state disappeared");
|
|
2167
|
+
const refund = state.refunds.find((r) => r.requestId === input.requestId);
|
|
2168
|
+
if (!refund) reject$1("Refund reservation disappeared");
|
|
2169
|
+
refund.committed = true;
|
|
2170
|
+
refreshStatus$1(state);
|
|
2171
|
+
await save$1(invoice, state);
|
|
2172
|
+
return state;
|
|
2173
|
+
});
|
|
2174
|
+
}
|
|
2175
|
+
async function reconcileOrdinaryCreditAccounting(input) {
|
|
2176
|
+
principal$1(input);
|
|
2177
|
+
const mapping = await locked$1(input, async (_invoice, state) => {
|
|
2178
|
+
const reserved = state?.credits.find((credit) => credit.creditId === input.creditInvoiceId);
|
|
2179
|
+
if (!reserved) reject$1("Ordinary credit has no approved provider reservation");
|
|
2180
|
+
return structuredClone(reserved.mapping);
|
|
2181
|
+
});
|
|
2182
|
+
return withOrdinaryAccountingAuthority(input.tenantId, input.invoiceId, () => reconcileFrozenInvoiceAccountingSync({
|
|
2183
|
+
...input,
|
|
2184
|
+
invoiceId: input.creditInvoiceId,
|
|
2185
|
+
mapping
|
|
2186
|
+
}));
|
|
2187
|
+
}
|
|
2188
|
+
async function reviewOrdinaryCredit(input, read) {
|
|
2189
|
+
if (getTenantId() !== input.tenantId) reject$1("Ordinary review requires its active tenant");
|
|
2190
|
+
return withDocumentMoneyTransaction(input.db, async (tx) => {
|
|
2191
|
+
await lockTenantMoneyRuntime(tx, input.tenantId);
|
|
2192
|
+
const { invoice } = await lockAndReloadInvoice(tx, input.invoiceId, input.tenantId);
|
|
2193
|
+
if (!readOrdinaryInvoiceAccountingState(invoice)) reject$1("Ordinary provider contract is missing");
|
|
2194
|
+
const credit = await (await InvoiceCollection.create({ db: tx })).get({ id: input.creditInvoiceId }, { cache: false });
|
|
2195
|
+
if (!credit || credit.tenantId !== input.tenantId || credit.creditedInvoiceId !== invoice.id || credit.kind !== "credit_note" || credit.customerId !== invoice.customerId || credit.currency !== invoice.currency || credit.moneyAuthority !== "calculated") reject$1("Ordinary credit review requires exact source lineage");
|
|
2196
|
+
return withOrdinaryAccountingAuthority(input.tenantId, input.invoiceId, () => read({
|
|
2197
|
+
db: tx,
|
|
2198
|
+
tenantId: input.tenantId,
|
|
2199
|
+
invoiceId: input.creditInvoiceId
|
|
2200
|
+
}));
|
|
2201
|
+
});
|
|
2202
|
+
}
|
|
2203
|
+
function loadOrdinaryCreditAccountingSnapshot(input) {
|
|
2204
|
+
return reviewOrdinaryCredit(input, loadFrozenInvoiceAccountingSnapshot);
|
|
2205
|
+
}
|
|
2206
|
+
function toOrdinaryCreditAccountingInput(input) {
|
|
2207
|
+
return reviewOrdinaryCredit(input, async (scope) => await toFrozenInvoiceAccountingInput({
|
|
2208
|
+
...scope,
|
|
2209
|
+
mapping: input.mapping
|
|
2210
|
+
}));
|
|
2211
|
+
}
|
|
2212
|
+
//#endregion
|
|
1732
2213
|
//#region src/retention-accounting.ts
|
|
1733
2214
|
var hash = (value) => createHash("sha256").update(JSON.stringify(value)).digest("hex");
|
|
1734
2215
|
function reject(message) {
|
|
@@ -1841,57 +2322,11 @@ function source(invoice, state) {
|
|
|
1841
2322
|
};
|
|
1842
2323
|
}
|
|
1843
2324
|
async function executeStep(input, key, make) {
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
if (!prior.result) reject("Confirmed retention step has no readback");
|
|
1850
|
-
return { complete: prior.result };
|
|
1851
|
-
}
|
|
1852
|
-
if (prior && prior.status !== "rejected") reject("Retention operation has an unresolved remote owner");
|
|
1853
|
-
if (state.steps.some((s) => s.key !== key && s.status !== "confirmed")) reject("Another retention operation requires reconciliation");
|
|
1854
|
-
const step2 = {
|
|
1855
|
-
key,
|
|
1856
|
-
status: "in_flight",
|
|
1857
|
-
attemptId: randomUUID(),
|
|
1858
|
-
request: expected,
|
|
1859
|
-
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1860
|
-
priorAttempts: prior ? [...prior.priorAttempts ?? [], {
|
|
1861
|
-
attemptId: prior.attemptId,
|
|
1862
|
-
status: prior.status,
|
|
1863
|
-
startedAt: prior.startedAt,
|
|
1864
|
-
reconciliation: prior.reconciliation
|
|
1865
|
-
}] : []
|
|
1866
|
-
};
|
|
1867
|
-
if (prior) state.steps[state.steps.indexOf(prior)] = step2;
|
|
1868
|
-
else state.steps.push(step2);
|
|
1869
|
-
await save(invoice, state);
|
|
1870
|
-
return { step: step2 };
|
|
1871
|
-
});
|
|
1872
|
-
if ("complete" in prepared && prepared.complete) return prepared.complete;
|
|
1873
|
-
if (!prepared.step) reject("Prepared retention step is missing");
|
|
1874
|
-
const step = prepared.step;
|
|
1875
|
-
let result;
|
|
1876
|
-
try {
|
|
1877
|
-
result = await input.provider.receivableTransfers.execute(step.request);
|
|
1878
|
-
} catch (error) {
|
|
1879
|
-
await locked(input, async (invoice, state) => {
|
|
1880
|
-
const current = state?.steps.find((s) => s.key === key);
|
|
1881
|
-
if (!state || current?.attemptId !== step.attemptId || current.status !== "in_flight") reject("Retention step ownership changed after remote failure");
|
|
1882
|
-
current.status = error?.outcome === "rejected" ? "rejected" : "unknown";
|
|
1883
|
-
await save(invoice, state);
|
|
1884
|
-
});
|
|
1885
|
-
throw error;
|
|
1886
|
-
}
|
|
1887
|
-
return locked(input, async (invoice, state) => {
|
|
1888
|
-
const current = state?.steps.find((s) => s.key === key);
|
|
1889
|
-
if (!state || current?.attemptId !== step.attemptId || current.status !== "in_flight" || result.payloadHash !== step.request.payloadHash) reject("Retention step ownership or readback changed after write");
|
|
1890
|
-
current.status = "confirmed";
|
|
1891
|
-
current.result = structuredClone(result);
|
|
1892
|
-
await save(invoice, state);
|
|
1893
|
-
return result;
|
|
1894
|
-
});
|
|
2325
|
+
return executeAccountingStep({
|
|
2326
|
+
locked: (operation) => locked(input, operation),
|
|
2327
|
+
save,
|
|
2328
|
+
provider: input.provider.receivableTransfers
|
|
2329
|
+
}, key, make);
|
|
1895
2330
|
}
|
|
1896
2331
|
async function syncRetainedInvoiceToAccounting(input) {
|
|
1897
2332
|
principal(input);
|
|
@@ -2057,31 +2492,11 @@ async function releaseRetainedInvoiceToAccounting(input) {
|
|
|
2057
2492
|
}
|
|
2058
2493
|
async function reconcileRetainedAccountingStep(input) {
|
|
2059
2494
|
principal(input);
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
});
|
|
2066
|
-
const result = input.outcome.status === "found" ? await input.provider.receivableTransfers.read(step.request, input.outcome.externalId) : null;
|
|
2067
|
-
if (input.outcome.status === "absent") text(input.outcome.evidenceRef, "Authoritative provider absence evidence");
|
|
2068
|
-
return locked(input, async (invoice, state) => {
|
|
2069
|
-
const current = state?.steps.find((s) => s.key === input.stepKey);
|
|
2070
|
-
if (!state || current?.attemptId !== step.attemptId || current.status !== step.status) reject("Retention reconciliation owner changed");
|
|
2071
|
-
current.reconciliation = {
|
|
2072
|
-
outcome: input.outcome.status,
|
|
2073
|
-
recordedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2074
|
-
interruptedOwnerStopped: input.confirmInterruptedOwnerStopped === true,
|
|
2075
|
-
...input.outcome.status === "found" ? { externalId: input.outcome.externalId } : { evidenceRef: input.outcome.evidenceRef }
|
|
2076
|
-
};
|
|
2077
|
-
if (result) {
|
|
2078
|
-
if (result.payloadHash !== step.request.payloadHash) reject("Retention reconciliation readback differs");
|
|
2079
|
-
current.result = result;
|
|
2080
|
-
current.status = "confirmed";
|
|
2081
|
-
} else current.status = "rejected";
|
|
2082
|
-
await save(invoice, state);
|
|
2083
|
-
return state;
|
|
2084
|
-
});
|
|
2495
|
+
return reconcileAccountingStep({
|
|
2496
|
+
locked: (operation) => locked(input, operation),
|
|
2497
|
+
save,
|
|
2498
|
+
provider: input.provider.receivableTransfers
|
|
2499
|
+
}, input);
|
|
2085
2500
|
}
|
|
2086
2501
|
async function reconcileRetainedBaseInvoice(input) {
|
|
2087
2502
|
principal(input);
|
|
@@ -2318,6 +2733,6 @@ async function reconcileRetainedCreditAccounting(input) {
|
|
|
2318
2733
|
}));
|
|
2319
2734
|
}
|
|
2320
2735
|
//#endregion
|
|
2321
|
-
export {
|
|
2736
|
+
export { PaymentAllocationCollection_exports as A, invoice_accounting_sync_exports as C, syncFrozenInvoiceToAccounting as D, reconcileFrozenInvoiceAccountingSync as E, Payment as F, historicalCreditRefundCommittedTotal as M, PaymentCollection as N, toFrozenInvoiceAccountingInput as O, PaymentCollection_exports as P, calculateInvoiceMinorLine as S, readFrozenAccountingSyncState as T, reconcileOrdinaryCreditAccounting as _, reconcileRetainedBaseInvoice as a, syncOrdinaryRefundToAccounting as b, syncRetainedCreditToAccounting as c, toRetainedCreditAccountingInput as d, toRetainedInvoiceAccountingInput as f, reconcileOrdinaryBaseInvoice as g, reconcileOrdinaryAccountingStep as h, reconcileRetainedAccountingStep as i, PaymentAllocation as j, PaymentAllocationCollection as k, syncRetainedInvoiceToAccounting as l, readOrdinaryInvoiceAccountingState as m, loadRetainedInvoiceAccountingSnapshot as n, reconcileRetainedCreditAccounting as o, loadOrdinaryCreditAccountingSnapshot as p, readRetainedInvoiceAccountingState as r, releaseRetainedInvoiceToAccounting as s, loadRetainedCreditAccountingSnapshot as t, syncRetainedRefundToAccounting as u, syncOrdinaryCreditToAccounting as v, loadFrozenInvoiceAccountingSnapshot as w, toOrdinaryCreditAccountingInput as x, syncOrdinaryInvoiceToAccounting as y };
|
|
2322
2737
|
|
|
2323
|
-
//# sourceMappingURL=retention-accounting-
|
|
2738
|
+
//# sourceMappingURL=retention-accounting-c7kY7gFU.js.map
|