@happyvertical/smrt-commerce 0.53.3 → 0.53.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +19 -3
- package/README.md +29 -1
- package/agents/commercial-ui.md +78 -0
- package/agents/customer-vendor-ui.md +76 -0
- package/agents/invoice-calculations.md +17 -0
- package/agents/invoice-preparation.md +86 -0
- package/agents/invoice-quantity.md +46 -0
- package/agents/pricing-versions.md +28 -0
- package/agents/purchasing-ui.md +82 -0
- package/agents/quote-ui.md +82 -0
- package/dist/billing/units.d.ts +1 -7
- package/dist/billing/units.d.ts.map +1 -1
- package/dist/chunks/calculations-DPuldr1J.js +242 -0
- package/dist/chunks/calculations-DPuldr1J.js.map +1 -0
- package/dist/chunks/types-CRnln577.js.map +1 -1
- package/dist/chunks/{units-CpBepqG1.js → units-CJcAOQm1.js} +3 -39
- package/dist/chunks/units-CJcAOQm1.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +164 -44
- package/dist/index.js.map +1 -1
- package/dist/invoices.d.ts +3 -0
- package/dist/invoices.d.ts.map +1 -0
- package/dist/invoices.js +2 -0
- package/dist/manifest.json +66 -8
- package/dist/migrations/invoiceEditorStorage.d.ts +39 -0
- package/dist/migrations/invoiceEditorStorage.d.ts.map +1 -0
- package/dist/models/ContractLineItem.d.ts +1 -1
- package/dist/models/InvoiceLineItem.d.ts +18 -36
- package/dist/models/InvoiceLineItem.d.ts.map +1 -1
- package/dist/playground.js +201 -3
- package/dist/playground.js.map +1 -1
- package/dist/smrt-knowledge.json +109 -7
- package/dist/svelte/components/CustomerDetail.svelte +150 -0
- package/dist/svelte/components/CustomerDetail.svelte.d.ts +28 -0
- package/dist/svelte/components/CustomerDetail.svelte.d.ts.map +1 -0
- package/dist/svelte/components/CustomerDirectory.svelte +177 -0
- package/dist/svelte/components/CustomerDirectory.svelte.d.ts +42 -0
- package/dist/svelte/components/CustomerDirectory.svelte.d.ts.map +1 -0
- package/dist/svelte/components/CustomerForm.svelte +234 -0
- package/dist/svelte/components/CustomerForm.svelte.d.ts +44 -0
- package/dist/svelte/components/CustomerForm.svelte.d.ts.map +1 -0
- package/dist/svelte/components/InvoiceAllocationFields.svelte +72 -0
- package/dist/svelte/components/InvoiceAllocationFields.svelte.d.ts +8 -0
- package/dist/svelte/components/InvoiceAllocationFields.svelte.d.ts.map +1 -0
- package/dist/svelte/components/InvoiceCard.svelte +2 -2
- package/dist/svelte/components/InvoiceEditor.svelte +134 -0
- package/dist/svelte/components/InvoiceEditor.svelte.d.ts +8 -0
- package/dist/svelte/components/InvoiceEditor.svelte.d.ts.map +1 -0
- package/dist/svelte/components/InvoiceLineEditor.svelte +85 -0
- package/dist/svelte/components/InvoiceLineEditor.svelte.d.ts +8 -0
- package/dist/svelte/components/InvoiceLineEditor.svelte.d.ts.map +1 -0
- package/dist/svelte/components/InvoiceReview.svelte +23 -0
- package/dist/svelte/components/InvoiceReview.svelte.d.ts +10 -0
- package/dist/svelte/components/InvoiceReview.svelte.d.ts.map +1 -0
- package/dist/svelte/components/PartyAddressFields.svelte +95 -0
- package/dist/svelte/components/PartyAddressFields.svelte.d.ts +25 -0
- package/dist/svelte/components/PartyAddressFields.svelte.d.ts.map +1 -0
- package/dist/svelte/components/PartyContactFields.svelte +153 -0
- package/dist/svelte/components/PartyContactFields.svelte.d.ts +28 -0
- package/dist/svelte/components/PartyContactFields.svelte.d.ts.map +1 -0
- package/dist/svelte/components/PricingVersionDecision.svelte +88 -0
- package/dist/svelte/components/PricingVersionDecision.svelte.d.ts +35 -0
- package/dist/svelte/components/PricingVersionDecision.svelte.d.ts.map +1 -0
- package/dist/svelte/components/PricingVersionSummary.svelte +46 -0
- package/dist/svelte/components/PricingVersionSummary.svelte.d.ts +9 -0
- package/dist/svelte/components/PricingVersionSummary.svelte.d.ts.map +1 -0
- package/dist/svelte/components/PurchaseOrderEditor.svelte +210 -0
- package/dist/svelte/components/PurchaseOrderEditor.svelte.d.ts +79 -0
- package/dist/svelte/components/PurchaseOrderEditor.svelte.d.ts.map +1 -0
- package/dist/svelte/components/PurchaseSourceSelector.svelte +56 -0
- package/dist/svelte/components/PurchaseSourceSelector.svelte.d.ts +30 -0
- package/dist/svelte/components/PurchaseSourceSelector.svelte.d.ts.map +1 -0
- package/dist/svelte/components/PurchasingPlayground.svelte +133 -0
- package/dist/svelte/components/PurchasingPlayground.svelte.d.ts +9 -0
- package/dist/svelte/components/PurchasingPlayground.svelte.d.ts.map +1 -0
- package/dist/svelte/components/QuoteEditor.svelte +177 -0
- package/dist/svelte/components/QuoteEditor.svelte.d.ts +76 -0
- package/dist/svelte/components/QuoteEditor.svelte.d.ts.map +1 -0
- package/dist/svelte/components/QuotePlayground.svelte +108 -0
- package/dist/svelte/components/QuotePlayground.svelte.d.ts +10 -0
- package/dist/svelte/components/QuotePlayground.svelte.d.ts.map +1 -0
- package/dist/svelte/components/QuoteRevisionComparison.svelte +44 -0
- package/dist/svelte/components/QuoteRevisionComparison.svelte.d.ts +14 -0
- package/dist/svelte/components/QuoteRevisionComparison.svelte.d.ts.map +1 -0
- package/dist/svelte/components/QuoteRevisionHistory.svelte +55 -0
- package/dist/svelte/components/QuoteRevisionHistory.svelte.d.ts +15 -0
- package/dist/svelte/components/QuoteRevisionHistory.svelte.d.ts.map +1 -0
- package/dist/svelte/components/VendorDetail.svelte +134 -0
- package/dist/svelte/components/VendorDetail.svelte.d.ts +26 -0
- package/dist/svelte/components/VendorDetail.svelte.d.ts.map +1 -0
- package/dist/svelte/components/VendorDirectory.svelte +178 -0
- package/dist/svelte/components/VendorDirectory.svelte.d.ts +42 -0
- package/dist/svelte/components/VendorDirectory.svelte.d.ts.map +1 -0
- package/dist/svelte/components/VendorForm.svelte +224 -0
- package/dist/svelte/components/VendorForm.svelte.d.ts +42 -0
- package/dist/svelte/components/VendorForm.svelte.d.ts.map +1 -0
- package/dist/svelte/i18n.d.ts +102 -0
- package/dist/svelte/i18n.d.ts.map +1 -1
- package/dist/svelte/i18n.js +102 -0
- package/dist/svelte/index.d.ts +52 -0
- package/dist/svelte/index.d.ts.map +1 -1
- package/dist/svelte/index.js +49 -0
- package/dist/svelte/invoices/calculations.d.ts +121 -0
- package/dist/svelte/invoices/calculations.d.ts.map +1 -0
- package/dist/svelte/invoices/calculations.js +259 -0
- package/dist/svelte/invoices/currency.d.ts +9 -0
- package/dist/svelte/invoices/currency.d.ts.map +1 -0
- package/dist/svelte/invoices/currency.js +55 -0
- package/dist/svelte/invoices/fields.d.ts +6 -0
- package/dist/svelte/invoices/fields.d.ts.map +1 -0
- package/dist/svelte/invoices/fields.js +22 -0
- package/dist/svelte/invoices/messages.d.ts +53 -0
- package/dist/svelte/invoices/messages.d.ts.map +1 -0
- package/dist/svelte/invoices/messages.js +53 -0
- package/dist/svelte/invoices/types.d.ts +194 -0
- package/dist/svelte/invoices/types.d.ts.map +1 -0
- package/dist/svelte/invoices/types.js +1 -0
- package/dist/svelte/party-types.d.ts +203 -0
- package/dist/svelte/party-types.d.ts.map +1 -0
- package/dist/svelte/party-types.js +82 -0
- package/dist/svelte/playground/InvoicePreparationPreview.svelte +116 -0
- package/dist/svelte/playground/InvoicePreparationPreview.svelte.d.ts +4 -0
- package/dist/svelte/playground/InvoicePreparationPreview.svelte.d.ts.map +1 -0
- package/dist/svelte/playground/PartyFormPreview.svelte +211 -0
- package/dist/svelte/playground/PartyFormPreview.svelte.d.ts +8 -0
- package/dist/svelte/playground/PartyFormPreview.svelte.d.ts.map +1 -0
- package/dist/svelte/playground/PricingVersionPreview.svelte +55 -0
- package/dist/svelte/playground/PricingVersionPreview.svelte.d.ts +4 -0
- package/dist/svelte/playground/PricingVersionPreview.svelte.d.ts.map +1 -0
- package/dist/svelte/playground.d.ts +442 -3
- package/dist/svelte/playground.d.ts.map +1 -1
- package/dist/svelte/playground.js +199 -3
- package/dist/svelte/pricing-i18n.d.ts +10 -0
- package/dist/svelte/pricing-i18n.d.ts.map +1 -0
- package/dist/svelte/pricing-i18n.js +10 -0
- package/dist/svelte/pricing-types.d.ts +38 -0
- package/dist/svelte/pricing-types.d.ts.map +1 -0
- package/dist/svelte/pricing-types.js +1 -0
- package/dist/svelte/purchasing-i18n.d.ts +53 -0
- package/dist/svelte/purchasing-i18n.d.ts.map +1 -0
- package/dist/svelte/purchasing-i18n.js +53 -0
- package/dist/svelte/purchasing-types.d.ts +91 -0
- package/dist/svelte/purchasing-types.d.ts.map +1 -0
- package/dist/svelte/purchasing-types.js +13 -0
- package/dist/svelte/quote-i18n.d.ts +49 -0
- package/dist/svelte/quote-i18n.d.ts.map +1 -0
- package/dist/svelte/quote-i18n.js +49 -0
- package/dist/svelte/quote-types.d.ts +81 -0
- package/dist/svelte/quote-types.d.ts.map +1 -0
- package/dist/svelte/quote-types.js +51 -0
- package/dist/types/index.d.ts +13 -3
- package/dist/types/index.d.ts.map +1 -1
- package/dist/ui.d.ts.map +1 -1
- package/dist/ui.js +150 -6
- package/dist/ui.js.map +1 -1
- package/package.json +25 -13
- package/dist/chunks/units-CpBepqG1.js.map +0 -1
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
/** Exact, transport-neutral invoice draft validation shared by browser and server. */
|
|
2
|
+
import { currencyMinorUnitExponent } from './currency.js';
|
|
3
|
+
const MAX_MINOR = BigInt(Number.MAX_SAFE_INTEGER);
|
|
4
|
+
const MAX_TEXT = 128;
|
|
5
|
+
function decimal(value, precision) {
|
|
6
|
+
if (typeof value !== 'string' ||
|
|
7
|
+
value.length > MAX_TEXT ||
|
|
8
|
+
!/^\d+(?:\.\d+)?$/.test(value))
|
|
9
|
+
return 'invalid-decimal';
|
|
10
|
+
const [whole, fraction = ''] = value.split('.');
|
|
11
|
+
if (fraction.length > precision)
|
|
12
|
+
return 'precision';
|
|
13
|
+
return {
|
|
14
|
+
coefficient: BigInt(whole + fraction),
|
|
15
|
+
scale: 10n ** BigInt(fraction.length),
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
function roundHalfUp(numerator, denominator) {
|
|
19
|
+
return (numerator * 2n + denominator) / (denominator * 2n);
|
|
20
|
+
}
|
|
21
|
+
function minor(value, digits) {
|
|
22
|
+
const parsed = decimal(value, MAX_TEXT);
|
|
23
|
+
if (typeof parsed === 'string')
|
|
24
|
+
return parsed;
|
|
25
|
+
const scaled = parsed.coefficient * 10n ** BigInt(digits);
|
|
26
|
+
if (scaled % parsed.scale !== 0n)
|
|
27
|
+
return 'precision';
|
|
28
|
+
const result = scaled / parsed.scale;
|
|
29
|
+
return result > MAX_MINOR ? 'range' : result;
|
|
30
|
+
}
|
|
31
|
+
function rate(value) {
|
|
32
|
+
const parsed = decimal(value, 6);
|
|
33
|
+
if (typeof parsed === 'string')
|
|
34
|
+
return parsed;
|
|
35
|
+
return parsed.coefficient > 100n * parsed.scale ? 'range' : parsed;
|
|
36
|
+
}
|
|
37
|
+
function numberDecimal(value, precision) {
|
|
38
|
+
if (!Number.isFinite(value) || value < 0 || value > Number.MAX_SAFE_INTEGER)
|
|
39
|
+
throw new RangeError('Invoice rate or quantity is outside the supported range.');
|
|
40
|
+
// The shortest canonical decimal preserves the caller's Number. toFixed can
|
|
41
|
+
// invent different decimal digits that round back to that same binary value.
|
|
42
|
+
const [mantissa, exponent = '0'] = value.toString().split('e');
|
|
43
|
+
const [whole, fraction = ''] = mantissa.split('.');
|
|
44
|
+
const places = fraction.length - Number(exponent);
|
|
45
|
+
if (places > precision)
|
|
46
|
+
throw new RangeError('Invoice rate or quantity exceeds the supported decimal precision.');
|
|
47
|
+
const coefficient = BigInt(whole + fraction);
|
|
48
|
+
return places < 0
|
|
49
|
+
? { coefficient: coefficient * 10n ** BigInt(-places), scale: 1n }
|
|
50
|
+
: { coefficient, scale: 10n ** BigInt(places) };
|
|
51
|
+
}
|
|
52
|
+
function roundSigned(numerator, denominator) {
|
|
53
|
+
// Preserve Math.round's existing credit-line ties toward positive infinity.
|
|
54
|
+
return numerator >= 0n
|
|
55
|
+
? roundHalfUp(numerator, denominator)
|
|
56
|
+
: -((-numerator * 2n + denominator - 1n) / (denominator * 2n));
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Exact model calculation, including existing negative-price credit lines.
|
|
60
|
+
* Throws before persistence for unsafe money, invalid quantity, or invalid tax.
|
|
61
|
+
*/
|
|
62
|
+
export function calculateInvoiceMinorLine(input) {
|
|
63
|
+
for (const amount of [input.unitPrice, input.discount])
|
|
64
|
+
if (!Number.isSafeInteger(amount))
|
|
65
|
+
throw new RangeError('Invoice money must be safe integer minor units.');
|
|
66
|
+
if (input.discount < 0 || input.taxRate > 1)
|
|
67
|
+
throw new RangeError('Invoice discount or tax rate is outside the supported range.');
|
|
68
|
+
const quantity = numberDecimal(input.quantity, 6);
|
|
69
|
+
const tax = numberDecimal(input.taxRate, 8);
|
|
70
|
+
const gross = roundSigned(quantity.coefficient * BigInt(input.unitPrice), quantity.scale);
|
|
71
|
+
const discount = BigInt(input.discount);
|
|
72
|
+
const subtotal = gross - discount;
|
|
73
|
+
const taxMinor = roundSigned(subtotal * tax.coefficient, tax.scale);
|
|
74
|
+
const total = subtotal + taxMinor;
|
|
75
|
+
for (const amount of [gross, subtotal, taxMinor, total])
|
|
76
|
+
if (amount > MAX_MINOR || amount < -MAX_MINOR)
|
|
77
|
+
throw new RangeError('Invoice line amount exceeds safe integer minor units.');
|
|
78
|
+
return {
|
|
79
|
+
grossMinor: Number(gross),
|
|
80
|
+
discountMinor: input.discount,
|
|
81
|
+
subtotalMinor: Number(subtotal),
|
|
82
|
+
taxMinor: Number(taxMinor),
|
|
83
|
+
totalMinor: Number(total),
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Resolve a validated editor draft to model fields. Percentage discounts become
|
|
88
|
+
* exact flat minor units; inherited tax becomes the authorized effective rate.
|
|
89
|
+
* Keep the raw draft alongside these fields when reopening its editing modes.
|
|
90
|
+
*/
|
|
91
|
+
export function resolveInvoiceLineDraft(line, context) {
|
|
92
|
+
const result = calculateInvoiceLine(line, context);
|
|
93
|
+
if (!result.valid)
|
|
94
|
+
throw new RangeError(`Invalid invoice line: ${JSON.stringify(result.errors)}`);
|
|
95
|
+
const quantity = Number(line.quantity);
|
|
96
|
+
const sourceQuantity = decimal(line.quantity, 6);
|
|
97
|
+
const storedQuantity = numberDecimal(quantity, 6);
|
|
98
|
+
if (typeof sourceQuantity === 'string' ||
|
|
99
|
+
sourceQuantity.coefficient * storedQuantity.scale !==
|
|
100
|
+
storedQuantity.coefficient * sourceQuantity.scale)
|
|
101
|
+
throw new RangeError('Invoice quantity cannot round-trip through numeric persistence.');
|
|
102
|
+
const [whole, fraction = ''] = result.effectiveTaxRate.split('.');
|
|
103
|
+
const rateDigits = BigInt(whole + fraction)
|
|
104
|
+
.toString()
|
|
105
|
+
.padStart(fraction.length + 3, '0');
|
|
106
|
+
const split = rateDigits.length - fraction.length - 2;
|
|
107
|
+
const taxRate = Number(`${rateDigits.slice(0, split)}.${rateDigits.slice(split)}`);
|
|
108
|
+
const unitPrice = minor(line.unitPrice, currencyMinorUnitExponent(context.currency));
|
|
109
|
+
if (typeof unitPrice !== 'bigint')
|
|
110
|
+
throw new RangeError('Invalid invoice unit price.');
|
|
111
|
+
const resolved = {
|
|
112
|
+
description: line.description,
|
|
113
|
+
sku: line.sku,
|
|
114
|
+
quantity,
|
|
115
|
+
unitPrice: Number(unitPrice),
|
|
116
|
+
discount: result.discountMinor,
|
|
117
|
+
taxRate,
|
|
118
|
+
amount: result.totalMinor,
|
|
119
|
+
};
|
|
120
|
+
const model = calculateInvoiceMinorLine(resolved);
|
|
121
|
+
if (model.totalMinor !== result.totalMinor ||
|
|
122
|
+
model.subtotalMinor !== result.subtotalMinor ||
|
|
123
|
+
model.taxMinor !== result.taxMinor)
|
|
124
|
+
throw new RangeError('Invoice draft and persisted calculation disagree.');
|
|
125
|
+
return resolved;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Validate and calculate one line using decimal integer arithmetic. Round half
|
|
129
|
+
* up separately at gross, percentage discount, and tax boundaries. No rounding
|
|
130
|
+
* is permitted when parsing a price or flat discount into currency minor units.
|
|
131
|
+
*/
|
|
132
|
+
export function calculateInvoiceLine(line, context) {
|
|
133
|
+
const errors = {};
|
|
134
|
+
if (!line.key?.trim())
|
|
135
|
+
errors.key = 'required';
|
|
136
|
+
if (!line.description?.trim())
|
|
137
|
+
errors.description = 'required';
|
|
138
|
+
if (!/^[A-Z]{3}$/.test(context.currency))
|
|
139
|
+
return { valid: false, errors: { unitPrice: 'invalid-currency' } };
|
|
140
|
+
const digits = currencyMinorUnitExponent(context.currency);
|
|
141
|
+
const quantity = decimal(line.quantity, 6);
|
|
142
|
+
const price = minor(line.unitPrice, digits);
|
|
143
|
+
if (typeof quantity === 'string')
|
|
144
|
+
errors.quantity = quantity;
|
|
145
|
+
else if (quantity.coefficient > MAX_MINOR * quantity.scale)
|
|
146
|
+
errors.quantity = 'range';
|
|
147
|
+
else {
|
|
148
|
+
try {
|
|
149
|
+
const persisted = numberDecimal(Number(line.quantity), 6);
|
|
150
|
+
if (quantity.coefficient * persisted.scale !==
|
|
151
|
+
persisted.coefficient * quantity.scale)
|
|
152
|
+
errors.quantity = 'precision';
|
|
153
|
+
}
|
|
154
|
+
catch {
|
|
155
|
+
errors.quantity = 'precision';
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
if (typeof price === 'string')
|
|
159
|
+
errors.unitPrice = price;
|
|
160
|
+
if (line.discountType !== 'flat' && line.discountType !== 'percent')
|
|
161
|
+
errors.discountType = 'invalid-mode';
|
|
162
|
+
if (line.taxMode !== 'inherit' && line.taxMode !== 'override')
|
|
163
|
+
errors.taxMode = 'invalid-mode';
|
|
164
|
+
const discount = line.discountType === 'percent'
|
|
165
|
+
? rate(line.discountValue)
|
|
166
|
+
: minor(line.discountValue, digits);
|
|
167
|
+
if (typeof discount === 'string')
|
|
168
|
+
errors.discountValue = discount;
|
|
169
|
+
const effectiveTaxRate = line.taxMode === 'inherit' ? context.inheritedTaxRate : line.taxRate;
|
|
170
|
+
const tax = rate(effectiveTaxRate);
|
|
171
|
+
if (typeof tax === 'string')
|
|
172
|
+
errors.taxRate = tax;
|
|
173
|
+
if (Object.keys(errors).length ||
|
|
174
|
+
typeof quantity === 'string' ||
|
|
175
|
+
typeof price === 'string' ||
|
|
176
|
+
typeof discount === 'string' ||
|
|
177
|
+
typeof tax === 'string')
|
|
178
|
+
return { valid: false, errors };
|
|
179
|
+
const gross = roundHalfUp(quantity.coefficient * price, quantity.scale);
|
|
180
|
+
const reduction = typeof discount === 'bigint'
|
|
181
|
+
? discount
|
|
182
|
+
: roundHalfUp(gross * discount.coefficient, discount.scale * 100n);
|
|
183
|
+
if (gross > MAX_MINOR)
|
|
184
|
+
return { valid: false, errors: { quantity: 'range' } };
|
|
185
|
+
if (reduction > gross)
|
|
186
|
+
return {
|
|
187
|
+
valid: false,
|
|
188
|
+
errors: { discountValue: 'discount-exceeds-gross' },
|
|
189
|
+
};
|
|
190
|
+
const subtotal = gross - reduction;
|
|
191
|
+
const taxMinor = roundHalfUp(subtotal * tax.coefficient, tax.scale * 100n);
|
|
192
|
+
const total = subtotal + taxMinor;
|
|
193
|
+
if (total > MAX_MINOR)
|
|
194
|
+
return { valid: false, errors: { taxRate: 'range' } };
|
|
195
|
+
return {
|
|
196
|
+
valid: true,
|
|
197
|
+
effectiveTaxRate,
|
|
198
|
+
grossMinor: Number(gross),
|
|
199
|
+
discountMinor: Number(reduction),
|
|
200
|
+
subtotalMinor: Number(subtotal),
|
|
201
|
+
taxMinor: Number(taxMinor),
|
|
202
|
+
totalMinor: Number(total),
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
function calendarDate(value) {
|
|
206
|
+
if (!/^\d{4}-\d{2}-\d{2}$/.test(value) || value.startsWith('0000'))
|
|
207
|
+
return false;
|
|
208
|
+
const date = new Date(`${value}T00:00:00.000Z`);
|
|
209
|
+
return (Number.isFinite(date.getTime()) && date.toISOString().slice(0, 10) === value);
|
|
210
|
+
}
|
|
211
|
+
/** Validate the header and sum validated lines without floating-point arithmetic. */
|
|
212
|
+
export function calculateInvoiceDraft(values) {
|
|
213
|
+
const errors = {};
|
|
214
|
+
if (!values.customerId?.trim())
|
|
215
|
+
errors.customerId = 'required';
|
|
216
|
+
if (!calendarDate(values.issuedOn))
|
|
217
|
+
errors.issuedOn = 'invalid-date';
|
|
218
|
+
if (!calendarDate(values.dueOn) || values.dueOn < values.issuedOn)
|
|
219
|
+
errors.dueOn = 'invalid-date';
|
|
220
|
+
if (!/^[A-Z]{3}$/.test(values.currency))
|
|
221
|
+
errors.currency = 'invalid-currency';
|
|
222
|
+
const inheritedRate = rate(values.taxRate);
|
|
223
|
+
if (typeof inheritedRate === 'string')
|
|
224
|
+
errors.taxRate = inheritedRate;
|
|
225
|
+
if (values.lines.length === 0)
|
|
226
|
+
errors.lines = 'required';
|
|
227
|
+
if (new Set(values.lines.map((line) => line.key)).size !== values.lines.length)
|
|
228
|
+
errors.lines = 'duplicate-key';
|
|
229
|
+
const lineResults = values.lines.map((line) => calculateInvoiceLine(line, {
|
|
230
|
+
currency: values.currency,
|
|
231
|
+
inheritedTaxRate: values.taxRate,
|
|
232
|
+
}));
|
|
233
|
+
const sums = {
|
|
234
|
+
grossMinor: 0n,
|
|
235
|
+
discountMinor: 0n,
|
|
236
|
+
subtotalMinor: 0n,
|
|
237
|
+
taxMinor: 0n,
|
|
238
|
+
totalMinor: 0n,
|
|
239
|
+
};
|
|
240
|
+
for (const result of lineResults) {
|
|
241
|
+
if (!result.valid)
|
|
242
|
+
continue;
|
|
243
|
+
for (const key of Object.keys(sums))
|
|
244
|
+
sums[key] += BigInt(result[key]);
|
|
245
|
+
}
|
|
246
|
+
if (Object.values(sums).some((value) => value > MAX_MINOR))
|
|
247
|
+
errors.total = 'range';
|
|
248
|
+
if (Object.keys(errors).length || lineResults.some((result) => !result.valid))
|
|
249
|
+
return { valid: false, errors, lineResults };
|
|
250
|
+
return {
|
|
251
|
+
valid: true,
|
|
252
|
+
lineResults,
|
|
253
|
+
grossMinor: Number(sums.grossMinor),
|
|
254
|
+
discountMinor: Number(sums.discountMinor),
|
|
255
|
+
subtotalMinor: Number(sums.subtotalMinor),
|
|
256
|
+
taxMinor: Number(sums.taxMinor),
|
|
257
|
+
totalMinor: Number(sums.totalMinor),
|
|
258
|
+
};
|
|
259
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/** Svelte-free currency precision shared by invoice previews and billing. */
|
|
2
|
+
/** Upper-case, validate, and return an ISO 4217-shaped currency code. */
|
|
3
|
+
export declare function normalizeCurrency(currency: string): string;
|
|
4
|
+
/**
|
|
5
|
+
* The ISO 4217 minor-unit exponent of a currency (2 for USD and RSD, 0 for
|
|
6
|
+
* JPY, 3 for IQD). Any code not listed as 0, 3, or 4 is 2.
|
|
7
|
+
*/
|
|
8
|
+
export declare function currencyMinorUnitExponent(currency: string): number;
|
|
9
|
+
//# sourceMappingURL=currency.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"currency.d.ts","sourceRoot":"","sources":["../../../src/svelte/invoices/currency.ts"],"names":[],"mappings":"AAAA,6EAA6E;AAI7E,yEAAyE;AACzE,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAQ1D;AAuCD;;;GAGG;AACH,wBAAgB,yBAAyB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAElE"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/** Svelte-free currency precision shared by invoice previews and billing. */
|
|
2
|
+
const CURRENCY_PATTERN = /^[A-Z]{3}$/;
|
|
3
|
+
/** Upper-case, validate, and return an ISO 4217-shaped currency code. */
|
|
4
|
+
export function normalizeCurrency(currency) {
|
|
5
|
+
const code = String(currency ?? '')
|
|
6
|
+
.trim()
|
|
7
|
+
.toUpperCase();
|
|
8
|
+
if (!CURRENCY_PATTERN.test(code)) {
|
|
9
|
+
throw new Error(`Currency '${currency}' must be a three-letter code.`);
|
|
10
|
+
}
|
|
11
|
+
return code;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* ISO 4217 minor-unit exponents other than 2, matching
|
|
15
|
+
* `@happyvertical/accounting`'s table (sdk#1269). An explicit table, not the
|
|
16
|
+
* runtime's `Intl` display digits: CLDR rounds some currencies for display
|
|
17
|
+
* (IQD, MGA, and ALL show 0 decimals) and its data changes between ICU
|
|
18
|
+
* releases (RSD has shown both 0 and 2), which would put minor units off by a
|
|
19
|
+
* power of ten against the SDK.
|
|
20
|
+
*/
|
|
21
|
+
const ISO_EXPONENTS = {
|
|
22
|
+
BIF: 0,
|
|
23
|
+
CLP: 0,
|
|
24
|
+
DJF: 0,
|
|
25
|
+
GNF: 0,
|
|
26
|
+
ISK: 0,
|
|
27
|
+
JPY: 0,
|
|
28
|
+
KMF: 0,
|
|
29
|
+
KRW: 0,
|
|
30
|
+
PYG: 0,
|
|
31
|
+
RWF: 0,
|
|
32
|
+
UGX: 0,
|
|
33
|
+
UYI: 0,
|
|
34
|
+
VND: 0,
|
|
35
|
+
VUV: 0,
|
|
36
|
+
XAF: 0,
|
|
37
|
+
XOF: 0,
|
|
38
|
+
XPF: 0,
|
|
39
|
+
BHD: 3,
|
|
40
|
+
IQD: 3,
|
|
41
|
+
JOD: 3,
|
|
42
|
+
KWD: 3,
|
|
43
|
+
LYD: 3,
|
|
44
|
+
OMR: 3,
|
|
45
|
+
TND: 3,
|
|
46
|
+
CLF: 4,
|
|
47
|
+
UYW: 4,
|
|
48
|
+
};
|
|
49
|
+
/**
|
|
50
|
+
* The ISO 4217 minor-unit exponent of a currency (2 for USD and RSD, 0 for
|
|
51
|
+
* JPY, 3 for IQD). Any code not listed as 0, 3, or 4 is 2.
|
|
52
|
+
*/
|
|
53
|
+
export function currencyMinorUnitExponent(currency) {
|
|
54
|
+
return ISO_EXPONENTS[normalizeCurrency(currency)] ?? 2;
|
|
55
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { InvoiceFieldNames, InvoiceLineFieldNames } from './types.js';
|
|
2
|
+
/** Stable native header field defaults. */
|
|
3
|
+
export declare const invoiceFieldNames: InvoiceFieldNames;
|
|
4
|
+
/** Stable repeated native line field defaults. */
|
|
5
|
+
export declare const invoiceLineFieldNames: InvoiceLineFieldNames;
|
|
6
|
+
//# sourceMappingURL=fields.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fields.d.ts","sourceRoot":"","sources":["../../../src/svelte/invoices/fields.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAC3E,2CAA2C;AAC3C,eAAO,MAAM,iBAAiB,EAAE,iBAQ/B,CAAC;AACF,kDAAkD;AAClD,eAAO,MAAM,qBAAqB,EAAE,qBAUnC,CAAC"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/** Stable native header field defaults. */
|
|
2
|
+
export const invoiceFieldNames = {
|
|
3
|
+
customerId: 'customerId',
|
|
4
|
+
issuedOn: 'issuedOn',
|
|
5
|
+
dueOn: 'dueOn',
|
|
6
|
+
currency: 'currency',
|
|
7
|
+
paymentTerms: 'paymentTerms',
|
|
8
|
+
taxRate: 'invoiceTaxRate',
|
|
9
|
+
intent: 'intent',
|
|
10
|
+
};
|
|
11
|
+
/** Stable repeated native line field defaults. */
|
|
12
|
+
export const invoiceLineFieldNames = {
|
|
13
|
+
key: 'lineKey',
|
|
14
|
+
description: 'lineDescription',
|
|
15
|
+
sku: 'lineSku',
|
|
16
|
+
quantity: 'lineQuantity',
|
|
17
|
+
unitPrice: 'lineUnitPrice',
|
|
18
|
+
discountType: 'lineDiscountType',
|
|
19
|
+
discountValue: 'lineDiscountValue',
|
|
20
|
+
taxMode: 'lineTaxMode',
|
|
21
|
+
taxRate: 'lineTaxRate',
|
|
22
|
+
};
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
export declare const invoicePreparationMessages: {
|
|
2
|
+
readonly 'commerce.preparation.title': "commerce.preparation.title";
|
|
3
|
+
readonly 'commerce.invoice.customer': "commerce.invoice.customer";
|
|
4
|
+
readonly 'commerce.invoice.chooseCustomer': "commerce.invoice.chooseCustomer";
|
|
5
|
+
readonly 'commerce.invoice.issued': "commerce.invoice.issued";
|
|
6
|
+
readonly 'commerce.invoice.due': "commerce.invoice.due";
|
|
7
|
+
readonly 'commerce.invoice.currency': "commerce.invoice.currency";
|
|
8
|
+
readonly 'commerce.invoice.terms': "commerce.invoice.terms";
|
|
9
|
+
readonly 'commerce.invoice.defaultTax': "commerce.invoice.defaultTax";
|
|
10
|
+
readonly 'commerce.invoice.line': "commerce.invoice.line";
|
|
11
|
+
readonly 'commerce.invoice.description': "commerce.invoice.description";
|
|
12
|
+
readonly 'commerce.invoice.sku': "commerce.invoice.sku";
|
|
13
|
+
readonly 'commerce.invoice.quantity': "commerce.invoice.quantity";
|
|
14
|
+
readonly 'commerce.invoice.unitPrice': "commerce.invoice.unitPrice";
|
|
15
|
+
readonly 'commerce.invoice.discountType': "commerce.invoice.discountType";
|
|
16
|
+
readonly 'commerce.invoice.flat': "commerce.invoice.flat";
|
|
17
|
+
readonly 'commerce.invoice.percent': "commerce.invoice.percent";
|
|
18
|
+
readonly 'commerce.invoice.discountValue': "commerce.invoice.discountValue";
|
|
19
|
+
readonly 'commerce.invoice.taxMode': "commerce.invoice.taxMode";
|
|
20
|
+
readonly 'commerce.invoice.inherit': "commerce.invoice.inherit";
|
|
21
|
+
readonly 'commerce.invoice.override': "commerce.invoice.override";
|
|
22
|
+
readonly 'commerce.invoice.taxRate': "commerce.invoice.taxRate";
|
|
23
|
+
readonly 'commerce.invoice.remove': "commerce.invoice.remove";
|
|
24
|
+
readonly 'commerce.invoice.add': "commerce.invoice.add";
|
|
25
|
+
readonly 'commerce.invoice.empty': "commerce.invoice.empty";
|
|
26
|
+
readonly 'commerce.invoice.gross': "commerce.invoice.gross";
|
|
27
|
+
readonly 'commerce.invoice.discount': "commerce.invoice.discount";
|
|
28
|
+
readonly 'commerce.invoice.subtotal': "commerce.invoice.subtotal";
|
|
29
|
+
readonly 'commerce.invoice.tax': "commerce.invoice.tax";
|
|
30
|
+
readonly 'commerce.invoice.total': "commerce.invoice.total";
|
|
31
|
+
readonly 'commerce.invoice.invalid': "commerce.invoice.invalid";
|
|
32
|
+
readonly 'commerce.invoice.preview': "commerce.invoice.preview";
|
|
33
|
+
readonly 'commerce.invoice.retained': "commerce.invoice.retained";
|
|
34
|
+
readonly 'commerce.preparation.allocation': "commerce.preparation.allocation";
|
|
35
|
+
readonly 'commerce.preparation.source': "commerce.preparation.source";
|
|
36
|
+
readonly 'commerce.preparation.reference': "commerce.preparation.reference";
|
|
37
|
+
readonly 'commerce.preparation.choose': "commerce.preparation.choose";
|
|
38
|
+
readonly 'commerce.preparation.retained': "commerce.preparation.retained";
|
|
39
|
+
readonly 'commerce.preparation.available': "commerce.preparation.available";
|
|
40
|
+
readonly 'commerce.preparation.amount': "commerce.preparation.amount";
|
|
41
|
+
readonly 'commerce.preparation.help': "commerce.preparation.help";
|
|
42
|
+
readonly 'commerce.preparation.remove': "commerce.preparation.remove";
|
|
43
|
+
readonly 'commerce.preparation.add': "commerce.preparation.add";
|
|
44
|
+
readonly 'commerce.preparation.save': "commerce.preparation.save";
|
|
45
|
+
readonly 'commerce.preparation.cancel': "commerce.preparation.cancel";
|
|
46
|
+
readonly 'commerce.preparation.restricted': "commerce.preparation.restricted";
|
|
47
|
+
readonly 'commerce.preparation.empty': "commerce.preparation.empty";
|
|
48
|
+
readonly 'commerce.preparation.no_rows': "commerce.preparation.no_rows";
|
|
49
|
+
readonly 'commerce.preparation.readonly': "commerce.preparation.readonly";
|
|
50
|
+
readonly 'commerce.preparation.uncertain': "commerce.preparation.uncertain";
|
|
51
|
+
readonly 'commerce.preparation.review': "commerce.preparation.review";
|
|
52
|
+
};
|
|
53
|
+
//# sourceMappingURL=messages.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"messages.d.ts","sourceRoot":"","sources":["../../../src/svelte/invoices/messages.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,0BAA0B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA4DrC,CAAC"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { defineMessages } from '@happyvertical/smrt-ui/i18n';
|
|
2
|
+
export const invoicePreparationMessages = defineMessages({
|
|
3
|
+
'commerce.preparation.title': 'Prepare invoice',
|
|
4
|
+
'commerce.invoice.customer': 'Customer',
|
|
5
|
+
'commerce.invoice.chooseCustomer': 'Choose a customer',
|
|
6
|
+
'commerce.invoice.issued': 'Invoice date',
|
|
7
|
+
'commerce.invoice.due': 'Due date',
|
|
8
|
+
'commerce.invoice.currency': 'Currency',
|
|
9
|
+
'commerce.invoice.terms': 'Payment terms',
|
|
10
|
+
'commerce.invoice.defaultTax': 'Default tax (%)',
|
|
11
|
+
'commerce.invoice.line': 'Invoice line {number}',
|
|
12
|
+
'commerce.invoice.description': 'Description',
|
|
13
|
+
'commerce.invoice.sku': 'SKU',
|
|
14
|
+
'commerce.invoice.quantity': 'Quantity',
|
|
15
|
+
'commerce.invoice.unitPrice': 'Unit price ({currency})',
|
|
16
|
+
'commerce.invoice.discountType': 'Discount type',
|
|
17
|
+
'commerce.invoice.flat': 'Flat amount',
|
|
18
|
+
'commerce.invoice.percent': 'Percentage',
|
|
19
|
+
'commerce.invoice.discountValue': 'Discount value',
|
|
20
|
+
'commerce.invoice.taxMode': 'Tax mode',
|
|
21
|
+
'commerce.invoice.inherit': 'Inherit default tax',
|
|
22
|
+
'commerce.invoice.override': 'Override tax',
|
|
23
|
+
'commerce.invoice.taxRate': 'Line tax (%)',
|
|
24
|
+
'commerce.invoice.remove': 'Remove line',
|
|
25
|
+
'commerce.invoice.add': 'Add line',
|
|
26
|
+
'commerce.invoice.empty': 'No invoice lines. Add a line to begin.',
|
|
27
|
+
'commerce.invoice.gross': 'Gross',
|
|
28
|
+
'commerce.invoice.discount': 'Discount',
|
|
29
|
+
'commerce.invoice.subtotal': 'Subtotal',
|
|
30
|
+
'commerce.invoice.tax': 'Tax',
|
|
31
|
+
'commerce.invoice.total': 'Total',
|
|
32
|
+
'commerce.invoice.invalid': 'Totals unavailable until quantity, price, discount, tax and currency are valid.',
|
|
33
|
+
'commerce.invoice.preview': 'Calculated preview; the server revalidates prices, tax and totals.',
|
|
34
|
+
'commerce.invoice.retained': 'Retained reference: {reference}',
|
|
35
|
+
'commerce.preparation.allocation': 'Allocation {number}',
|
|
36
|
+
'commerce.preparation.source': 'Reviewed source',
|
|
37
|
+
'commerce.preparation.reference': 'Source reference',
|
|
38
|
+
'commerce.preparation.choose': 'Choose a source or leave this row blank',
|
|
39
|
+
'commerce.preparation.retained': 'Retained source reference: {reference}',
|
|
40
|
+
'commerce.preparation.available': '{label} · Available: {amount} {currency} minor units',
|
|
41
|
+
'commerce.preparation.amount': 'Amount ({currency})',
|
|
42
|
+
'commerce.preparation.help': 'Enter currency amounts, for example 125.00 for CAD. The server validates currency precision and source availability.',
|
|
43
|
+
'commerce.preparation.remove': 'Remove this allocation',
|
|
44
|
+
'commerce.preparation.add': 'Add allocation row',
|
|
45
|
+
'commerce.preparation.save': 'Save invoice draft',
|
|
46
|
+
'commerce.preparation.cancel': 'Cancel',
|
|
47
|
+
'commerce.preparation.restricted': 'Source details are restricted. Enter references supplied through your authorized workflow.',
|
|
48
|
+
'commerce.preparation.empty': 'No reviewed sources are available. Retained references remain visible for review.',
|
|
49
|
+
'commerce.preparation.no_rows': 'No allocations. Add a row to prepare this draft.',
|
|
50
|
+
'commerce.preparation.readonly': 'This draft is read-only.',
|
|
51
|
+
'commerce.preparation.uncertain': 'The outcome is uncertain. Your values and request identity are retained; follow your application’s retry guidance.',
|
|
52
|
+
'commerce.preparation.review': 'Invoice review',
|
|
53
|
+
});
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import type { FormRetryStatus } from '@happyvertical/smrt-ui/form-retry';
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
import type { HTMLFormAttributes } from 'svelte/elements';
|
|
4
|
+
/** Authorized source projection supplied by the application, not fetched by UI. */
|
|
5
|
+
export interface InvoicePreparationSource {
|
|
6
|
+
/** Opaque source reference submitted verbatim. */
|
|
7
|
+
id: string;
|
|
8
|
+
/** Caller-localized source description. */
|
|
9
|
+
label: string;
|
|
10
|
+
/** Available integer minor units, including this draft's reservation if editing. */
|
|
11
|
+
availableMinor: number;
|
|
12
|
+
/** Whether the caller permits selecting this source now. */
|
|
13
|
+
selectable?: boolean;
|
|
14
|
+
}
|
|
15
|
+
/** Lossless native-form values; even invalid submitted strings remain visible. */
|
|
16
|
+
export interface InvoiceAllocationDraft {
|
|
17
|
+
/** Stable row identity, unique within one editor. Never minted as a request key. */
|
|
18
|
+
key: string;
|
|
19
|
+
/** Source reference, including references no longer in the source list. */
|
|
20
|
+
sourceId: string;
|
|
21
|
+
/** Currency-unit text as entered (e.g. CAD 125.00). Server parses currency precision. */
|
|
22
|
+
amount: string;
|
|
23
|
+
/** Retained native removal selection after a rejected action. */
|
|
24
|
+
remove?: boolean;
|
|
25
|
+
/** Caller validation feedback for the source field. */
|
|
26
|
+
sourceError?: string;
|
|
27
|
+
/** Caller validation feedback for the amount field. */
|
|
28
|
+
amountError?: string;
|
|
29
|
+
}
|
|
30
|
+
/** Server-authoritative presentation snapshot; no status is inferred from edits. */
|
|
31
|
+
export interface InvoicePreparationReview {
|
|
32
|
+
/** Caller-localized state, including unknown/future states. */
|
|
33
|
+
label: string;
|
|
34
|
+
/** Caller explanation of approval, invalidation, expiry or review requirements. */
|
|
35
|
+
message?: string;
|
|
36
|
+
}
|
|
37
|
+
/** Native name mapping for adapters. Row names repeat in displayed order. */
|
|
38
|
+
export interface InvoicePreparationFields {
|
|
39
|
+
/** Source identifier field, default sourceId. */
|
|
40
|
+
source: string;
|
|
41
|
+
/** Currency-unit text field, default allocationAmount. */
|
|
42
|
+
amount: string;
|
|
43
|
+
/** Checked zero-based row positions, default removeRow. */
|
|
44
|
+
remove: string;
|
|
45
|
+
/** Clicked submitter name, default intent. */
|
|
46
|
+
intent: string;
|
|
47
|
+
}
|
|
48
|
+
/** Explicit caller request values; arrays preserve repeated native fields. */
|
|
49
|
+
export interface InvoicePreparationHiddenField {
|
|
50
|
+
/** Native request field name. */
|
|
51
|
+
name: string;
|
|
52
|
+
/** Value forwarded without normalization or rotation. */
|
|
53
|
+
value: string;
|
|
54
|
+
}
|
|
55
|
+
/** Invoice draft types shared with the Svelte-free exact calculator. */
|
|
56
|
+
export type { InvoiceDraftValues, InvoiceLineDraft } from './calculations.js';
|
|
57
|
+
import type { InvoiceDraftValues, InvoiceLineDraft } from './calculations.js';
|
|
58
|
+
/** Optional allocation fields, rendered without an enclosing form. */
|
|
59
|
+
export interface InvoiceAllocationFieldsProps {
|
|
60
|
+
/** Currency for allocation presentation. */
|
|
61
|
+
currency: string;
|
|
62
|
+
/** Retained source-allocation rows. */
|
|
63
|
+
allocations: readonly InvoiceAllocationDraft[];
|
|
64
|
+
/** Authorized source projections only. */
|
|
65
|
+
sources?: readonly InvoicePreparationSource[];
|
|
66
|
+
/** Hide sensitive source labels and balances when false. */
|
|
67
|
+
mayReadSources?: boolean;
|
|
68
|
+
/** Presentation capability; server authorizes every action. */
|
|
69
|
+
canEdit?: boolean;
|
|
70
|
+
/** Disable the structural add action while pending. */
|
|
71
|
+
pending?: boolean;
|
|
72
|
+
/** Native allocation field adapters. */
|
|
73
|
+
fields?: Partial<InvoicePreparationFields>;
|
|
74
|
+
/** Native add-allocation intent, default addAllocation. */
|
|
75
|
+
addIntent?: string;
|
|
76
|
+
/** Stable label identity. */
|
|
77
|
+
id?: string;
|
|
78
|
+
}
|
|
79
|
+
/** Editable line field names repeat in displayed order. */
|
|
80
|
+
export interface InvoiceLineFieldNames {
|
|
81
|
+
/** Stable line identity. */
|
|
82
|
+
key: string;
|
|
83
|
+
/** Description text. */
|
|
84
|
+
description: string;
|
|
85
|
+
/** Product/SKU reference text. */
|
|
86
|
+
sku: string;
|
|
87
|
+
/** Fractional quantity text. */
|
|
88
|
+
quantity: string;
|
|
89
|
+
/** Currency-unit price text. */
|
|
90
|
+
unitPrice: string;
|
|
91
|
+
/** Flat or percentage discount selection. */
|
|
92
|
+
discountType: string;
|
|
93
|
+
/** Currency-unit or percent discount text. */
|
|
94
|
+
discountValue: string;
|
|
95
|
+
/** Inherited or explicit tax selection. */
|
|
96
|
+
taxMode: string;
|
|
97
|
+
/** Explicit percentage tax text, including zero. */
|
|
98
|
+
taxRate: string;
|
|
99
|
+
}
|
|
100
|
+
/** Invoice header and submitter name adapters. */
|
|
101
|
+
export interface InvoiceFieldNames {
|
|
102
|
+
/** Customer reference. */
|
|
103
|
+
customerId: string;
|
|
104
|
+
/** Invoice date text. */
|
|
105
|
+
issuedOn: string;
|
|
106
|
+
/** Due date text. */
|
|
107
|
+
dueOn: string;
|
|
108
|
+
/** Currency code. */
|
|
109
|
+
currency: string;
|
|
110
|
+
/** Payment terms text. */
|
|
111
|
+
paymentTerms: string;
|
|
112
|
+
/** Default tax percentage text. */
|
|
113
|
+
taxRate: string;
|
|
114
|
+
/** Clicked submitter name. */
|
|
115
|
+
intent: string;
|
|
116
|
+
}
|
|
117
|
+
/** General invoice line fields; never renders a nested form. */
|
|
118
|
+
export interface InvoiceLineEditorProps {
|
|
119
|
+
/** Lossless caller-owned line values. */
|
|
120
|
+
line: InvoiceLineDraft;
|
|
121
|
+
/** Current currency code for shared calculation. */
|
|
122
|
+
currency: string;
|
|
123
|
+
/** External default tax percentage; explicit line override remains distinct. */
|
|
124
|
+
inheritedTaxRate: string;
|
|
125
|
+
/** One-based display position. */
|
|
126
|
+
number?: number;
|
|
127
|
+
/** Stable control-label prefix. */
|
|
128
|
+
id?: string;
|
|
129
|
+
/** Native field-name adapters. */
|
|
130
|
+
fields?: Partial<InvoiceLineFieldNames>;
|
|
131
|
+
/** Caller errors keyed by canonical line field name. */
|
|
132
|
+
errors?: Partial<Record<keyof InvoiceLineFieldNames, string>>;
|
|
133
|
+
/** Presentation capability. */
|
|
134
|
+
canEdit?: boolean;
|
|
135
|
+
/** Disable structural actions while pending. */
|
|
136
|
+
pending?: boolean;
|
|
137
|
+
/** Native submitter name. */
|
|
138
|
+
intentField?: string;
|
|
139
|
+
/** Remove intent prefix, followed by caller line key. */
|
|
140
|
+
removePrefix?: string;
|
|
141
|
+
/** Optional notification with a new lossless draft; the supplied line is never mutated. */
|
|
142
|
+
onchange?: (line: InvoiceLineDraft) => void;
|
|
143
|
+
}
|
|
144
|
+
/** General native invoice editor with optional domain extensions. */
|
|
145
|
+
export interface InvoiceEditorProps {
|
|
146
|
+
/** Caller native endpoint. */
|
|
147
|
+
action: string;
|
|
148
|
+
/** Native method, default POST. */
|
|
149
|
+
method?: 'post' | 'get';
|
|
150
|
+
/** Stable form identity. */
|
|
151
|
+
id?: string;
|
|
152
|
+
/** Caller-localized title. */
|
|
153
|
+
title?: string;
|
|
154
|
+
/** Retained editable strings; never reconstructed from model numbers. */
|
|
155
|
+
values: InvoiceDraftValues;
|
|
156
|
+
/** Authorized customer projections; retained unknown references stay visible. */
|
|
157
|
+
customers?: readonly {
|
|
158
|
+
id: string;
|
|
159
|
+
label: string;
|
|
160
|
+
}[];
|
|
161
|
+
/** Caller field errors: header name or lineKey.fieldName. */
|
|
162
|
+
errors?: Record<string, string>;
|
|
163
|
+
/** Header and submitter field adapters. */
|
|
164
|
+
fields?: Partial<InvoiceFieldNames>;
|
|
165
|
+
/** Repeated line field adapters. */
|
|
166
|
+
lineFields?: Partial<InvoiceLineFieldNames>;
|
|
167
|
+
/** Presentation capability only; server reauthorizes all mutations. */
|
|
168
|
+
canEdit?: boolean;
|
|
169
|
+
/** Disable submission while a request is pending. */
|
|
170
|
+
pending?: boolean;
|
|
171
|
+
/** Caller-owned retry-controller status. */
|
|
172
|
+
retryStatus?: FormRetryStatus;
|
|
173
|
+
/** Caller error or uncertain-outcome message. */
|
|
174
|
+
message?: string;
|
|
175
|
+
/** Server-authoritative review snapshot. */
|
|
176
|
+
review?: InvoicePreparationReview;
|
|
177
|
+
/** Exact caller tokens/context; repeated names preserved. */
|
|
178
|
+
hiddenFields?: readonly InvoicePreparationHiddenField[];
|
|
179
|
+
/** Save intent, default save. */
|
|
180
|
+
saveIntent?: string;
|
|
181
|
+
/** Add-line intent, default addLine. */
|
|
182
|
+
addIntent?: string;
|
|
183
|
+
/** Remove-line intent prefix, default removeLine:. */
|
|
184
|
+
removePrefix?: string;
|
|
185
|
+
/** Caller-localized save label. */
|
|
186
|
+
saveLabel?: string;
|
|
187
|
+
/** Optional cancel navigation URL. */
|
|
188
|
+
cancelHref?: string;
|
|
189
|
+
/** Domain fields inside the single form; receives current draft for currency-aware extensions. */
|
|
190
|
+
children?: Snippet<[InvoiceDraftValues]>;
|
|
191
|
+
/** Optional enhancement; native submission remains the default. */
|
|
192
|
+
onsubmit?: HTMLFormAttributes['onsubmit'];
|
|
193
|
+
}
|
|
194
|
+
//# sourceMappingURL=types.d.ts.map
|