@happyvertical/smrt-commerce 0.58.0 → 0.59.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/agents/accounting-snapshot-mapper.md +3 -0
  2. package/agents/quickbooks-pilot.md +56 -0
  3. package/dist/chunks/{ContractLineItemCollection-D74LbDOI.js → ContractLineItemCollection-BFWJ2e6a.js} +4 -4
  4. package/dist/chunks/{ContractLineItemCollection-D74LbDOI.js.map → ContractLineItemCollection-BFWJ2e6a.js.map} +1 -1
  5. package/dist/chunks/{DocumentTaxCollection-BmKyJGZi.js → DocumentTaxCollection-MFfprLH-.js} +9 -9
  6. package/dist/chunks/{DocumentTaxCollection-BmKyJGZi.js.map → DocumentTaxCollection-MFfprLH-.js.map} +1 -1
  7. package/dist/chunks/{InvoiceCollection-BPEzXaKm.js → InvoiceCollection-D_8NXuaw.js} +9 -9
  8. package/dist/chunks/{InvoiceCollection-BPEzXaKm.js.map → InvoiceCollection-D_8NXuaw.js.map} +1 -1
  9. package/dist/chunks/{TaxCodeCollection-DpQeIqP4.js → TaxCodeCollection-Bc-Z8alx.js} +2 -2
  10. package/dist/chunks/{TaxCodeCollection-DpQeIqP4.js.map → TaxCodeCollection-Bc-Z8alx.js.map} +1 -1
  11. package/dist/chunks/{TaxComponentCollection-wFGWOaxy.js → TaxComponentCollection-DUY7A2sU.js} +3 -3
  12. package/dist/chunks/{TaxComponentCollection-wFGWOaxy.js.map → TaxComponentCollection-DUY7A2sU.js.map} +1 -1
  13. package/dist/chunks/{allocation-integrity-DgnPec2a.js → allocation-integrity-ri5cUYXB.js} +6 -6
  14. package/dist/chunks/{allocation-integrity-DgnPec2a.js.map → allocation-integrity-ri5cUYXB.js.map} +1 -1
  15. package/dist/chunks/{contract-money-BxvY2Kcp.js → contract-money-By1XIzp_.js} +6 -6
  16. package/dist/chunks/{contract-money-BxvY2Kcp.js.map → contract-money-By1XIzp_.js.map} +1 -1
  17. package/dist/chunks/{invoice-posting-C1q3CIYz.js → invoice-posting-BfnOyqBR.js} +3 -3
  18. package/dist/chunks/{invoice-posting-C1q3CIYz.js.map → invoice-posting-BfnOyqBR.js.map} +1 -1
  19. package/dist/chunks/{retention-accounting-BAKsmbwJ.js → retention-accounting-CASwH8OH.js} +239 -74
  20. package/dist/chunks/retention-accounting-CASwH8OH.js.map +1 -0
  21. package/dist/chunks/{tenant-money-barrier-j1m0r6pe.js → tenant-money-barrier-BbbInDGP.js} +3 -3
  22. package/dist/chunks/{tenant-money-barrier-j1m0r6pe.js.map → tenant-money-barrier-BbbInDGP.js.map} +1 -1
  23. package/dist/index.d.ts +2 -1
  24. package/dist/index.d.ts.map +1 -1
  25. package/dist/index.js +13 -13
  26. package/dist/index.js.map +1 -1
  27. package/dist/invoice-accounting-sync.d.ts +33 -1
  28. package/dist/invoice-accounting-sync.d.ts.map +1 -1
  29. package/dist/invoices.js +1 -1
  30. package/dist/manifest.json +1 -1
  31. package/dist/quickbooks-pilot-evidence.d.ts +47 -0
  32. package/dist/quickbooks-pilot-evidence.d.ts.map +1 -0
  33. package/dist/smrt-knowledge.json +3 -3
  34. package/package.json +13 -13
  35. package/dist/chunks/retention-accounting-BAKsmbwJ.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-BmKyJGZi.js";
2
+ import { Y as InvoiceLineItemCollection, t as DocumentTaxCollection, x as lockAndReloadInvoice } from "./DocumentTaxCollection-MFfprLH-.js";
3
3
  import { d as PaymentStatus, s as InvoiceStatus, u as PaymentMethod } from "./types-Bbm0Xflc.js";
4
- import { o as minorToMajorUnits } from "./units-8ElbjVZK.js";
4
+ import { n as deterministicId, o as minorToMajorUnits } from "./units-8ElbjVZK.js";
5
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-DgnPec2a.js";
6
+ import { i as claimAllocationParent, n as allocationTotal, r as assertAllocationParent, t as allocationOwner } from "./allocation-integrity-ri5cUYXB.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-BPEzXaKm.js";
13
- import { t as lockTenantMoneyRuntime } from "./tenant-money-barrier-j1m0r6pe.js";
12
+ import { c as validateRetentionEvidence, o as calculateInvoicePayable, s as parseInvoiceRetention, t as InvoiceCollection } from "./InvoiceCollection-D_8NXuaw.js";
13
+ import { t as lockTenantMoneyRuntime } from "./tenant-money-barrier-BbbInDGP.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 { AsyncLocalStorage } from "node:async_hooks";
@@ -969,7 +969,7 @@ var PaymentAllocation = class extends SmrtObject {
969
969
  if (!this.paymentId || !this.invoiceId) throw new Error("PaymentAllocation requires payment and invoice");
970
970
  return this.withTransaction(async () => {
971
971
  const { PaymentCollection } = await Promise.resolve().then(() => PaymentCollection_exports);
972
- const { InvoiceCollection } = await import("./InvoiceCollection-BPEzXaKm.js").then((n) => n.n);
972
+ const { InvoiceCollection } = await import("./InvoiceCollection-D_8NXuaw.js").then((n) => n.n);
973
973
  const { PaymentAllocationCollection } = await Promise.resolve().then(() => PaymentAllocationCollection_exports);
974
974
  const payments = await PaymentCollection.create({ db: this.db });
975
975
  const invoices = await InvoiceCollection.create({ db: this.db });
@@ -1076,7 +1076,7 @@ var PaymentAllocationCollection = class PaymentAllocationCollection extends Smrt
1076
1076
  async allocatePayment(input) {
1077
1077
  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");
1078
1078
  const { PaymentCollection } = await Promise.resolve().then(() => PaymentCollection_exports);
1079
- const { InvoiceCollection } = await import("./InvoiceCollection-BPEzXaKm.js").then((n) => n.n);
1079
+ const { InvoiceCollection } = await import("./InvoiceCollection-D_8NXuaw.js").then((n) => n.n);
1080
1080
  const { InvoiceStatus } = await import("./types-Bbm0Xflc.js").then((n) => n.m);
1081
1081
  const payment = await (await PaymentCollection.create({ db: this.db })).get({ id: input.paymentId }, { cache: false });
1082
1082
  if (!payment) throw new Error("Payment does not exist");
@@ -1119,7 +1119,7 @@ var PaymentAllocationCollection = class PaymentAllocationCollection extends Smrt
1119
1119
  const allocation = await this.get({ id }, { cache: false });
1120
1120
  if (!allocation) return;
1121
1121
  const { PaymentCollection } = await Promise.resolve().then(() => PaymentCollection_exports);
1122
- const { InvoiceCollection } = await import("./InvoiceCollection-BPEzXaKm.js").then((n) => n.n);
1122
+ const { InvoiceCollection } = await import("./InvoiceCollection-D_8NXuaw.js").then((n) => n.n);
1123
1123
  const payment = await (await PaymentCollection.create({ db: this.db })).get({ id: allocation.paymentId }, { cache: false });
1124
1124
  if (!payment) throw new Error("Allocation payment does not exist");
1125
1125
  await payment.withTransaction(async () => {
@@ -1417,11 +1417,69 @@ function mapFrozenAccountingSnapshot(snapshot, mapping) {
1417
1417
  };
1418
1418
  }
1419
1419
  //#endregion
1420
+ //#region src/quickbooks-pilot-evidence.ts
1421
+ function pilotDisagreement(message) {
1422
+ throw new MoneyValidationError("ACCOUNTING_SYNC_RECONCILIATION_REQUIRED", message);
1423
+ }
1424
+ function quickBooksPilotInvoiceId(tenantId, obligationId) {
1425
+ return deterministicId([
1426
+ "quickbooks-pilot-invoice",
1427
+ tenantId,
1428
+ obligationId
1429
+ ]);
1430
+ }
1431
+ function projectQuickBooksPilotEvidence(evidence) {
1432
+ const { balanceMinor, totalMinor, voided, transactions } = evidence;
1433
+ if (evidence.complete !== true || typeof voided !== "boolean" || !Number.isSafeInteger(balanceMinor) || balanceMinor < 0 || balanceMinor > totalMinor || !Array.isArray(transactions)) pilotDisagreement("QuickBooks invoice readback is incomplete or invalid");
1434
+ const seen = /* @__PURE__ */ new Set();
1435
+ const facts = [];
1436
+ const sums = {
1437
+ payment: 0,
1438
+ credit: 0,
1439
+ refund: 0
1440
+ };
1441
+ for (const entry of evidence.transactions) {
1442
+ if (!entry || typeof entry !== "object") pilotDisagreement("QuickBooks linked transaction evidence is invalid");
1443
+ const { id, kind, amountMinor } = entry;
1444
+ if (![
1445
+ "payment",
1446
+ "credit",
1447
+ "refund"
1448
+ ].includes(kind) || typeof id !== "string" || !id || id.trim() !== id || !Number.isSafeInteger(amountMinor) || amountMinor <= 0 || seen.has(JSON.stringify([kind, id]))) pilotDisagreement("QuickBooks linked transaction evidence is invalid or duplicated");
1449
+ seen.add(JSON.stringify([kind, id]));
1450
+ facts.push({
1451
+ id,
1452
+ kind,
1453
+ amountMinor
1454
+ });
1455
+ sums[kind] += amountMinor;
1456
+ if (!Number.isSafeInteger(sums[kind])) pilotDisagreement("QuickBooks linked transaction totals exceed safe minor units");
1457
+ }
1458
+ const paidMinor = sums.payment - sums.refund;
1459
+ if (paidMinor < 0 || (voided ? balanceMinor !== 0 || paidMinor !== 0 || sums.credit !== 0 : BigInt(paidMinor) + BigInt(sums.credit) + BigInt(balanceMinor) !== BigInt(totalMinor))) pilotDisagreement("QuickBooks balance disagrees with linked transaction evidence");
1460
+ let state = "unpaid";
1461
+ if (voided) state = "voided";
1462
+ else if (sums.refund > 0) state = "refunded";
1463
+ else if (balanceMinor === 0 && sums.credit > 0) state = "credited";
1464
+ else if (balanceMinor === 0) state = "paid";
1465
+ else if (paidMinor > 0 || sums.credit > 0) state = "partial";
1466
+ return {
1467
+ balanceMinor,
1468
+ paidMinor,
1469
+ creditedMinor: sums.credit,
1470
+ refundedMinor: sums.refund,
1471
+ state,
1472
+ transactions: facts,
1473
+ observedAt: (/* @__PURE__ */ new Date()).toISOString()
1474
+ };
1475
+ }
1476
+ //#endregion
1420
1477
  //#region src/invoice-accounting-sync.ts
1421
1478
  var invoice_accounting_sync_exports = /* @__PURE__ */ __exportAll({
1422
1479
  loadFrozenInvoiceAccountingSnapshot: () => loadFrozenInvoiceAccountingSnapshot,
1423
1480
  readFrozenAccountingSyncState: () => readFrozenAccountingSyncState,
1424
1481
  reconcileFrozenInvoiceAccountingSync: () => reconcileFrozenInvoiceAccountingSync,
1482
+ runQuickBooksPilot: () => runQuickBooksPilot,
1425
1483
  syncFrozenInvoiceToAccounting: () => syncFrozenInvoiceToAccounting,
1426
1484
  toFrozenInvoiceAccountingInput: () => toFrozenInvoiceAccountingInput
1427
1485
  });
@@ -1613,7 +1671,7 @@ function requestIdFor(snapshot, mapping) {
1613
1671
  snapshot.moneySnapshotId
1614
1672
  ])).digest("hex").slice(0, 40)}`;
1615
1673
  }
1616
- async function prepareSync(input) {
1674
+ async function prepareSync(input, pilot) {
1617
1675
  if (input.provider.type !== "quickbooks") unsupported(`Frozen accounting sync does not support provider '${input.provider.type}'`);
1618
1676
  return withDocumentMoneyTransaction(input.db, async (tx) => {
1619
1677
  await lockTenantMoneyRuntime(tx, input.tenantId);
@@ -1622,6 +1680,8 @@ async function prepareSync(input) {
1622
1680
  const snapshot = await loadSnapshot(tx, input.tenantId, invoice);
1623
1681
  const sdkInput = toSdkInput(snapshot, input.mapping);
1624
1682
  const prior = parseSyncState(invoice.accountingSyncStateJson);
1683
+ checkPilotAcceptance(invoice, prior, pilot);
1684
+ if (pilot) assertPilotInvoice(invoice);
1625
1685
  if (prior?.status === "synced" && sameSyncScope(prior.identity, snapshot, input.mapping)) {
1626
1686
  if (!prior.externalId || !prior.syncedAt) throw new MoneyValidationError("ACCOUNTING_SYNC_RECONCILIATION_REQUIRED", "Completed accounting sync state is incomplete");
1627
1687
  return {
@@ -1643,17 +1703,7 @@ async function prepareSync(input) {
1643
1703
  unsupported(error instanceof Error ? error.message : String(error));
1644
1704
  }
1645
1705
  const { accountingInput, request } = prepareRequest(snapshot, input.mapping, sdkInput);
1646
- const identity = {
1647
- provider: "quickbooks",
1648
- documentKind: snapshot.document.kind,
1649
- realmId: request.realmId,
1650
- environment: request.environment,
1651
- invoiceId: snapshot.document.id,
1652
- moneySnapshotId: snapshot.moneySnapshotId,
1653
- requestId: request.requestId,
1654
- payloadHash: request.payloadHash,
1655
- creditedInvoiceExternalId: "creditedInvoiceExternalId" in request ? request.creditedInvoiceExternalId : void 0
1656
- };
1706
+ const identity = frozenIdentity(snapshot, request);
1657
1707
  if (prior && !sameIdentity(prior.identity, identity)) throw new MoneyValidationError("ACCOUNTING_SYNC_RECONCILIATION_REQUIRED", "Invoice accounting identity changed across provider, realm, environment, or snapshot");
1658
1708
  const attemptId = randomUUID();
1659
1709
  invoice.accountingSyncStateJson = JSON.stringify({
@@ -1661,20 +1711,22 @@ async function prepareSync(input) {
1661
1711
  status: "in_flight",
1662
1712
  identity,
1663
1713
  attemptId,
1664
- startedAt: (/* @__PURE__ */ new Date()).toISOString()
1714
+ startedAt: (/* @__PURE__ */ new Date()).toISOString(),
1715
+ pilot
1665
1716
  });
1666
1717
  await invoice.saveDerived(DOCUMENT_MONEY_WRITE);
1667
1718
  return {
1668
1719
  kind: "push",
1669
1720
  documentKind: snapshot.document.kind,
1670
1721
  accountingInput,
1722
+ invoice,
1671
1723
  identity,
1672
1724
  attemptId
1673
1725
  };
1674
1726
  });
1675
1727
  }
1676
- async function recordFailure(input, prepared, error) {
1677
- const rejected = !!error && typeof error === "object" && error.code === "QBO_WRITE_ERROR" && error.outcome === "rejected";
1728
+ async function recordFailure(input, prepared, error, pushReturned) {
1729
+ const rejected = !pushReturned && !!error && typeof error === "object" && error.code === "QBO_WRITE_ERROR" && error.outcome === "rejected";
1678
1730
  await withDocumentMoneyTransaction(input.db, async (tx) => {
1679
1731
  await lockTenantMoneyRuntime(tx, input.tenantId);
1680
1732
  const { invoice } = await lockAndReloadInvoice(tx, input.invoiceId, input.tenantId);
@@ -1687,38 +1739,49 @@ async function recordFailure(input, prepared, error) {
1687
1739
  await invoice.saveDerived(DOCUMENT_MONEY_WRITE);
1688
1740
  });
1689
1741
  }
1690
- async function recordSuccess(input, prepared, result) {
1742
+ async function saveSyncedInvoice(invoice, state, externalId, syncedAt) {
1743
+ invoice.externalId = externalId;
1744
+ invoice.externalProvider = state.identity.provider;
1745
+ invoice.syncedAt = syncedAt;
1746
+ invoice.accountingSyncStateJson = JSON.stringify({
1747
+ ...state,
1748
+ status: "synced",
1749
+ externalId,
1750
+ syncedAt: syncedAt.toISOString()
1751
+ });
1752
+ await invoice.saveDerived(DOCUMENT_MONEY_WRITE);
1753
+ return {
1754
+ status: "synced",
1755
+ externalId,
1756
+ syncedAt,
1757
+ moneySnapshotId: state.identity.moneySnapshotId
1758
+ };
1759
+ }
1760
+ async function recordSuccess(input, prepared, result, pilotProjection) {
1691
1761
  requiredText(result.externalId, "Accounting external ID");
1692
1762
  if (!(result.syncedAt instanceof Date) || Number.isNaN(result.syncedAt.valueOf())) throw new MoneyValidationError("INVALID_MONEY_INPUT", "Accounting provider returned an invalid sync timestamp");
1693
1763
  return withDocumentMoneyTransaction(input.db, async (tx) => {
1694
1764
  await lockTenantMoneyRuntime(tx, input.tenantId);
1695
1765
  const { invoice } = await lockAndReloadInvoice(tx, input.invoiceId, input.tenantId);
1696
1766
  const state = parseSyncState(invoice.accountingSyncStateJson);
1697
- if (state?.status !== "in_flight" || state.attemptId !== prepared.attemptId || !sameIdentity(state.identity, prepared.identity) || invoice.moneySnapshotId !== prepared.identity.moneySnapshotId) throw new MoneyValidationError("ACCOUNTING_SYNC_RECONCILIATION_REQUIRED", "Accounting sync ownership or frozen snapshot changed after provider write");
1698
- const syncedAt = result.syncedAt.toISOString();
1699
- invoice.externalId = result.externalId;
1700
- invoice.externalProvider = prepared.identity.provider;
1701
- invoice.syncedAt = result.syncedAt;
1702
- invoice.accountingSyncStateJson = JSON.stringify({
1767
+ if (state?.status !== "in_flight" || state.attemptId !== prepared.attemptId || !sameIdentity(state.identity, prepared.identity) || invoice.moneySnapshotId !== prepared.identity.moneySnapshotId || state.pilot && invoice.customerExternalId !== prepared.invoice.customerExternalId) throw new MoneyValidationError("ACCOUNTING_SYNC_RECONCILIATION_REQUIRED", "Accounting sync ownership or frozen snapshot changed after provider write");
1768
+ if (state.pilot) await assertUnusedPilotExternalId(tx, invoice, state.identity, result.externalId);
1769
+ return saveSyncedInvoice(invoice, {
1703
1770
  ...state,
1704
- status: "synced",
1705
- externalId: result.externalId,
1706
- syncedAt
1707
- });
1708
- await invoice.saveDerived(DOCUMENT_MONEY_WRITE);
1709
- return {
1710
- status: "synced",
1711
- externalId: result.externalId,
1712
- syncedAt: result.syncedAt,
1713
- moneySnapshotId: prepared.identity.moneySnapshotId
1714
- };
1771
+ pilotProjection
1772
+ }, result.externalId, result.syncedAt);
1715
1773
  });
1716
1774
  }
1717
1775
  async function syncFrozenInvoiceToAccounting(input) {
1776
+ return syncFrozenInvoice(input);
1777
+ }
1778
+ async function syncFrozenInvoice(input, pilot) {
1718
1779
  assertPrincipalTenant(input.tenantId);
1719
- const prepared = await prepareSync(input);
1780
+ const prepared = await prepareSync(input, pilot?.acceptance);
1720
1781
  if (prepared.kind === "unchanged") return prepared.result;
1721
1782
  let result;
1783
+ let pushReturned = false;
1784
+ let projection;
1722
1785
  try {
1723
1786
  if (prepared.documentKind === "invoice") {
1724
1787
  if (!("invoiceNumber" in prepared.accountingInput)) throw new MoneyValidationError("INVALID_MONEY_INPUT", "Prepared invoice has the wrong accounting document kind");
@@ -1727,11 +1790,13 @@ async function syncFrozenInvoiceToAccounting(input) {
1727
1790
  if (!("creditMemoNumber" in prepared.accountingInput)) throw new MoneyValidationError("INVALID_MONEY_INPUT", "Prepared credit note has the wrong accounting document kind");
1728
1791
  result = await requireCreditMemoOperations(input.provider).push(prepared.accountingInput);
1729
1792
  }
1793
+ pushReturned = true;
1794
+ projection = await pilot?.verify(prepared.invoice, result);
1730
1795
  } catch (error) {
1731
- await recordFailure(input, prepared, error);
1796
+ await recordFailure(input, prepared, error, pushReturned);
1732
1797
  throw error;
1733
1798
  }
1734
- return recordSuccess(input, prepared, result);
1799
+ return recordSuccess(input, prepared, result, projection);
1735
1800
  }
1736
1801
  async function reconcileFrozenInvoiceAccountingSync(input) {
1737
1802
  assertPrincipalTenant(input.tenantId);
@@ -1743,18 +1808,9 @@ async function reconcileFrozenInvoiceAccountingSync(input) {
1743
1808
  const snapshot = await loadSnapshot(tx, input.tenantId, invoice);
1744
1809
  const sdkInput = toSdkInput(snapshot, input.mapping);
1745
1810
  const { request } = prepareRequest(snapshot, input.mapping, sdkInput);
1746
- const identity = {
1747
- provider: "quickbooks",
1748
- documentKind: snapshot.document.kind,
1749
- realmId: request.realmId,
1750
- environment: request.environment,
1751
- invoiceId: snapshot.document.id,
1752
- moneySnapshotId: snapshot.moneySnapshotId,
1753
- requestId: request.requestId,
1754
- payloadHash: request.payloadHash,
1755
- creditedInvoiceExternalId: "creditedInvoiceExternalId" in request ? request.creditedInvoiceExternalId : void 0
1756
- };
1811
+ const identity = frozenIdentity(snapshot, request);
1757
1812
  const state = parseSyncState(invoice.accountingSyncStateJson);
1813
+ checkPilotAcceptance(invoice, state);
1758
1814
  if (!state || state.status !== "unknown" && state.status !== "in_flight" || state.identity.requestId !== input.requestId || state.attemptId !== input.attemptId || !sameIdentity(state.identity, identity)) throw new MoneyValidationError("ACCOUNTING_SYNC_RECONCILIATION_REQUIRED", "Accounting reconciliation does not match the unresolved frozen request");
1759
1815
  if (state.status === "in_flight" && input.confirmInterruptedOwnerStopped !== true) throw new MoneyValidationError("ACCOUNTING_SYNC_IN_PROGRESS", "Accounting reconciliation cannot release a live sync owner");
1760
1816
  if (input.outcome.status === "absent") {
@@ -1771,23 +1827,7 @@ async function reconcileFrozenInvoiceAccountingSync(input) {
1771
1827
  }
1772
1828
  const externalId = requiredText(input.outcome.externalId, "Accounting external ID");
1773
1829
  if (!(input.outcome.syncedAt instanceof Date) || Number.isNaN(input.outcome.syncedAt.valueOf())) throw new MoneyValidationError("INVALID_MONEY_INPUT", "Accounting reconciliation requires a valid sync timestamp");
1774
- const syncedAt = input.outcome.syncedAt.toISOString();
1775
- invoice.externalId = externalId;
1776
- invoice.externalProvider = identity.provider;
1777
- invoice.syncedAt = input.outcome.syncedAt;
1778
- invoice.accountingSyncStateJson = JSON.stringify({
1779
- ...state,
1780
- status: "synced",
1781
- externalId,
1782
- syncedAt
1783
- });
1784
- await invoice.saveDerived(DOCUMENT_MONEY_WRITE);
1785
- return {
1786
- status: "synced",
1787
- externalId,
1788
- syncedAt: input.outcome.syncedAt,
1789
- moneySnapshotId: identity.moneySnapshotId
1790
- };
1830
+ return saveSyncedInvoice(invoice, state, externalId, input.outcome.syncedAt);
1791
1831
  });
1792
1832
  }
1793
1833
  function readFrozenAccountingSyncState(invoice) {
@@ -1803,6 +1843,7 @@ function readFrozenAccountingSyncState(invoice) {
1803
1843
  requestId: state.identity.requestId,
1804
1844
  attemptId: state.attemptId,
1805
1845
  startedAt: state.startedAt,
1846
+ ...state.pilotProjection ? { pilotProjection: state.pilotProjection } : {},
1806
1847
  request: {
1807
1848
  requestId: state.identity.requestId,
1808
1849
  realmId: state.identity.realmId,
@@ -1812,6 +1853,130 @@ function readFrozenAccountingSyncState(invoice) {
1812
1853
  }
1813
1854
  };
1814
1855
  }
1856
+ function frozenIdentity(snapshot, request) {
1857
+ return {
1858
+ provider: "quickbooks",
1859
+ documentKind: snapshot.document.kind,
1860
+ realmId: request.realmId,
1861
+ environment: request.environment,
1862
+ invoiceId: snapshot.document.id,
1863
+ moneySnapshotId: snapshot.moneySnapshotId,
1864
+ requestId: request.requestId,
1865
+ payloadHash: request.payloadHash,
1866
+ creditedInvoiceExternalId: "creditedInvoiceExternalId" in request ? request.creditedInvoiceExternalId : void 0
1867
+ };
1868
+ }
1869
+ function checkPilotAcceptance(invoice, state, acceptance) {
1870
+ if (acceptance && acceptance.moneySnapshotId !== invoice.moneySnapshotId) pilotDisagreement("QuickBooks pilot acceptance does not match the frozen invoice");
1871
+ if (state && (state.pilot?.obligationId !== acceptance?.obligationId || state.pilot?.quoteVersionId !== acceptance?.quoteVersionId || state.pilot?.moneySnapshotId !== acceptance?.moneySnapshotId)) pilotDisagreement("QuickBooks pilot acceptance differs from the existing reservation");
1872
+ }
1873
+ async function assertUnusedPilotExternalId(db, invoice, identity, externalId) {
1874
+ const matches = await (await InvoiceCollection.create({ db })).list({ where: {
1875
+ externalId,
1876
+ tenantId: invoice.tenantId
1877
+ } });
1878
+ for (const other of matches) {
1879
+ if (other.id === invoice.id) continue;
1880
+ const binding = parseSyncState(other.accountingSyncStateJson);
1881
+ if (!binding || binding.identity.realmId === identity.realmId && binding.identity.environment === identity.environment) pilotDisagreement("QuickBooks invoice is already bound to another local invoice");
1882
+ }
1883
+ }
1884
+ function assertPilotInvoice(invoice) {
1885
+ if (invoice.kind !== "invoice" || invoice.totalAmount <= 0 || !invoice.customerExternalId || invoice.ordinaryAccountingStateJson || invoice.retentionAccountingStateJson) unsupported("QuickBooks pilot requires an ordinary positive invoice and a mapped customer");
1886
+ }
1887
+ function checkPilotEvidence(evidence, scope, externalId, invoice) {
1888
+ if (!evidence || evidence.tenantId !== scope.tenantId || evidence.realmId !== scope.realmId || evidence.environment !== scope.environment || evidence.externalId !== externalId || !invoice.customerExternalId || evidence.customerExternalId !== invoice.customerExternalId || evidence.currency !== invoice.currency || evidence.subtotalMinor !== invoice.subtotal || evidence.discountMinor !== invoice.discountTotal || evidence.taxMinor !== invoice.taxAmount || evidence.totalMinor !== invoice.totalAmount || evidence.dueDate !== (invoice.dueDate?.toISOString().slice(0, 10) ?? null)) pilotDisagreement("QuickBooks invoice identity or frozen money differs");
1889
+ return projectQuickBooksPilotEvidence(evidence);
1890
+ }
1891
+ async function runQuickBooksPilot(input) {
1892
+ assertPrincipalTenant(input.tenantId);
1893
+ const { acceptance, fixture, mapping } = input;
1894
+ if (![
1895
+ "issue",
1896
+ "poll",
1897
+ "link",
1898
+ "recover"
1899
+ ].includes(input.action)) unsupported("Unsupported QuickBooks pilot action");
1900
+ if (fixture?.mode !== "fixture" || fixture.scope?.environment !== "sandbox" || mapping.environment !== "sandbox" || fixture.scope.tenantId !== input.tenantId || fixture.scope.realmId !== mapping.realmId) unsupported("QuickBooks pilot requires an explicitly scoped sandbox fixture");
1901
+ requiredText(mapping.realmId, "QuickBooks realm");
1902
+ requiredText(acceptance.quoteVersionId, "Accepted quote version");
1903
+ requiredText(acceptance.moneySnapshotId, "Accepted money snapshot");
1904
+ const obligationId = requiredText(acceptance.obligationId, "Accepted obligation");
1905
+ if (input.invoiceId !== await quickBooksPilotInvoiceId(input.tenantId, obligationId)) pilotDisagreement("QuickBooks pilot requires the canonical obligation invoice");
1906
+ const scope = { ...fixture.scope };
1907
+ if (input.action === "issue") {
1908
+ let projection;
1909
+ await syncFrozenInvoice({
1910
+ ...input,
1911
+ provider: {
1912
+ type: "quickbooks",
1913
+ invoices: { push: (invoice) => fixture.pushInvoice(invoice) }
1914
+ }
1915
+ }, {
1916
+ acceptance,
1917
+ async verify(invoice, result) {
1918
+ if (result.action !== "created") pilotDisagreement("QuickBooks pilot did not return a new invoice");
1919
+ const id = requiredText(result.externalId, "QuickBooks invoice ID");
1920
+ projection = checkPilotEvidence(await fixture.readInvoice(id), scope, id, invoice);
1921
+ return projection;
1922
+ }
1923
+ });
1924
+ if (projection) return projection;
1925
+ }
1926
+ return withDocumentMoneyTransaction(input.db, async (tx) => {
1927
+ await lockTenantMoneyRuntime(tx, input.tenantId);
1928
+ const { invoice } = await lockAndReloadInvoice(tx, input.invoiceId, input.tenantId);
1929
+ const state = parseSyncState(invoice.accountingSyncStateJson);
1930
+ checkPilotAcceptance(invoice, state, acceptance);
1931
+ assertPilotInvoice(invoice);
1932
+ const snapshot = await loadSnapshot(tx, input.tenantId, invoice);
1933
+ const sdkInput = toSdkInput({
1934
+ ...snapshot,
1935
+ document: {
1936
+ ...snapshot.document,
1937
+ externalId: void 0
1938
+ }
1939
+ }, mapping);
1940
+ const { request } = prepareRequest(snapshot, mapping, sdkInput);
1941
+ const identity = frozenIdentity(snapshot, request);
1942
+ if (state && !sameIdentity(state.identity, identity)) pilotDisagreement("QuickBooks pilot binding or frozen request changed");
1943
+ if (state?.status === "synced") {
1944
+ const id = requiredText(state.externalId ?? "", "QuickBooks invoice ID");
1945
+ if (invoice.externalId !== id || invoice.externalProvider !== "quickbooks") pilotDisagreement("QuickBooks pilot provider binding changed");
1946
+ if (input.action === "link" && input.externalId !== id) pilotDisagreement("QuickBooks invoice already has a different binding");
1947
+ const projection2 = checkPilotEvidence(await fixture.readInvoice(id), scope, id, invoice);
1948
+ invoice.accountingSyncStateJson = JSON.stringify({
1949
+ ...state,
1950
+ pilotProjection: projection2
1951
+ });
1952
+ await invoice.saveDerived(DOCUMENT_MONEY_WRITE);
1953
+ return projection2;
1954
+ }
1955
+ let externalId;
1956
+ let evidence;
1957
+ if (input.action === "link" && (!state || state.status === "rejected") && !invoice.externalId) {
1958
+ externalId = requiredText(input.externalId, "QuickBooks invoice ID");
1959
+ evidence = await fixture.readInvoice(externalId);
1960
+ } else if (input.action === "recover" && (state?.status === "unknown" || state?.status === "in_flight" && input.confirmInterruptedOwnerStopped === true)) {
1961
+ const match = await fixture.findInvoiceForRequest?.(request);
1962
+ if (!match || match.requestId !== identity.requestId || match.payloadHash !== identity.payloadHash) pilotDisagreement("QuickBooks create remains unresolved without verified request identity");
1963
+ evidence = match.invoice;
1964
+ externalId = requiredText(evidence.externalId, "QuickBooks invoice ID");
1965
+ } else pilotDisagreement("QuickBooks pilot has no verified invoice binding for this action");
1966
+ const projection = checkPilotEvidence(evidence, scope, externalId, invoice);
1967
+ await assertUnusedPilotExternalId(tx, invoice, identity, externalId);
1968
+ await saveSyncedInvoice(invoice, {
1969
+ version: 1,
1970
+ status: "synced",
1971
+ identity,
1972
+ attemptId: state?.attemptId ?? randomUUID(),
1973
+ startedAt: state?.startedAt ?? projection.observedAt,
1974
+ pilot: acceptance,
1975
+ pilotProjection: projection
1976
+ }, externalId, new Date(projection.observedAt));
1977
+ return projection;
1978
+ });
1979
+ }
1815
1980
  //#endregion
1816
1981
  //#region src/svelte/invoices/calculations.ts
1817
1982
  function numberDecimal(value, precision) {
@@ -2889,6 +3054,6 @@ async function reconcileRetainedCreditAccounting(input) {
2889
3054
  }));
2890
3055
  }
2891
3056
  //#endregion
2892
- export { PaymentAllocationCollection_exports as A, invoice_accounting_sync_exports as C, syncFrozenInvoiceToAccounting as D, reconcileFrozenInvoiceAccountingSync as E, Payment as F, hasConnectedPaymentAuthority as I, withConnectedPaymentAuthority as L, 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 };
3057
+ export { quickBooksPilotInvoiceId as A, invoice_accounting_sync_exports as C, runQuickBooksPilot as D, reconcileFrozenInvoiceAccountingSync as E, PaymentCollection as F, PaymentCollection_exports as I, Payment as L, PaymentAllocationCollection_exports as M, PaymentAllocation as N, syncFrozenInvoiceToAccounting as O, historicalCreditRefundCommittedTotal as P, hasConnectedPaymentAuthority as R, 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, PaymentAllocationCollection as j, toFrozenInvoiceAccountingInput 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, withConnectedPaymentAuthority as z };
2893
3058
 
2894
- //# sourceMappingURL=retention-accounting-BAKsmbwJ.js.map
3059
+ //# sourceMappingURL=retention-accounting-CASwH8OH.js.map