@spaceinvoices/react-ui 0.4.4 → 0.4.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (147) hide show
  1. package/cli/dist/index.js +1 -1
  2. package/package.json +1 -1
  3. package/src/components/advance-invoices/create/create-advance-invoice-form.tsx +61 -11
  4. package/src/components/advance-invoices/create/locales/de.ts +15 -0
  5. package/src/components/advance-invoices/create/locales/es.ts +15 -0
  6. package/src/components/advance-invoices/create/locales/fr.ts +15 -0
  7. package/src/components/advance-invoices/create/locales/hr.ts +15 -0
  8. package/src/components/advance-invoices/create/locales/it.ts +15 -0
  9. package/src/components/advance-invoices/create/locales/nl.ts +15 -0
  10. package/src/components/advance-invoices/create/locales/pl.ts +15 -0
  11. package/src/components/advance-invoices/create/locales/pt.ts +15 -0
  12. package/src/components/advance-invoices/create/locales/sl.ts +15 -0
  13. package/src/components/advance-invoices/list/list-row-actions.tsx +48 -1
  14. package/src/components/advance-invoices/list/list-table.tsx +21 -1
  15. package/src/components/advance-invoices/list/locales/de.ts +4 -0
  16. package/src/components/advance-invoices/list/locales/en.ts +3 -0
  17. package/src/components/advance-invoices/list/locales/es.ts +4 -0
  18. package/src/components/advance-invoices/list/locales/fr.ts +4 -0
  19. package/src/components/advance-invoices/list/locales/hr.ts +3 -0
  20. package/src/components/advance-invoices/list/locales/it.ts +4 -0
  21. package/src/components/advance-invoices/list/locales/nl.ts +4 -0
  22. package/src/components/advance-invoices/list/locales/pl.ts +3 -0
  23. package/src/components/advance-invoices/list/locales/pt.ts +4 -0
  24. package/src/components/advance-invoices/list/locales/sl.ts +3 -0
  25. package/src/components/credit-notes/create/create-credit-note-form.tsx +71 -8
  26. package/src/components/credit-notes/create/locales/de.ts +16 -0
  27. package/src/components/credit-notes/create/locales/es.ts +16 -0
  28. package/src/components/credit-notes/create/locales/fr.ts +16 -0
  29. package/src/components/credit-notes/create/locales/hr.ts +16 -0
  30. package/src/components/credit-notes/create/locales/it.ts +16 -0
  31. package/src/components/credit-notes/create/locales/nl.ts +16 -0
  32. package/src/components/credit-notes/create/locales/pl.ts +16 -0
  33. package/src/components/credit-notes/create/locales/pt.ts +16 -0
  34. package/src/components/credit-notes/create/locales/sl.ts +17 -1
  35. package/src/components/credit-notes/list/list-row-actions.tsx +44 -1
  36. package/src/components/credit-notes/list/list-table.tsx +16 -2
  37. package/src/components/credit-notes/list/locales/de.ts +2 -0
  38. package/src/components/credit-notes/list/locales/en.ts +2 -0
  39. package/src/components/credit-notes/list/locales/es.ts +2 -0
  40. package/src/components/credit-notes/list/locales/fr.ts +2 -0
  41. package/src/components/credit-notes/list/locales/hr.ts +2 -0
  42. package/src/components/credit-notes/list/locales/it.ts +2 -0
  43. package/src/components/credit-notes/list/locales/nl.ts +2 -0
  44. package/src/components/credit-notes/list/locales/pl.ts +2 -0
  45. package/src/components/credit-notes/list/locales/pt.ts +2 -0
  46. package/src/components/credit-notes/list/locales/sl.ts +2 -0
  47. package/src/components/dashboard/collection-rate-card/use-collection-rate.ts +48 -9
  48. package/src/components/dashboard/revenue-trend-chart/use-revenue-trend.ts +77 -48
  49. package/src/components/dashboard/shared/use-revenue-data.ts +77 -9
  50. package/src/components/delivery-notes/create/create-delivery-note-form.tsx +67 -7
  51. package/src/components/delivery-notes/create/locales/de.ts +16 -0
  52. package/src/components/delivery-notes/create/locales/es.ts +16 -0
  53. package/src/components/delivery-notes/create/locales/fr.ts +16 -0
  54. package/src/components/delivery-notes/create/locales/hr.ts +16 -0
  55. package/src/components/delivery-notes/create/locales/it.ts +16 -0
  56. package/src/components/delivery-notes/create/locales/nl.ts +16 -0
  57. package/src/components/delivery-notes/create/locales/pl.ts +16 -0
  58. package/src/components/delivery-notes/create/locales/pt.ts +16 -0
  59. package/src/components/delivery-notes/create/locales/sl.ts +17 -1
  60. package/src/components/delivery-notes/list/list-table.tsx +17 -8
  61. package/src/components/delivery-notes/list/locales/de.ts +32 -1
  62. package/src/components/delivery-notes/list/locales/en.ts +31 -0
  63. package/src/components/delivery-notes/list/locales/es.ts +32 -1
  64. package/src/components/delivery-notes/list/locales/fr.ts +32 -1
  65. package/src/components/delivery-notes/list/locales/hr.ts +32 -1
  66. package/src/components/delivery-notes/list/locales/it.ts +32 -1
  67. package/src/components/delivery-notes/list/locales/nl.ts +32 -1
  68. package/src/components/delivery-notes/list/locales/pl.ts +32 -1
  69. package/src/components/delivery-notes/list/locales/pt.ts +32 -1
  70. package/src/components/delivery-notes/list/locales/sl.ts +32 -1
  71. package/src/components/documents/create/document-add-item-form.tsx +70 -0
  72. package/src/components/documents/create/document-details-section.tsx +122 -2
  73. package/src/components/documents/create/document-items-section.tsx +21 -4
  74. package/src/components/documents/create/live-preview.tsx +24 -4
  75. package/src/components/documents/create/mark-as-paid-section.tsx +29 -20
  76. package/src/components/documents/create/prepare-document-submission.ts +19 -7
  77. package/src/components/documents/shared/document-preview-display.tsx +3 -4
  78. package/src/components/documents/view/document-actions-bar.tsx +7 -27
  79. package/src/components/documents/view/document-details-card.tsx +26 -9
  80. package/src/components/documents/view/locales/de.ts +2 -0
  81. package/src/components/documents/view/locales/es.ts +2 -0
  82. package/src/components/documents/view/locales/fr.ts +2 -0
  83. package/src/components/documents/view/locales/hr.ts +2 -0
  84. package/src/components/documents/view/locales/it.ts +2 -0
  85. package/src/components/documents/view/locales/nl.ts +2 -0
  86. package/src/components/documents/view/locales/pl.ts +2 -0
  87. package/src/components/documents/view/locales/pt.ts +2 -0
  88. package/src/components/documents/view/locales/sl.ts +3 -1
  89. package/src/components/documents/view/use-document-download.ts +6 -3
  90. package/src/components/entities/create-entity-form.tsx +2 -1
  91. package/src/components/entities/entity-settings-form/input-with-preview.tsx +30 -2
  92. package/src/components/entities/entity-settings-form/locales/de.ts +1 -0
  93. package/src/components/entities/entity-settings-form/locales/es.ts +1 -0
  94. package/src/components/entities/entity-settings-form/locales/fr.ts +1 -0
  95. package/src/components/entities/entity-settings-form/locales/hr.ts +1 -0
  96. package/src/components/entities/entity-settings-form/locales/it.ts +1 -0
  97. package/src/components/entities/entity-settings-form/locales/nl.ts +1 -0
  98. package/src/components/entities/entity-settings-form/locales/pl.ts +1 -0
  99. package/src/components/entities/entity-settings-form/locales/pt.ts +1 -0
  100. package/src/components/entities/entity-settings-form/locales/sl.ts +1 -0
  101. package/src/components/entities/settings/company-settings-form.tsx +173 -20
  102. package/src/components/estimates/create/create-estimate-form.tsx +64 -6
  103. package/src/components/estimates/create/locales/de.ts +16 -0
  104. package/src/components/estimates/create/locales/es.ts +16 -0
  105. package/src/components/estimates/create/locales/fr.ts +16 -0
  106. package/src/components/estimates/create/locales/hr.ts +16 -0
  107. package/src/components/estimates/create/locales/it.ts +16 -0
  108. package/src/components/estimates/create/locales/nl.ts +16 -0
  109. package/src/components/estimates/create/locales/pl.ts +16 -0
  110. package/src/components/estimates/create/locales/pt.ts +16 -0
  111. package/src/components/estimates/create/locales/sl.ts +17 -1
  112. package/src/components/estimates/list/list-table.tsx +11 -2
  113. package/src/components/estimates/list/locales/de.ts +1 -0
  114. package/src/components/estimates/list/locales/en.ts +1 -0
  115. package/src/components/estimates/list/locales/es.ts +1 -0
  116. package/src/components/estimates/list/locales/fr.ts +1 -0
  117. package/src/components/estimates/list/locales/hr.ts +1 -0
  118. package/src/components/estimates/list/locales/it.ts +1 -0
  119. package/src/components/estimates/list/locales/nl.ts +1 -0
  120. package/src/components/estimates/list/locales/pl.ts +1 -0
  121. package/src/components/estimates/list/locales/pt.ts +1 -0
  122. package/src/components/estimates/list/locales/sl.ts +1 -0
  123. package/src/components/export/document-export-form.tsx +46 -12
  124. package/src/components/invoices/create/create-invoice-form.tsx +58 -10
  125. package/src/components/invoices/create/locales/de.ts +15 -0
  126. package/src/components/invoices/create/locales/es.ts +15 -0
  127. package/src/components/invoices/create/locales/fr.ts +15 -0
  128. package/src/components/invoices/create/locales/hr.ts +15 -0
  129. package/src/components/invoices/create/locales/it.ts +15 -0
  130. package/src/components/invoices/create/locales/nl.ts +15 -0
  131. package/src/components/invoices/create/locales/pl.ts +15 -0
  132. package/src/components/invoices/create/locales/pt.ts +15 -0
  133. package/src/components/invoices/create/locales/sl.ts +16 -1
  134. package/src/components/invoices/view/fiscalization-status-card.tsx +10 -8
  135. package/src/components/table/search-input.tsx +17 -0
  136. package/src/generated/schemas/advanceinvoice.ts +4 -2
  137. package/src/generated/schemas/creditnote.ts +2 -1
  138. package/src/generated/schemas/deliverynote.ts +2 -1
  139. package/src/generated/schemas/estimate.ts +4 -2
  140. package/src/generated/schemas/invoice.ts +2 -1
  141. package/src/generated/schemas/renderadvanceinvoicepreview_body.ts +17 -23
  142. package/src/generated/schemas/rendercreditnotepreview_body.ts +17 -23
  143. package/src/generated/schemas/renderdeliverynotepreview_body.ts +17 -23
  144. package/src/generated/schemas/renderestimatepreview_body.ts +17 -23
  145. package/src/generated/schemas/renderinvoicepreview_body.ts +19 -23
  146. package/src/hooks/use-duplicate-document.ts +16 -9
  147. package/src/hooks/use-vies-check.ts +3 -0
@@ -36,7 +36,8 @@ export default {
36
36
  "Valid until": "Veljavno do",
37
37
  Customer: "Stranka",
38
38
  Subtotal: "Vmesni seštevek",
39
- Tax: "Davek",
39
+ Tax: "DDV",
40
+ of: "od",
40
41
  Total: "Skupaj",
41
42
  Paid: "Plačano",
42
43
  Due: "Za plačilo",
@@ -107,6 +108,7 @@ export default {
107
108
  Pending: "V čakanju",
108
109
  Failed: "Neuspešno",
109
110
  Skipped: "Preskočeno",
111
+ "Skipped by user": "Uporabnik preskočil",
110
112
  "Retry fiscalization": "Ponovi fiskalizacijo",
111
113
 
112
114
  // PDF language names
@@ -36,9 +36,11 @@ export function useDocumentDownload({
36
36
  const [isDownloadingEslog, setIsDownloadingEslog] = useState(false);
37
37
 
38
38
  /**
39
- * Download PDF in specified locale
39
+ * Download PDF with optional language override for translations
40
+ * Formatting (decimal separators, dates) always uses entity locale.
41
+ * The language param only changes labels/translations on the PDF.
40
42
  */
41
- const downloadPdf = async (document: Document, documentType: DocumentType, locale: string) => {
43
+ const downloadPdf = async (document: Document, documentType: DocumentType, language?: string) => {
42
44
  if (!sdk || !activeEntity?.id) {
43
45
  onDownloadError?.("Download failed");
44
46
  return;
@@ -51,7 +53,8 @@ export function useDocumentDownload({
51
53
  // SDK signature: renderPdf(id, params?, SDKMethodOptions?)
52
54
  // entity_id goes in SDKMethodOptions (last arg), not params
53
55
  // Note: renderPdf is on invoices module but works with any document ID via /documents/{id}/pdf
54
- const params = locale ? { locale } : {};
56
+ // Don't send locale entity locale drives formatting. Send language for translation override.
57
+ const params = language ? { language } : {};
55
58
  const blob = await sdk.invoices.renderPdf(document.id, params, { entity_id: activeEntity.id });
56
59
  const downloadUrl = window.URL.createObjectURL(blob);
57
60
  const link = window.document.createElement("a");
@@ -99,10 +99,11 @@ export function CreateEntityForm({
99
99
  useEffect(() => {
100
100
  if (countryValue !== autoFilledCountryRef.current) {
101
101
  setActiveCountryCode(undefined);
102
+ form.setValue("country_code", "");
102
103
  } else {
103
104
  setActiveCountryCode(countryCode);
104
105
  }
105
- }, [countryValue, countryCode]);
106
+ }, [countryValue, countryCode, form]);
106
107
 
107
108
  const handleCompanySelect = (company: CompanyRegistryResult) => {
108
109
  form.setValue("name", company.name);
@@ -17,9 +17,10 @@ interface InputWithPreviewProps {
17
17
  }
18
18
 
19
19
  function formatVariableName(varName: string): string {
20
- // Convert snake_case to Title Case with spaces
21
- // e.g., "document_number" -> "Document Number"
20
+ // Convert snake_case (with optional dot notation) to Title Case with spaces
21
+ // e.g., "document_number" -> "Document Number", "bank_account.iban" -> "Bank Account Iban"
22
22
  return varName
23
+ .replace(/\./g, "_")
23
24
  .split("_")
24
25
  .map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
25
26
  .join(" ");
@@ -78,6 +79,33 @@ function getVariableValue(varName: string, entity: Entity, document?: Invoice |
78
79
  }
79
80
  }
80
81
 
82
+ // Bank account variables (from entity settings)
83
+ const bankAccounts = (entity.settings as any)?.bank_accounts as
84
+ | Array<{
85
+ iban?: string;
86
+ bank_name?: string;
87
+ bic?: string;
88
+ account_number?: string;
89
+ routing_number?: string;
90
+ sort_code?: string;
91
+ is_default?: boolean;
92
+ }>
93
+ | undefined;
94
+ const bankAccount = bankAccounts?.find((acc) => acc.is_default) ?? bankAccounts?.[0];
95
+
96
+ if (varName === "bank_account" && bankAccount) {
97
+ const lines: string[] = [];
98
+ if (bankAccount.bank_name) lines.push(bankAccount.bank_name);
99
+ if (bankAccount.iban) lines.push(`IBAN: ${bankAccount.iban}`);
100
+ else if (bankAccount.account_number) lines.push(`Account: ${bankAccount.account_number}`);
101
+ if (bankAccount.bic) lines.push(`BIC: ${bankAccount.bic}`);
102
+ return lines.join(", ") || null;
103
+ }
104
+ if (varName === "bank_account.iban") return bankAccount?.iban || null;
105
+ if (varName === "bank_account.bank_name") return bankAccount?.bank_name || null;
106
+ if (varName === "bank_account.bic") return bankAccount?.bic || null;
107
+ if (varName === "bank_account.account_number") return bankAccount?.account_number || null;
108
+
81
109
  // Return null for unavailable variables - they will show as placeholders
82
110
  return null;
83
111
  }
@@ -1,5 +1,6 @@
1
1
  export default {
2
2
  // Company settings
3
+ "Tax subject": "Steuerpflichtig",
3
4
  "Tax ID 2": "Steuernummer",
4
5
  "Secondary tax identification number (optional)": "Sekundäre Steueridentifikationsnummer (optional)",
5
6
  "Company Information": "Unternehmensinformationen",
@@ -1,5 +1,6 @@
1
1
  export default {
2
2
  // Company settings
3
+ "Tax subject": "Sujeto fiscal",
3
4
  "Tax ID 2": "Tax ID 2",
4
5
  "Secondary tax identification number (optional)": "Secondary tax identification number (optional)",
5
6
  "Company Information": "Información de la empresa",
@@ -1,5 +1,6 @@
1
1
  export default {
2
2
  // Company settings
3
+ "Tax subject": "Assujetti à la TVA",
3
4
  "Tax ID 2": "Tax ID 2",
4
5
  "Secondary tax identification number (optional)": "Secondary tax identification number (optional)",
5
6
  "Company Information": "Informations sur l'entreprise",
@@ -1,5 +1,6 @@
1
1
  export default {
2
2
  // Company settings
3
+ "Tax subject": "Porezni obveznik",
3
4
  "Tax ID 2": "Tax ID 2",
4
5
  "Secondary tax identification number (optional)": "Secondary tax identification number (optional)",
5
6
  "Company Information": "Podaci o tvrtki",
@@ -1,5 +1,6 @@
1
1
  export default {
2
2
  // Company settings
3
+ "Tax subject": "Soggetto fiscale",
3
4
  "Tax ID 2": "Tax ID 2",
4
5
  "Secondary tax identification number (optional)": "Secondary tax identification number (optional)",
5
6
  "Company Information": "Informazioni aziendali",
@@ -1,5 +1,6 @@
1
1
  export default {
2
2
  // Company settings
3
+ "Tax subject": "Belastingplichtige",
3
4
  "Tax ID 2": "Tax ID 2",
4
5
  "Secondary tax identification number (optional)": "Secondary tax identification number (optional)",
5
6
  "Company Information": "Bedrijfsinformatie",
@@ -1,5 +1,6 @@
1
1
  export default {
2
2
  // Company settings
3
+ "Tax subject": "Podatnik",
3
4
  "Tax ID 2": "Tax ID 2",
4
5
  "Secondary tax identification number (optional)": "Secondary tax identification number (optional)",
5
6
  "Company Information": "Informacje o firmie",
@@ -1,5 +1,6 @@
1
1
  export default {
2
2
  // Company settings
3
+ "Tax subject": "Sujeito fiscal",
3
4
  "Tax ID 2": "Tax ID 2",
4
5
  "Secondary tax identification number (optional)": "Secondary tax identification number (optional)",
5
6
  "Company Information": "Informações da empresa",
@@ -1,5 +1,6 @@
1
1
  export default {
2
2
  // Company settings
3
+ "Tax subject": "Davčni zavezanec",
3
4
  "Tax ID 2": "Davčna številka 2",
4
5
  "Secondary tax identification number (optional)": "Sekundarna davčna identifikacijska številka (neobvezno)",
5
6
  "Company Information": "Podatki o podjetju",
@@ -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
  );
@@ -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";
@@ -20,6 +21,7 @@ import {
20
21
  DocumentDetailsSection,
21
22
  DocumentNoteField,
22
23
  DocumentPaymentTermsField,
24
+ DocumentTaxClauseField,
23
25
  } from "../../documents/create/document-details-section";
24
26
  import { DocumentItemsSection, type PriceModesMap } from "../../documents/create/document-items-section";
25
27
  import { DocumentRecipientSection } from "../../documents/create/document-recipient-section";
@@ -126,13 +128,18 @@ export default function CreateEstimateForm({
126
128
  // Cast customer to form schema type (API type may have additional fields)
127
129
  customer: (initialValues?.customer as CreateEstimateFormValues["customer"]) ?? undefined,
128
130
  items: initialValues?.items?.length
129
- ? initialValues.items.map((item) => ({
131
+ ? initialValues.items.map((item: any) => ({
132
+ type: item.type,
130
133
  name: item.name || "",
131
134
  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 || [],
135
+ ...(item.type !== "separator"
136
+ ? {
137
+ quantity: item.quantity ?? 1,
138
+ // Use gross_price if set, otherwise use price
139
+ price: item.gross_price ?? item.price,
140
+ taxes: item.taxes || [],
141
+ }
142
+ : {}),
136
143
  }))
137
144
  : [
138
145
  {
@@ -145,6 +152,7 @@ export default function CreateEstimateForm({
145
152
  ],
146
153
  currency_code: initialValues?.currency_code || activeEntity?.currency_code || "EUR",
147
154
  note: initialValues?.note ?? defaultEstimateNote,
155
+ tax_clause: (initialValues as any)?.tax_clause ?? "",
148
156
  payment_terms: initialValues?.payment_terms ?? defaultPaymentTerms,
149
157
  date_valid_till:
150
158
  initialValues?.date_valid_till ||
@@ -234,6 +242,37 @@ export default function CreateEstimateForm({
234
242
  control: form.control,
235
243
  });
236
244
 
245
+ // ============================================================================
246
+ // VIES Check - determine if reverse charge applies
247
+ // ============================================================================
248
+ const {
249
+ reverseChargeApplies,
250
+ transactionType,
251
+ isFetching: isViesFetching,
252
+ warning: viesWarning,
253
+ } = useViesCheck({
254
+ issuerCountryCode: activeEntity?.country_code,
255
+ isTaxSubject: activeEntity?.is_tax_subject ?? true,
256
+ customerCountry: formValues.customer?.country,
257
+ customerCountryCode: formValues.customer?.country_code,
258
+ customerTaxNumber: formValues.customer?.tax_number,
259
+ enabled: !!activeEntity,
260
+ });
261
+
262
+ // Auto-populate tax_clause from entity settings when transaction type changes
263
+ const effectiveTransactionType = transactionType ?? "domestic";
264
+ const prevTransactionTypeRef = useRef<string | undefined>(undefined);
265
+ useEffect(() => {
266
+ if (effectiveTransactionType === prevTransactionTypeRef.current) return;
267
+ prevTransactionTypeRef.current = effectiveTransactionType;
268
+
269
+ const taxClauseDefaults = (activeEntity?.settings as any)?.tax_clause_defaults;
270
+ if (!taxClauseDefaults) return;
271
+
272
+ const clause = taxClauseDefaults[effectiveTransactionType] ?? "";
273
+ form.setValue("tax_clause", clause);
274
+ }, [effectiveTransactionType, activeEntity, form]);
275
+
237
276
  // Extract customer management logic into a custom hook
238
277
  const {
239
278
  originalCustomer,
@@ -304,7 +343,7 @@ export default function CreateEstimateForm({
304
343
  useFormFooterRegistration({
305
344
  formId: "create-estimate-form",
306
345
  isPending,
307
- isDirty: form.formState.isDirty,
346
+ isDirty: form.formState.isDirty || !!initialValues,
308
347
  label: titleType === "estimate" ? t("Create Estimate") : t("Create Quote"),
309
348
  secondaryAction,
310
349
  });
@@ -392,6 +431,10 @@ export default function CreateEstimateForm({
392
431
  maxTaxesPerItem={activeEntity?.country_rules?.max_taxes_per_item}
393
432
  priceModesRef={priceModesRef}
394
433
  initialPriceModes={initialPriceModes}
434
+ taxesDisabled={reverseChargeApplies}
435
+ taxesDisabledMessage={
436
+ reverseChargeApplies ? t("Reverse charge - tax exempt EU B2B sale") : viesWarning ? viesWarning : undefined
437
+ }
395
438
  />
396
439
 
397
440
  <DocumentNoteField
@@ -407,6 +450,21 @@ export default function CreateEstimateForm({
407
450
  }}
408
451
  />
409
452
 
453
+ <DocumentTaxClauseField
454
+ control={form.control}
455
+ t={t}
456
+ entity={activeEntity}
457
+ document={{
458
+ number: formValues.number,
459
+ date: formValues.date,
460
+ date_valid_till: formValues.date_valid_till,
461
+ currency_code: formValues.currency_code,
462
+ customer: formValues.customer as any,
463
+ }}
464
+ transactionType={transactionType}
465
+ isTransactionTypeFetching={isViesFetching}
466
+ />
467
+
410
468
  <DocumentPaymentTermsField
411
469
  control={form.control}
412
470
  t={t}
@@ -61,4 +61,20 @@ export default {
61
61
  "Net price": "Nettopreis",
62
62
  "Gross price (tax included)": "Bruttopreis (inkl. MwSt.)",
63
63
  "Net price (before tax)": "Nettopreis (exkl. MwSt.)",
64
+ // Separator items
65
+ "Add separator": "Trennzeile hinzufügen",
66
+ "Section header": "Abschnittsüberschrift",
67
+ "Section title...": "Abschnittstitel...",
68
+ // Transaction type
69
+ "Transaction type": "Transaktionstyp",
70
+ Domestic: "Inland",
71
+ "EU B2B": "EU B2B",
72
+ "EU B2C": "EU B2C",
73
+ Export: "Export",
74
+ "Determining transaction type...": "Transaktionstyp wird ermittelt...",
75
+ "This invoice will not be fiscalized (non-domestic transaction)":
76
+ "Diese Rechnung wird nicht fiskalisiert (nicht-inländische Transaktion)",
77
+ "Tax Clause": "Steuerklausel",
78
+ "Add tax clause...": "Steuerklausel hinzufügen...",
79
+ "Reverse charge - tax exempt EU B2B sale": "Umkehrung der Steuerschuldnerschaft - steuerbefreiter EU B2B Verkauf",
64
80
  } as const;
@@ -54,4 +54,20 @@ export default {
54
54
  "Net price": "Precio neto",
55
55
  "Gross price (tax included)": "Precio bruto (impuestos incluidos)",
56
56
  "Net price (before tax)": "Precio neto (antes de impuestos)",
57
+ // Separator items
58
+ "Add separator": "Añadir separador",
59
+ "Section header": "Encabezado de sección",
60
+ "Section title...": "Título de sección...",
61
+ // Transaction type
62
+ "Transaction type": "Tipo de transacción",
63
+ Domestic: "Nacional",
64
+ "EU B2B": "EU B2B",
65
+ "EU B2C": "EU B2C",
66
+ Export: "Exportación",
67
+ "Determining transaction type...": "Determinando tipo de transacción...",
68
+ "This invoice will not be fiscalized (non-domestic transaction)":
69
+ "Esta factura no será fiscalizada (transacción no nacional)",
70
+ "Tax Clause": "Cláusula fiscal",
71
+ "Add tax clause...": "Agregar cláusula fiscal...",
72
+ "Reverse charge - tax exempt EU B2B sale": "Inversión del sujeto pasivo - venta EU B2B exenta de impuestos",
57
73
  } as const;
@@ -55,4 +55,20 @@ export default {
55
55
  "Net price": "Prix net",
56
56
  "Gross price (tax included)": "Prix brut (taxes incluses)",
57
57
  "Net price (before tax)": "Prix net (avant taxes)",
58
+ // Separator items
59
+ "Add separator": "Ajouter un séparateur",
60
+ "Section header": "En-tête de section",
61
+ "Section title...": "Titre de section...",
62
+ // Transaction type
63
+ "Transaction type": "Type de transaction",
64
+ Domestic: "Nationale",
65
+ "EU B2B": "EU B2B",
66
+ "EU B2C": "EU B2C",
67
+ Export: "Exportation",
68
+ "Determining transaction type...": "Détermination du type de transaction...",
69
+ "This invoice will not be fiscalized (non-domestic transaction)":
70
+ "Cette facture ne sera pas fiscalisée (transaction non nationale)",
71
+ "Tax Clause": "Clause fiscale",
72
+ "Add tax clause...": "Ajouter une clause fiscale...",
73
+ "Reverse charge - tax exempt EU B2B sale": "Autoliquidation - vente B2B UE exonérée de taxe",
58
74
  } as const;
@@ -54,4 +54,20 @@ export default {
54
54
  "Net price": "Neto cijena",
55
55
  "Gross price (tax included)": "Bruto cijena (s porezom)",
56
56
  "Net price (before tax)": "Neto cijena (prije poreza)",
57
+ // Separator items
58
+ "Add separator": "Dodaj separator",
59
+ "Section header": "Naslov odjeljka",
60
+ "Section title...": "Naslov odjeljka...",
61
+ // Transaction type
62
+ "Transaction type": "Vrsta transakcije",
63
+ Domestic: "Domaća",
64
+ "EU B2B": "EU B2B",
65
+ "EU B2C": "EU B2C",
66
+ Export: "Izvoz",
67
+ "Determining transaction type...": "Određivanje vrste transakcije...",
68
+ "This invoice will not be fiscalized (non-domestic transaction)":
69
+ "Ovaj račun neće biti fiskaliziran (nedomaća transakcija)",
70
+ "Tax Clause": "Porezna klauzula",
71
+ "Add tax clause...": "Dodajte poreznu klauzulu...",
72
+ "Reverse charge - tax exempt EU B2B sale": "Prijenos porezne obveze - porezno oslobođena EU B2B prodaja",
57
73
  } as const;