@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
@@ -2,6 +2,7 @@ import { zodResolver } from "@hookform/resolvers/zod";
2
2
  import type { Entity } from "@spaceinvoices/js-sdk";
3
3
  import { useForm } from "react-hook-form";
4
4
  import { z } from "zod";
5
+ import { Checkbox } from "@/ui/components/ui/checkbox";
5
6
  import {
6
7
  Form,
7
8
  FormControl,
@@ -24,12 +25,23 @@ const translations = { sl, de } as const;
24
25
  const companySettingsSchema = z.object({
25
26
  name: z.string().min(1, "Name is required"),
26
27
  tax_number: z.union([z.string(), z.null()]).optional(),
28
+ is_tax_subject: z.boolean(),
27
29
  tax_number_2: z.union([z.string(), z.null()]).optional(),
28
30
  address: z.union([z.string(), z.null()]).optional(),
29
31
  address_2: z.union([z.string(), z.null()]).optional(),
30
32
  post_code: z.union([z.string(), z.null()]).optional(),
31
33
  city: z.union([z.string(), z.null()]).optional(),
32
34
  state: z.union([z.string(), z.null()]).optional(),
35
+ // Bank account fields (stored in settings.bank_accounts array)
36
+ bank_account_iban: z
37
+ .union([z.string(), z.null()])
38
+ .refine((val) => !val || /^[A-Z]{2}[0-9A-Z]{2,32}$/.test(val.replace(/\s/g, "")), {
39
+ message: "Must be a valid IBAN",
40
+ })
41
+ .optional(),
42
+ bank_account_name: z.union([z.string(), z.null()]).optional(),
43
+ bank_account_bank_name: z.union([z.string(), z.null()]).optional(),
44
+ bank_account_bic: z.union([z.string(), z.null()]).optional(),
33
45
  });
34
46
 
35
47
  type CompanySettingsSchema = z.infer<typeof companySettingsSchema>;
@@ -50,17 +62,24 @@ export function CompanySettingsForm({
50
62
  }: CompanySettingsFormProps) {
51
63
  const t = createTranslation({ t: translateProp, namespace, locale, translations });
52
64
 
65
+ const currentSettings = (entity.settings as any) || {};
66
+
53
67
  const form = useForm<CompanySettingsSchema>({
54
68
  resolver: zodResolver(companySettingsSchema),
55
69
  defaultValues: {
56
70
  name: entity.name || "",
57
71
  tax_number: (entity as any).tax_number || null,
72
+ is_tax_subject: entity.is_tax_subject ?? true,
58
73
  tax_number_2: (entity as any).tax_number_2 || null,
59
74
  address: (entity as any).address || null,
60
75
  address_2: (entity as any).address_2 || null,
61
76
  post_code: (entity as any).post_code || null,
62
77
  city: (entity as any).city || null,
63
78
  state: (entity as any).state || null,
79
+ bank_account_iban: currentSettings.bank_accounts?.[0]?.iban || null,
80
+ bank_account_name: currentSettings.bank_accounts?.[0]?.name || null,
81
+ bank_account_bank_name: currentSettings.bank_accounts?.[0]?.bank_name || null,
82
+ bank_account_bic: currentSettings.bank_accounts?.[0]?.bic || null,
64
83
  },
65
84
  });
66
85
 
@@ -85,6 +104,7 @@ export function CompanySettingsForm({
85
104
 
86
105
  if (values.name !== entity.name) updatePayload.name = values.name;
87
106
  if (values.tax_number !== (entity as any).tax_number) updatePayload.tax_number = values.tax_number;
107
+ if (values.is_tax_subject !== entity.is_tax_subject) updatePayload.is_tax_subject = values.is_tax_subject;
88
108
  if (values.tax_number_2 !== (entity as any).tax_number_2) updatePayload.tax_number_2 = values.tax_number_2;
89
109
  if (values.address !== (entity as any).address) updatePayload.address = values.address;
90
110
  if (values.address_2 !== (entity as any).address_2) updatePayload.address_2 = values.address_2;
@@ -92,6 +112,37 @@ export function CompanySettingsForm({
92
112
  if (values.city !== (entity as any).city) updatePayload.city = values.city;
93
113
  if (values.state !== (entity as any).state) updatePayload.state = values.state;
94
114
 
115
+ // Check if bank account fields changed
116
+ const currentIban = currentSettings.bank_accounts?.[0]?.iban || null;
117
+ const currentBankName = currentSettings.bank_accounts?.[0]?.name || null;
118
+ const currentBankBankName = currentSettings.bank_accounts?.[0]?.bank_name || null;
119
+ const currentBic = currentSettings.bank_accounts?.[0]?.bic || null;
120
+
121
+ const bankChanged =
122
+ values.bank_account_iban !== currentIban ||
123
+ values.bank_account_name !== currentBankName ||
124
+ values.bank_account_bank_name !== currentBankBankName ||
125
+ values.bank_account_bic !== currentBic;
126
+
127
+ if (bankChanged) {
128
+ updatePayload.settings = {
129
+ ...currentSettings,
130
+ bank_accounts: values.bank_account_iban
131
+ ? [
132
+ {
133
+ type: "iban" as const,
134
+ iban: values.bank_account_iban,
135
+ name: values.bank_account_name || undefined,
136
+ bank_name: values.bank_account_bank_name || undefined,
137
+ bic: values.bank_account_bic || undefined,
138
+ is_default: true,
139
+ },
140
+ ...(currentSettings.bank_accounts?.slice(1) || []),
141
+ ]
142
+ : currentSettings.bank_accounts || undefined,
143
+ };
144
+ }
145
+
95
146
  if (Object.keys(updatePayload).length > 0) {
96
147
  updateEntity({ id: entity.id, data: updatePayload });
97
148
  } else {
@@ -117,26 +168,40 @@ export function CompanySettingsForm({
117
168
  )}
118
169
  />
119
170
 
120
- <FormField
121
- control={form.control}
122
- name="tax_number"
123
- render={({ field }) => (
124
- <FormItem className="max-w-xs">
125
- <FormLabel className="font-medium text-base">{t("Tax ID")}</FormLabel>
126
- <FormControl>
127
- <Input
128
- {...field}
129
- value={field.value || ""}
130
- onChange={(e) => field.onChange(e.target.value || null)}
131
- placeholder="12-3456789"
132
- className="h-10"
133
- />
134
- </FormControl>
135
- <FormDescription className="text-xs">{t("Tax identification number (optional)")}</FormDescription>
136
- <FormMessage />
137
- </FormItem>
138
- )}
139
- />
171
+ <div className="grid grid-cols-[1fr_auto] items-end gap-4">
172
+ <FormField
173
+ control={form.control}
174
+ name="tax_number"
175
+ render={({ field }) => (
176
+ <FormItem className="max-w-xs">
177
+ <FormLabel className="font-medium text-base">{t("Tax ID")}</FormLabel>
178
+ <FormControl>
179
+ <Input
180
+ {...field}
181
+ value={field.value || ""}
182
+ onChange={(e) => field.onChange(e.target.value || null)}
183
+ placeholder="12-3456789"
184
+ className="h-10"
185
+ />
186
+ </FormControl>
187
+ <FormDescription className="text-xs">{t("Tax identification number (optional)")}</FormDescription>
188
+ <FormMessage />
189
+ </FormItem>
190
+ )}
191
+ />
192
+ <FormField
193
+ control={form.control}
194
+ name="is_tax_subject"
195
+ render={({ field }) => (
196
+ <FormItem className="flex flex-row items-center space-x-2 space-y-0 pb-7">
197
+ <FormControl>
198
+ <Checkbox checked={field.value} onCheckedChange={field.onChange} />
199
+ </FormControl>
200
+ <FormLabel className="font-normal">{t("Tax subject")}</FormLabel>
201
+ </FormItem>
202
+ )}
203
+ />
204
+ </div>
140
205
 
141
206
  {(entity as any).country_rules?.features?.includes("tax_number_2") && (
142
207
  <FormField
@@ -278,6 +343,94 @@ export function CompanySettingsForm({
278
343
  <FormDescription className="text-xs">{t("Country cannot be changed")}</FormDescription>
279
344
  </FormItem>
280
345
  </div>
346
+
347
+ <div className="border-t pt-6">
348
+ <p className="mb-4 font-medium text-base">{t("Bank Account")}</p>
349
+
350
+ <div className="space-y-4">
351
+ <FormField
352
+ control={form.control}
353
+ name="bank_account_iban"
354
+ render={({ field }) => (
355
+ <FormItem>
356
+ <FormLabel>{t("IBAN")}</FormLabel>
357
+ <FormControl>
358
+ <Input
359
+ {...field}
360
+ value={field.value || ""}
361
+ onChange={(e) => field.onChange(e.target.value.toUpperCase().replace(/\s/g, "") || null)}
362
+ placeholder="SI56 0123 4567 8901 234"
363
+ className="h-10 font-mono"
364
+ />
365
+ </FormControl>
366
+ <FormMessage />
367
+ </FormItem>
368
+ )}
369
+ />
370
+
371
+ <FormField
372
+ control={form.control}
373
+ name="bank_account_name"
374
+ render={({ field }) => (
375
+ <FormItem>
376
+ <FormLabel>{t("Account Name")}</FormLabel>
377
+ <FormControl>
378
+ <Input
379
+ {...field}
380
+ value={field.value || ""}
381
+ onChange={(e) => field.onChange(e.target.value || null)}
382
+ placeholder={t("Main Business Account")}
383
+ className="h-10"
384
+ />
385
+ </FormControl>
386
+ <FormMessage />
387
+ </FormItem>
388
+ )}
389
+ />
390
+
391
+ <div className="grid gap-4 md:grid-cols-2">
392
+ <FormField
393
+ control={form.control}
394
+ name="bank_account_bank_name"
395
+ render={({ field }) => (
396
+ <FormItem>
397
+ <FormLabel>{t("Bank Name")}</FormLabel>
398
+ <FormControl>
399
+ <Input
400
+ {...field}
401
+ value={field.value || ""}
402
+ onChange={(e) => field.onChange(e.target.value || null)}
403
+ placeholder="NLB d.d."
404
+ className="h-10"
405
+ />
406
+ </FormControl>
407
+ <FormMessage />
408
+ </FormItem>
409
+ )}
410
+ />
411
+
412
+ <FormField
413
+ control={form.control}
414
+ name="bank_account_bic"
415
+ render={({ field }) => (
416
+ <FormItem>
417
+ <FormLabel>{t("BIC/SWIFT")}</FormLabel>
418
+ <FormControl>
419
+ <Input
420
+ {...field}
421
+ value={field.value || ""}
422
+ onChange={(e) => field.onChange(e.target.value.toUpperCase() || null)}
423
+ placeholder="LJBASI2X"
424
+ className="h-10 font-mono"
425
+ />
426
+ </FormControl>
427
+ <FormMessage />
428
+ </FormItem>
429
+ )}
430
+ />
431
+ </div>
432
+ </div>
433
+ </div>
281
434
  </form>
282
435
  </Form>
283
436
  );
@@ -51,6 +51,7 @@ const defaultsSettingsSchema = z.object({
51
51
  default_credit_note_payment_terms: z.union([z.string(), z.null()]).optional(),
52
52
  // Shared
53
53
  document_footer: z.union([z.string(), z.null()]).optional(),
54
+ default_document_signature: z.union([z.string(), z.null()]).optional(),
54
55
  });
55
56
 
56
57
  type DefaultsSettingsSchema = z.infer<typeof defaultsSettingsSchema>;
@@ -89,6 +90,8 @@ export function DefaultsSettingsForm({
89
90
  const creditNotePaymentTermsRef = useRef<HTMLTextAreaElement>(null);
90
91
  // Ref for document footer (shared)
91
92
  const documentFooterRef = useRef<HTMLTextAreaElement>(null);
93
+ // Ref for document signature (shared)
94
+ const documentSignatureRef = useRef<HTMLTextAreaElement>(null);
92
95
 
93
96
  const form = useForm<DefaultsSettingsSchema>({
94
97
  resolver: zodResolver(defaultsSettingsSchema),
@@ -106,6 +109,7 @@ export function DefaultsSettingsForm({
106
109
  default_credit_note_payment_terms: currentSettings.default_credit_note_payment_terms || null,
107
110
  // Shared
108
111
  document_footer: currentSettings.document_footer || null,
112
+ default_document_signature: currentSettings.default_document_signature || null,
109
113
  },
110
114
  });
111
115
 
@@ -140,6 +144,7 @@ export function DefaultsSettingsForm({
140
144
  default_credit_note_payment_terms: values.default_credit_note_payment_terms || null,
141
145
  // Shared
142
146
  document_footer: values.document_footer || null,
147
+ default_document_signature: values.default_document_signature || null,
143
148
  },
144
149
  };
145
150
 
@@ -452,7 +457,39 @@ export function DefaultsSettingsForm({
452
457
 
453
458
  // Footer section content
454
459
  const footerContent = (
455
- <div className="border-t pt-6">
460
+ <div className="space-y-6 border-t pt-6">
461
+ <FormField
462
+ control={form.control}
463
+ name="default_document_signature"
464
+ render={({ field }) => (
465
+ <FormItem>
466
+ <div className="flex items-center justify-between">
467
+ <FormLabel className="font-medium text-sm">{t("Document Signature")}</FormLabel>
468
+ <SmartCodeInsertButton
469
+ textareaRef={documentSignatureRef}
470
+ value={field.value || ""}
471
+ onInsert={(newValue) => field.onChange(newValue)}
472
+ t={t}
473
+ />
474
+ </div>
475
+ <FormControl>
476
+ <InputWithPreview
477
+ ref={documentSignatureRef}
478
+ value={field.value || ""}
479
+ onChange={field.onChange}
480
+ placeholder={t("{entity_name}")}
481
+ entity={entity}
482
+ multiline
483
+ rows={2}
484
+ className="resize-y"
485
+ />
486
+ </FormControl>
487
+ <FormDescription className="text-xs">{t("Signature text displayed on all PDF documents")}</FormDescription>
488
+ <FormMessage />
489
+ </FormItem>
490
+ )}
491
+ />
492
+
456
493
  <FormField
457
494
  control={form.control}
458
495
  name="document_footer"
@@ -3,7 +3,6 @@ import type {
3
3
  Entity,
4
4
  EntitySettings,
5
5
  EntitySettingsTaxClauseDefaults,
6
- GetEntities200DataItem,
7
6
  TaxRules,
8
7
  } from "@spaceinvoices/js-sdk";
9
8
  import { ChevronDown, Globe, MessageSquareText } from "lucide-react";
@@ -50,7 +49,7 @@ type TaxRulesSettingsSchema = z.infer<typeof taxRulesSettingsSchema>;
50
49
  type SectionType = "tax-rules" | "tax-clauses";
51
50
 
52
51
  /** Entity type with country_rules included (from getEntities response) */
53
- type EntityWithRules = GetEntities200DataItem;
52
+ type EntityWithRules = Entity;
54
53
 
55
54
  export type TaxRulesSettingsFormProps = {
56
55
  entity: EntityWithRules;
@@ -11,6 +11,7 @@ import { Form } from "@/ui/components/ui/form";
11
11
  import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/ui/components/ui/tooltip";
12
12
  import { createEstimateSchema } from "@/ui/generated/schemas";
13
13
  import { useNextDocumentNumber } from "@/ui/hooks/use-next-document-number";
14
+ import { useViesCheck } from "@/ui/hooks/use-vies-check";
14
15
  import type { ComponentTranslationProps } from "@/ui/lib/translation";
15
16
  import { createTranslation } from "@/ui/lib/translation";
16
17
  import { useEntities } from "@/ui/providers/entities-context";
@@ -18,8 +19,11 @@ import { useFormFooterRegistration } from "@/ui/providers/form-footer-context";
18
19
  import { CUSTOMERS_CACHE_KEY } from "../../customers/customers.hooks";
19
20
  import {
20
21
  DocumentDetailsSection,
22
+ DocumentFooterField,
21
23
  DocumentNoteField,
22
24
  DocumentPaymentTermsField,
25
+ DocumentSignatureField,
26
+ DocumentTaxClauseField,
23
27
  } from "../../documents/create/document-details-section";
24
28
  import { DocumentItemsSection, type PriceModesMap } from "../../documents/create/document-items-section";
25
29
  import { DocumentRecipientSection } from "../../documents/create/document-recipient-section";
@@ -114,8 +118,9 @@ export default function CreateEstimateForm({
114
118
  enabled: !!entityId,
115
119
  });
116
120
 
117
- // Get default payment terms from entity settings
121
+ // Get default payment terms and footer from entity settings
118
122
  const defaultPaymentTerms = (activeEntity?.settings as any)?.default_estimate_payment_terms || "";
123
+ const defaultFooter = (activeEntity?.settings as any)?.document_footer || "";
119
124
 
120
125
  const form = useForm<CreateEstimateFormValues>({
121
126
  resolver: zodResolver(createEstimateSchema) as Resolver<CreateEstimateFormValues>,
@@ -126,13 +131,18 @@ export default function CreateEstimateForm({
126
131
  // Cast customer to form schema type (API type may have additional fields)
127
132
  customer: (initialValues?.customer as CreateEstimateFormValues["customer"]) ?? undefined,
128
133
  items: initialValues?.items?.length
129
- ? initialValues.items.map((item) => ({
134
+ ? initialValues.items.map((item: any) => ({
135
+ type: item.type,
130
136
  name: item.name || "",
131
137
  description: item.description || "",
132
- quantity: item.quantity ?? 1,
133
- // Use gross_price if set, otherwise use price
134
- price: item.gross_price ?? item.price,
135
- taxes: item.taxes || [],
138
+ ...(item.type !== "separator"
139
+ ? {
140
+ quantity: item.quantity ?? 1,
141
+ // Use gross_price if set, otherwise use price
142
+ price: item.gross_price ?? item.price,
143
+ taxes: item.taxes || [],
144
+ }
145
+ : {}),
136
146
  }))
137
147
  : [
138
148
  {
@@ -144,8 +154,11 @@ export default function CreateEstimateForm({
144
154
  },
145
155
  ],
146
156
  currency_code: initialValues?.currency_code || activeEntity?.currency_code || "EUR",
157
+ reference: (initialValues as any)?.reference ?? "",
147
158
  note: initialValues?.note ?? defaultEstimateNote,
159
+ tax_clause: (initialValues as any)?.tax_clause ?? "",
148
160
  payment_terms: initialValues?.payment_terms ?? defaultPaymentTerms,
161
+ footer: (initialValues as any)?.footer ?? defaultFooter,
149
162
  date_valid_till:
150
163
  initialValues?.date_valid_till ||
151
164
  calculateDueDate(initialValues?.date || new Date().toISOString(), defaultEstimateValidDays),
@@ -181,6 +194,10 @@ export default function CreateEstimateForm({
181
194
  if (entityDefaultNote && !form.getValues("note")) {
182
195
  form.setValue("note", entityDefaultNote);
183
196
  }
197
+ const entityDefaultSignature = (activeEntity?.settings as any)?.default_document_signature;
198
+ if (entityDefaultSignature && !form.getValues("signature")) {
199
+ form.setValue("signature", entityDefaultSignature);
200
+ }
184
201
  if (!initialValues?.date_valid_till) {
185
202
  const validDays = (activeEntity?.settings as any)?.default_estimate_valid_days ?? 30;
186
203
  const currentDate = form.getValues("date");
@@ -234,6 +251,37 @@ export default function CreateEstimateForm({
234
251
  control: form.control,
235
252
  });
236
253
 
254
+ // ============================================================================
255
+ // VIES Check - determine if reverse charge applies
256
+ // ============================================================================
257
+ const {
258
+ reverseChargeApplies,
259
+ transactionType,
260
+ isFetching: isViesFetching,
261
+ warning: viesWarning,
262
+ } = useViesCheck({
263
+ issuerCountryCode: activeEntity?.country_code,
264
+ isTaxSubject: activeEntity?.is_tax_subject ?? true,
265
+ customerCountry: formValues.customer?.country,
266
+ customerCountryCode: formValues.customer?.country_code,
267
+ customerTaxNumber: formValues.customer?.tax_number,
268
+ enabled: !!activeEntity,
269
+ });
270
+
271
+ // Auto-populate tax_clause from entity settings when transaction type changes
272
+ const effectiveTransactionType = transactionType ?? "domestic";
273
+ const prevTransactionTypeRef = useRef<string | undefined>(undefined);
274
+ useEffect(() => {
275
+ if (effectiveTransactionType === prevTransactionTypeRef.current) return;
276
+ prevTransactionTypeRef.current = effectiveTransactionType;
277
+
278
+ const taxClauseDefaults = (activeEntity?.settings as any)?.tax_clause_defaults;
279
+ if (!taxClauseDefaults) return;
280
+
281
+ const clause = taxClauseDefaults[effectiveTransactionType] ?? "";
282
+ form.setValue("tax_clause", clause);
283
+ }, [effectiveTransactionType, activeEntity, form]);
284
+
237
285
  // Extract customer management logic into a custom hook
238
286
  const {
239
287
  originalCustomer,
@@ -304,17 +352,21 @@ export default function CreateEstimateForm({
304
352
  useFormFooterRegistration({
305
353
  formId: "create-estimate-form",
306
354
  isPending,
307
- isDirty: form.formState.isDirty,
355
+ isDirty: form.formState.isDirty || !!initialValues,
308
356
  label: titleType === "estimate" ? t("Create Estimate") : t("Create Quote"),
309
357
  secondaryAction,
310
358
  });
311
359
 
312
- // Set default payment terms from entity settings when entity data is available
360
+ // Set default payment terms and footer from entity settings when entity data is available
313
361
  useEffect(() => {
314
362
  const entityDefaultPaymentTerms = (activeEntity?.settings as any)?.default_estimate_payment_terms;
315
363
  if (entityDefaultPaymentTerms && !form.getValues("payment_terms")) {
316
364
  form.setValue("payment_terms", entityDefaultPaymentTerms);
317
365
  }
366
+ const entityDefaultFooter = (activeEntity?.settings as any)?.document_footer;
367
+ if (entityDefaultFooter && !form.getValues("footer")) {
368
+ form.setValue("footer", entityDefaultFooter);
369
+ }
318
370
  }, [activeEntity, form]);
319
371
 
320
372
  // Recalculate valid-till date when document date changes
@@ -350,8 +402,10 @@ export default function CreateEstimateForm({
350
402
  customer: formValues.customer,
351
403
  items: transformedItems,
352
404
  currency_code: formValues.currency_code,
405
+ reference: formValues.reference,
353
406
  note: formValues.note,
354
407
  payment_terms: formValues.payment_terms,
408
+ signature: formValues.signature,
355
409
  title_type: titleType,
356
410
  };
357
411
  callback(payload);
@@ -392,6 +446,10 @@ export default function CreateEstimateForm({
392
446
  maxTaxesPerItem={activeEntity?.country_rules?.max_taxes_per_item}
393
447
  priceModesRef={priceModesRef}
394
448
  initialPriceModes={initialPriceModes}
449
+ taxesDisabled={reverseChargeApplies}
450
+ taxesDisabledMessage={
451
+ reverseChargeApplies ? t("Reverse charge - tax exempt EU B2B sale") : viesWarning ? viesWarning : undefined
452
+ }
395
453
  />
396
454
 
397
455
  <DocumentNoteField
@@ -407,6 +465,21 @@ export default function CreateEstimateForm({
407
465
  }}
408
466
  />
409
467
 
468
+ <DocumentTaxClauseField
469
+ control={form.control}
470
+ t={t}
471
+ entity={activeEntity}
472
+ document={{
473
+ number: formValues.number,
474
+ date: formValues.date,
475
+ date_valid_till: formValues.date_valid_till,
476
+ currency_code: formValues.currency_code,
477
+ customer: formValues.customer as any,
478
+ }}
479
+ transactionType={transactionType}
480
+ isTransactionTypeFetching={isViesFetching}
481
+ />
482
+
410
483
  <DocumentPaymentTermsField
411
484
  control={form.control}
412
485
  t={t}
@@ -419,6 +492,32 @@ export default function CreateEstimateForm({
419
492
  customer: formValues.customer as any,
420
493
  }}
421
494
  />
495
+
496
+ <DocumentSignatureField
497
+ control={form.control}
498
+ t={t}
499
+ entity={activeEntity}
500
+ document={{
501
+ number: formValues.number,
502
+ date: formValues.date,
503
+ date_valid_till: formValues.date_valid_till,
504
+ currency_code: formValues.currency_code,
505
+ customer: formValues.customer as any,
506
+ }}
507
+ />
508
+
509
+ <DocumentFooterField
510
+ control={form.control}
511
+ t={t}
512
+ entity={activeEntity}
513
+ document={{
514
+ number: formValues.number,
515
+ date: formValues.date,
516
+ date_valid_till: formValues.date_valid_till,
517
+ currency_code: formValues.currency_code,
518
+ customer: formValues.customer as any,
519
+ }}
520
+ />
422
521
  </form>
423
522
  </Form>
424
523
  );
@@ -35,6 +35,9 @@ export default {
35
35
  "Insert variable": "Variable einfügen",
36
36
  "Add payment instructions, terms, or other notes...":
37
37
  "Zahlungsanweisungen, Bedingungen oder andere Notizen hinzufügen...",
38
+ // Signature field
39
+ Signature: "Unterschrift",
40
+ "Add signature text...": "Unterschriftstext hinzufügen...",
38
41
  // Payment terms field
39
42
  "Payment Terms": "Zahlungsbedingungen",
40
43
  "Add payment terms...": "Zahlungsbedingungen hinzufügen...",
@@ -61,4 +64,22 @@ export default {
61
64
  "Net price": "Nettopreis",
62
65
  "Gross price (tax included)": "Bruttopreis (inkl. MwSt.)",
63
66
  "Net price (before tax)": "Nettopreis (exkl. MwSt.)",
67
+ // Separator items
68
+ "Add separator": "Trennzeile hinzufügen",
69
+ "Section header": "Abschnittsüberschrift",
70
+ "Section title...": "Abschnittstitel...",
71
+ // Transaction type
72
+ "Transaction type": "Transaktionstyp",
73
+ Domestic: "Inland",
74
+ "EU B2B": "EU B2B",
75
+ "EU B2C": "EU B2C",
76
+ Export: "Export",
77
+ "Determining transaction type...": "Transaktionstyp wird ermittelt...",
78
+ "This invoice will not be fiscalized (non-domestic transaction)":
79
+ "Diese Rechnung wird nicht fiskalisiert (nicht-inländische Transaktion)",
80
+ "Tax Clause": "Steuerklausel",
81
+ "Add tax clause...": "Steuerklausel hinzufügen...",
82
+ Footer: "Fußzeile",
83
+ "Add document footer...": "Dokumentfußzeile hinzufügen...",
84
+ "Reverse charge - tax exempt EU B2B sale": "Umkehrung der Steuerschuldnerschaft - steuerbefreiter EU B2B Verkauf",
64
85
  } as const;
@@ -33,6 +33,9 @@ export default {
33
33
  Note: "Nota",
34
34
  "Insert variable": "Insertar variable",
35
35
  "Add payment instructions, terms, or other notes...": "Añada instrucciones de pago, condiciones u otras notas...",
36
+ // Signature field
37
+ Signature: "Firma",
38
+ "Add signature text...": "Añadir texto de firma...",
36
39
  "Payment Terms": "Condiciones de pago",
37
40
  "Add payment terms...": "Añada condiciones de pago...",
38
41
  Quantity: "Cantidad",
@@ -54,4 +57,22 @@ export default {
54
57
  "Net price": "Precio neto",
55
58
  "Gross price (tax included)": "Precio bruto (impuestos incluidos)",
56
59
  "Net price (before tax)": "Precio neto (antes de impuestos)",
60
+ // Separator items
61
+ "Add separator": "Añadir separador",
62
+ "Section header": "Encabezado de sección",
63
+ "Section title...": "Título de sección...",
64
+ // Transaction type
65
+ "Transaction type": "Tipo de transacción",
66
+ Domestic: "Nacional",
67
+ "EU B2B": "EU B2B",
68
+ "EU B2C": "EU B2C",
69
+ Export: "Exportación",
70
+ "Determining transaction type...": "Determinando tipo de transacción...",
71
+ "This invoice will not be fiscalized (non-domestic transaction)":
72
+ "Esta factura no será fiscalizada (transacción no nacional)",
73
+ "Tax Clause": "Cláusula fiscal",
74
+ "Add tax clause...": "Agregar cláusula fiscal...",
75
+ Footer: "Pie de página",
76
+ "Add document footer...": "Añadir pie de página del documento...",
77
+ "Reverse charge - tax exempt EU B2B sale": "Inversión del sujeto pasivo - venta EU B2B exenta de impuestos",
57
78
  } as const;
@@ -34,6 +34,9 @@ export default {
34
34
  "Insert variable": "Insérer une variable",
35
35
  "Add payment instructions, terms, or other notes...":
36
36
  "Ajoutez des instructions de paiement, des conditions ou d'autres notes...",
37
+ // Signature field
38
+ Signature: "Signature",
39
+ "Add signature text...": "Ajouter un texte de signature...",
37
40
  "Payment Terms": "Conditions de paiement",
38
41
  "Add payment terms...": "Ajoutez des conditions de paiement...",
39
42
  Quantity: "Quantité",
@@ -55,4 +58,22 @@ export default {
55
58
  "Net price": "Prix net",
56
59
  "Gross price (tax included)": "Prix brut (taxes incluses)",
57
60
  "Net price (before tax)": "Prix net (avant taxes)",
61
+ // Separator items
62
+ "Add separator": "Ajouter un séparateur",
63
+ "Section header": "En-tête de section",
64
+ "Section title...": "Titre de section...",
65
+ // Transaction type
66
+ "Transaction type": "Type de transaction",
67
+ Domestic: "Nationale",
68
+ "EU B2B": "EU B2B",
69
+ "EU B2C": "EU B2C",
70
+ Export: "Exportation",
71
+ "Determining transaction type...": "Détermination du type de transaction...",
72
+ "This invoice will not be fiscalized (non-domestic transaction)":
73
+ "Cette facture ne sera pas fiscalisée (transaction non nationale)",
74
+ "Tax Clause": "Clause fiscale",
75
+ "Add tax clause...": "Ajouter une clause fiscale...",
76
+ Footer: "Pied de page",
77
+ "Add document footer...": "Ajouter un pied de page...",
78
+ "Reverse charge - tax exempt EU B2B sale": "Autoliquidation - vente B2B UE exonérée de taxe",
58
79
  } as const;