@happyvertical/smrt-commerce 0.54.4 → 0.55.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/agents/commercial-ui.md +8 -0
- package/agents/customer-vendor-ui.md +15 -0
- package/dist/chunks/{calculations-DPuldr1J.js → calculations-DwBf83UE.js} +7 -30
- package/dist/chunks/calculations-DwBf83UE.js.map +1 -0
- package/dist/chunks/{units-CJcAOQm1.js → units-BhVZswof.js} +2 -2
- package/dist/chunks/{units-CJcAOQm1.js.map → units-BhVZswof.js.map} +1 -1
- package/dist/index.js +3 -3
- package/dist/index.js.map +1 -1
- package/dist/invoices.js +1 -1
- package/dist/manifest.json +1 -1
- package/dist/smrt-knowledge.json +5 -5
- package/dist/svelte/components/CustomerDirectory.svelte +28 -2
- package/dist/svelte/components/CustomerDirectory.svelte.d.ts +9 -1
- package/dist/svelte/components/CustomerDirectory.svelte.d.ts.map +1 -1
- package/dist/svelte/components/InvoiceCard.svelte +57 -7
- package/dist/svelte/components/InvoiceCard.svelte.d.ts +14 -1
- package/dist/svelte/components/InvoiceCard.svelte.d.ts.map +1 -1
- package/dist/svelte/components/InvoiceHeader.svelte +34 -14
- package/dist/svelte/components/InvoiceHeader.svelte.d.ts +9 -1
- package/dist/svelte/components/InvoiceHeader.svelte.d.ts.map +1 -1
- package/dist/svelte/components/InvoiceLineItems.svelte +60 -12
- package/dist/svelte/components/InvoiceLineItems.svelte.d.ts +14 -1
- package/dist/svelte/components/InvoiceLineItems.svelte.d.ts.map +1 -1
- package/dist/svelte/components/InvoiceTotals.svelte +44 -13
- package/dist/svelte/components/InvoiceTotals.svelte.d.ts +14 -1
- package/dist/svelte/components/InvoiceTotals.svelte.d.ts.map +1 -1
- package/dist/svelte/components/VendorDirectory.svelte +28 -2
- package/dist/svelte/components/VendorDirectory.svelte.d.ts +9 -1
- package/dist/svelte/components/VendorDirectory.svelte.d.ts.map +1 -1
- package/dist/svelte/i18n.d.ts +2 -0
- package/dist/svelte/i18n.d.ts.map +1 -1
- package/dist/svelte/i18n.js +2 -0
- package/dist/svelte/index.d.ts +3 -1
- package/dist/svelte/index.d.ts.map +1 -1
- package/dist/svelte/index.js +1 -0
- package/dist/svelte/invoice-display.d.ts +98 -0
- package/dist/svelte/invoice-display.d.ts.map +1 -0
- package/dist/svelte/invoice-display.js +21 -0
- package/dist/svelte/invoices/currency.d.ts +3 -1
- package/dist/svelte/invoices/currency.d.ts.map +1 -1
- package/dist/svelte/invoices/currency.js +11 -38
- package/dist/svelte/party-types.d.ts +2 -0
- package/dist/svelte/party-types.d.ts.map +1 -1
- package/package.json +9 -9
- package/dist/chunks/calculations-DPuldr1J.js.map +0 -1
package/dist/invoices.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { i as resolveInvoiceLineDraft, n as calculateInvoiceLine, r as calculateInvoiceMinorLine, t as calculateInvoiceDraft } from "./chunks/calculations-
|
|
1
|
+
import { i as resolveInvoiceLineDraft, n as calculateInvoiceLine, r as calculateInvoiceMinorLine, t as calculateInvoiceDraft } from "./chunks/calculations-DwBf83UE.js";
|
|
2
2
|
export { calculateInvoiceDraft, calculateInvoiceLine, calculateInvoiceMinorLine, resolveInvoiceLineDraft };
|
package/dist/manifest.json
CHANGED
package/dist/smrt-knowledge.json
CHANGED
|
@@ -3,15 +3,15 @@
|
|
|
3
3
|
"sensitiveFieldsExcluded": true,
|
|
4
4
|
"generatedAt": "1970-01-01T00:00:00.000Z",
|
|
5
5
|
"packageName": "@happyvertical/smrt-commerce",
|
|
6
|
-
"packageVersion": "0.
|
|
6
|
+
"packageVersion": "0.55.1",
|
|
7
7
|
"sourceManifestPath": "dist/manifest.json",
|
|
8
8
|
"agentDocPath": "AGENTS.md",
|
|
9
9
|
"sourceHashes": {
|
|
10
|
-
"manifest": "
|
|
11
|
-
"packageJson": "
|
|
10
|
+
"manifest": "a9bdb6f2fa4d16cacc642dec06b66afa6065ab4fe9d59f4e9722af1c88aa04fc",
|
|
11
|
+
"packageJson": "b49efffddbbf3e3c5a2df0e650e30f8c6745e02b838275d466ba463093a8b3fb",
|
|
12
12
|
"agents": "350f098d5e6fad88cae8e1e63ebbbee51d8b33ca065273827cd47edc3a841a98",
|
|
13
13
|
"moduleDoc:agents/invoice-quantity.md": "bc3b617d4fb8cc87511763b281283107557ba64795e64522193ef967a3715d32",
|
|
14
|
-
"moduleDoc:agents/commercial-ui.md": "
|
|
14
|
+
"moduleDoc:agents/commercial-ui.md": "e806954526ffcb41864a185a42bb5425e58db107aafc95c15152a30c49167f43",
|
|
15
15
|
"moduleDoc:agents/quote-ui.md": "514568e4e7815c47113627ac044a1f31a6933377b3f6c70f5d54b2508d94200c",
|
|
16
16
|
"moduleDoc:agents/purchasing-ui.md": "51ab713d89949c9779973191dfa3529e5120d087321236fc48ad1948c88f9866"
|
|
17
17
|
},
|
|
@@ -19526,7 +19526,7 @@
|
|
|
19526
19526
|
{
|
|
19527
19527
|
"path": "agents/commercial-ui.md",
|
|
19528
19528
|
"module": "commercial-ui",
|
|
19529
|
-
"content": "# Commercial UI composition\n\nThe commercial UI release is tracked in [#3423](https://github.com/happyvertical/smrt/issues/3423).\nComponents compose the provider-free controls in `@happyvertical/smrt-ui/forms`\nand visual controls in `@happyvertical/smrt-ui`. They do not require SvelteKit,\na database, or the top-level `smrt-svelte` integration package.\n\n## Native forms and recovery\n\nThe application owns action URLs, HTTP methods, submitter names/values, field\nnames, request identity, tenant context and returned validation errors. Native\nforms use the base `Form` with `preventDefault={false}`. Enhancement is optional;\na handler that takes over submission is responsible for preventing navigation.\n\nA failed action returns the submitted strings, including invalid amounts and\nrepeated fields, alongside errors. Components must not reconstruct these values\nfrom saved models or generate a replacement request token. Unknown commit outcomes\nretain the same identity and payload until the application resolves the result.\nFile inputs cannot be repopulated after navigation: explain that the file must be\nselected again and retain the caller's recovery identity.\n\nThe existing [form retry contract](../../../docs/content/form-retry.md) provides\noptional browser enhancement. It does not replace server-side tenant/actor checks\nor idempotency. Permission props control presentation only; applications must\nsupply already-authorized data and reauthorize every submitted action.\n\n## Amounts and public compatibility\n\nNew commercial editors use explicitly documented integer minor-unit amounts.\nEditable values remain strings until the owning server validates them; blank,\ninvalid and zero are distinct. Currency and its display precision are explicit.\nDo not mix currencies in totals or infer an unknown allocation as zero.\n\nThe original invoice display DTOs in `src/svelte/types.ts` and their components\naccept decimal major-unit amounts. Preserve that published behavior. Applications\nadapting model amounts into those legacy components must explicitly convert units;\ndo not pass a model's integer amount directly or silently change a legacy prop's\nmeaning. New editor DTOs are separate contracts.\n\n## Domain boundaries\n\nCustomer and Vendor reference Profiles identity data. A consumer's Client record\nis not automatically a Commerce Customer; UI adoption performs no identity merge\nor persistence migration. Domain-specific contact fields, trades, project/package\nallocations and approval policies enter through typed adapters and extension slots.\n\nQuote/estimate preparation, customer acceptance, vendor award, expense review and\ninvoice approval are separate decisions. A UI status or selection must never\nimplicitly perform another transition. The server supplies current decision and\nsource availability, including restricted-reader presentation.\n\n## Backend capability assessment\n\nAssessment baseline: SMRT `98c872be4` and reviewed DomaCraft epic-138\n`b03b322f75fbfdcb9193b18e73d3ec818ef18811`.\n\n| Responsibility | Existing capability | Release decision |\n| --- | --- | --- |\n| Commitment versus actual | `ExpenseCollection.commitmentPosition()` sums reviewed, non-duplicate expenses in the commitment currency, optionally per line. | Reuse Expenses; keep DomaCraft award lineage and package-cap constraints in its adapter. |\n| Receipt identity and duplicate review | `ExpenseReceiptCollection` owns receipt joins and duplicate detection; `Expense` owns guarded review transitions. | Reuse these contracts. Private upload authorization, draft capacity and storage compensation stay in application composition. |\n| Idempotent submission and uncertain outcomes | Core `runOnce()` and UI `createFormRetry()` provide shared server/browser primitives. | Preserve the caller's request identity. Do not replace DomaCraft's transaction-bound financial submission logic without proving equivalent transaction and recovery semantics. |\n| Invoice source reservations | DomaCraft `finance.ts` reserves its own cost allocations under project transaction locks; Commerce Invoice has no equivalent source-allocation contract. | Keep the reservation service local: extracting it changes a published financial contract and requires an agreed cross-domain source model. UI accepts supplied source availability. |\n| Source-change approval invalidation | DomaCraft `withdrawInvoiceApprovalForSource()` and invoice fingerprint checks bind project policy, actor decisions and retained sources. | Keep this policy local. Shared presentation renders server-supplied approval state and cannot authorize execution. |\n\nNo generic backend redesign is required by these UI slices. A future extraction\nneeds evidence that a contract works beyond the application-specific project and\npolicy model; UI reuse alone does not establish that requirement.\n\n## Validation and consumer adoption\n\nEach slice covers its public exports, SSR/native payload, retained errors,\nread-only and empty states, keyboard operation and a 390px viewport. The release\nalso validates the complete touched packages and integrated workbench. A browser\nviewport test does not establish physical-device camera behavior.\n\nDomaCraft adopts a published release through normal dependency admission. Its\nnative financial, authorization, tenant and uncertain-outcome tests remain required;\nshared component tests do not establish those server-side properties.\n"
|
|
19529
|
+
"content": "# Commercial UI composition\n\nThe commercial UI release is tracked in [#3423](https://github.com/happyvertical/smrt/issues/3423).\nComponents compose the provider-free controls in `@happyvertical/smrt-ui/forms`\nand visual controls in `@happyvertical/smrt-ui`. They do not require SvelteKit,\na database, or the top-level `smrt-svelte` integration package.\n\n## Native forms and recovery\n\nThe application owns action URLs, HTTP methods, submitter names/values, field\nnames, request identity, tenant context and returned validation errors. Native\nforms use the base `Form` with `preventDefault={false}`. Enhancement is optional;\na handler that takes over submission is responsible for preventing navigation.\n\nA failed action returns the submitted strings, including invalid amounts and\nrepeated fields, alongside errors. Components must not reconstruct these values\nfrom saved models or generate a replacement request token. Unknown commit outcomes\nretain the same identity and payload until the application resolves the result.\nFile inputs cannot be repopulated after navigation: explain that the file must be\nselected again and retain the caller's recovery identity.\n\nThe existing [form retry contract](../../../docs/content/form-retry.md) provides\noptional browser enhancement. It does not replace server-side tenant/actor checks\nor idempotency. Permission props control presentation only; applications must\nsupply already-authorized data and reauthorize every submitted action.\n\n## Amounts and public compatibility\n\nNew commercial editors use explicitly documented integer minor-unit amounts.\nEditable values remain strings until the owning server validates them; blank,\ninvalid and zero are distinct. Currency and its display precision are explicit.\nDo not mix currencies in totals or infer an unknown allocation as zero.\n\nThe original invoice display DTOs in `src/svelte/types.ts` and their components\naccept decimal major-unit amounts. Preserve that published behavior. Applications\nadapting model amounts into those legacy components must explicitly convert units;\ndo not pass a model's integer amount directly or silently change a legacy prop's\nmeaning. New editor DTOs are separate contracts.\n\nApproved or otherwise retained invoice facts use the explicit `retained` display\nbranches and DTOs from `src/svelte/invoice-display.ts`. Their amounts are safe\nintegers in currency minor units, formatted through the package ISO exponent\ntable without floating-point division. Retained line subtotals, discounts, tax\nrows, holdbacks, totals, paid amounts and payable amounts are caller-authoritative;\nthe components never derive one from another, and an omitted fact stays omitted.\nApplication-specific evidence belongs in the typed detail snippets.\n\n## Domain boundaries\n\nCustomer and Vendor reference Profiles identity data. A consumer's Client record\nis not automatically a Commerce Customer; UI adoption performs no identity merge\nor persistence migration. Domain-specific contact fields, trades, project/package\nallocations and approval policies enter through typed adapters and extension slots.\n\nQuote/estimate preparation, customer acceptance, vendor award, expense review and\ninvoice approval are separate decisions. A UI status or selection must never\nimplicitly perform another transition. The server supplies current decision and\nsource availability, including restricted-reader presentation.\n\n## Backend capability assessment\n\nAssessment baseline: SMRT `98c872be4` and reviewed DomaCraft epic-138\n`b03b322f75fbfdcb9193b18e73d3ec818ef18811`.\n\n| Responsibility | Existing capability | Release decision |\n| --- | --- | --- |\n| Commitment versus actual | `ExpenseCollection.commitmentPosition()` sums reviewed, non-duplicate expenses in the commitment currency, optionally per line. | Reuse Expenses; keep DomaCraft award lineage and package-cap constraints in its adapter. |\n| Receipt identity and duplicate review | `ExpenseReceiptCollection` owns receipt joins and duplicate detection; `Expense` owns guarded review transitions. | Reuse these contracts. Private upload authorization, draft capacity and storage compensation stay in application composition. |\n| Idempotent submission and uncertain outcomes | Core `runOnce()` and UI `createFormRetry()` provide shared server/browser primitives. | Preserve the caller's request identity. Do not replace DomaCraft's transaction-bound financial submission logic without proving equivalent transaction and recovery semantics. |\n| Invoice source reservations | DomaCraft `finance.ts` reserves its own cost allocations under project transaction locks; Commerce Invoice has no equivalent source-allocation contract. | Keep the reservation service local: extracting it changes a published financial contract and requires an agreed cross-domain source model. UI accepts supplied source availability. |\n| Source-change approval invalidation | DomaCraft `withdrawInvoiceApprovalForSource()` and invoice fingerprint checks bind project policy, actor decisions and retained sources. | Keep this policy local. Shared presentation renders server-supplied approval state and cannot authorize execution. |\n\nNo generic backend redesign is required by these UI slices. A future extraction\nneeds evidence that a contract works beyond the application-specific project and\npolicy model; UI reuse alone does not establish that requirement.\n\n## Validation and consumer adoption\n\nEach slice covers its public exports, SSR/native payload, retained errors,\nread-only and empty states, keyboard operation and a 390px viewport. The release\nalso validates the complete touched packages and integrated workbench. A browser\nviewport test does not establish physical-device camera behavior.\n\nDomaCraft adopts a published release through normal dependency admission. Its\nnative financial, authorization, tenant and uncertain-outcome tests remain required;\nshared component tests do not establish those server-side properties.\n"
|
|
19530
19530
|
},
|
|
19531
19531
|
{
|
|
19532
19532
|
"path": "agents/quote-ui.md",
|
|
@@ -9,6 +9,7 @@ import { M } from '../i18n.js';
|
|
|
9
9
|
import type {
|
|
10
10
|
CustomerDisplayData,
|
|
11
11
|
PartyDirectoryExtension,
|
|
12
|
+
PartyDirectoryFilters,
|
|
12
13
|
PartyDirectoryItem,
|
|
13
14
|
PartySurfaceLabels,
|
|
14
15
|
} from '../party-types.js';
|
|
@@ -35,6 +36,14 @@ export interface Props {
|
|
|
35
36
|
statusName?: string;
|
|
36
37
|
/** Caller-owned state carried through the native GET form. Query and status names are ignored. */
|
|
37
38
|
hiddenFields?: Record<string, string>;
|
|
39
|
+
/** Caller-owned GET controls rendered beside search and status. */
|
|
40
|
+
filters?: PartyDirectoryFilters;
|
|
41
|
+
/** Names rendered by `filters`; same-named hidden fields are omitted. */
|
|
42
|
+
filterNames?: string[];
|
|
43
|
+
/** Whether caller-owned filters currently narrow the result. */
|
|
44
|
+
hasAdditionalFilters?: boolean;
|
|
45
|
+
/** Overrides the empty-state guidance for this directory. */
|
|
46
|
+
emptyDescription?: string;
|
|
38
47
|
/** Caller-owned URL for the previous page. */
|
|
39
48
|
previousHref?: string;
|
|
40
49
|
/** Caller-owned URL for the next page. */
|
|
@@ -64,6 +73,10 @@ const {
|
|
|
64
73
|
queryName = 'q',
|
|
65
74
|
statusName = 'status',
|
|
66
75
|
hiddenFields = {},
|
|
76
|
+
filters,
|
|
77
|
+
filterNames = [],
|
|
78
|
+
hasAdditionalFilters = false,
|
|
79
|
+
emptyDescription,
|
|
67
80
|
previousHref,
|
|
68
81
|
nextHref,
|
|
69
82
|
addHref,
|
|
@@ -87,9 +100,21 @@ const last = $derived(
|
|
|
87
100
|
);
|
|
88
101
|
const searchHiddenFields = $derived(
|
|
89
102
|
Object.entries(hiddenFields).filter(
|
|
90
|
-
([name]) =>
|
|
103
|
+
([name]) =>
|
|
104
|
+
name !== queryName && name !== statusName && !filterNames.includes(name),
|
|
91
105
|
),
|
|
92
106
|
);
|
|
107
|
+
const hasActiveFilters = $derived(
|
|
108
|
+
query.trim().length > 0 || status.length > 0 || hasAdditionalFilters,
|
|
109
|
+
);
|
|
110
|
+
const resolvedEmptyDescription = $derived(
|
|
111
|
+
emptyDescription ??
|
|
112
|
+
(hasActiveFilters
|
|
113
|
+
? t(M['commerce.party.try_filters'])
|
|
114
|
+
: canCreate && addHref
|
|
115
|
+
? t(M['commerce.party.empty_initial_create'], { singular })
|
|
116
|
+
: t(M['commerce.party.empty_initial'], { plural })),
|
|
117
|
+
);
|
|
93
118
|
|
|
94
119
|
function statusLabel(value: string): string {
|
|
95
120
|
if (value === 'active') return t(M['commerce.party.status_active']);
|
|
@@ -131,6 +156,7 @@ function customerTypeLabel(value: string | undefined): string {
|
|
|
131
156
|
<option value="suspended">{t(M['commerce.party.status_suspended'])}</option>
|
|
132
157
|
</Select>
|
|
133
158
|
</FormGroup>
|
|
159
|
+
{@render filters?.()}
|
|
134
160
|
<Button type="submit" variant="secondary">{t(M['commerce.party.search'])}</Button>
|
|
135
161
|
</Form>
|
|
136
162
|
|
|
@@ -141,7 +167,7 @@ function customerTypeLabel(value: string | undefined): string {
|
|
|
141
167
|
<Skeleton height="6rem" /><Skeleton height="6rem" /><Skeleton height="6rem" />
|
|
142
168
|
</div>
|
|
143
169
|
{:else if items.length === 0}
|
|
144
|
-
<EmptyState title={labels.empty ?? t(M['commerce.customer.no_results'])} description={
|
|
170
|
+
<EmptyState title={labels.empty ?? t(M['commerce.customer.no_results'])} description={resolvedEmptyDescription} />
|
|
145
171
|
{:else}
|
|
146
172
|
<p class="result-count">
|
|
147
173
|
{pageSize === undefined
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { CustomerDisplayData, PartyDirectoryExtension, PartyDirectoryItem, PartySurfaceLabels } from '../party-types.js';
|
|
1
|
+
import type { CustomerDisplayData, PartyDirectoryExtension, PartyDirectoryFilters, PartyDirectoryItem, PartySurfaceLabels } from '../party-types.js';
|
|
2
2
|
/** Props for the searchable, paginated customer directory. */
|
|
3
3
|
export interface Props {
|
|
4
4
|
/** Customer rows and caller-owned detail links. */
|
|
@@ -21,6 +21,14 @@ export interface Props {
|
|
|
21
21
|
statusName?: string;
|
|
22
22
|
/** Caller-owned state carried through the native GET form. Query and status names are ignored. */
|
|
23
23
|
hiddenFields?: Record<string, string>;
|
|
24
|
+
/** Caller-owned GET controls rendered beside search and status. */
|
|
25
|
+
filters?: PartyDirectoryFilters;
|
|
26
|
+
/** Names rendered by `filters`; same-named hidden fields are omitted. */
|
|
27
|
+
filterNames?: string[];
|
|
28
|
+
/** Whether caller-owned filters currently narrow the result. */
|
|
29
|
+
hasAdditionalFilters?: boolean;
|
|
30
|
+
/** Overrides the empty-state guidance for this directory. */
|
|
31
|
+
emptyDescription?: string;
|
|
24
32
|
/** Caller-owned URL for the previous page. */
|
|
25
33
|
previousHref?: string;
|
|
26
34
|
/** Caller-owned URL for the next page. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CustomerDirectory.svelte.d.ts","sourceRoot":"","sources":["../../../src/svelte/components/CustomerDirectory.svelte.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EACV,mBAAmB,EACnB,uBAAuB,EACvB,kBAAkB,EAClB,kBAAkB,EACnB,MAAM,mBAAmB,CAAC;AAG3B,8DAA8D;AAC9D,MAAM,WAAW,KAAK;IACpB,mDAAmD;IACnD,KAAK,CAAC,EAAE,kBAAkB,CAAC,mBAAmB,CAAC,EAAE,CAAC;IAClD,6CAA6C;IAC7C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,qCAAqC;IACrC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,yCAAyC;IACzC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,2DAA2D;IAC3D,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,6DAA6D;IAC7D,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,mDAAmD;IACnD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,iDAAiD;IACjD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,2CAA2C;IAC3C,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,kGAAkG;IAClG,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACtC,8CAA8C;IAC9C,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,0CAA0C;IAC1C,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,gDAAgD;IAChD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,2DAA2D;IAC3D,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,4DAA4D;IAC5D,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,qEAAqE;IACrE,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,uDAAuD;IACvD,MAAM,CAAC,EAAE,kBAAkB,CAAC;IAC5B,uDAAuD;IACvD,SAAS,CAAC,EAAE,uBAAuB,CAAC,mBAAmB,CAAC,CAAC;CAC1D;
|
|
1
|
+
{"version":3,"file":"CustomerDirectory.svelte.d.ts","sourceRoot":"","sources":["../../../src/svelte/components/CustomerDirectory.svelte.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EACV,mBAAmB,EACnB,uBAAuB,EACvB,qBAAqB,EACrB,kBAAkB,EAClB,kBAAkB,EACnB,MAAM,mBAAmB,CAAC;AAG3B,8DAA8D;AAC9D,MAAM,WAAW,KAAK;IACpB,mDAAmD;IACnD,KAAK,CAAC,EAAE,kBAAkB,CAAC,mBAAmB,CAAC,EAAE,CAAC;IAClD,6CAA6C;IAC7C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,qCAAqC;IACrC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,yCAAyC;IACzC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,2DAA2D;IAC3D,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,6DAA6D;IAC7D,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,mDAAmD;IACnD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,iDAAiD;IACjD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,2CAA2C;IAC3C,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,kGAAkG;IAClG,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACtC,mEAAmE;IACnE,OAAO,CAAC,EAAE,qBAAqB,CAAC;IAChC,yEAAyE;IACzE,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,gEAAgE;IAChE,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,6DAA6D;IAC7D,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,8CAA8C;IAC9C,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,0CAA0C;IAC1C,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,gDAAgD;IAChD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,2DAA2D;IAC3D,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,4DAA4D;IAC5D,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,qEAAqE;IACrE,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,uDAAuD;IACvD,MAAM,CAAC,EAAE,kBAAkB,CAAC;IAC5B,uDAAuD;IACvD,SAAS,CAAC,EAAE,uBAAuB,CAAC,mBAAmB,CAAC,CAAC;CAC1D;AAuJD,QAAA,MAAM,iBAAiB,2CAAwC,CAAC;AAChE,KAAK,iBAAiB,GAAG,UAAU,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAC9D,eAAe,iBAAiB,CAAC"}
|
|
@@ -7,10 +7,15 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import { ripple } from '@happyvertical/smrt-ui';
|
|
10
|
-
import type {
|
|
10
|
+
import type { Snippet } from 'svelte';
|
|
11
|
+
import {
|
|
12
|
+
formatInvoiceMinorUnits,
|
|
13
|
+
type RetainedInvoiceCardView,
|
|
14
|
+
} from '../invoice-display.js';
|
|
15
|
+
import type { InvoiceData } from '../types.js';
|
|
11
16
|
|
|
12
17
|
/** Props for InvoiceCard component */
|
|
13
|
-
export interface
|
|
18
|
+
export interface LegacyInvoiceCardProps {
|
|
14
19
|
/** Invoice data */
|
|
15
20
|
invoice: InvoiceData;
|
|
16
21
|
/** Currency code */
|
|
@@ -21,7 +26,28 @@ export interface Props {
|
|
|
21
26
|
onclick?: () => void;
|
|
22
27
|
}
|
|
23
28
|
|
|
24
|
-
|
|
29
|
+
/** Exact retained card branch. */
|
|
30
|
+
export interface RetainedInvoiceCardProps {
|
|
31
|
+
/** Caller-authoritative retained card. */
|
|
32
|
+
retained: RetainedInvoiceCardView;
|
|
33
|
+
/** Navigation href. */
|
|
34
|
+
href?: string;
|
|
35
|
+
/** Renders caller-owned retained evidence. */
|
|
36
|
+
details?: Snippet<[RetainedInvoiceCardView]>;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Legacy decimal-major card or exact retained card. */
|
|
40
|
+
export type Props = LegacyInvoiceCardProps | RetainedInvoiceCardProps;
|
|
41
|
+
|
|
42
|
+
const props: Props = $props();
|
|
43
|
+
const retained = $derived('retained' in props ? props.retained : null);
|
|
44
|
+
const invoice = $derived(retained ?? (props as LegacyInvoiceCardProps).invoice);
|
|
45
|
+
const currency = $derived(
|
|
46
|
+
retained?.currency ?? (props as LegacyInvoiceCardProps).currency ?? 'CAD',
|
|
47
|
+
);
|
|
48
|
+
const href = $derived(props.href);
|
|
49
|
+
const onclick = $derived('onclick' in props ? props.onclick : undefined);
|
|
50
|
+
const details = $derived('details' in props ? props.details : undefined);
|
|
25
51
|
|
|
26
52
|
// This legacy InvoiceData display contract uses major-unit amounts.
|
|
27
53
|
// Convert integer minor-unit model values in the caller adapter.
|
|
@@ -33,6 +59,12 @@ function formatMoney(amount: number): string {
|
|
|
33
59
|
}).format(amount);
|
|
34
60
|
}
|
|
35
61
|
|
|
62
|
+
const amountText = $derived(
|
|
63
|
+
retained
|
|
64
|
+
? formatInvoiceMinorUnits(retained.totalMinor, retained.currency)
|
|
65
|
+
: formatMoney((invoice as InvoiceData).totalAmount),
|
|
66
|
+
);
|
|
67
|
+
|
|
36
68
|
// Format date
|
|
37
69
|
function formatDate(date: Date | string | null | undefined): string {
|
|
38
70
|
if (!date) return '';
|
|
@@ -63,6 +95,7 @@ const statusClass = $derived.by(() => {
|
|
|
63
95
|
|
|
64
96
|
// Check if overdue
|
|
65
97
|
const isOverdue = $derived.by(() => {
|
|
98
|
+
if (retained) return false;
|
|
66
99
|
if (invoice.status === 'paid' || invoice.status === 'cancelled') return false;
|
|
67
100
|
if (!invoice.dueDate) return false;
|
|
68
101
|
const due =
|
|
@@ -78,7 +111,7 @@ const isOverdue = $derived.by(() => {
|
|
|
78
111
|
<div class="card-header">
|
|
79
112
|
<span class="invoice-number">{invoice.invoiceNumber}</span>
|
|
80
113
|
<span class="status-badge {isOverdue ? 'status-error' : statusClass}">
|
|
81
|
-
{isOverdue ? 'Overdue' : invoice.status}
|
|
114
|
+
{isOverdue ? 'Overdue' : retained?.statusLabel ?? invoice.status}
|
|
82
115
|
</span>
|
|
83
116
|
</div>
|
|
84
117
|
|
|
@@ -86,7 +119,7 @@ const isOverdue = $derived.by(() => {
|
|
|
86
119
|
{#if invoice.customerName}
|
|
87
120
|
<span class="customer-name">{invoice.customerName}</span>
|
|
88
121
|
{/if}
|
|
89
|
-
<span class="invoice-amount">{
|
|
122
|
+
<span class="invoice-amount">{amountText}</span>
|
|
90
123
|
</div>
|
|
91
124
|
|
|
92
125
|
<div class="card-footer">
|
|
@@ -97,7 +130,24 @@ const isOverdue = $derived.by(() => {
|
|
|
97
130
|
</span>
|
|
98
131
|
{/if}
|
|
99
132
|
</div>
|
|
133
|
+
{#if retained}{@render details?.(retained)}{/if}
|
|
100
134
|
</a>
|
|
135
|
+
{:else if retained}
|
|
136
|
+
<article class="invoice-card">
|
|
137
|
+
<div class="card-header">
|
|
138
|
+
<span class="invoice-number">{invoice.invoiceNumber}</span>
|
|
139
|
+
<span class="status-badge {statusClass}">{retained.statusLabel}</span>
|
|
140
|
+
</div>
|
|
141
|
+
<div class="card-body">
|
|
142
|
+
{#if invoice.customerName}<span class="customer-name">{invoice.customerName}</span>{/if}
|
|
143
|
+
<span class="invoice-amount">{amountText}</span>
|
|
144
|
+
</div>
|
|
145
|
+
<div class="card-footer">
|
|
146
|
+
<span class="issue-date">{formatDate(invoice.issueDate)}</span>
|
|
147
|
+
{#if invoice.dueDate}<span class="due-date">Due {formatDate(invoice.dueDate)}</span>{/if}
|
|
148
|
+
</div>
|
|
149
|
+
{@render details?.(retained)}
|
|
150
|
+
</article>
|
|
101
151
|
{:else}
|
|
102
152
|
<!-- raw-primitive-allow: large pressable invoice card wrapping rich header/body/footer content with use:ripple, a structural selection control no Button primitive should own (mirrors the href anchor branch) -->
|
|
103
153
|
<button type="button" class="invoice-card" onclick={onclick} use:ripple>
|
|
@@ -112,7 +162,7 @@ const isOverdue = $derived.by(() => {
|
|
|
112
162
|
{#if invoice.customerName}
|
|
113
163
|
<span class="customer-name">{invoice.customerName}</span>
|
|
114
164
|
{/if}
|
|
115
|
-
<span class="invoice-amount">{
|
|
165
|
+
<span class="invoice-amount">{amountText}</span>
|
|
116
166
|
</div>
|
|
117
167
|
|
|
118
168
|
<div class="card-footer">
|
|
@@ -232,4 +282,4 @@ const isOverdue = $derived.by(() => {
|
|
|
232
282
|
color: var(--smrt-color-error);
|
|
233
283
|
font-weight: var(--smrt-typography-weight-semibold, 600);
|
|
234
284
|
}
|
|
235
|
-
</style>
|
|
285
|
+
</style>
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
import { type RetainedInvoiceCardView } from '../invoice-display.js';
|
|
1
3
|
import type { InvoiceData } from '../types.js';
|
|
2
4
|
/** Props for InvoiceCard component */
|
|
3
|
-
export interface
|
|
5
|
+
export interface LegacyInvoiceCardProps {
|
|
4
6
|
/** Invoice data */
|
|
5
7
|
invoice: InvoiceData;
|
|
6
8
|
/** Currency code */
|
|
@@ -10,6 +12,17 @@ export interface Props {
|
|
|
10
12
|
/** Click handler */
|
|
11
13
|
onclick?: () => void;
|
|
12
14
|
}
|
|
15
|
+
/** Exact retained card branch. */
|
|
16
|
+
export interface RetainedInvoiceCardProps {
|
|
17
|
+
/** Caller-authoritative retained card. */
|
|
18
|
+
retained: RetainedInvoiceCardView;
|
|
19
|
+
/** Navigation href. */
|
|
20
|
+
href?: string;
|
|
21
|
+
/** Renders caller-owned retained evidence. */
|
|
22
|
+
details?: Snippet<[RetainedInvoiceCardView]>;
|
|
23
|
+
}
|
|
24
|
+
/** Legacy decimal-major card or exact retained card. */
|
|
25
|
+
export type Props = LegacyInvoiceCardProps | RetainedInvoiceCardProps;
|
|
13
26
|
declare const InvoiceCard: import("svelte").Component<Props, {}, "">;
|
|
14
27
|
type InvoiceCard = ReturnType<typeof InvoiceCard>;
|
|
15
28
|
export default InvoiceCard;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"InvoiceCard.svelte.d.ts","sourceRoot":"","sources":["../../../src/svelte/components/InvoiceCard.svelte.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,WAAW,
|
|
1
|
+
{"version":3,"file":"InvoiceCard.svelte.d.ts","sourceRoot":"","sources":["../../../src/svelte/components/InvoiceCard.svelte.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AACtC,OAAO,EAEL,KAAK,uBAAuB,EAC7B,MAAM,uBAAuB,CAAC;AAC/B,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAG/C,sCAAsC;AACtC,MAAM,WAAW,sBAAsB;IACrC,mBAAmB;IACnB,OAAO,EAAE,WAAW,CAAC;IACrB,oBAAoB;IACpB,QAAQ,CAAC,EAAE,KAAK,GAAG,KAAK,CAAC;IACzB,sBAAsB;IACtB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,oBAAoB;IACpB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;CACtB;AAED,kCAAkC;AAClC,MAAM,WAAW,wBAAwB;IACvC,0CAA0C;IAC1C,QAAQ,EAAE,uBAAuB,CAAC;IAClC,uBAAuB;IACvB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,8CAA8C;IAC9C,OAAO,CAAC,EAAE,OAAO,CAAC,CAAC,uBAAuB,CAAC,CAAC,CAAC;CAC9C;AAED,wDAAwD;AACxD,MAAM,MAAM,KAAK,GAAG,sBAAsB,GAAG,wBAAwB,CAAC;AAyJtE,QAAA,MAAM,WAAW,2CAAwC,CAAC;AAC1D,KAAK,WAAW,GAAG,UAAU,CAAC,OAAO,WAAW,CAAC,CAAC;AAClD,eAAe,WAAW,CAAC"}
|
|
@@ -7,12 +7,13 @@
|
|
|
7
7
|
|
|
8
8
|
import { useI18n } from '@happyvertical/smrt-ui/i18n';
|
|
9
9
|
import { M } from '../i18n.js';
|
|
10
|
+
import type { RetainedInvoiceHeaderView } from '../invoice-display.js';
|
|
10
11
|
import type { InvoiceStatus } from '../types.js';
|
|
11
12
|
|
|
12
13
|
const { t } = useI18n();
|
|
13
14
|
|
|
14
15
|
/** Props for InvoiceHeader component */
|
|
15
|
-
export interface
|
|
16
|
+
export interface LegacyInvoiceHeaderProps {
|
|
16
17
|
/** Invoice number/reference */
|
|
17
18
|
invoiceNumber: string;
|
|
18
19
|
/** Current status */
|
|
@@ -33,17 +34,31 @@ export interface Props {
|
|
|
33
34
|
onstatuschange?: (status: InvoiceStatus) => void;
|
|
34
35
|
}
|
|
35
36
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
37
|
+
/** Exact retained metadata branch with an open caller-owned status. */
|
|
38
|
+
export interface RetainedInvoiceHeaderProps {
|
|
39
|
+
/** Caller-authoritative retained header. */
|
|
40
|
+
retained: RetainedInvoiceHeaderView;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Legacy metadata or an explicit retained view. */
|
|
44
|
+
export type Props = LegacyInvoiceHeaderProps | RetainedInvoiceHeaderProps;
|
|
45
|
+
|
|
46
|
+
const props: Props = $props();
|
|
47
|
+
const retained = $derived('retained' in props ? props.retained : null);
|
|
48
|
+
const legacy = $derived(retained ? null : (props as LegacyInvoiceHeaderProps));
|
|
49
|
+
const invoiceNumber = $derived(
|
|
50
|
+
retained ? retained.invoiceNumber : legacy?.invoiceNumber,
|
|
51
|
+
);
|
|
52
|
+
const status = $derived(retained ? retained.status : legacy?.status);
|
|
53
|
+
const issueDate = $derived(retained ? retained.issueDate : legacy?.issueDate);
|
|
54
|
+
const dueDate = $derived(retained ? retained.dueDate : legacy?.dueDate);
|
|
55
|
+
const paidDate = $derived(retained ? retained.paidDate : legacy?.paidDate);
|
|
56
|
+
const customerName = $derived(
|
|
57
|
+
retained ? retained.customerName : legacy?.customerName,
|
|
58
|
+
);
|
|
59
|
+
const projectName = $derived(
|
|
60
|
+
retained ? retained.projectName : legacy?.projectName,
|
|
61
|
+
);
|
|
47
62
|
|
|
48
63
|
// Format date
|
|
49
64
|
function formatDate(date: Date | string | null | undefined): string {
|
|
@@ -93,10 +108,15 @@ const statusConfig: Record<
|
|
|
93
108
|
},
|
|
94
109
|
};
|
|
95
110
|
|
|
96
|
-
const statusInfo = $derived(
|
|
111
|
+
const statusInfo = $derived(
|
|
112
|
+
retained
|
|
113
|
+
? { ...statusConfig.draft, label: retained.statusLabel }
|
|
114
|
+
: (statusConfig[status as InvoiceStatus] ?? statusConfig.draft),
|
|
115
|
+
);
|
|
97
116
|
|
|
98
117
|
// Check if overdue
|
|
99
118
|
const isOverdue = $derived.by(() => {
|
|
119
|
+
if (retained) return false;
|
|
100
120
|
if (status === 'paid' || status === 'cancelled') return false;
|
|
101
121
|
if (!dueDate) return false;
|
|
102
122
|
const due = typeof dueDate === 'string' ? new Date(dueDate) : dueDate;
|
|
@@ -154,7 +174,7 @@ const isOverdue = $derived.by(() => {
|
|
|
154
174
|
</div>
|
|
155
175
|
{/if}
|
|
156
176
|
|
|
157
|
-
{#if paidDate && status === 'paid'}
|
|
177
|
+
{#if paidDate && (retained || status === 'paid')}
|
|
158
178
|
<div class="meta-item paid">
|
|
159
179
|
<span class="meta-label">{t(M['commerce.invoice_header.paid_date'])}</span>
|
|
160
180
|
<span class="meta-value">{formatDate(paidDate)}</span>
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
import type { RetainedInvoiceHeaderView } from '../invoice-display.js';
|
|
1
2
|
import type { InvoiceStatus } from '../types.js';
|
|
2
3
|
/** Props for InvoiceHeader component */
|
|
3
|
-
export interface
|
|
4
|
+
export interface LegacyInvoiceHeaderProps {
|
|
4
5
|
/** Invoice number/reference */
|
|
5
6
|
invoiceNumber: string;
|
|
6
7
|
/** Current status */
|
|
@@ -20,6 +21,13 @@ export interface Props {
|
|
|
20
21
|
/** Called when status changes */
|
|
21
22
|
onstatuschange?: (status: InvoiceStatus) => void;
|
|
22
23
|
}
|
|
24
|
+
/** Exact retained metadata branch with an open caller-owned status. */
|
|
25
|
+
export interface RetainedInvoiceHeaderProps {
|
|
26
|
+
/** Caller-authoritative retained header. */
|
|
27
|
+
retained: RetainedInvoiceHeaderView;
|
|
28
|
+
}
|
|
29
|
+
/** Legacy metadata or an explicit retained view. */
|
|
30
|
+
export type Props = LegacyInvoiceHeaderProps | RetainedInvoiceHeaderProps;
|
|
23
31
|
declare const InvoiceHeader: import("svelte").Component<Props, {}, "">;
|
|
24
32
|
type InvoiceHeader = ReturnType<typeof InvoiceHeader>;
|
|
25
33
|
export default InvoiceHeader;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"InvoiceHeader.svelte.d.ts","sourceRoot":"","sources":["../../../src/svelte/components/InvoiceHeader.svelte.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAGjD,wCAAwC;AACxC,MAAM,WAAW,
|
|
1
|
+
{"version":3,"file":"InvoiceHeader.svelte.d.ts","sourceRoot":"","sources":["../../../src/svelte/components/InvoiceHeader.svelte.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,uBAAuB,CAAC;AACvE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAGjD,wCAAwC;AACxC,MAAM,WAAW,wBAAwB;IACvC,+BAA+B;IAC/B,aAAa,EAAE,MAAM,CAAC;IACtB,qBAAqB;IACrB,MAAM,EAAE,aAAa,CAAC;IACtB,iBAAiB;IACjB,SAAS,EAAE,IAAI,GAAG,MAAM,CAAC;IACzB,eAAe;IACf,OAAO,CAAC,EAAE,IAAI,GAAG,MAAM,GAAG,IAAI,CAAC;IAC/B,gBAAgB;IAChB,QAAQ,CAAC,EAAE,IAAI,GAAG,MAAM,GAAG,IAAI,CAAC;IAChC,2BAA2B;IAC3B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,mBAAmB;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,oBAAoB;IACpB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,iCAAiC;IACjC,cAAc,CAAC,EAAE,CAAC,MAAM,EAAE,aAAa,KAAK,IAAI,CAAC;CAClD;AAED,uEAAuE;AACvE,MAAM,WAAW,0BAA0B;IACzC,4CAA4C;IAC5C,QAAQ,EAAE,yBAAyB,CAAC;CACrC;AAED,oDAAoD;AACpD,MAAM,MAAM,KAAK,GAAG,wBAAwB,GAAG,0BAA0B,CAAC;AA0J1E,QAAA,MAAM,aAAa,2CAAwC,CAAC;AAC5D,KAAK,aAAa,GAAG,UAAU,CAAC,OAAO,aAAa,CAAC,CAAC;AACtD,eAAe,aAAa,CAAC"}
|
|
@@ -10,12 +10,17 @@ import { useI18n } from '@happyvertical/smrt-ui/i18n';
|
|
|
10
10
|
import { Button } from '@happyvertical/smrt-ui/ui';
|
|
11
11
|
import type { Snippet } from 'svelte';
|
|
12
12
|
import { M } from '../i18n.js';
|
|
13
|
+
import {
|
|
14
|
+
formatInvoiceMinorUnits,
|
|
15
|
+
type RetainedInvoiceLineItemsView,
|
|
16
|
+
type RetainedInvoiceLineView,
|
|
17
|
+
} from '../invoice-display.js';
|
|
13
18
|
import type { LineItem } from '../types.js';
|
|
14
19
|
|
|
15
20
|
const { t } = useI18n();
|
|
16
21
|
|
|
17
22
|
/** Props for InvoiceLineItems component */
|
|
18
|
-
export interface
|
|
23
|
+
export interface LegacyInvoiceLineItemsProps {
|
|
19
24
|
/** Line items to display */
|
|
20
25
|
items: LineItem[];
|
|
21
26
|
/** Enable editing mode */
|
|
@@ -34,16 +39,32 @@ export interface Props {
|
|
|
34
39
|
emptyMessage?: string;
|
|
35
40
|
}
|
|
36
41
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
42
|
+
/** Exact retained line-items branch. */
|
|
43
|
+
export interface RetainedInvoiceLineItemsProps {
|
|
44
|
+
/** Caller-authoritative retained line items and subtotal. */
|
|
45
|
+
retained: RetainedInvoiceLineItemsView;
|
|
46
|
+
/** Renders additional retained evidence for each line. */
|
|
47
|
+
details?: Snippet<[RetainedInvoiceLineView]>;
|
|
48
|
+
/** Empty state message. */
|
|
49
|
+
emptyMessage?: string;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Legacy decimal-major items or exact retained items. */
|
|
53
|
+
export type Props = LegacyInvoiceLineItemsProps | RetainedInvoiceLineItemsProps;
|
|
54
|
+
|
|
55
|
+
const props: Props = $props();
|
|
56
|
+
const retained = $derived('retained' in props ? props.retained : null);
|
|
57
|
+
const details = $derived('details' in props ? props.details : undefined);
|
|
58
|
+
const legacy = $derived(
|
|
59
|
+
retained ? null : (props as LegacyInvoiceLineItemsProps),
|
|
60
|
+
);
|
|
61
|
+
const items = $derived(legacy?.items ?? []);
|
|
62
|
+
const editable = $derived(legacy?.editable ?? false);
|
|
63
|
+
const currency = $derived(legacy?.currency ?? 'CAD');
|
|
64
|
+
const ondelete = $derived(legacy?.ondelete);
|
|
65
|
+
const onadd = $derived(legacy?.onadd);
|
|
66
|
+
const showSource = $derived(legacy?.showSource ?? false);
|
|
67
|
+
const emptyMessage = $derived(props.emptyMessage ?? 'No line items');
|
|
47
68
|
|
|
48
69
|
// Format decimal-dollar amounts. The commerce models store DECIMAL dollars
|
|
49
70
|
// (AGENTS.md "Currency in decimal fields"), not integer cents.
|
|
@@ -67,7 +88,34 @@ const total = $derived(items.reduce((sum, item) => sum + item.amount, 0));
|
|
|
67
88
|
</script>
|
|
68
89
|
|
|
69
90
|
<div class="line-items-container">
|
|
70
|
-
{#if
|
|
91
|
+
{#if retained}
|
|
92
|
+
{#if retained.lines.length === 0}
|
|
93
|
+
<div class="empty-state"><p>{emptyMessage}</p></div>
|
|
94
|
+
{:else}
|
|
95
|
+
<table class="line-items-table">
|
|
96
|
+
<thead><tr><th class="col-description">Description</th><th class="col-source">Source</th><th class="col-qty">Qty</th><th class="col-price">{t(M['commerce.invoice_line_items.unit_price'])}</th><th class="col-amount">Amount</th></tr></thead>
|
|
97
|
+
<tbody>
|
|
98
|
+
{#each retained.lines as line (line.id)}
|
|
99
|
+
<tr>
|
|
100
|
+
<td class="col-description">
|
|
101
|
+
{#if line.category}<span class="item-category">{line.category}</span>{/if}
|
|
102
|
+
<span class="item-description">{line.description}</span>
|
|
103
|
+
{#if line.discountLabel}<span>{line.discountLabel}</span>{/if}
|
|
104
|
+
{#each line.taxLabels ?? [] as tax}<span>{tax}</span>{/each}
|
|
105
|
+
{#if line.correctionLabel}<span>{line.correctionLabel}</span>{/if}
|
|
106
|
+
{@render details?.(line)}
|
|
107
|
+
</td>
|
|
108
|
+
<td class="col-source">{line.sourceLabel ?? '—'}</td>
|
|
109
|
+
<td class="col-qty">{line.quantity}</td>
|
|
110
|
+
<td class="col-price">{line.unitPriceMinor === undefined || line.unitPriceMinor === null ? '—' : formatInvoiceMinorUnits(line.unitPriceMinor, retained.currency)}</td>
|
|
111
|
+
<td class="col-amount">{formatInvoiceMinorUnits(line.amountMinor, retained.currency)}</td>
|
|
112
|
+
</tr>
|
|
113
|
+
{/each}
|
|
114
|
+
</tbody>
|
|
115
|
+
<tfoot><tr class="total-row"><td colspan="4" class="total-label">Subtotal</td><td class="col-amount">{formatInvoiceMinorUnits(retained.subtotalMinor, retained.currency)}</td></tr></tfoot>
|
|
116
|
+
</table>
|
|
117
|
+
{/if}
|
|
118
|
+
{:else if items.length === 0}
|
|
71
119
|
<div class="empty-state">
|
|
72
120
|
<p>{emptyMessage}</p>
|
|
73
121
|
{#if editable && onadd}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
import { type RetainedInvoiceLineItemsView, type RetainedInvoiceLineView } from '../invoice-display.js';
|
|
1
3
|
import type { LineItem } from '../types.js';
|
|
2
4
|
/** Props for InvoiceLineItems component */
|
|
3
|
-
export interface
|
|
5
|
+
export interface LegacyInvoiceLineItemsProps {
|
|
4
6
|
/** Line items to display */
|
|
5
7
|
items: LineItem[];
|
|
6
8
|
/** Enable editing mode */
|
|
@@ -18,6 +20,17 @@ export interface Props {
|
|
|
18
20
|
/** Empty state message */
|
|
19
21
|
emptyMessage?: string;
|
|
20
22
|
}
|
|
23
|
+
/** Exact retained line-items branch. */
|
|
24
|
+
export interface RetainedInvoiceLineItemsProps {
|
|
25
|
+
/** Caller-authoritative retained line items and subtotal. */
|
|
26
|
+
retained: RetainedInvoiceLineItemsView;
|
|
27
|
+
/** Renders additional retained evidence for each line. */
|
|
28
|
+
details?: Snippet<[RetainedInvoiceLineView]>;
|
|
29
|
+
/** Empty state message. */
|
|
30
|
+
emptyMessage?: string;
|
|
31
|
+
}
|
|
32
|
+
/** Legacy decimal-major items or exact retained items. */
|
|
33
|
+
export type Props = LegacyInvoiceLineItemsProps | RetainedInvoiceLineItemsProps;
|
|
21
34
|
declare const InvoiceLineItems: import("svelte").Component<Props, {}, "">;
|
|
22
35
|
type InvoiceLineItems = ReturnType<typeof InvoiceLineItems>;
|
|
23
36
|
export default InvoiceLineItems;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"InvoiceLineItems.svelte.d.ts","sourceRoot":"","sources":["../../../src/svelte/components/InvoiceLineItems.svelte.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"InvoiceLineItems.svelte.d.ts","sourceRoot":"","sources":["../../../src/svelte/components/InvoiceLineItems.svelte.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AAEtC,OAAO,EAEL,KAAK,4BAA4B,EACjC,KAAK,uBAAuB,EAC7B,MAAM,uBAAuB,CAAC;AAC/B,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAG5C,2CAA2C;AAC3C,MAAM,WAAW,2BAA2B;IAC1C,4BAA4B;IAC5B,KAAK,EAAE,QAAQ,EAAE,CAAC;IAClB,0BAA0B;IAC1B,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,oBAAoB;IACpB,QAAQ,CAAC,EAAE,KAAK,GAAG,KAAK,CAAC;IACzB,kCAAkC;IAClC,QAAQ,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,CAAC,QAAQ,CAAC,KAAK,IAAI,CAAC;IACzD,kCAAkC;IAClC,QAAQ,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IAChC,wCAAwC;IACxC,KAAK,CAAC,EAAE,MAAM,IAAI,CAAC;IACnB,8BAA8B;IAC9B,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,0BAA0B;IAC1B,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,wCAAwC;AACxC,MAAM,WAAW,6BAA6B;IAC5C,6DAA6D;IAC7D,QAAQ,EAAE,4BAA4B,CAAC;IACvC,0DAA0D;IAC1D,OAAO,CAAC,EAAE,OAAO,CAAC,CAAC,uBAAuB,CAAC,CAAC,CAAC;IAC7C,2BAA2B;IAC3B,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,0DAA0D;AAC1D,MAAM,MAAM,KAAK,GAAG,2BAA2B,GAAG,6BAA6B,CAAC;AAiKhF,QAAA,MAAM,gBAAgB,2CAAwC,CAAC;AAC/D,KAAK,gBAAgB,GAAG,UAAU,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAC5D,eAAe,gBAAgB,CAAC"}
|