@happyvertical/smrt-commerce 0.58.0 → 0.59.1
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/accounting-snapshot-mapper.md +3 -0
- package/agents/quickbooks-pilot.md +56 -0
- package/dist/chunks/{ContractLineItemCollection-D74LbDOI.js → ContractLineItemCollection-BFWJ2e6a.js} +4 -4
- package/dist/chunks/{ContractLineItemCollection-D74LbDOI.js.map → ContractLineItemCollection-BFWJ2e6a.js.map} +1 -1
- package/dist/chunks/{DocumentTaxCollection-BmKyJGZi.js → DocumentTaxCollection-MFfprLH-.js} +9 -9
- package/dist/chunks/{DocumentTaxCollection-BmKyJGZi.js.map → DocumentTaxCollection-MFfprLH-.js.map} +1 -1
- package/dist/chunks/{InvoiceCollection-BPEzXaKm.js → InvoiceCollection-D_8NXuaw.js} +9 -9
- package/dist/chunks/{InvoiceCollection-BPEzXaKm.js.map → InvoiceCollection-D_8NXuaw.js.map} +1 -1
- package/dist/chunks/{TaxCodeCollection-DpQeIqP4.js → TaxCodeCollection-Bc-Z8alx.js} +2 -2
- package/dist/chunks/{TaxCodeCollection-DpQeIqP4.js.map → TaxCodeCollection-Bc-Z8alx.js.map} +1 -1
- package/dist/chunks/{TaxComponentCollection-wFGWOaxy.js → TaxComponentCollection-DUY7A2sU.js} +3 -3
- package/dist/chunks/{TaxComponentCollection-wFGWOaxy.js.map → TaxComponentCollection-DUY7A2sU.js.map} +1 -1
- package/dist/chunks/{allocation-integrity-DgnPec2a.js → allocation-integrity-ri5cUYXB.js} +6 -6
- package/dist/chunks/{allocation-integrity-DgnPec2a.js.map → allocation-integrity-ri5cUYXB.js.map} +1 -1
- package/dist/chunks/{contract-money-BxvY2Kcp.js → contract-money-By1XIzp_.js} +6 -6
- package/dist/chunks/{contract-money-BxvY2Kcp.js.map → contract-money-By1XIzp_.js.map} +1 -1
- package/dist/chunks/{invoice-posting-C1q3CIYz.js → invoice-posting-BfnOyqBR.js} +3 -3
- package/dist/chunks/{invoice-posting-C1q3CIYz.js.map → invoice-posting-BfnOyqBR.js.map} +1 -1
- package/dist/chunks/{retention-accounting-BAKsmbwJ.js → retention-accounting-CASwH8OH.js} +239 -74
- package/dist/chunks/retention-accounting-CASwH8OH.js.map +1 -0
- package/dist/chunks/{tenant-money-barrier-j1m0r6pe.js → tenant-money-barrier-BbbInDGP.js} +3 -3
- package/dist/chunks/{tenant-money-barrier-j1m0r6pe.js.map → tenant-money-barrier-BbbInDGP.js.map} +1 -1
- package/dist/index.d.ts +2 -1
- 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 +33 -1
- package/dist/invoice-accounting-sync.d.ts.map +1 -1
- package/dist/invoices.js +1 -1
- package/dist/manifest.json +1 -1
- package/dist/quickbooks-pilot-evidence.d.ts +47 -0
- package/dist/quickbooks-pilot-evidence.d.ts.map +1 -0
- package/dist/smrt-knowledge.json +3 -3
- package/package.json +13 -13
- 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-
|
|
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-
|
|
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-
|
|
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-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-
|
|
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-
|
|
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-
|
|
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
|
|
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
|
-
|
|
1699
|
-
invoice
|
|
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
|
-
|
|
1705
|
-
|
|
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
|
-
|
|
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 {
|
|
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-
|
|
3059
|
+
//# sourceMappingURL=retention-accounting-CASwH8OH.js.map
|