@spaceinvoices/react-ui 0.4.4 → 0.4.6

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 (178) hide show
  1. package/cli/dist/index.js +1 -1
  2. package/package.json +1 -1
  3. package/src/components/advance-invoices/advance-invoices.hooks.ts +2 -2
  4. package/src/components/advance-invoices/create/create-advance-invoice-form.tsx +151 -45
  5. package/src/components/advance-invoices/create/locales/de.ts +20 -0
  6. package/src/components/advance-invoices/create/locales/es.ts +20 -0
  7. package/src/components/advance-invoices/create/locales/fr.ts +20 -0
  8. package/src/components/advance-invoices/create/locales/hr.ts +20 -0
  9. package/src/components/advance-invoices/create/locales/it.ts +20 -0
  10. package/src/components/advance-invoices/create/locales/nl.ts +20 -0
  11. package/src/components/advance-invoices/create/locales/pl.ts +20 -0
  12. package/src/components/advance-invoices/create/locales/pt.ts +20 -0
  13. package/src/components/advance-invoices/create/locales/sl.ts +20 -0
  14. package/src/components/advance-invoices/create/prepare-advance-invoice-submission.ts +5 -5
  15. package/src/components/advance-invoices/list/list-row-actions.tsx +48 -1
  16. package/src/components/advance-invoices/list/list-table.tsx +21 -1
  17. package/src/components/advance-invoices/list/locales/de.ts +4 -0
  18. package/src/components/advance-invoices/list/locales/en.ts +3 -0
  19. package/src/components/advance-invoices/list/locales/es.ts +4 -0
  20. package/src/components/advance-invoices/list/locales/fr.ts +4 -0
  21. package/src/components/advance-invoices/list/locales/hr.ts +3 -0
  22. package/src/components/advance-invoices/list/locales/it.ts +4 -0
  23. package/src/components/advance-invoices/list/locales/nl.ts +4 -0
  24. package/src/components/advance-invoices/list/locales/pl.ts +3 -0
  25. package/src/components/advance-invoices/list/locales/pt.ts +4 -0
  26. package/src/components/advance-invoices/list/locales/sl.ts +3 -0
  27. package/src/components/credit-notes/create/create-credit-note-form.tsx +161 -42
  28. package/src/components/credit-notes/create/locales/de.ts +21 -0
  29. package/src/components/credit-notes/create/locales/es.ts +21 -0
  30. package/src/components/credit-notes/create/locales/fr.ts +21 -0
  31. package/src/components/credit-notes/create/locales/hr.ts +21 -0
  32. package/src/components/credit-notes/create/locales/it.ts +21 -0
  33. package/src/components/credit-notes/create/locales/nl.ts +21 -0
  34. package/src/components/credit-notes/create/locales/pl.ts +21 -0
  35. package/src/components/credit-notes/create/locales/pt.ts +21 -0
  36. package/src/components/credit-notes/create/locales/sl.ts +22 -1
  37. package/src/components/credit-notes/credit-notes.hooks.ts +2 -2
  38. package/src/components/credit-notes/list/list-row-actions.tsx +44 -1
  39. package/src/components/credit-notes/list/list-table.tsx +16 -2
  40. package/src/components/credit-notes/list/locales/de.ts +2 -0
  41. package/src/components/credit-notes/list/locales/en.ts +2 -0
  42. package/src/components/credit-notes/list/locales/es.ts +2 -0
  43. package/src/components/credit-notes/list/locales/fr.ts +2 -0
  44. package/src/components/credit-notes/list/locales/hr.ts +2 -0
  45. package/src/components/credit-notes/list/locales/it.ts +2 -0
  46. package/src/components/credit-notes/list/locales/nl.ts +2 -0
  47. package/src/components/credit-notes/list/locales/pl.ts +2 -0
  48. package/src/components/credit-notes/list/locales/pt.ts +2 -0
  49. package/src/components/credit-notes/list/locales/sl.ts +2 -0
  50. package/src/components/dashboard/collection-rate-card/use-collection-rate.ts +48 -9
  51. package/src/components/dashboard/revenue-trend-chart/use-revenue-trend.ts +77 -48
  52. package/src/components/dashboard/shared/use-revenue-data.ts +77 -9
  53. package/src/components/delivery-notes/create/create-delivery-note-form.tsx +114 -7
  54. package/src/components/delivery-notes/create/locales/de.ts +21 -0
  55. package/src/components/delivery-notes/create/locales/es.ts +21 -0
  56. package/src/components/delivery-notes/create/locales/fr.ts +21 -0
  57. package/src/components/delivery-notes/create/locales/hr.ts +21 -0
  58. package/src/components/delivery-notes/create/locales/it.ts +21 -0
  59. package/src/components/delivery-notes/create/locales/nl.ts +21 -0
  60. package/src/components/delivery-notes/create/locales/pl.ts +21 -0
  61. package/src/components/delivery-notes/create/locales/pt.ts +21 -0
  62. package/src/components/delivery-notes/create/locales/sl.ts +22 -1
  63. package/src/components/delivery-notes/list/list-table.tsx +17 -8
  64. package/src/components/delivery-notes/list/locales/de.ts +32 -1
  65. package/src/components/delivery-notes/list/locales/en.ts +31 -0
  66. package/src/components/delivery-notes/list/locales/es.ts +32 -1
  67. package/src/components/delivery-notes/list/locales/fr.ts +32 -1
  68. package/src/components/delivery-notes/list/locales/hr.ts +32 -1
  69. package/src/components/delivery-notes/list/locales/it.ts +32 -1
  70. package/src/components/delivery-notes/list/locales/nl.ts +32 -1
  71. package/src/components/delivery-notes/list/locales/pl.ts +32 -1
  72. package/src/components/delivery-notes/list/locales/pt.ts +32 -1
  73. package/src/components/delivery-notes/list/locales/sl.ts +32 -1
  74. package/src/components/documents/create/document-add-item-form.tsx +70 -0
  75. package/src/components/documents/create/document-details-section.tsx +590 -344
  76. package/src/components/documents/create/document-items-section.tsx +21 -4
  77. package/src/components/documents/create/live-preview.tsx +24 -4
  78. package/src/components/documents/create/mark-as-paid-section.tsx +29 -20
  79. package/src/components/documents/create/prepare-document-submission.ts +22 -8
  80. package/src/components/documents/create/smart-code-insert-button.tsx +6 -0
  81. package/src/components/documents/shared/document-preview-display.tsx +3 -4
  82. package/src/components/documents/view/document-actions-bar.tsx +7 -27
  83. package/src/components/documents/view/document-details-card.tsx +32 -9
  84. package/src/components/documents/view/locales/de.ts +3 -0
  85. package/src/components/documents/view/locales/es.ts +3 -0
  86. package/src/components/documents/view/locales/fr.ts +3 -0
  87. package/src/components/documents/view/locales/hr.ts +3 -0
  88. package/src/components/documents/view/locales/it.ts +3 -0
  89. package/src/components/documents/view/locales/nl.ts +3 -0
  90. package/src/components/documents/view/locales/pl.ts +3 -0
  91. package/src/components/documents/view/locales/pt.ts +3 -0
  92. package/src/components/documents/view/locales/sl.ts +4 -1
  93. package/src/components/documents/view/use-document-download.ts +6 -3
  94. package/src/components/entities/create-entity-form.tsx +2 -1
  95. package/src/components/entities/entity-settings-form/email-template-variables-info.tsx +6 -0
  96. package/src/components/entities/entity-settings-form/input-with-preview.tsx +2 -117
  97. package/src/components/entities/entity-settings-form/locales/de.ts +5 -0
  98. package/src/components/entities/entity-settings-form/locales/es.ts +5 -0
  99. package/src/components/entities/entity-settings-form/locales/fr.ts +5 -0
  100. package/src/components/entities/entity-settings-form/locales/hr.ts +5 -0
  101. package/src/components/entities/entity-settings-form/locales/it.ts +5 -0
  102. package/src/components/entities/entity-settings-form/locales/nl.ts +5 -0
  103. package/src/components/entities/entity-settings-form/locales/pl.ts +5 -0
  104. package/src/components/entities/entity-settings-form/locales/pt.ts +5 -0
  105. package/src/components/entities/entity-settings-form/locales/sl.ts +5 -0
  106. package/src/components/entities/fina-settings-form/fina-settings-form.tsx +15 -0
  107. package/src/components/entities/fina-settings-form/fina-settings.hooks.ts +5 -1
  108. package/src/components/entities/fina-settings-form/locales/de.ts +3 -0
  109. package/src/components/entities/fina-settings-form/locales/en.ts +3 -0
  110. package/src/components/entities/fina-settings-form/locales/es.ts +3 -0
  111. package/src/components/entities/fina-settings-form/locales/fr.ts +3 -0
  112. package/src/components/entities/fina-settings-form/locales/hr.ts +3 -0
  113. package/src/components/entities/fina-settings-form/locales/it.ts +3 -0
  114. package/src/components/entities/fina-settings-form/locales/nl.ts +3 -0
  115. package/src/components/entities/fina-settings-form/locales/pl.ts +3 -0
  116. package/src/components/entities/fina-settings-form/locales/pt.ts +3 -0
  117. package/src/components/entities/fina-settings-form/locales/sl.ts +3 -0
  118. package/src/components/entities/fina-settings-form/sections/premises-management-section.tsx +4 -4
  119. package/src/components/entities/fina-settings-form/sections/register-premise-dialog.tsx +3 -3
  120. package/src/components/entities/settings/company-settings-form.tsx +173 -20
  121. package/src/components/entities/settings/defaults-settings-form.tsx +38 -1
  122. package/src/components/entities/settings/tax-rules-settings-form.tsx +1 -2
  123. package/src/components/estimates/create/create-estimate-form.tsx +107 -8
  124. package/src/components/estimates/create/locales/de.ts +21 -0
  125. package/src/components/estimates/create/locales/es.ts +21 -0
  126. package/src/components/estimates/create/locales/fr.ts +21 -0
  127. package/src/components/estimates/create/locales/hr.ts +21 -0
  128. package/src/components/estimates/create/locales/it.ts +21 -0
  129. package/src/components/estimates/create/locales/nl.ts +21 -0
  130. package/src/components/estimates/create/locales/pl.ts +21 -0
  131. package/src/components/estimates/create/locales/pt.ts +21 -0
  132. package/src/components/estimates/create/locales/sl.ts +22 -1
  133. package/src/components/estimates/list/list-table.tsx +11 -2
  134. package/src/components/estimates/list/locales/de.ts +1 -0
  135. package/src/components/estimates/list/locales/en.ts +1 -0
  136. package/src/components/estimates/list/locales/es.ts +1 -0
  137. package/src/components/estimates/list/locales/fr.ts +1 -0
  138. package/src/components/estimates/list/locales/hr.ts +1 -0
  139. package/src/components/estimates/list/locales/it.ts +1 -0
  140. package/src/components/estimates/list/locales/nl.ts +1 -0
  141. package/src/components/estimates/list/locales/pl.ts +1 -0
  142. package/src/components/estimates/list/locales/pt.ts +1 -0
  143. package/src/components/estimates/list/locales/sl.ts +1 -0
  144. package/src/components/export/document-export-form.tsx +46 -12
  145. package/src/components/invoices/create/create-invoice-form.tsx +186 -48
  146. package/src/components/invoices/create/locales/de.ts +28 -0
  147. package/src/components/invoices/create/locales/es.ts +28 -0
  148. package/src/components/invoices/create/locales/fr.ts +28 -0
  149. package/src/components/invoices/create/locales/hr.ts +28 -0
  150. package/src/components/invoices/create/locales/it.ts +28 -0
  151. package/src/components/invoices/create/locales/nl.ts +28 -0
  152. package/src/components/invoices/create/locales/pl.ts +28 -0
  153. package/src/components/invoices/create/locales/pt.ts +28 -0
  154. package/src/components/invoices/create/locales/sl.ts +29 -1
  155. package/src/components/invoices/create/prepare-invoice-submission.ts +5 -5
  156. package/src/components/invoices/invoices.hooks.ts +2 -2
  157. package/src/components/invoices/view/fiscalization-status-card.tsx +10 -8
  158. package/src/components/table/search-input.tsx +17 -0
  159. package/src/components/table/table-pagination.tsx +1 -1
  160. package/src/generated/schemas/advanceinvoice.ts +6 -2
  161. package/src/generated/schemas/creditnote.ts +3 -1
  162. package/src/generated/schemas/deliverynote.ts +3 -1
  163. package/src/generated/schemas/entity.ts +4 -4
  164. package/src/generated/schemas/entityapikey.ts +19 -0
  165. package/src/generated/schemas/estimate.ts +6 -2
  166. package/src/generated/schemas/index.ts +1 -0
  167. package/src/generated/schemas/invoice.ts +4 -1
  168. package/src/generated/schemas/renderadvanceinvoicepreview_body.ts +17 -23
  169. package/src/generated/schemas/rendercreditnotepreview_body.ts +17 -23
  170. package/src/generated/schemas/renderdeliverynotepreview_body.ts +17 -23
  171. package/src/generated/schemas/renderestimatepreview_body.ts +17 -23
  172. package/src/generated/schemas/renderinvoicepreview_body.ts +19 -23
  173. package/src/generated/schemas/startpdfexport_body.ts +14 -2
  174. package/src/generated/schemas/webhook.ts +4 -0
  175. package/src/hooks/use-duplicate-document.ts +16 -9
  176. package/src/hooks/use-vies-check.ts +3 -0
  177. package/src/lib/template-variables.tsx +167 -0
  178. package/src/providers/entities-context.tsx +2 -2
@@ -81,6 +81,7 @@ const PartialDeliveryNotePreview = z.object({
81
81
  .array(
82
82
  z
83
83
  .object({
84
+ type: z.literal("separator"),
84
85
  name: z.string(),
85
86
  quantity: z.number(),
86
87
  price: z.number(),
@@ -123,29 +124,22 @@ const LineDiscount = z.object({
123
124
 
124
125
 
125
126
  // Dependency schema for renderdeliverynotepreview_body
126
- const CreateDocumentItem = z.object({
127
- name: z.string().min(1).optional(),
128
- description: z.union([z.string(), z.null()]).optional(),
129
- price: z.number().optional(),
130
- gross_price: z.number().optional(),
131
- quantity: z.number().gte(-140737488355328).lte(140737488355327),
132
- unit: z.union([z.string(), z.null()]).optional(),
133
- taxes: z.array(DocumentItemTax).optional(),
134
- discounts: z.array(LineDiscount).max(5).optional(),
135
- delivery_note_id: z.union([z.string(), z.null()]).optional(),
136
- metadata: z
137
- .union([
138
- z.string(),
139
- z.number(),
140
- z.boolean(),
141
- z.null(),
142
- z.object({}).partial().passthrough(),
143
- z.array(z.unknown()),
144
- z.null(),
145
- ])
146
- .optional(),
147
- item_id: z.string().optional(),
148
- });
127
+ const CreateDocumentItem = z
128
+ .object({
129
+ type: z.literal("separator"),
130
+ name: z.string().min(1),
131
+ description: z.union([z.string(), z.null()]),
132
+ price: z.number(),
133
+ gross_price: z.number(),
134
+ quantity: z.number().gte(-140737488355328).lte(140737488355327),
135
+ unit: z.union([z.string(), z.null()]),
136
+ taxes: z.array(DocumentItemTax),
137
+ discounts: z.array(LineDiscount).max(5),
138
+ item_id: z.string(),
139
+ metadata: z.union([z.record(z.string(), z.any()), z.null()]),
140
+ save_item: z.boolean().default(true),
141
+ })
142
+ .partial();
149
143
 
150
144
 
151
145
  // Dependency schema for renderdeliverynotepreview_body
@@ -82,6 +82,7 @@ const PartialEstimatePreview = z.object({
82
82
  .array(
83
83
  z
84
84
  .object({
85
+ type: z.literal("separator"),
85
86
  name: z.string(),
86
87
  quantity: z.number(),
87
88
  price: z.number(),
@@ -123,29 +124,22 @@ const LineDiscount = z.object({
123
124
 
124
125
 
125
126
  // Dependency schema for renderestimatepreview_body
126
- const CreateDocumentItem = z.object({
127
- name: z.string().min(1).optional(),
128
- description: z.union([z.string(), z.null()]).optional(),
129
- price: z.number().optional(),
130
- gross_price: z.number().optional(),
131
- quantity: z.number().gte(-140737488355328).lte(140737488355327),
132
- unit: z.union([z.string(), z.null()]).optional(),
133
- taxes: z.array(DocumentItemTax).optional(),
134
- discounts: z.array(LineDiscount).max(5).optional(),
135
- delivery_note_id: z.union([z.string(), z.null()]).optional(),
136
- metadata: z
137
- .union([
138
- z.string(),
139
- z.number(),
140
- z.boolean(),
141
- z.null(),
142
- z.object({}).partial().passthrough(),
143
- z.array(z.unknown()),
144
- z.null(),
145
- ])
146
- .optional(),
147
- item_id: z.string().optional(),
148
- });
127
+ const CreateDocumentItem = z
128
+ .object({
129
+ type: z.literal("separator"),
130
+ name: z.string().min(1),
131
+ description: z.union([z.string(), z.null()]),
132
+ price: z.number(),
133
+ gross_price: z.number(),
134
+ quantity: z.number().gte(-140737488355328).lte(140737488355327),
135
+ unit: z.union([z.string(), z.null()]),
136
+ taxes: z.array(DocumentItemTax),
137
+ discounts: z.array(LineDiscount).max(5),
138
+ item_id: z.string(),
139
+ metadata: z.union([z.record(z.string(), z.any()), z.null()]),
140
+ save_item: z.boolean().default(true),
141
+ })
142
+ .partial();
149
143
 
150
144
 
151
145
  // Dependency schema for renderestimatepreview_body
@@ -113,6 +113,7 @@ const PartialInvoicePreview = z.object({
113
113
  .array(
114
114
  z
115
115
  .object({
116
+ type: z.literal("separator"),
116
117
  name: z.string(),
117
118
  quantity: z.number(),
118
119
  price: z.number(),
@@ -132,6 +133,7 @@ const PartialInvoicePreview = z.object({
132
133
  .min(1),
133
134
  fina: CreateFinaInvoiceData.optional(),
134
135
  expected_total_with_tax: z.number().gt(0).optional(),
136
+ force_linked_documents: z.boolean().optional(),
135
137
  });
136
138
 
137
139
 
@@ -155,29 +157,22 @@ const LineDiscount = z.object({
155
157
 
156
158
 
157
159
  // Dependency schema for renderinvoicepreview_body
158
- const CreateDocumentItem = z.object({
159
- name: z.string().min(1).optional(),
160
- description: z.union([z.string(), z.null()]).optional(),
161
- price: z.number().optional(),
162
- gross_price: z.number().optional(),
163
- quantity: z.number().gte(-140737488355328).lte(140737488355327),
164
- unit: z.union([z.string(), z.null()]).optional(),
165
- taxes: z.array(DocumentItemTax).optional(),
166
- discounts: z.array(LineDiscount).max(5).optional(),
167
- delivery_note_id: z.union([z.string(), z.null()]).optional(),
168
- metadata: z
169
- .union([
170
- z.string(),
171
- z.number(),
172
- z.boolean(),
173
- z.null(),
174
- z.object({}).partial().passthrough(),
175
- z.array(z.unknown()),
176
- z.null(),
177
- ])
178
- .optional(),
179
- item_id: z.string().optional(),
180
- });
160
+ const CreateDocumentItem = z
161
+ .object({
162
+ type: z.literal("separator"),
163
+ name: z.string().min(1),
164
+ description: z.union([z.string(), z.null()]),
165
+ price: z.number(),
166
+ gross_price: z.number(),
167
+ quantity: z.number().gte(-140737488355328).lte(140737488355327),
168
+ unit: z.union([z.string(), z.null()]),
169
+ taxes: z.array(DocumentItemTax),
170
+ discounts: z.array(LineDiscount).max(5),
171
+ item_id: z.string(),
172
+ metadata: z.union([z.record(z.string(), z.any()), z.null()]),
173
+ save_item: z.boolean().default(true),
174
+ })
175
+ .partial();
181
176
 
182
177
 
183
178
  // Dependency schema for renderinvoicepreview_body
@@ -201,6 +196,7 @@ const CompleteInvoicePreview = z.object({
201
196
  items: z.array(CreateDocumentItem).min(1),
202
197
  fina: CreateFinaInvoiceData.optional(),
203
198
  expected_total_with_tax: z.number().gt(0).optional(),
199
+ force_linked_documents: z.boolean().optional(),
204
200
  });
205
201
 
206
202
 
@@ -11,7 +11,13 @@ import { z } from 'zod';
11
11
  // Dependency schema for startpdfexport_body
12
12
  const PdfExportByDocumentIds = z
13
13
  .object({
14
- type: z.enum(["invoice", "estimate", "credit_note", "advance_invoice"]),
14
+ type: z.enum([
15
+ "invoice",
16
+ "estimate",
17
+ "credit_note",
18
+ "advance_invoice",
19
+ "delivery_note",
20
+ ]),
15
21
  document_ids: z.array(z.string()).min(1),
16
22
  })
17
23
  .passthrough();
@@ -20,7 +26,13 @@ const PdfExportByDocumentIds = z
20
26
  // Dependency schema for startpdfexport_body
21
27
  const PdfExportByDateRange = z
22
28
  .object({
23
- type: z.enum(["invoice", "estimate", "credit_note", "advance_invoice"]),
29
+ type: z.enum([
30
+ "invoice",
31
+ "estimate",
32
+ "credit_note",
33
+ "advance_invoice",
34
+ "delivery_note",
35
+ ]),
24
36
  date_from: z.string().optional(),
25
37
  date_to: z.string().optional(),
26
38
  })
@@ -49,6 +49,7 @@ const createWebhookSchemaDefinition = z.object({
49
49
  "advance_invoice.created",
50
50
  "advance_invoice.paid",
51
51
  "advance_invoice.applied",
52
+ "advance_invoice.voided",
52
53
  "advance_invoice.deleted",
53
54
  "advance_invoice.restored",
54
55
  "item.created",
@@ -87,6 +88,7 @@ const createWebhookSchemaDefinition = z.object({
87
88
  "delivery_note.created",
88
89
  "delivery_note.sent",
89
90
  "delivery_note.cancelled",
91
+ "delivery_note.voided",
90
92
  "delivery_note.deleted",
91
93
  "delivery_note.restored",
92
94
  ])
@@ -142,6 +144,7 @@ const updateWebhookSchemaDefinition = z
142
144
  "advance_invoice.created",
143
145
  "advance_invoice.paid",
144
146
  "advance_invoice.applied",
147
+ "advance_invoice.voided",
145
148
  "advance_invoice.deleted",
146
149
  "advance_invoice.restored",
147
150
  "item.created",
@@ -180,6 +183,7 @@ const updateWebhookSchemaDefinition = z
180
183
  "delivery_note.created",
181
184
  "delivery_note.sent",
182
185
  "delivery_note.cancelled",
186
+ "delivery_note.voided",
183
187
  "delivery_note.deleted",
184
188
  "delivery_note.restored",
185
189
  ])
@@ -64,6 +64,7 @@ function transformDocumentForDuplication(source: Document, targetType: DocumentT
64
64
  // Transform items - copy only the fields needed for creation
65
65
  // Use type assertion for items since all document item types share the same shape
66
66
  const sourceItems = source.items as Array<{
67
+ type?: string | null;
67
68
  name: string;
68
69
  description: string | null;
69
70
  quantity: number;
@@ -72,15 +73,21 @@ function transformDocumentForDuplication(source: Document, targetType: DocumentT
72
73
  taxes: Array<{ tax_id?: string }>;
73
74
  }>;
74
75
  const items = sourceItems?.map((item) => ({
76
+ type: item.type ?? undefined,
75
77
  name: item.name,
76
78
  description: item.description,
77
- quantity: item.quantity,
78
- // Use gross_price if set, otherwise use price. The form uses is_gross_price as a UI toggle.
79
- price: item.gross_price ?? item.price,
80
- // Copy tax references (tax_id), not computed tax data
81
- taxes: item.taxes?.map((tax) => ({ tax_id: tax.tax_id })),
82
- // Derive is_gross_price from whether gross_price is set
83
- gross_price: item.gross_price ?? undefined,
79
+ // Separator items skip financial fields
80
+ ...(item.type !== "separator"
81
+ ? {
82
+ quantity: item.quantity,
83
+ // Use gross_price if set, otherwise use price. The form uses is_gross_price as a UI toggle.
84
+ price: item.gross_price ?? item.price,
85
+ // Copy tax references (tax_id), not computed tax data
86
+ taxes: item.taxes?.map((tax) => ({ tax_id: tax.tax_id })),
87
+ // Derive is_gross_price from whether gross_price is set
88
+ gross_price: item.gross_price ?? undefined,
89
+ }
90
+ : {}),
84
91
  }));
85
92
 
86
93
  // Build customer data - always copy if available (form needs this for display)
@@ -108,8 +115,8 @@ function transformDocumentForDuplication(source: Document, targetType: DocumentT
108
115
  // The form needs customer data for display, even when customer_id is set
109
116
  ...(source.customer_id ? { customer_id: source.customer_id } : {}),
110
117
  ...(customerData ? { customer: customerData } : {}),
111
- // Items
112
- items,
118
+ // Items (cast needed: separator items omit financial fields like quantity)
119
+ items: items as any,
113
120
  // Currency
114
121
  currency_code: source.currency_code,
115
122
  // Notes
@@ -42,6 +42,8 @@ export interface UseViesCheckResult {
42
42
  reverseChargeApplies: boolean;
43
43
  /** Transaction type determined by VIES check */
44
44
  transactionType: ViesCheckResponse["transaction_type"] | undefined;
45
+ /** Customer country code returned by VIES check */
46
+ customerCountryCode: string | null;
45
47
  /** Warning message from VIES validation */
46
48
  warning: string | null;
47
49
  /** Whether VIES validation was successful */
@@ -124,6 +126,7 @@ export function useViesCheck({
124
126
  error: query.error,
125
127
  reverseChargeApplies: query.data?.reverse_charge_applies ?? false,
126
128
  transactionType: query.data?.transaction_type,
129
+ customerCountryCode: query.data?.customer_country_code ?? null,
127
130
  warning: query.data?.warning ?? null,
128
131
  viesValid: query.data?.vies_valid ?? null,
129
132
  };
@@ -0,0 +1,167 @@
1
+ import type { Entity, Estimate, Invoice } from "@spaceinvoices/js-sdk";
2
+ import { cn } from "@/ui/lib/utils";
3
+
4
+ /**
5
+ * Convert snake_case variable name to Title Case for display
6
+ * e.g., "document_number" -> "Document Number", "bank_account.iban" -> "Bank Account Iban"
7
+ */
8
+ export function formatVariableName(varName: string): string {
9
+ return varName
10
+ .replace(/\./g, "_")
11
+ .split("_")
12
+ .map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
13
+ .join(" ");
14
+ }
15
+
16
+ /**
17
+ * Resolve a template variable to its actual value from entity/document data.
18
+ * Returns null if the variable can't be resolved (shown as a placeholder in preview).
19
+ */
20
+ export function getVariableValue(
21
+ varName: string,
22
+ entity?: Entity | null,
23
+ document?: Partial<Invoice | Estimate> | null,
24
+ ): string | null {
25
+ if (!entity) return null;
26
+
27
+ // Entity-related variables
28
+ if (varName === "entity_name") return entity.name || null;
29
+ if (varName === "entity_email") return (entity.settings as any)?.email || null;
30
+ if (varName === "entity_address") return entity.address || null;
31
+ if (varName === "entity_post_code") return entity.post_code || null;
32
+ if (varName === "entity_city") return entity.city || null;
33
+ if (varName === "entity_country") return entity.country || null;
34
+ if (varName === "entity_tax_number") return entity.tax_number || null;
35
+ if (varName === "entity_company_number") return entity.company_number || null;
36
+
37
+ // Date variables
38
+ if (varName === "current_date") {
39
+ return new Date().toLocaleDateString("en-US", { month: "long", day: "numeric", year: "numeric" });
40
+ }
41
+ if (varName === "current_year") return new Date().getFullYear().toString();
42
+
43
+ // Document-specific variables
44
+ if (document) {
45
+ if (varName === "document_number") return (document as any).number || null;
46
+ if (varName === "document_date" && (document as any).date) {
47
+ return new Date((document as any).date).toLocaleDateString("en-US", {
48
+ month: "long",
49
+ day: "numeric",
50
+ year: "numeric",
51
+ });
52
+ }
53
+ if (varName === "document_total" && (document as any).total_with_tax) {
54
+ return new Intl.NumberFormat("en-US", {
55
+ style: "currency",
56
+ currency: (document as any).currency_code || "USD",
57
+ }).format(Number((document as any).total_with_tax));
58
+ }
59
+ if (varName === "document_currency") return (document as any).currency_code || null;
60
+
61
+ // Invoice due date
62
+ if ("date_due" in document && varName === "document_due_date") {
63
+ return document.date_due
64
+ ? new Date(document.date_due).toLocaleDateString("en-US", {
65
+ month: "long",
66
+ day: "numeric",
67
+ year: "numeric",
68
+ })
69
+ : null;
70
+ }
71
+
72
+ // Estimate valid until
73
+ if ("date_valid_till" in document && varName === "document_valid_until") {
74
+ return document.date_valid_till
75
+ ? new Date(document.date_valid_till).toLocaleDateString("en-US", {
76
+ month: "long",
77
+ day: "numeric",
78
+ year: "numeric",
79
+ })
80
+ : null;
81
+ }
82
+
83
+ // Customer variables
84
+ if ((document as any).customer) {
85
+ if (varName === "customer_name") return (document as any).customer.name || null;
86
+ if (varName === "customer_email") return (document as any).customer.email || null;
87
+ }
88
+ }
89
+
90
+ // Bank account variables (from entity settings)
91
+ const bankAccounts = (entity.settings as any)?.bank_accounts as
92
+ | Array<{
93
+ iban?: string;
94
+ bank_name?: string;
95
+ bic?: string;
96
+ account_number?: string;
97
+ routing_number?: string;
98
+ sort_code?: string;
99
+ is_default?: boolean;
100
+ }>
101
+ | undefined;
102
+ const bankAccount = bankAccounts?.find((acc) => acc.is_default) ?? bankAccounts?.[0];
103
+
104
+ if (varName === "bank_account" && bankAccount) {
105
+ const lines: string[] = [];
106
+ if (bankAccount.bank_name) lines.push(bankAccount.bank_name);
107
+ if (bankAccount.iban) lines.push(`IBAN: ${bankAccount.iban}`);
108
+ else if (bankAccount.account_number) lines.push(`Account: ${bankAccount.account_number}`);
109
+ if (bankAccount.bic) lines.push(`BIC: ${bankAccount.bic}`);
110
+ return lines.join(", ") || null;
111
+ }
112
+ if (varName === "bank_account.iban") return bankAccount?.iban || null;
113
+ if (varName === "bank_account.bank_name") return bankAccount?.bank_name || null;
114
+ if (varName === "bank_account.bic") return bankAccount?.bic || null;
115
+ if (varName === "bank_account.account_number") return bankAccount?.account_number || null;
116
+
117
+ return null;
118
+ }
119
+
120
+ /**
121
+ * Replace template variables in a string with styled React nodes for preview.
122
+ * Resolved values get a secondary bg, unresolved show as primary-colored placeholders.
123
+ */
124
+ export function replaceTemplateVariablesForPreview(
125
+ template: string,
126
+ entity?: Entity | null,
127
+ document?: Partial<Invoice | Estimate> | null,
128
+ ): React.ReactNode[] {
129
+ if (!template) return [];
130
+
131
+ const parts: React.ReactNode[] = [];
132
+ const regex = /\{([^}]+)\}/g;
133
+ let lastIndex = 0;
134
+ let match: RegExpExecArray | null = null;
135
+
136
+ match = regex.exec(template);
137
+ while (match !== null) {
138
+ if (match.index > lastIndex) {
139
+ parts.push(template.slice(lastIndex, match.index));
140
+ }
141
+
142
+ const varName = match[1];
143
+ const actualValue = getVariableValue(varName, entity, document);
144
+ const displayValue = actualValue || formatVariableName(varName);
145
+
146
+ parts.push(
147
+ <span
148
+ key={match.index}
149
+ className={cn(
150
+ "rounded px-1.5 py-0.5 font-medium text-xs",
151
+ actualValue ? "bg-secondary text-secondary-foreground" : "bg-primary/10 text-primary",
152
+ )}
153
+ >
154
+ {displayValue}
155
+ </span>,
156
+ );
157
+
158
+ lastIndex = regex.lastIndex;
159
+ match = regex.exec(template);
160
+ }
161
+
162
+ if (lastIndex < template.length) {
163
+ parts.push(template.slice(lastIndex));
164
+ }
165
+
166
+ return parts;
167
+ }
@@ -1,9 +1,9 @@
1
- import type { GetEntities200DataItem } from "@spaceinvoices/js-sdk";
1
+ import type { Entity as SDKEntity } from "@spaceinvoices/js-sdk";
2
2
 
3
3
  import { createContext, useContext } from "react";
4
4
 
5
5
  /** Entity type with country_rules included (from getEntities response) */
6
- export type Entity = GetEntities200DataItem;
6
+ export type Entity = SDKEntity;
7
7
 
8
8
  export type EntityEnvironment = "live" | "sandbox";
9
9