@happyvertical/smrt-commerce 0.55.0 → 0.55.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/AGENTS.md +12 -0
  2. package/agents/commercial-ui.md +8 -0
  3. package/agents/customer-vendor-ui.md +15 -0
  4. package/dist/chunks/{calculations-DPuldr1J.js → calculations-DwBf83UE.js} +7 -30
  5. package/dist/chunks/calculations-DwBf83UE.js.map +1 -0
  6. package/dist/chunks/{units-CJcAOQm1.js → units-BhVZswof.js} +2 -2
  7. package/dist/chunks/{units-CJcAOQm1.js.map → units-BhVZswof.js.map} +1 -1
  8. package/dist/index.d.ts +1 -0
  9. package/dist/index.d.ts.map +1 -1
  10. package/dist/index.js +139 -115
  11. package/dist/index.js.map +1 -1
  12. package/dist/invoices.js +1 -1
  13. package/dist/manifest.json +1293 -186
  14. package/dist/models/Contract.d.ts.map +1 -1
  15. package/dist/models/Customer.d.ts.map +1 -1
  16. package/dist/models/Vendor.d.ts.map +1 -1
  17. package/dist/recipes.d.ts +106 -0
  18. package/dist/recipes.d.ts.map +1 -0
  19. package/dist/smrt-knowledge.json +509 -165
  20. package/dist/svelte/components/CustomerDirectory.svelte +28 -2
  21. package/dist/svelte/components/CustomerDirectory.svelte.d.ts +9 -1
  22. package/dist/svelte/components/CustomerDirectory.svelte.d.ts.map +1 -1
  23. package/dist/svelte/components/InvoiceCard.svelte +57 -7
  24. package/dist/svelte/components/InvoiceCard.svelte.d.ts +14 -1
  25. package/dist/svelte/components/InvoiceCard.svelte.d.ts.map +1 -1
  26. package/dist/svelte/components/InvoiceHeader.svelte +34 -14
  27. package/dist/svelte/components/InvoiceHeader.svelte.d.ts +9 -1
  28. package/dist/svelte/components/InvoiceHeader.svelte.d.ts.map +1 -1
  29. package/dist/svelte/components/InvoiceLineItems.svelte +60 -12
  30. package/dist/svelte/components/InvoiceLineItems.svelte.d.ts +14 -1
  31. package/dist/svelte/components/InvoiceLineItems.svelte.d.ts.map +1 -1
  32. package/dist/svelte/components/InvoiceTotals.svelte +44 -13
  33. package/dist/svelte/components/InvoiceTotals.svelte.d.ts +14 -1
  34. package/dist/svelte/components/InvoiceTotals.svelte.d.ts.map +1 -1
  35. package/dist/svelte/components/VendorDirectory.svelte +28 -2
  36. package/dist/svelte/components/VendorDirectory.svelte.d.ts +9 -1
  37. package/dist/svelte/components/VendorDirectory.svelte.d.ts.map +1 -1
  38. package/dist/svelte/i18n.d.ts +2 -0
  39. package/dist/svelte/i18n.d.ts.map +1 -1
  40. package/dist/svelte/i18n.js +2 -0
  41. package/dist/svelte/index.d.ts +3 -1
  42. package/dist/svelte/index.d.ts.map +1 -1
  43. package/dist/svelte/index.js +1 -0
  44. package/dist/svelte/invoice-display.d.ts +98 -0
  45. package/dist/svelte/invoice-display.d.ts.map +1 -0
  46. package/dist/svelte/invoice-display.js +21 -0
  47. package/dist/svelte/invoices/currency.d.ts +3 -1
  48. package/dist/svelte/invoices/currency.d.ts.map +1 -1
  49. package/dist/svelte/invoices/currency.js +11 -38
  50. package/dist/svelte/party-types.d.ts +2 -0
  51. package/dist/svelte/party-types.d.ts.map +1 -1
  52. package/package.json +11 -11
  53. package/dist/chunks/calculations-DPuldr1J.js.map +0 -1
@@ -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]) => name !== queryName && name !== statusName,
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={t(M['commerce.party.try_filters'])} />
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;AAsID,QAAA,MAAM,iBAAiB,2CAAwC,CAAC;AAChE,KAAK,iBAAiB,GAAG,UAAU,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAC9D,eAAe,iBAAiB,CAAC"}
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 { InvoiceData, InvoiceStatus } from '../types.js';
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 Props {
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
- const { invoice, currency = 'CAD', href, onclick }: Props = $props();
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">{formatMoney(invoice.totalAmount)}</span>
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">{formatMoney(invoice.totalAmount)}</span>
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 Props {
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,EAAiB,MAAM,aAAa,CAAC;AAG9D,sCAAsC;AACtC,MAAM,WAAW,KAAK;IACpB,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;AAoHD,QAAA,MAAM,WAAW,2CAAwC,CAAC;AAC1D,KAAK,WAAW,GAAG,UAAU,CAAC,OAAO,WAAW,CAAC,CAAC;AAClD,eAAe,WAAW,CAAC"}
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 Props {
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
- const {
37
- invoiceNumber,
38
- status,
39
- issueDate,
40
- dueDate,
41
- paidDate,
42
- customerName,
43
- projectName,
44
- editable = false,
45
- onstatuschange,
46
- }: Props = $props();
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(statusConfig[status] ?? statusConfig.draft);
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 Props {
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,KAAK;IACpB,+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;AA6ID,QAAA,MAAM,aAAa,2CAAwC,CAAC;AAC5D,KAAK,aAAa,GAAG,UAAU,CAAC,OAAO,aAAa,CAAC,CAAC;AACtD,eAAe,aAAa,CAAC"}
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 Props {
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
- const {
38
- items,
39
- editable = false,
40
- currency = 'CAD',
41
- onupdate,
42
- ondelete,
43
- onadd,
44
- showSource = false,
45
- emptyMessage = 'No line items',
46
- }: Props = $props();
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 items.length === 0}
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 Props {
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":"AAaA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAG5C,2CAA2C;AAC3C,MAAM,WAAW,KAAK;IACpB,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;AAgID,QAAA,MAAM,gBAAgB,2CAAwC,CAAC;AAC/D,KAAK,gBAAgB,GAAG,UAAU,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAC5D,eAAe,gBAAgB,CAAC"}
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"}
@@ -6,12 +6,17 @@
6
6
  */
7
7
 
8
8
  import { useI18n } from '@happyvertical/smrt-ui/i18n';
9
+ import type { Snippet } from 'svelte';
9
10
  import { M } from '../i18n.js';
11
+ import {
12
+ formatInvoiceMinorUnits,
13
+ type RetainedInvoiceTotalsView,
14
+ } from '../invoice-display.js';
10
15
 
11
16
  const { t } = useI18n();
12
17
 
13
18
  /** Props for InvoiceTotals component */
14
- export interface Props {
19
+ export interface LegacyInvoiceTotalsProps {
15
20
  /** Subtotal in decimal dollars */
16
21
  subtotal: number;
17
22
  /** Tax rate as percentage (e.g., 5 for 5%) */
@@ -34,18 +39,33 @@ export interface Props {
34
39
  size?: 'sm' | 'md' | 'lg';
35
40
  }
36
41
 
37
- const {
38
- subtotal,
39
- taxRate = 0,
40
- taxAmount,
41
- total,
42
- amountPaid = 0,
43
- currency = 'CAD',
44
- showTax = true,
45
- showPaid = false,
46
- taxLabel = 'GST',
47
- size = 'md',
48
- }: Props = $props();
42
+ /** Exact retained totals branch. */
43
+ export interface RetainedInvoiceTotalsProps {
44
+ /** Caller-authoritative totals; omitted facts remain omitted. */
45
+ retained: RetainedInvoiceTotalsView;
46
+ /** Renders additional retained totals evidence. */
47
+ details?: Snippet<[RetainedInvoiceTotalsView]>;
48
+ /** Size variant. */
49
+ size?: 'sm' | 'md' | 'lg';
50
+ }
51
+
52
+ /** Legacy decimal-major totals or exact retained totals. */
53
+ export type Props = LegacyInvoiceTotalsProps | RetainedInvoiceTotalsProps;
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(retained ? null : (props as LegacyInvoiceTotalsProps));
59
+ const subtotal = $derived(legacy?.subtotal ?? 0);
60
+ const taxRate = $derived(legacy?.taxRate ?? 0);
61
+ const taxAmount = $derived(legacy?.taxAmount);
62
+ const total = $derived(legacy?.total ?? 0);
63
+ const amountPaid = $derived(legacy?.amountPaid ?? 0);
64
+ const currency = $derived(legacy?.currency ?? 'CAD');
65
+ const showTax = $derived(legacy?.showTax ?? true);
66
+ const showPaid = $derived(legacy?.showPaid ?? false);
67
+ const taxLabel = $derived(legacy?.taxLabel ?? 'GST');
68
+ const size = $derived(props.size ?? 'md');
49
69
 
50
70
  // Calculate tax if not provided. `taxRate` is a percentage (e.g. 5 for 5%), so
51
71
  // the `/ 100` converts percent → fraction — it is NOT a cents conversion. The
@@ -67,6 +87,16 @@ function formatMoney(amount: number): string {
67
87
  </script>
68
88
 
69
89
  <div class="invoice-totals" class:sm={size === 'sm'} class:lg={size === 'lg'}>
90
+ {#if retained}
91
+ <div class="totals-row"><span class="totals-label">Subtotal</span><span class="totals-value">{formatInvoiceMinorUnits(retained.subtotalMinor, retained.currency)}</span></div>
92
+ {#if retained.discountMinor !== undefined}<div class="totals-row"><span class="totals-label">Discount</span><span class="totals-value">{formatInvoiceMinorUnits(retained.discountMinor, retained.currency)}</span></div>{/if}
93
+ {#each retained.taxes ?? [] as tax (tax.id)}<div class="totals-row"><span class="totals-label">{tax.label}</span><span class="totals-value">{formatInvoiceMinorUnits(tax.amountMinor, retained.currency)}</span></div>{/each}
94
+ {#if retained.holdbackMinor !== undefined}<div class="totals-row"><span class="totals-label">{retained.holdbackLabel ?? 'Holdback'}</span><span class="totals-value">{formatInvoiceMinorUnits(retained.holdbackMinor, retained.currency)}</span></div>{/if}
95
+ <div class="totals-row total"><span class="totals-label">Total</span><span class="totals-value">{formatInvoiceMinorUnits(retained.totalMinor, retained.currency)}</span></div>
96
+ {#if retained.paidMinor !== undefined}<div class="totals-row paid"><span class="totals-label">{t(M['commerce.invoice_totals.amount_paid'])}</span><span class="totals-value">{formatInvoiceMinorUnits(retained.paidMinor, retained.currency)}</span></div>{/if}
97
+ {#if retained.payableMinor !== undefined}<div class="totals-row balance"><span class="totals-label">Payable</span><span class="totals-value">{formatInvoiceMinorUnits(retained.payableMinor, retained.currency)}</span></div>{/if}
98
+ {@render details?.(retained)}
99
+ {:else}
70
100
  <div class="totals-row">
71
101
  <span class="totals-label">Subtotal</span>
72
102
  <span class="totals-value">{formatMoney(subtotal)}</span>
@@ -103,6 +133,7 @@ function formatMoney(amount: number): string {
103
133
  <span class="totals-value">{formatMoney(Math.abs(balanceDue))}</span>
104
134
  </div>
105
135
  {/if}
136
+ {/if}
106
137
  </div>
107
138
 
108
139
  <style>
@@ -1,5 +1,7 @@
1
+ import type { Snippet } from 'svelte';
2
+ import { type RetainedInvoiceTotalsView } from '../invoice-display.js';
1
3
  /** Props for InvoiceTotals component */
2
- export interface Props {
4
+ export interface LegacyInvoiceTotalsProps {
3
5
  /** Subtotal in decimal dollars */
4
6
  subtotal: number;
5
7
  /** Tax rate as percentage (e.g., 5 for 5%) */
@@ -21,6 +23,17 @@ export interface Props {
21
23
  /** Size variant */
22
24
  size?: 'sm' | 'md' | 'lg';
23
25
  }
26
+ /** Exact retained totals branch. */
27
+ export interface RetainedInvoiceTotalsProps {
28
+ /** Caller-authoritative totals; omitted facts remain omitted. */
29
+ retained: RetainedInvoiceTotalsView;
30
+ /** Renders additional retained totals evidence. */
31
+ details?: Snippet<[RetainedInvoiceTotalsView]>;
32
+ /** Size variant. */
33
+ size?: 'sm' | 'md' | 'lg';
34
+ }
35
+ /** Legacy decimal-major totals or exact retained totals. */
36
+ export type Props = LegacyInvoiceTotalsProps | RetainedInvoiceTotalsProps;
24
37
  declare const InvoiceTotals: import("svelte").Component<Props, {}, "">;
25
38
  type InvoiceTotals = ReturnType<typeof InvoiceTotals>;
26
39
  export default InvoiceTotals;
@@ -1 +1 @@
1
- {"version":3,"file":"InvoiceTotals.svelte.d.ts","sourceRoot":"","sources":["../../../src/svelte/components/InvoiceTotals.svelte.ts"],"names":[],"mappings":"AAYA,wCAAwC;AACxC,MAAM,WAAW,KAAK;IACpB,kCAAkC;IAClC,QAAQ,EAAE,MAAM,CAAC;IACjB,8CAA8C;IAC9C,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,2EAA2E;IAC3E,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,+BAA+B;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,6CAA6C;IAC7C,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,oBAAoB;IACpB,QAAQ,CAAC,EAAE,KAAK,GAAG,KAAK,CAAC;IACzB,yBAAyB;IACzB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,0BAA0B;IAC1B,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,gBAAgB;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,mBAAmB;IACnB,IAAI,CAAC,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;CAC3B;AAqFD,QAAA,MAAM,aAAa,2CAAwC,CAAC;AAC5D,KAAK,aAAa,GAAG,UAAU,CAAC,OAAO,aAAa,CAAC,CAAC;AACtD,eAAe,aAAa,CAAC"}
1
+ {"version":3,"file":"InvoiceTotals.svelte.d.ts","sourceRoot":"","sources":["../../../src/svelte/components/InvoiceTotals.svelte.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AAEtC,OAAO,EAEL,KAAK,yBAAyB,EAC/B,MAAM,uBAAuB,CAAC;AAG/B,wCAAwC;AACxC,MAAM,WAAW,wBAAwB;IACvC,kCAAkC;IAClC,QAAQ,EAAE,MAAM,CAAC;IACjB,8CAA8C;IAC9C,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,2EAA2E;IAC3E,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,+BAA+B;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,6CAA6C;IAC7C,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,oBAAoB;IACpB,QAAQ,CAAC,EAAE,KAAK,GAAG,KAAK,CAAC;IACzB,yBAAyB;IACzB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,0BAA0B;IAC1B,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,gBAAgB;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,mBAAmB;IACnB,IAAI,CAAC,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;CAC3B;AAED,oCAAoC;AACpC,MAAM,WAAW,0BAA0B;IACzC,iEAAiE;IACjE,QAAQ,EAAE,yBAAyB,CAAC;IACpC,mDAAmD;IACnD,OAAO,CAAC,EAAE,OAAO,CAAC,CAAC,yBAAyB,CAAC,CAAC,CAAC;IAC/C,oBAAoB;IACpB,IAAI,CAAC,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;CAC3B;AAED,4DAA4D;AAC5D,MAAM,MAAM,KAAK,GAAG,wBAAwB,GAAG,0BAA0B,CAAC;AAsG1E,QAAA,MAAM,aAAa,2CAAwC,CAAC;AAC5D,KAAK,aAAa,GAAG,UAAU,CAAC,OAAO,aAAa,CAAC,CAAC;AACtD,eAAe,aAAa,CAAC"}