@happyvertical/accounting 0.103.5 → 0.103.7

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/README.md CHANGED
@@ -70,27 +70,58 @@ const invoice = {
70
70
 
71
71
  The QuickBooks adapter maps these values to `SalesItemLineDetail.ItemRef`,
72
72
  `SalesItemLineDetail.TaxCodeRef`, `GlobalTaxCalculation`, and
73
- `TxnTaxDetail.TotalTax`. It supports `TaxExcluded` and zero-tax
74
- `NotApplicable`; tax-inclusive inputs are rejected because the provider-neutral
75
- line model does not allocate included tax. Every mapped line must supply both
76
- realm-specific references. The caller must validate that those IDs belong to
77
- the configured company. See Intuit's [invoice API](https://developer.intuit.com/app/developer/qbo/docs/api/accounting/most-commonly-used/invoice)
78
- and [item API](https://developer.intuit.com/app/developer/qbo/docs/api/accounting/all-entities/item).
73
+ `TxnTaxDetail`. It supports `TaxExcluded`, zero-tax `NotApplicable`, and frozen
74
+ `TaxInclusive` snapshots. For an inclusive line, pass the stored net amount as
75
+ `amount`, its stored tax as `taxAmount`, the stored gross amount as
76
+ `taxInclusiveAmount`, and stored per-component amounts as `taxComponents`.
77
+ An explicit zero-tax inclusive snapshot may supply an empty component list when
78
+ its net and gross facts are equal, including when an off-grid reduced unit price
79
+ requires the generated negative discount line.
80
+ The adapter sends net `Amount` and gross
81
+ `SalesItemLineDetail.TaxInclusiveAmt`; Intuit defines that nested field as the
82
+ total including tax in its [SDK reference](https://static.developer.intuit.com/sdkdocs/qbv3doc/ippphpdevkitv3/entities/files/IPPSalesOrderItemLineDetail.html).
83
+ It emits the stored component tax exactly as `TxnTaxDetail.TaxLine[].Amount`.
84
+ When any component detail is present, every taxed line must carry it and the
85
+ minor-unit aggregate must equal the frozen header tax. Each component therefore
86
+ needs a realm `quickbooksTaxRateRef`; its frozen
87
+ `rate` is a fraction from `0` through `1` and becomes QBO's percent-form
88
+ `TaxPercent` (for example `0.13` becomes `13`). Intuit's invoice example shows
89
+ the writable `TaxLineDetail` fields, and its TaxLineDetail reference requires
90
+ an international `TaxRateRef` to belong to a tax code used on a transaction
91
+ line: [invoice API](https://developer.intuit.com/app/developer/qbo/docs/api/accounting/all-entities/invoice),
92
+ [TaxLineDetail reference](https://static.developer.intuit.com/sdkdocs/qbv3doc/ippdotnetdevkitv3/html/393902ec-f79c-bb8f-82fd-0f3bd296dd27.htm).
93
+ It validates every component sum, rate-to-frozen-amount allocation (each stored
94
+ component is the exact floor or ceiling allowed by line/document rounding), and
95
+ header reconciliation before any network request. Every mapped line must supply both realm-specific references;
96
+ the caller must validate that those IDs belong to the configured company.
79
97
 
80
98
  Legacy untaxed invoices without `quickbooksMapping` remain supported. The
81
99
  adapter fails before authentication or HTTP when an input would otherwise be
82
- silently discarded: nonzero line `discount`, nonzero numeric `taxRate`, generic
83
- `taxCode`, `automaticTax`, inconsistent totals, partial mappings, or invalid
84
- references. Explicit zero `discount` and `taxRate` are accepted. This mapping
85
- API has no discount representation and does not silently net discounts into
86
- prices. An explicit line `amount` must agree with `quantity × unitPrice`, either
87
- as the existing exact-product calculation or as its decimal **half-up** result at
88
- the explicitly supplied currency's ISO 4217 minor precision. For example, CAD
89
- quantity `1.0005` × unit price `10` accepts the retained line amount `10.01`.
90
- The adapter preserves all three supplied values; it does not replace the amount,
91
- round an omitted amount, or change subtotal/tax reconciliation. The rounded path
92
- requires an amount exactly on the currency's minor-unit grid, not an arbitrary
93
- monetary tolerance. Negative quantities, prices and amounts remain unsupported.
100
+ silently discarded: nonzero numeric `taxRate`, generic `taxCode`, `automaticTax`,
101
+ inconsistent totals, partial mappings, or invalid references. `unitPrice` and
102
+ `discount` are in the document's amount mode: net for exclusive inputs and gross
103
+ for inclusive ones; `amount` is always the already-discounted net snapshot. A
104
+ discount uses a reduced QBO unit price only when that price fits the currency's
105
+ ISO 4217 minor-unit grid. Otherwise, for an inclusive line, the adapter derives
106
+ the negative net amount from the frozen gross discount and frozen component
107
+ rates, and then adds a negative `Discount on line N` sales line with the same item and tax-code
108
+ references. It never sends QBO's unsupported line-discount field or subtracts a
109
+ discount twice. Negative generated discount lines are internal; caller invoice
110
+ lines remain non-negative. An explicit exclusive line `amount` must agree with
111
+ `quantity × unitPrice` after discount, either exactly or as its decimal
112
+ **half-up** result at the explicitly supplied currency's ISO 4217 minor
113
+ precision. For example, CAD quantity `1.0005` × unit price `10` accepts the
114
+ retained line amount `10.01`. The adapter preserves approved snapshots and never
115
+ changes subtotal/tax reconciliation.
116
+
117
+ A frozen monetary snapshot means a `TaxInclusive` document, or a line carrying
118
+ `taxAmount`, `taxComponents`, `taxInclusiveAmount`, or a nonzero `discount`.
119
+ Those snapshots (document subtotal/tax/total and relevant line net/gross,
120
+ component tax, and discount) must fit the explicitly supplied ISO 4217 grid.
121
+ The source `unitPrice` may remain fractional when its retained line amount is
122
+ on that grid. Legacy exclusive exact-product inputs that carry only an `amount`
123
+ or QuickBooks mapping retain the base validation and do not require a known
124
+ currency. Unknown or missing currency never authorizes frozen snapshot rounding.
94
125
 
95
126
  Precision comes from [ISO 4217 List One published 2026-09-17](https://www.six-group.com/dam/download/financial-information/data-center/iso-currrency/lists/list-one.xml),
96
127
  not runtime display formatting (which differs for IQD, MGA and RSD). Zero-, two-,
@@ -179,6 +210,44 @@ environment, request ID, timestamps, and returned IDs without credentials.
179
210
  No replay retention period or exactly-once guarantee is asserted here; reconcile
180
211
  old or uncertain requests against QuickBooks before any new creation identity.
181
212
 
213
+ ## QuickBooks credit memos
214
+
215
+ `creditMemos` is an optional provider capability. QuickBooks supports positive
216
+ frozen customer credit notes through `CreditMemo`; providers that omit the
217
+ capability must be refused by the caller. A `CreditMemoInput` uses the same
218
+ net/gross/tax snapshot rules as an invoice, has a
219
+ `creditedInvoiceExternalId` for durable reconciliation, and is created with a
220
+ separate descriptor:
221
+
222
+ ```ts
223
+ import {
224
+ prepareQuickBooksCreditMemoRequest,
225
+ requireCreditMemoOperations,
226
+ } from '@happyvertical/accounting';
227
+
228
+ const request = prepareQuickBooksCreditMemoRequest(creditMemo, {
229
+ requestId: outbox.requestId,
230
+ realmId: outbox.realmId,
231
+ environment: 'sandbox',
232
+ });
233
+ await requireCreditMemoOperations(qbo).push({
234
+ ...creditMemo,
235
+ quickbooksCreditMemoRequest: request,
236
+ });
237
+ ```
238
+
239
+ The descriptor carries its realm, environment, and caller-owned request ID, and
240
+ hashes the mapped frozen credit payload together with
241
+ `creditedInvoiceExternalId`. It does not include mutable payment, allocation,
242
+ or status fields, so a retry of the same frozen revision keeps the same request
243
+ identity. Creating a QBO
244
+ `CreditMemo` does not itself apply that credit to the named invoice through this
245
+ API contract; an application must record any later QBO payment/application step
246
+ explicitly. This package does not assume an account's automatic-credit setting.
247
+ It has the same retry and unknown-outcome rules as a keyed invoice create. No
248
+ live Intuit sandbox assertion is made by this package's contract tests; they
249
+ verify serialized payloads, request IDs, retries, and refusal paths.
250
+
182
251
  ## Stripe invoices
183
252
 
184
253
  Accounting amounts use currency major units: `12.34` means USD 12.34 and
@@ -0,0 +1,404 @@
1
+ import { createHash } from "node:crypto";
2
+ //#region src/providers/quickbooks/line-amount.ts
3
+ var currencyPrecision = new Map([
4
+ [0, "BIF CLP DJF GNF ISK JPY KMF KRW PYG RWF UGX UYI VND VUV XAF XOF XPF"],
5
+ [2, "AED AFN ALL AMD AOA ARS AUD AWG AZN BAM BBD BDT BMD BND BOB BOV BRL BSD BTN BWP BYN BZD CAD CDF CHE CHF CHW CNY COP COU CRC CUP CVE CZK DKK DOP DZD EGP ERN ETB EUR FJD FKP GBP GEL GHS GIP GMD GTQ GYD HKD HNL HTG HUF IDR ILS INR IRR JMD KES KGS KHR KPW KYD KZT LAK LBP LKR LRD LSL MAD MDL MGA MKD MMK MNT MOP MRU MUR MVR MWK MXN MXV MYR MZN NAD NGN NIO NOK NPR NZD PAB PEN PGK PHP PKR PLN QAR RON RSD RUB SAR SBD SCR SDG SEK SGD SHP SLE SOS SRD SSP STN SVC SYP SZL THB TJS TMT TOP TRY TTD TWD TZS UAH USD USN UYU UZS VED VES WST XAD XCD XCG YER ZAR ZMW ZWG"],
6
+ [3, "BHD IQD JOD KWD LYD OMR TND"],
7
+ [4, "CLF UYW"]
8
+ ].flatMap(([precision, codes]) => codes.split(" ").map((code) => [code, precision])));
9
+ /** ISO minor-unit factor, or undefined when the code is unknown or omitted. */
10
+ function currencyMinorUnitFactor(currency) {
11
+ if (typeof currency !== "string") return void 0;
12
+ const precision = currencyPrecision.get(currency.toUpperCase());
13
+ return precision === void 0 ? void 0 : 10 ** precision;
14
+ }
15
+ /** True only for a finite amount exactly representable on the ISO minor-unit grid. */
16
+ function isCurrencyMinorUnit(amount, currency) {
17
+ if (!Number.isFinite(amount) || typeof currency !== "string") return false;
18
+ const precision = currencyPrecision.get(currency.toUpperCase());
19
+ if (precision === void 0) return false;
20
+ const value = decimalRatio(Math.abs(amount));
21
+ return value.numerator * 10n ** BigInt(precision) % value.denominator === 0n;
22
+ }
23
+ /** Round a finite major-unit amount half-up to a known ISO currency grid. */
24
+ function roundCurrencyAmount(amount, currency) {
25
+ if (!Number.isFinite(amount) || typeof currency !== "string") return void 0;
26
+ const precision = currencyPrecision.get(currency.toUpperCase());
27
+ if (precision === void 0) return void 0;
28
+ const sign = amount < 0 ? -1n : 1n;
29
+ const value = decimalRatio(Math.abs(amount));
30
+ const scale = 10n ** BigInt(precision);
31
+ const numerator = value.numerator * scale;
32
+ const rounded = numerator / value.denominator + (numerator % value.denominator * 2n >= value.denominator ? 1n : 0n);
33
+ return Number(sign * rounded) / Number(scale);
34
+ }
35
+ /** Exact decimal interpretation of the finite nonnegative public number value. */
36
+ function decimalRatio(value) {
37
+ const [significand, exponent = "0"] = value.toString().split("e");
38
+ const [whole, fraction = ""] = significand.split(".");
39
+ const coefficient = BigInt(whole + fraction);
40
+ const scale = fraction.length - Number(exponent);
41
+ return scale >= 0 ? {
42
+ numerator: coefficient,
43
+ denominator: 10n ** BigInt(scale)
44
+ } : {
45
+ numerator: coefficient * 10n ** BigInt(-scale),
46
+ denominator: 1n
47
+ };
48
+ }
49
+ /** Admit only the exact half-up minor-unit result; never rewrite caller money. */
50
+ function matchesCurrencyRoundedLineAmount(quantity, unitPrice, amount, currency) {
51
+ if (typeof currency !== "string") return false;
52
+ const precision = currencyPrecision.get(currency.toUpperCase());
53
+ if (precision === void 0) return false;
54
+ const minorScale = 10n ** BigInt(precision);
55
+ const retained = decimalRatio(amount);
56
+ const retainedMinor = retained.numerator * minorScale;
57
+ if (retainedMinor % retained.denominator !== 0n) return false;
58
+ const qty = decimalRatio(quantity);
59
+ const price = decimalRatio(unitPrice);
60
+ const numerator = qty.numerator * price.numerator * minorScale;
61
+ const denominator = qty.denominator * price.denominator;
62
+ const rounded = numerator / denominator + (numerator % denominator * 2n >= denominator ? 1n : 0n);
63
+ return retainedMinor / retained.denominator === rounded;
64
+ }
65
+ //#endregion
66
+ //#region src/providers/quickbooks/invoice-request.ts
67
+ /** Prepare once, persist before sending, and reuse unchanged. Performs no I/O. */
68
+ function prepareQuickBooksInvoiceRequest(invoice, identity) {
69
+ if (invoice.externalId) throw new Error("QuickBooks request identity is only for invoice creation");
70
+ return prepareRequest(mapInvoiceToQBO(invoice), identity, "invoice");
71
+ }
72
+ /** Prepare once, persist before sending, and reuse unchanged. Performs no I/O. */
73
+ function prepareQuickBooksCreditMemoRequest(creditMemo, identity) {
74
+ if (creditMemo.externalId) throw new Error("QuickBooks request identity is only for credit memo creation");
75
+ return prepareRequest(mapCreditMemoToQBO(creditMemo), {
76
+ ...identity,
77
+ creditedInvoiceExternalId: creditMemo.creditedInvoiceExternalId
78
+ }, "credit memo");
79
+ }
80
+ function prepareRequest(mapped, identity, kind) {
81
+ const payload = JSON.stringify(mapped);
82
+ const request = {
83
+ ...identity,
84
+ payloadHash: hashDescriptorPayload(payload, identity, kind)
85
+ };
86
+ validateRequest(request, identity.realmId, identity.environment, payload, kind);
87
+ return Object.freeze(request);
88
+ }
89
+ function hash(payload) {
90
+ return createHash("sha256").update(payload).digest("hex");
91
+ }
92
+ /** Bind a credit memo's immutable source invoice without sending it to QBO. */
93
+ function hashDescriptorPayload(payload, request, kind) {
94
+ if (kind === "invoice") return hash(payload);
95
+ return hash(JSON.stringify({
96
+ payload,
97
+ linkage: { creditedInvoiceExternalId: request.creditedInvoiceExternalId }
98
+ }));
99
+ }
100
+ function validateQuickBooksInvoiceRequest(request, realmId, environment, payload) {
101
+ validateRequest(request, realmId, environment, payload, "invoice");
102
+ }
103
+ function validateQuickBooksCreditMemoRequest(request, realmId, environment, payload) {
104
+ validateRequest(request, realmId, environment, payload, "credit memo");
105
+ }
106
+ function validateRequest(request, realmId, environment, payload, kind) {
107
+ if (!request || typeof request.requestId !== "string" || !/^[A-Za-z0-9._-]{1,50}$/.test(request.requestId)) throw new Error("QuickBooks requestId must contain 1–50 URL-safe letters, digits, dots, underscores or hyphens");
108
+ if (!request.realmId || request.realmId !== realmId || !["sandbox", "production"].includes(request.environment) || request.environment !== environment) throw new Error(`QuickBooks ${kind} request realm/environment mismatch`);
109
+ if (!payload || request.payloadHash !== hashDescriptorPayload(payload, request, kind)) throw new Error(`QuickBooks ${kind} request payload mismatch; reconcile the original request`);
110
+ if (kind === "credit memo" && (!("creditedInvoiceExternalId" in request) || !request.creditedInvoiceExternalId || request.creditedInvoiceExternalId.trim() !== request.creditedInvoiceExternalId)) throw new Error("QuickBooks credit memo request requires a credited invoice external ID");
111
+ const parsed = JSON.parse(payload);
112
+ if (parsed.Id !== void 0 || parsed.SyncToken !== void 0) throw new Error(`QuickBooks request identity is only for ${kind} creation`);
113
+ }
114
+ function formatLocalDate(date) {
115
+ return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, "0")}-${String(date.getDate()).padStart(2, "0")}`;
116
+ }
117
+ function close(a, b) {
118
+ return Number.isFinite(a) && Number.isFinite(b) && Math.abs(a - b) <= Number.EPSILON * Math.max(1, Math.abs(a), Math.abs(b)) * 8;
119
+ }
120
+ function finite(value, label, allowNegative = false) {
121
+ if (!Number.isFinite(value) || !allowNegative && value < 0) throw new Error(`QuickBooks ${label} must be a finite ${allowNegative ? "" : "non-negative "}amount`);
122
+ return value;
123
+ }
124
+ function reference(value, label) {
125
+ if (!value || value.trim() !== value || value.length > 255) throw new Error(`QuickBooks ${label} must be a non-empty realm reference`);
126
+ return { value };
127
+ }
128
+ function canonicalRateNumber(value) {
129
+ for (let places = 0; places <= 12; places++) {
130
+ const candidate = Number(value.toFixed(places));
131
+ if (close(value, candidate)) return candidate.toString();
132
+ }
133
+ return value.toString();
134
+ }
135
+ function decimalRate(value, label) {
136
+ const text = typeof value === "string" && /^\d+(?:\.\d+)?$/.test(value) ? value : typeof value === "number" && Number.isFinite(value) && value >= 0 ? canonicalRateNumber(value) : void 0;
137
+ if (!text || !/^\d+(?:\.\d+)?$/.test(text)) throw new Error(`QuickBooks ${label} needs a frozen component rate from 0 through 1`);
138
+ const [whole, fraction = ""] = text.split(".");
139
+ const denominator = 10n ** BigInt(fraction.length);
140
+ const numerator = BigInt(`${whole}${fraction}`);
141
+ if (numerator > denominator) throw new Error(`QuickBooks ${label} needs a frozen component rate from 0 through 1`);
142
+ return {
143
+ numerator,
144
+ denominator,
145
+ value: Number(text)
146
+ };
147
+ }
148
+ function ratesEqual(left, right) {
149
+ return left.numerator * right.denominator === right.numerator * left.denominator;
150
+ }
151
+ function addRates(left, right) {
152
+ const numerator = left.numerator * right.denominator + right.numerator * left.denominator;
153
+ const denominator = left.denominator * right.denominator;
154
+ return {
155
+ numerator,
156
+ denominator,
157
+ value: Number(numerator) / Number(denominator)
158
+ };
159
+ }
160
+ function taxPercent(rate) {
161
+ return Number(rate.numerator * 100n) / Number(rate.denominator);
162
+ }
163
+ function currencyMinor(amount, currency, label) {
164
+ const factor = currencyMinorUnitFactor(currency);
165
+ const rounded = roundCurrencyAmount(amount, currency);
166
+ if (factor === void 0 || rounded === void 0 || !isCurrencyMinorUnit(amount, currency) && !close(amount, rounded)) throw new Error(`QuickBooks ${label} must fit the explicitly supplied currency's ISO 4217 minor-unit grid`);
167
+ return BigInt(Math.round((isCurrencyMinorUnit(amount, currency) ? amount : rounded) * factor));
168
+ }
169
+ function currencyMajor(minor, currency) {
170
+ const factor = currencyMinorUnitFactor(currency);
171
+ if (factor === void 0) throw new Error("QuickBooks needs a known currency");
172
+ return Number(minor) / factor;
173
+ }
174
+ function taxMinorMatchesAllocation(taxMinor, numerator, denominator) {
175
+ const floor = numerator / denominator;
176
+ const remainder = numerator % denominator;
177
+ return taxMinor === floor || remainder !== 0n && taxMinor === floor + 1n;
178
+ }
179
+ function validateFrozenComponentRates(components, taxableAmount, currency, inclusive, label) {
180
+ const taxableMinor = currencyMinor(taxableAmount, currency, `${label} taxable amount`);
181
+ const totalRate = components.reduce((sum, component) => addRates(sum, component.rate), {
182
+ numerator: 0n,
183
+ denominator: 1n,
184
+ value: 0
185
+ });
186
+ for (const component of components) {
187
+ const numerator = inclusive ? taxableMinor * component.rate.numerator * totalRate.denominator : taxableMinor * component.rate.numerator;
188
+ const denominator = inclusive ? component.rate.denominator * (totalRate.denominator + totalRate.numerator) : component.rate.denominator;
189
+ if (!taxMinorMatchesAllocation(component.taxMinor, numerator, denominator)) throw new Error(`QuickBooks ${label} tax component rate is inconsistent with its frozen tax amount`);
190
+ }
191
+ }
192
+ function frozenCurrencyAmount(value, label, currency, allowNegative = false) {
193
+ finite(value, label, allowNegative);
194
+ const rounded = roundCurrencyAmount(value, currency);
195
+ if (!isCurrencyMinorUnit(value, currency) && (rounded === void 0 || !close(value, rounded))) throw new Error(`QuickBooks ${label} must fit the explicitly supplied currency's ISO 4217 minor-unit grid`);
196
+ return value;
197
+ }
198
+ function hasFrozenSnapshots(input) {
199
+ return input.quickbooksMapping?.globalTaxCalculation === "TaxInclusive" || input.lineItems.some((item) => item.taxAmount !== void 0 || item.taxComponents !== void 0 || item.taxInclusiveAmount !== void 0 || item.discount !== void 0 && item.discount !== 0);
200
+ }
201
+ /** Limit accumulated binary error when a valid document has many decimal lines. */
202
+ function compensatedSum(values) {
203
+ let sum = 0;
204
+ let compensation = 0;
205
+ for (const value of values) {
206
+ const adjusted = value - compensation;
207
+ const next = sum + adjusted;
208
+ compensation = next - sum - adjusted;
209
+ sum = next;
210
+ }
211
+ return sum;
212
+ }
213
+ function mapDocumentLines(input) {
214
+ const mode = input.quickbooksMapping?.globalTaxCalculation;
215
+ if (mode && ![
216
+ "TaxExcluded",
217
+ "TaxInclusive",
218
+ "NotApplicable"
219
+ ].includes(mode)) throw new Error("QuickBooks globalTaxCalculation must be TaxExcluded, TaxInclusive or NotApplicable");
220
+ const frozen = hasFrozenSnapshots(input);
221
+ const validateAmount = (value, label) => frozen ? frozenCurrencyAmount(value, label, input.currency) : finite(value, label);
222
+ validateAmount(input.subtotal, "subtotal");
223
+ validateAmount(input.taxAmount, "tax amount");
224
+ validateAmount(input.totalAmount, "total amount");
225
+ if (input.lineItems.length === 0) throw new Error("QuickBooks invoices require at least one sales line");
226
+ const hasLineMapping = input.lineItems.some((item) => item.quickbooksMapping);
227
+ if ((input.taxAmount > 0 || hasLineMapping) && !input.quickbooksMapping) throw new Error("QuickBooks mapped or nonzero-tax invoices require explicit globalTaxCalculation");
228
+ if (input.quickbooksMapping && input.lineItems.some((item) => !item.quickbooksMapping)) throw new Error("QuickBooks mapped invoices require itemRef and taxCodeRef on every line");
229
+ if (input.taxAmount > 0 && mode === "NotApplicable") throw new Error("QuickBooks nonzero tax cannot use NotApplicable global tax calculation");
230
+ const mapped = [];
231
+ const taxLines = /* @__PURE__ */ new Map();
232
+ const snapshotLineTaxes = [];
233
+ let hasAnyLineTax = false;
234
+ let hasCompleteLineTax = true;
235
+ const lineTaxSnapshots = [];
236
+ for (const [index, item] of input.lineItems.entries()) {
237
+ const line = mapLine(item, index, input.currency, mode, frozen);
238
+ mapped.push(...line.lines);
239
+ lineTaxSnapshots.push({
240
+ taxAmount: line.taxAmount,
241
+ taxComponents: line.taxComponents
242
+ });
243
+ if (line.taxAmount === void 0) hasCompleteLineTax = false;
244
+ else {
245
+ hasAnyLineTax = true;
246
+ snapshotLineTaxes.push(line.taxAmount);
247
+ }
248
+ for (const component of line.taxComponents) {
249
+ const existing = taxLines.get(component.taxRateRef);
250
+ if (existing) {
251
+ if (!ratesEqual(existing.rate, component.rate)) throw new Error(`QuickBooks tax rate ${component.taxRateRef} has inconsistent frozen rates`);
252
+ existing.amountMinor += component.taxMinor;
253
+ } else taxLines.set(component.taxRateRef, {
254
+ rate: component.rate,
255
+ amountMinor: component.taxMinor
256
+ });
257
+ }
258
+ }
259
+ if (!close(compensatedSum(mapped.map((line) => line.Amount)), input.subtotal)) throw new Error("QuickBooks line amounts must equal the invoice subtotal");
260
+ if (!close(input.subtotal + input.taxAmount, input.totalAmount)) throw new Error("QuickBooks subtotal plus tax must equal the invoice total");
261
+ if (taxLines.size > 0) {
262
+ if (lineTaxSnapshots.some((line) => line.taxAmount === void 0 || line.taxAmount > 0 && line.taxComponents.length === 0)) throw new Error("QuickBooks component tax detail requires components on every taxed line");
263
+ const totalTaxMinor = currencyMinor(input.taxAmount, input.currency, "tax amount");
264
+ if ([...taxLines.values()].reduce((sum, line) => sum + line.amountMinor, 0n) !== totalTaxMinor) throw new Error("QuickBooks component tax detail must equal the invoice tax amount");
265
+ }
266
+ if (hasAnyLineTax && !hasCompleteLineTax) throw new Error("QuickBooks stored line tax must be supplied on every line when any line supplies it");
267
+ if (hasAnyLineTax && !close(compensatedSum(snapshotLineTaxes), input.taxAmount)) throw new Error("QuickBooks stored line tax must equal the invoice tax amount");
268
+ return {
269
+ lines: mapped,
270
+ taxLines: [...taxLines.entries()].map(([taxRateRef, detail]) => ({
271
+ Amount: currencyMajor(detail.amountMinor, input.currency),
272
+ DetailType: "TaxLineDetail",
273
+ TaxLineDetail: {
274
+ TaxRateRef: { value: taxRateRef },
275
+ TaxPercent: taxPercent(detail.rate),
276
+ PercentBased: true
277
+ }
278
+ }))
279
+ };
280
+ }
281
+ function mapLine(item, index, currency, mode, enforceCurrencyGrid) {
282
+ const label = `line ${index + 1}`;
283
+ if (item.taxRate !== void 0 && item.taxRate !== 0) throw new Error("QuickBooks numeric taxRate is not supported; use a realm taxCodeRef");
284
+ if (item.taxCode !== void 0) throw new Error("QuickBooks generic taxCode is not a realm tax reference; use quickbooksMapping.taxCodeRef");
285
+ finite(item.quantity, `${label} quantity`);
286
+ if (item.quantity <= 0) throw new Error(`QuickBooks ${label} quantity must be greater than zero`);
287
+ finite(item.unitPrice, `${label} unit price`);
288
+ const baseDocumentAmount = item.quantity * item.unitPrice;
289
+ finite(baseDocumentAmount, `${label} pre-discount amount`);
290
+ const validateAmount = (value, amountLabel, allowNegative = false) => enforceCurrencyGrid ? frozenCurrencyAmount(value, amountLabel, currency, allowNegative) : finite(value, amountLabel, allowNegative);
291
+ const discount = validateAmount(item.discount ?? 0, `${label} discount`);
292
+ if (discount > baseDocumentAmount && !close(discount, baseDocumentAmount)) throw new Error(`QuickBooks ${label} discount cannot exceed quantity times unit price`);
293
+ const netAmount = validateAmount(item.amount ?? baseDocumentAmount - discount, `${label} amount`);
294
+ const taxAmount = item.taxAmount === void 0 ? void 0 : validateAmount(item.taxAmount, `${label} tax amount`);
295
+ const taxComponents = [];
296
+ if (item.taxComponents !== void 0) {
297
+ if (taxAmount === void 0) throw new Error(`QuickBooks ${label} tax components require a line tax amount`);
298
+ if (!close(item.taxComponents.reduce((sum, component, componentIndex) => {
299
+ if (!component.taxCode || component.taxCode.trim() !== component.taxCode) throw new Error(`QuickBooks ${label} tax component ${componentIndex + 1} needs a tax code`);
300
+ const rate = decimalRate(component.rate, `${label} tax component ${componentIndex + 1}`);
301
+ const taxRateRef = reference(component.quickbooksTaxRateRef, `${label} tax component ${componentIndex + 1} quickbooksTaxRateRef`).value;
302
+ const componentAmount = validateAmount(component.taxAmount, `${label} tax component ${componentIndex + 1}`);
303
+ taxComponents.push({
304
+ taxRateRef,
305
+ rate,
306
+ taxMinor: currencyMinor(componentAmount, currency, `${label} tax component ${componentIndex + 1}`)
307
+ });
308
+ return sum + componentAmount;
309
+ }, 0), taxAmount)) throw new Error(`QuickBooks ${label} tax components must equal the line tax amount`);
310
+ }
311
+ const inclusive = mode === "TaxInclusive";
312
+ const grossAmount = item.taxInclusiveAmount;
313
+ if (!inclusive && grossAmount !== void 0) throw new Error(`QuickBooks ${label} tax-inclusive amount requires TaxInclusive global tax calculation`);
314
+ if (inclusive) {
315
+ if (grossAmount === void 0 || taxAmount === void 0 || taxAmount > 0 && !item.taxComponents?.length) throw new Error(`QuickBooks ${label} TaxInclusive snapshots require gross amount, line tax and tax components for nonzero tax`);
316
+ validateAmount(grossAmount, `${label} tax-inclusive amount`);
317
+ if (!close(netAmount + taxAmount, grossAmount)) throw new Error(`QuickBooks ${label} net amount plus tax must equal gross amount`);
318
+ if (!close(baseDocumentAmount - discount, grossAmount)) throw new Error(`QuickBooks ${label} gross amount must equal quantity times gross unit price less gross discount`);
319
+ } else if (!close(netAmount, baseDocumentAmount - discount) && !matchesCurrencyRoundedLineAmount(item.quantity, item.unitPrice, netAmount + discount, currency)) throw new Error(`QuickBooks ${label} amount must equal quantity times unit price less discount`);
320
+ if (taxComponents.length > 0) validateFrozenComponentRates(taxComponents, inclusive ? grossAmount : netAmount, currency, inclusive, label);
321
+ const itemRef = item.quickbooksMapping ? reference(item.quickbooksMapping.itemRef, `${label} itemRef`) : void 0;
322
+ const taxCodeRef = item.quickbooksMapping ? reference(item.quickbooksMapping.taxCodeRef, `${label} taxCodeRef`) : void 0;
323
+ const line = (amount, quantity, unitPrice, description, taxInclusiveAmount) => ({
324
+ LineNum: index + 1,
325
+ Description: description,
326
+ Amount: amount,
327
+ DetailType: "SalesItemLineDetail",
328
+ SalesItemLineDetail: {
329
+ Qty: quantity,
330
+ UnitPrice: unitPrice,
331
+ ItemRef: itemRef,
332
+ TaxCodeRef: taxCodeRef,
333
+ ...taxInclusiveAmount === void 0 ? {} : { TaxInclusiveAmt: taxInclusiveAmount }
334
+ }
335
+ });
336
+ if (discount === 0 && !inclusive) return {
337
+ lines: [line(netAmount, item.quantity, item.unitPrice, item.description)],
338
+ taxAmount,
339
+ taxComponents
340
+ };
341
+ if (discount === 0 || isCurrencyMinorUnit(netAmount / item.quantity, currency)) return {
342
+ lines: [line(netAmount, item.quantity, netAmount / item.quantity, item.description, inclusive ? grossAmount : void 0)],
343
+ taxAmount,
344
+ taxComponents
345
+ };
346
+ let preDiscountAmount = netAmount + discount;
347
+ let negativeAmount = -discount;
348
+ const preDiscountGross = inclusive ? baseDocumentAmount : void 0;
349
+ if (inclusive) {
350
+ const components = item.taxComponents;
351
+ const exemptSnapshot = taxAmount === 0 && !components?.length;
352
+ if (!components?.length && !exemptSnapshot) throw new Error(`QuickBooks ${label} needs frozen component rates for an inclusive negative discount line`);
353
+ const discountNet = roundCurrencyAmount(discount / (1 + (exemptSnapshot ? 0 : (components ?? []).reduce((sum, component, componentIndex) => sum + decimalRate(component.rate, `${label} tax component ${componentIndex + 1}`).value, 0))), currency);
354
+ if (discountNet === void 0) throw new Error(`QuickBooks ${label} needs a recognized currency to map an inclusive fractional-unit discount`);
355
+ preDiscountAmount = netAmount + discountNet;
356
+ negativeAmount = -discountNet;
357
+ }
358
+ if (enforceCurrencyGrid) {
359
+ frozenCurrencyAmount(preDiscountAmount, `${label} generated pre-discount amount`, currency);
360
+ frozenCurrencyAmount(negativeAmount, `${label} generated discount amount`, currency, true);
361
+ }
362
+ let negativeGross;
363
+ if (inclusive) {
364
+ if (grossAmount === void 0 || preDiscountGross === void 0) throw new Error(`QuickBooks ${label} needs frozen gross amounts for an inclusive negative discount line`);
365
+ const unroundedNegativeGross = grossAmount - preDiscountGross;
366
+ negativeGross = roundCurrencyAmount(unroundedNegativeGross, currency) ?? unroundedNegativeGross;
367
+ }
368
+ if (enforceCurrencyGrid && negativeGross !== void 0) frozenCurrencyAmount(negativeGross, `${label} generated gross discount amount`, currency, true);
369
+ return {
370
+ lines: [line(preDiscountAmount, item.quantity, preDiscountAmount / item.quantity, item.description, preDiscountGross), line(negativeAmount, 1, negativeAmount, `Discount on line ${index + 1}`, negativeGross)],
371
+ taxAmount,
372
+ taxComponents
373
+ };
374
+ }
375
+ function basePayload(input, number, dueDate) {
376
+ const { lines, taxLines } = mapDocumentLines(input);
377
+ return {
378
+ CustomerRef: { value: input.customerExternalId || input.customerId },
379
+ DocNumber: number,
380
+ TxnDate: formatLocalDate(input.issueDate),
381
+ ...dueDate ? { DueDate: formatLocalDate(dueDate) } : {},
382
+ Line: lines,
383
+ GlobalTaxCalculation: input.quickbooksMapping?.globalTaxCalculation,
384
+ TxnTaxDetail: input.quickbooksMapping ? {
385
+ TotalTax: input.taxAmount,
386
+ ...taxLines.length > 0 ? { TaxLine: taxLines } : {}
387
+ } : void 0,
388
+ CurrencyRef: input.currency ? { value: input.currency } : void 0,
389
+ CustomerMemo: input.memo ? { value: input.memo } : void 0
390
+ };
391
+ }
392
+ function mapInvoiceToQBO(invoice) {
393
+ if (invoice.collectionMethod === "charge_automatically") throw new Error("QuickBooks does not support charge_automatically invoice collection");
394
+ if (invoice.automaticTax) throw new Error("QuickBooks does not support automaticTax invoice calculation");
395
+ return basePayload(invoice, invoice.invoiceNumber, invoice.dueDate);
396
+ }
397
+ function mapCreditMemoToQBO(creditMemo) {
398
+ if (!creditMemo.creditedInvoiceExternalId || creditMemo.creditedInvoiceExternalId.trim() !== creditMemo.creditedInvoiceExternalId) throw new Error("QuickBooks credit memos require a credited invoice external ID");
399
+ return basePayload(creditMemo, creditMemo.creditMemoNumber);
400
+ }
401
+ //#endregion
402
+ export { validateQuickBooksCreditMemoRequest as a, prepareQuickBooksInvoiceRequest as i, mapInvoiceToQBO as n, validateQuickBooksInvoiceRequest as o, prepareQuickBooksCreditMemoRequest as r, mapCreditMemoToQBO as t };
403
+
404
+ //# sourceMappingURL=invoice-request-s7E51rMr.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"invoice-request-s7E51rMr.js","names":[],"sources":["../../src/providers/quickbooks/line-amount.ts","../../src/providers/quickbooks/invoice-request.ts"],"sourcesContent":["/** ISO 4217 List One, published 2026-09-17. Do not use Intl display digits:\n * ICU differs for e.g. IQD, MGA and RSD, and omits fund codes CLF/UYW.\n * https://www.six-group.com/dam/download/financial-information/data-center/iso-currrency/lists/list-one.xml\n * N.A., unknown and missing codes cannot authorize currency rounding.\n */\nconst precisionGroups: readonly (readonly [number, string])[] = [\n [0, 'BIF CLP DJF GNF ISK JPY KMF KRW PYG RWF UGX UYI VND VUV XAF XOF XPF'],\n [\n 2,\n 'AED AFN ALL AMD AOA ARS AUD AWG AZN BAM BBD BDT BMD BND BOB BOV BRL BSD BTN BWP BYN BZD CAD CDF CHE CHF CHW CNY COP COU CRC CUP CVE CZK DKK DOP DZD EGP ERN ETB EUR FJD FKP GBP GEL GHS GIP GMD GTQ GYD HKD HNL HTG HUF IDR ILS INR IRR JMD KES KGS KHR KPW KYD KZT LAK LBP LKR LRD LSL MAD MDL MGA MKD MMK MNT MOP MRU MUR MVR MWK MXN MXV MYR MZN NAD NGN NIO NOK NPR NZD PAB PEN PGK PHP PKR PLN QAR RON RSD RUB SAR SBD SCR SDG SEK SGD SHP SLE SOS SRD SSP STN SVC SYP SZL THB TJS TMT TOP TRY TTD TWD TZS UAH USD USN UYU UZS VED VES WST XAD XCD XCG YER ZAR ZMW ZWG',\n ],\n [3, 'BHD IQD JOD KWD LYD OMR TND'],\n [4, 'CLF UYW'],\n];\nconst currencyPrecision = new Map(\n precisionGroups.flatMap(([precision, codes]) =>\n codes.split(' ').map((code) => [code, precision] as const),\n ),\n);\n\n/** ISO minor-unit factor, or undefined when the code is unknown or omitted. */\nexport function currencyMinorUnitFactor(\n currency: string | undefined,\n): number | undefined {\n if (typeof currency !== 'string') return undefined;\n const precision = currencyPrecision.get(currency.toUpperCase());\n return precision === undefined ? undefined : 10 ** precision;\n}\n\n/** True only for a finite amount exactly representable on the ISO minor-unit grid. */\nexport function isCurrencyMinorUnit(\n amount: number,\n currency: string | undefined,\n): boolean {\n if (!Number.isFinite(amount) || typeof currency !== 'string') return false;\n const precision = currencyPrecision.get(currency.toUpperCase());\n if (precision === undefined) return false;\n const value = decimalRatio(Math.abs(amount));\n return (\n (value.numerator * 10n ** BigInt(precision)) % value.denominator === 0n\n );\n}\n\n/** Round a finite major-unit amount half-up to a known ISO currency grid. */\nexport function roundCurrencyAmount(\n amount: number,\n currency: string | undefined,\n): number | undefined {\n if (!Number.isFinite(amount) || typeof currency !== 'string')\n return undefined;\n const precision = currencyPrecision.get(currency.toUpperCase());\n if (precision === undefined) return undefined;\n const sign = amount < 0 ? -1n : 1n;\n const value = decimalRatio(Math.abs(amount));\n const scale = 10n ** BigInt(precision);\n const numerator = value.numerator * scale;\n const rounded =\n numerator / value.denominator +\n ((numerator % value.denominator) * 2n >= value.denominator ? 1n : 0n);\n return Number(sign * rounded) / Number(scale);\n}\n\n/** Exact decimal interpretation of the finite nonnegative public number value. */\nfunction decimalRatio(value: number): {\n numerator: bigint;\n denominator: bigint;\n} {\n const [significand, exponent = '0'] = value.toString().split('e');\n const [whole, fraction = ''] = significand.split('.');\n const coefficient = BigInt(whole + fraction);\n const scale = fraction.length - Number(exponent);\n return scale >= 0\n ? { numerator: coefficient, denominator: 10n ** BigInt(scale) }\n : { numerator: coefficient * 10n ** BigInt(-scale), denominator: 1n };\n}\n\n/** Admit only the exact half-up minor-unit result; never rewrite caller money. */\nexport function matchesCurrencyRoundedLineAmount(\n quantity: number,\n unitPrice: number,\n amount: number,\n currency: string | undefined,\n): boolean {\n if (typeof currency !== 'string') return false;\n const precision = currencyPrecision.get(currency.toUpperCase());\n if (precision === undefined) return false;\n const minorScale = 10n ** BigInt(precision);\n const retained = decimalRatio(amount);\n const retainedMinor = retained.numerator * minorScale;\n // An off-grid caller amount is not permission to round their supplied money.\n if (retainedMinor % retained.denominator !== 0n) return false;\n const qty = decimalRatio(quantity);\n const price = decimalRatio(unitPrice);\n const numerator = qty.numerator * price.numerator * minorScale;\n const denominator = qty.denominator * price.denominator;\n const quotient = numerator / denominator;\n const rounded =\n quotient + ((numerator % denominator) * 2n >= denominator ? 1n : 0n);\n return retainedMinor / retained.denominator === rounded;\n}\n","import { createHash } from 'node:crypto';\nimport type {\n CreditMemoInput,\n InvoiceInput,\n InvoiceLineItemInput,\n QuickBooksCreditMemoRequest,\n QuickBooksInvoiceRequest,\n} from '../../types.js';\nimport {\n currencyMinorUnitFactor,\n isCurrencyMinorUnit,\n matchesCurrencyRoundedLineAmount,\n roundCurrencyAmount,\n} from './line-amount.js';\n\ntype QuickBooksCreateRequest =\n | QuickBooksInvoiceRequest\n | QuickBooksCreditMemoRequest;\n\ninterface QuickBooksSalesLine {\n LineNum: number;\n Description: string;\n Amount: number;\n DetailType: 'SalesItemLineDetail';\n SalesItemLineDetail: {\n Qty: number;\n UnitPrice: number;\n ItemRef?: { value: string };\n TaxCodeRef?: { value: string };\n TaxInclusiveAmt?: number;\n };\n}\n\ninterface QuickBooksTaxLine {\n Amount: number;\n DetailType: 'TaxLineDetail';\n TaxLineDetail: {\n TaxRateRef: { value: string };\n TaxPercent: number;\n PercentBased: true;\n };\n}\n\ninterface FrozenTaxComponent {\n taxRateRef: string;\n rate: DecimalRate;\n taxMinor: bigint;\n}\n\ninterface DecimalRate {\n numerator: bigint;\n denominator: bigint;\n value: number;\n}\n\n/** Prepare once, persist before sending, and reuse unchanged. Performs no I/O. */\nexport function prepareQuickBooksInvoiceRequest(\n invoice: InvoiceInput,\n identity: Omit<QuickBooksInvoiceRequest, 'payloadHash'>,\n): QuickBooksInvoiceRequest {\n if (invoice.externalId)\n throw new Error('QuickBooks request identity is only for invoice creation');\n return prepareRequest(mapInvoiceToQBO(invoice), identity, 'invoice');\n}\n\n/** Prepare once, persist before sending, and reuse unchanged. Performs no I/O. */\nexport function prepareQuickBooksCreditMemoRequest(\n creditMemo: CreditMemoInput,\n identity: Omit<\n QuickBooksCreditMemoRequest,\n 'payloadHash' | 'creditedInvoiceExternalId'\n >,\n): QuickBooksCreditMemoRequest {\n if (creditMemo.externalId)\n throw new Error(\n 'QuickBooks request identity is only for credit memo creation',\n );\n return prepareRequest(\n mapCreditMemoToQBO(creditMemo),\n {\n ...identity,\n creditedInvoiceExternalId: creditMemo.creditedInvoiceExternalId,\n },\n 'credit memo',\n );\n}\n\nfunction prepareRequest<T extends QuickBooksCreateRequest>(\n mapped: unknown,\n identity: Omit<T, 'payloadHash'>,\n kind: 'invoice' | 'credit memo',\n): T {\n const payload = JSON.stringify(mapped);\n const request = {\n ...identity,\n payloadHash: hashDescriptorPayload(payload, identity, kind),\n } as T;\n validateRequest(\n request,\n identity.realmId,\n identity.environment,\n payload,\n kind,\n );\n return Object.freeze(request);\n}\n\nfunction hash(payload: string): string {\n return createHash('sha256').update(payload).digest('hex');\n}\n\n/** Bind a credit memo's immutable source invoice without sending it to QBO. */\nfunction hashDescriptorPayload(\n payload: string,\n request:\n | QuickBooksCreateRequest\n | Omit<QuickBooksCreateRequest, 'payloadHash'>,\n kind: 'invoice' | 'credit memo',\n): string {\n if (kind === 'invoice') return hash(payload);\n const creditMemoRequest = request as QuickBooksCreditMemoRequest;\n return hash(\n JSON.stringify({\n payload,\n linkage: {\n creditedInvoiceExternalId: creditMemoRequest.creditedInvoiceExternalId,\n },\n }),\n );\n}\n\nexport function validateQuickBooksInvoiceRequest(\n request: QuickBooksInvoiceRequest,\n realmId: string,\n environment: 'sandbox' | 'production',\n payload: string | undefined,\n): void {\n validateRequest(request, realmId, environment, payload, 'invoice');\n}\n\nexport function validateQuickBooksCreditMemoRequest(\n request: QuickBooksCreditMemoRequest,\n realmId: string,\n environment: 'sandbox' | 'production',\n payload: string | undefined,\n): void {\n validateRequest(request, realmId, environment, payload, 'credit memo');\n}\n\nfunction validateRequest(\n request: QuickBooksCreateRequest,\n realmId: string,\n environment: 'sandbox' | 'production',\n payload: string | undefined,\n kind: 'invoice' | 'credit memo',\n): void {\n if (\n !request ||\n typeof request.requestId !== 'string' ||\n !/^[A-Za-z0-9._-]{1,50}$/.test(request.requestId)\n ) {\n throw new Error(\n 'QuickBooks requestId must contain 1–50 URL-safe letters, digits, dots, underscores or hyphens',\n );\n }\n if (\n !request.realmId ||\n request.realmId !== realmId ||\n !['sandbox', 'production'].includes(request.environment) ||\n request.environment !== environment\n ) {\n throw new Error(`QuickBooks ${kind} request realm/environment mismatch`);\n }\n if (\n !payload ||\n request.payloadHash !== hashDescriptorPayload(payload, request, kind)\n ) {\n throw new Error(\n `QuickBooks ${kind} request payload mismatch; reconcile the original request`,\n );\n }\n if (\n kind === 'credit memo' &&\n (!('creditedInvoiceExternalId' in request) ||\n !request.creditedInvoiceExternalId ||\n request.creditedInvoiceExternalId.trim() !==\n request.creditedInvoiceExternalId)\n ) {\n throw new Error(\n 'QuickBooks credit memo request requires a credited invoice external ID',\n );\n }\n const parsed = JSON.parse(payload);\n if (parsed.Id !== undefined || parsed.SyncToken !== undefined) {\n throw new Error(`QuickBooks request identity is only for ${kind} creation`);\n }\n}\n\nfunction formatLocalDate(date: Date): string {\n const year = date.getFullYear();\n const month = String(date.getMonth() + 1).padStart(2, '0');\n const day = String(date.getDate()).padStart(2, '0');\n return `${year}-${month}-${day}`;\n}\n\nfunction close(a: number, b: number): boolean {\n return (\n Number.isFinite(a) &&\n Number.isFinite(b) &&\n Math.abs(a - b) <=\n Number.EPSILON * Math.max(1, Math.abs(a), Math.abs(b)) * 8\n );\n}\n\nfunction finite(value: number, label: string, allowNegative = false): number {\n if (!Number.isFinite(value) || (!allowNegative && value < 0))\n throw new Error(\n `QuickBooks ${label} must be a finite ${allowNegative ? '' : 'non-negative '}amount`,\n );\n return value;\n}\n\nfunction reference(value: string | undefined, label: string) {\n if (!value || value.trim() !== value || value.length > 255)\n throw new Error(`QuickBooks ${label} must be a non-empty realm reference`);\n return { value };\n}\n\nfunction canonicalRateNumber(value: number): string {\n // Number inputs can carry an adjacent binary representation of the exact\n // frozen decimal (for example 0.1 + 0.2). Prefer the shortest nearby decimal\n // through the calculator's supported precision, without changing a value\n // whose additional digits are materially distinct.\n for (let places = 0; places <= 12; places++) {\n const candidate = Number(value.toFixed(places));\n if (close(value, candidate)) return candidate.toString();\n }\n return value.toString();\n}\n\nfunction decimalRate(\n value: number | string | undefined,\n label: string,\n): DecimalRate {\n const text =\n typeof value === 'string' && /^\\d+(?:\\.\\d+)?$/.test(value)\n ? value\n : typeof value === 'number' && Number.isFinite(value) && value >= 0\n ? canonicalRateNumber(value)\n : undefined;\n if (!text || !/^\\d+(?:\\.\\d+)?$/.test(text))\n throw new Error(\n `QuickBooks ${label} needs a frozen component rate from 0 through 1`,\n );\n const [whole, fraction = ''] = text.split('.');\n const denominator = 10n ** BigInt(fraction.length);\n const numerator = BigInt(`${whole}${fraction}`);\n if (numerator > denominator)\n throw new Error(\n `QuickBooks ${label} needs a frozen component rate from 0 through 1`,\n );\n return { numerator, denominator, value: Number(text) };\n}\n\nfunction ratesEqual(left: DecimalRate, right: DecimalRate): boolean {\n return (\n left.numerator * right.denominator === right.numerator * left.denominator\n );\n}\n\nfunction addRates(left: DecimalRate, right: DecimalRate): DecimalRate {\n const numerator =\n left.numerator * right.denominator + right.numerator * left.denominator;\n const denominator = left.denominator * right.denominator;\n return {\n numerator,\n denominator,\n value: Number(numerator) / Number(denominator),\n };\n}\n\nfunction taxPercent(rate: DecimalRate): number {\n return Number(rate.numerator * 100n) / Number(rate.denominator);\n}\n\nfunction currencyMinor(\n amount: number,\n currency: string | undefined,\n label: string,\n): bigint {\n const factor = currencyMinorUnitFactor(currency);\n const rounded = roundCurrencyAmount(amount, currency);\n if (\n factor === undefined ||\n rounded === undefined ||\n (!isCurrencyMinorUnit(amount, currency) && !close(amount, rounded))\n )\n throw new Error(\n `QuickBooks ${label} must fit the explicitly supplied currency's ISO 4217 minor-unit grid`,\n );\n // Match frozenCurrencyAmount: only a value already admitted as ordinary\n // binary noise is normalized before exact minor-unit aggregation. A genuine\n // off-grid value still refuses before any request is mapped.\n return BigInt(\n Math.round(\n (isCurrencyMinorUnit(amount, currency) ? amount : rounded) * factor,\n ),\n );\n}\n\nfunction currencyMajor(minor: bigint, currency: string | undefined): number {\n const factor = currencyMinorUnitFactor(currency);\n if (factor === undefined)\n throw new Error('QuickBooks needs a known currency');\n return Number(minor) / factor;\n}\n\nfunction taxMinorMatchesAllocation(\n taxMinor: bigint,\n numerator: bigint,\n denominator: bigint,\n): boolean {\n const floor = numerator / denominator;\n const remainder = numerator % denominator;\n return taxMinor === floor || (remainder !== 0n && taxMinor === floor + 1n);\n}\n\nfunction validateFrozenComponentRates(\n components: readonly FrozenTaxComponent[],\n taxableAmount: number,\n currency: string | undefined,\n inclusive: boolean,\n label: string,\n): void {\n const taxableMinor = currencyMinor(\n taxableAmount,\n currency,\n `${label} taxable amount`,\n );\n const totalRate = components.reduce(\n (sum, component) => addRates(sum, component.rate),\n { numerator: 0n, denominator: 1n, value: 0 },\n );\n for (const component of components) {\n const numerator = inclusive\n ? taxableMinor * component.rate.numerator * totalRate.denominator\n : taxableMinor * component.rate.numerator;\n const denominator = inclusive\n ? component.rate.denominator *\n (totalRate.denominator + totalRate.numerator)\n : component.rate.denominator;\n if (!taxMinorMatchesAllocation(component.taxMinor, numerator, denominator))\n throw new Error(\n `QuickBooks ${label} tax component rate is inconsistent with its frozen tax amount`,\n );\n }\n}\n\nfunction frozenCurrencyAmount(\n value: number,\n label: string,\n currency: string | undefined,\n allowNegative = false,\n): number {\n finite(value, label, allowNegative);\n const rounded = roundCurrencyAmount(value, currency);\n // Stored decimal money can reach JavaScript as an adjacent binary value\n // (for example 0.1 * 3). Accept that representation only when the known\n // currency round-trip differs by ordinary floating-point noise; this never\n // authorizes a real off-grid amount such as CAD 0.005 or JPY 100.5.\n if (\n !isCurrencyMinorUnit(value, currency) &&\n (rounded === undefined || !close(value, rounded))\n )\n throw new Error(\n `QuickBooks ${label} must fit the explicitly supplied currency's ISO 4217 minor-unit grid`,\n );\n return value;\n}\n\nfunction hasFrozenSnapshots(\n input: Pick<InvoiceInput, 'quickbooksMapping' | 'lineItems'>,\n): boolean {\n return (\n input.quickbooksMapping?.globalTaxCalculation === 'TaxInclusive' ||\n input.lineItems.some(\n (item) =>\n item.taxAmount !== undefined ||\n item.taxComponents !== undefined ||\n item.taxInclusiveAmount !== undefined ||\n (item.discount !== undefined && item.discount !== 0),\n )\n );\n}\n\n/** Limit accumulated binary error when a valid document has many decimal lines. */\nfunction compensatedSum(values: readonly number[]): number {\n let sum = 0;\n let compensation = 0;\n for (const value of values) {\n const adjusted = value - compensation;\n const next = sum + adjusted;\n compensation = next - sum - adjusted;\n sum = next;\n }\n return sum;\n}\n\nfunction mapDocumentLines(\n input: Pick<\n InvoiceInput,\n | 'lineItems'\n | 'subtotal'\n | 'taxAmount'\n | 'totalAmount'\n | 'currency'\n | 'quickbooksMapping'\n >,\n): { lines: QuickBooksSalesLine[]; taxLines: QuickBooksTaxLine[] } {\n const mode = input.quickbooksMapping?.globalTaxCalculation;\n if (mode && !['TaxExcluded', 'TaxInclusive', 'NotApplicable'].includes(mode))\n throw new Error(\n 'QuickBooks globalTaxCalculation must be TaxExcluded, TaxInclusive or NotApplicable',\n );\n const frozen = hasFrozenSnapshots(input);\n const validateAmount = (value: number, label: string) =>\n frozen\n ? frozenCurrencyAmount(value, label, input.currency)\n : finite(value, label);\n validateAmount(input.subtotal, 'subtotal');\n validateAmount(input.taxAmount, 'tax amount');\n validateAmount(input.totalAmount, 'total amount');\n if (input.lineItems.length === 0)\n throw new Error('QuickBooks invoices require at least one sales line');\n const hasLineMapping = input.lineItems.some((item) => item.quickbooksMapping);\n if ((input.taxAmount > 0 || hasLineMapping) && !input.quickbooksMapping)\n throw new Error(\n 'QuickBooks mapped or nonzero-tax invoices require explicit globalTaxCalculation',\n );\n if (\n input.quickbooksMapping &&\n input.lineItems.some((item) => !item.quickbooksMapping)\n )\n throw new Error(\n 'QuickBooks mapped invoices require itemRef and taxCodeRef on every line',\n );\n if (input.taxAmount > 0 && mode === 'NotApplicable')\n throw new Error(\n 'QuickBooks nonzero tax cannot use NotApplicable global tax calculation',\n );\n\n const mapped: QuickBooksSalesLine[] = [];\n const taxLines = new Map<\n string,\n { rate: DecimalRate; amountMinor: bigint }\n >();\n const snapshotLineTaxes: number[] = [];\n let hasAnyLineTax = false;\n let hasCompleteLineTax = true;\n const lineTaxSnapshots: Array<{\n taxAmount: number | undefined;\n taxComponents: readonly FrozenTaxComponent[];\n }> = [];\n for (const [index, item] of input.lineItems.entries()) {\n const line = mapLine(item, index, input.currency, mode, frozen);\n mapped.push(...line.lines);\n lineTaxSnapshots.push({\n taxAmount: line.taxAmount,\n taxComponents: line.taxComponents,\n });\n if (line.taxAmount === undefined) hasCompleteLineTax = false;\n else {\n hasAnyLineTax = true;\n snapshotLineTaxes.push(line.taxAmount);\n }\n for (const component of line.taxComponents) {\n const existing = taxLines.get(component.taxRateRef);\n if (existing) {\n if (!ratesEqual(existing.rate, component.rate))\n throw new Error(\n `QuickBooks tax rate ${component.taxRateRef} has inconsistent frozen rates`,\n );\n existing.amountMinor += component.taxMinor;\n } else {\n taxLines.set(component.taxRateRef, {\n rate: component.rate,\n amountMinor: component.taxMinor,\n });\n }\n }\n }\n const mappedSubtotal = compensatedSum(mapped.map((line) => line.Amount));\n if (!close(mappedSubtotal, input.subtotal))\n throw new Error('QuickBooks line amounts must equal the invoice subtotal');\n if (!close(input.subtotal + input.taxAmount, input.totalAmount))\n throw new Error(\n 'QuickBooks subtotal plus tax must equal the invoice total',\n );\n if (taxLines.size > 0) {\n if (\n lineTaxSnapshots.some(\n (line) =>\n line.taxAmount === undefined ||\n (line.taxAmount > 0 && line.taxComponents.length === 0),\n )\n )\n throw new Error(\n 'QuickBooks component tax detail requires components on every taxed line',\n );\n const totalTaxMinor = currencyMinor(\n input.taxAmount,\n input.currency,\n 'tax amount',\n );\n const detailTaxMinor = [...taxLines.values()].reduce(\n (sum, line) => sum + line.amountMinor,\n 0n,\n );\n if (detailTaxMinor !== totalTaxMinor)\n throw new Error(\n 'QuickBooks component tax detail must equal the invoice tax amount',\n );\n }\n if (hasAnyLineTax && !hasCompleteLineTax)\n throw new Error(\n 'QuickBooks stored line tax must be supplied on every line when any line supplies it',\n );\n if (\n hasAnyLineTax &&\n !close(compensatedSum(snapshotLineTaxes), input.taxAmount)\n )\n throw new Error(\n 'QuickBooks stored line tax must equal the invoice tax amount',\n );\n return {\n lines: mapped,\n taxLines: [...taxLines.entries()].map(([taxRateRef, detail]) => ({\n Amount: currencyMajor(detail.amountMinor, input.currency),\n DetailType: 'TaxLineDetail',\n TaxLineDetail: {\n TaxRateRef: { value: taxRateRef },\n TaxPercent: taxPercent(detail.rate),\n PercentBased: true,\n },\n })),\n };\n}\n\nfunction mapLine(\n item: InvoiceLineItemInput,\n index: number,\n currency: string | undefined,\n mode: 'TaxExcluded' | 'TaxInclusive' | 'NotApplicable' | undefined,\n enforceCurrencyGrid: boolean,\n): {\n lines: QuickBooksSalesLine[];\n taxAmount?: number;\n taxComponents: FrozenTaxComponent[];\n} {\n const label = `line ${index + 1}`;\n if (item.taxRate !== undefined && item.taxRate !== 0)\n throw new Error(\n 'QuickBooks numeric taxRate is not supported; use a realm taxCodeRef',\n );\n if (item.taxCode !== undefined)\n throw new Error(\n 'QuickBooks generic taxCode is not a realm tax reference; use quickbooksMapping.taxCodeRef',\n );\n finite(item.quantity, `${label} quantity`);\n if (item.quantity <= 0)\n throw new Error(`QuickBooks ${label} quantity must be greater than zero`);\n finite(item.unitPrice, `${label} unit price`);\n const baseDocumentAmount = item.quantity * item.unitPrice;\n finite(baseDocumentAmount, `${label} pre-discount amount`);\n const validateAmount = (\n value: number,\n amountLabel: string,\n allowNegative = false,\n ) =>\n enforceCurrencyGrid\n ? frozenCurrencyAmount(value, amountLabel, currency, allowNegative)\n : finite(value, amountLabel, allowNegative);\n const discount = validateAmount(item.discount ?? 0, `${label} discount`);\n if (discount > baseDocumentAmount && !close(discount, baseDocumentAmount))\n throw new Error(\n `QuickBooks ${label} discount cannot exceed quantity times unit price`,\n );\n const netAmount = validateAmount(\n item.amount ?? baseDocumentAmount - discount,\n `${label} amount`,\n );\n const taxAmount =\n item.taxAmount === undefined\n ? undefined\n : validateAmount(item.taxAmount, `${label} tax amount`);\n const taxComponents: FrozenTaxComponent[] = [];\n if (item.taxComponents !== undefined) {\n if (taxAmount === undefined)\n throw new Error(\n `QuickBooks ${label} tax components require a line tax amount`,\n );\n const componentTax = item.taxComponents.reduce(\n (sum, component, componentIndex) => {\n if (\n !component.taxCode ||\n component.taxCode.trim() !== component.taxCode\n )\n throw new Error(\n `QuickBooks ${label} tax component ${componentIndex + 1} needs a tax code`,\n );\n const rate = decimalRate(\n component.rate,\n `${label} tax component ${componentIndex + 1}`,\n );\n const taxRateRef = reference(\n component.quickbooksTaxRateRef,\n `${label} tax component ${componentIndex + 1} quickbooksTaxRateRef`,\n ).value;\n const componentAmount = validateAmount(\n component.taxAmount,\n `${label} tax component ${componentIndex + 1}`,\n );\n taxComponents.push({\n taxRateRef,\n rate,\n taxMinor: currencyMinor(\n componentAmount,\n currency,\n `${label} tax component ${componentIndex + 1}`,\n ),\n });\n return sum + componentAmount;\n },\n 0,\n );\n if (!close(componentTax, taxAmount))\n throw new Error(\n `QuickBooks ${label} tax components must equal the line tax amount`,\n );\n }\n\n const inclusive = mode === 'TaxInclusive';\n const grossAmount = item.taxInclusiveAmount;\n if (!inclusive && grossAmount !== undefined)\n throw new Error(\n `QuickBooks ${label} tax-inclusive amount requires TaxInclusive global tax calculation`,\n );\n if (inclusive) {\n if (\n grossAmount === undefined ||\n taxAmount === undefined ||\n (taxAmount > 0 && !item.taxComponents?.length)\n )\n throw new Error(\n `QuickBooks ${label} TaxInclusive snapshots require gross amount, line tax and tax components for nonzero tax`,\n );\n validateAmount(grossAmount, `${label} tax-inclusive amount`);\n if (!close(netAmount + taxAmount, grossAmount))\n throw new Error(\n `QuickBooks ${label} net amount plus tax must equal gross amount`,\n );\n if (!close(baseDocumentAmount - discount, grossAmount))\n throw new Error(\n `QuickBooks ${label} gross amount must equal quantity times gross unit price less gross discount`,\n );\n } else if (\n !close(netAmount, baseDocumentAmount - discount) &&\n !matchesCurrencyRoundedLineAmount(\n item.quantity,\n item.unitPrice,\n netAmount + discount,\n currency,\n )\n ) {\n throw new Error(\n `QuickBooks ${label} amount must equal quantity times unit price less discount`,\n );\n }\n if (taxComponents.length > 0)\n validateFrozenComponentRates(\n taxComponents,\n inclusive ? (grossAmount as number) : netAmount,\n currency,\n inclusive,\n label,\n );\n\n const itemRef = item.quickbooksMapping\n ? reference(item.quickbooksMapping.itemRef, `${label} itemRef`)\n : undefined;\n const taxCodeRef = item.quickbooksMapping\n ? reference(item.quickbooksMapping.taxCodeRef, `${label} taxCodeRef`)\n : undefined;\n const line = (\n amount: number,\n quantity: number,\n unitPrice: number,\n description: string,\n taxInclusiveAmount?: number,\n ) => ({\n LineNum: index + 1,\n Description: description,\n Amount: amount,\n DetailType: 'SalesItemLineDetail' as const,\n SalesItemLineDetail: {\n Qty: quantity,\n UnitPrice: unitPrice,\n ItemRef: itemRef,\n TaxCodeRef: taxCodeRef,\n ...(taxInclusiveAmount === undefined\n ? {}\n : { TaxInclusiveAmt: taxInclusiveAmount }),\n },\n });\n\n if (discount === 0 && !inclusive) {\n return {\n lines: [line(netAmount, item.quantity, item.unitPrice, item.description)],\n taxAmount,\n taxComponents,\n };\n }\n if (\n discount === 0 ||\n isCurrencyMinorUnit(netAmount / item.quantity, currency)\n ) {\n return {\n lines: [\n line(\n netAmount,\n item.quantity,\n netAmount / item.quantity,\n item.description,\n inclusive ? grossAmount : undefined,\n ),\n ],\n taxAmount,\n taxComponents,\n };\n }\n\n let preDiscountAmount = netAmount + discount;\n let negativeAmount = -discount;\n const preDiscountGross = inclusive ? baseDocumentAmount : undefined;\n if (inclusive) {\n const components = item.taxComponents;\n const exemptSnapshot = taxAmount === 0 && !components?.length;\n if (!components?.length && !exemptSnapshot)\n throw new Error(\n `QuickBooks ${label} needs frozen component rates for an inclusive negative discount line`,\n );\n const rate = exemptSnapshot\n ? 0\n : (components ?? []).reduce(\n (sum, component, componentIndex) =>\n sum +\n decimalRate(\n component.rate,\n `${label} tax component ${componentIndex + 1}`,\n ).value,\n 0,\n );\n const discountNet = roundCurrencyAmount(discount / (1 + rate), currency);\n if (discountNet === undefined)\n throw new Error(\n `QuickBooks ${label} needs a recognized currency to map an inclusive fractional-unit discount`,\n );\n preDiscountAmount = netAmount + discountNet;\n negativeAmount = -discountNet;\n }\n if (enforceCurrencyGrid) {\n frozenCurrencyAmount(\n preDiscountAmount,\n `${label} generated pre-discount amount`,\n currency,\n );\n frozenCurrencyAmount(\n negativeAmount,\n `${label} generated discount amount`,\n currency,\n true,\n );\n }\n let negativeGross: number | undefined;\n if (inclusive) {\n if (grossAmount === undefined || preDiscountGross === undefined)\n throw new Error(\n `QuickBooks ${label} needs frozen gross amounts for an inclusive negative discount line`,\n );\n const unroundedNegativeGross = grossAmount - preDiscountGross;\n negativeGross =\n roundCurrencyAmount(unroundedNegativeGross, currency) ??\n unroundedNegativeGross;\n }\n if (enforceCurrencyGrid && negativeGross !== undefined) {\n frozenCurrencyAmount(\n negativeGross,\n `${label} generated gross discount amount`,\n currency,\n true,\n );\n }\n return {\n lines: [\n line(\n preDiscountAmount,\n item.quantity,\n preDiscountAmount / item.quantity,\n item.description,\n preDiscountGross,\n ),\n line(\n negativeAmount,\n 1,\n negativeAmount,\n `Discount on line ${index + 1}`,\n negativeGross,\n ),\n ],\n taxAmount,\n taxComponents,\n };\n}\n\nfunction basePayload(\n input: Pick<\n InvoiceInput,\n | 'customerId'\n | 'customerExternalId'\n | 'issueDate'\n | 'subtotal'\n | 'taxAmount'\n | 'totalAmount'\n | 'currency'\n | 'memo'\n | 'lineItems'\n | 'quickbooksMapping'\n >,\n number: string,\n dueDate?: Date,\n) {\n const { lines, taxLines } = mapDocumentLines(input);\n return {\n CustomerRef: { value: input.customerExternalId || input.customerId },\n DocNumber: number,\n TxnDate: formatLocalDate(input.issueDate),\n ...(dueDate ? { DueDate: formatLocalDate(dueDate) } : {}),\n Line: lines,\n GlobalTaxCalculation: input.quickbooksMapping?.globalTaxCalculation,\n TxnTaxDetail: input.quickbooksMapping\n ? {\n TotalTax: input.taxAmount,\n ...(taxLines.length > 0 ? { TaxLine: taxLines } : {}),\n }\n : undefined,\n CurrencyRef: input.currency ? { value: input.currency } : undefined,\n CustomerMemo: input.memo ? { value: input.memo } : undefined,\n };\n}\n\nexport function mapInvoiceToQBO(invoice: InvoiceInput) {\n if (invoice.collectionMethod === 'charge_automatically')\n throw new Error(\n 'QuickBooks does not support charge_automatically invoice collection',\n );\n if (invoice.automaticTax)\n throw new Error(\n 'QuickBooks does not support automaticTax invoice calculation',\n );\n return basePayload(\n invoice,\n invoice.invoiceNumber,\n invoice.dueDate,\n ) as ReturnType<typeof basePayload> & { DueDate: string };\n}\n\nexport function mapCreditMemoToQBO(creditMemo: CreditMemoInput) {\n if (\n !creditMemo.creditedInvoiceExternalId ||\n creditMemo.creditedInvoiceExternalId.trim() !==\n creditMemo.creditedInvoiceExternalId\n )\n throw new Error(\n 'QuickBooks credit memos require a credited invoice external ID',\n );\n return basePayload(creditMemo, creditMemo.creditMemoNumber);\n}\n"],"mappings":"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"}