@spaceinvoices/react-ui 0.4.6 → 0.4.8

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 (100) 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 +60 -44
  4. package/src/components/advance-invoices/create/locales/de.ts +2 -1
  5. package/src/components/advance-invoices/create/locales/es.ts +2 -1
  6. package/src/components/advance-invoices/create/locales/fr.ts +2 -1
  7. package/src/components/advance-invoices/create/locales/hr.ts +2 -1
  8. package/src/components/advance-invoices/create/locales/it.ts +2 -1
  9. package/src/components/advance-invoices/create/locales/nl.ts +2 -1
  10. package/src/components/advance-invoices/create/locales/pl.ts +2 -1
  11. package/src/components/advance-invoices/create/locales/pt.ts +2 -1
  12. package/src/components/advance-invoices/create/locales/sl.ts +2 -1
  13. package/src/components/credit-notes/create/create-credit-note-form.tsx +52 -42
  14. package/src/components/credit-notes/create/locales/de.ts +2 -1
  15. package/src/components/credit-notes/create/locales/es.ts +2 -1
  16. package/src/components/credit-notes/create/locales/fr.ts +2 -1
  17. package/src/components/credit-notes/create/locales/hr.ts +2 -1
  18. package/src/components/credit-notes/create/locales/it.ts +2 -1
  19. package/src/components/credit-notes/create/locales/nl.ts +2 -1
  20. package/src/components/credit-notes/create/locales/pl.ts +2 -1
  21. package/src/components/credit-notes/create/locales/pt.ts +2 -1
  22. package/src/components/credit-notes/create/locales/sl.ts +2 -1
  23. package/src/components/dashboard/collection-rate-card/use-collection-rate.ts +48 -92
  24. package/src/components/dashboard/invoice-status-chart/use-invoice-status.ts +48 -82
  25. package/src/components/dashboard/payment-methods-chart/use-payment-methods.ts +22 -31
  26. package/src/components/dashboard/payment-trend-chart/use-payment-trend.ts +33 -48
  27. package/src/components/dashboard/revenue-trend-chart/use-revenue-trend.ts +56 -76
  28. package/src/components/dashboard/shared/index.ts +1 -1
  29. package/src/components/dashboard/shared/use-revenue-data.ts +106 -182
  30. package/src/components/dashboard/shared/use-stats-counts.ts +18 -68
  31. package/src/components/dashboard/shared/use-stats-query.ts +35 -5
  32. package/src/components/dashboard/tax-collected-card/use-tax-collected.ts +57 -75
  33. package/src/components/dashboard/top-customers-chart/use-top-customers.ts +38 -49
  34. package/src/components/delivery-notes/create/create-delivery-note-form.tsx +3 -2
  35. package/src/components/delivery-notes/create/locales/de.ts +2 -1
  36. package/src/components/delivery-notes/create/locales/es.ts +2 -1
  37. package/src/components/delivery-notes/create/locales/fr.ts +2 -1
  38. package/src/components/delivery-notes/create/locales/hr.ts +2 -1
  39. package/src/components/delivery-notes/create/locales/it.ts +2 -1
  40. package/src/components/delivery-notes/create/locales/nl.ts +2 -1
  41. package/src/components/delivery-notes/create/locales/pl.ts +2 -1
  42. package/src/components/delivery-notes/create/locales/pt.ts +2 -1
  43. package/src/components/delivery-notes/create/locales/sl.ts +2 -1
  44. package/src/components/documents/create/document-details-section.tsx +6 -4
  45. package/src/components/documents/create/document-recipient-section.tsx +30 -1
  46. package/src/components/documents/create/live-preview.tsx +15 -28
  47. package/src/components/documents/create/prepare-document-submission.ts +1 -0
  48. package/src/components/documents/create/use-document-customer-form.ts +4 -0
  49. package/src/components/documents/shared/document-preview-skeleton.tsx +63 -0
  50. package/src/components/documents/shared/index.ts +1 -0
  51. package/src/components/documents/view/document-actions-bar.tsx +29 -7
  52. package/src/components/entities/entity-settings-form/locales/de.ts +6 -3
  53. package/src/components/entities/entity-settings-form/locales/es.ts +6 -3
  54. package/src/components/entities/entity-settings-form/locales/fr.ts +6 -3
  55. package/src/components/entities/entity-settings-form/locales/hr.ts +4 -2
  56. package/src/components/entities/entity-settings-form/locales/it.ts +6 -3
  57. package/src/components/entities/entity-settings-form/locales/nl.ts +6 -3
  58. package/src/components/entities/entity-settings-form/locales/pl.ts +6 -2
  59. package/src/components/entities/entity-settings-form/locales/pt.ts +6 -3
  60. package/src/components/entities/entity-settings-form/locales/sl.ts +4 -2
  61. package/src/components/entities/settings/tax-rules-settings-form.tsx +31 -13
  62. package/src/components/estimates/create/create-estimate-form.tsx +3 -2
  63. package/src/components/estimates/create/locales/de.ts +2 -1
  64. package/src/components/estimates/create/locales/es.ts +2 -1
  65. package/src/components/estimates/create/locales/fr.ts +2 -1
  66. package/src/components/estimates/create/locales/hr.ts +2 -1
  67. package/src/components/estimates/create/locales/it.ts +2 -1
  68. package/src/components/estimates/create/locales/nl.ts +2 -1
  69. package/src/components/estimates/create/locales/pl.ts +2 -1
  70. package/src/components/estimates/create/locales/pt.ts +2 -1
  71. package/src/components/estimates/create/locales/sl.ts +2 -1
  72. package/src/components/invoices/create/create-invoice-form.tsx +134 -62
  73. package/src/components/invoices/create/locales/de.ts +8 -1
  74. package/src/components/invoices/create/locales/es.ts +8 -1
  75. package/src/components/invoices/create/locales/fr.ts +8 -1
  76. package/src/components/invoices/create/locales/hr.ts +8 -1
  77. package/src/components/invoices/create/locales/it.ts +8 -1
  78. package/src/components/invoices/create/locales/nl.ts +8 -1
  79. package/src/components/invoices/create/locales/pl.ts +8 -1
  80. package/src/components/invoices/create/locales/pt.ts +8 -1
  81. package/src/components/invoices/create/locales/sl.ts +8 -1
  82. package/src/components/invoices/invoices.hooks.ts +1 -1
  83. package/src/components/ui/progress.tsx +27 -0
  84. package/src/generate-schemas.ts +15 -2
  85. package/src/generated/schemas/advanceinvoice.ts +2 -0
  86. package/src/generated/schemas/creditnote.ts +2 -0
  87. package/src/generated/schemas/customer.ts +2 -0
  88. package/src/generated/schemas/deliverynote.ts +2 -0
  89. package/src/generated/schemas/entity.ts +10 -0
  90. package/src/generated/schemas/estimate.ts +2 -0
  91. package/src/generated/schemas/finasettings.ts +4 -3
  92. package/src/generated/schemas/invoice.ts +2 -0
  93. package/src/generated/schemas/renderadvanceinvoicepreview_body.ts +16 -10
  94. package/src/generated/schemas/rendercreditnotepreview_body.ts +16 -10
  95. package/src/generated/schemas/renderdeliverynotepreview_body.ts +14 -7
  96. package/src/generated/schemas/renderestimatepreview_body.ts +14 -7
  97. package/src/generated/schemas/renderinvoicepreview_body.ts +16 -10
  98. package/src/generated/schemas/startpdfexport_body.ts +12 -17
  99. package/src/hooks/use-transaction-type-check.ts +152 -0
  100. package/src/hooks/use-vies-check.ts +7 -131
@@ -182,8 +182,10 @@ export default {
182
182
  "tax-clauses.intra_eu_b2b.description":
183
183
  "Za prodaju EU tvrtkama s valjanim PDV brojevima. Obično uključuje izjavu o prijenosu porezne obveze.",
184
184
  "Enter reverse charge clause...": "Unesite klauzulu o prijenosu porezne obveze...",
185
- "tax-clauses.export.label": "Izvoz (izvan EU)",
186
- "tax-clauses.export.description": "Za prodaju kupcima izvan EU. Obično navodi oslobođenje od PDV-a za izvoz.",
185
+ "tax-clauses.3w_b2b.label": "Izvan EU B2B (izvoz)",
186
+ "tax-clauses.3w_b2b.description": "Za prodaju poduzećima izvan EU. Obično navodi oslobođenje od PDV-a za izvoz.",
187
+ "tax-clauses.3w_b2c.label": "Izvan EU B2C (izvoz)",
188
+ "tax-clauses.3w_b2c.description": "Za prodaju potrošačima izvan EU. Obično navodi oslobođenje od PDV-a za izvoz.",
187
189
  "Enter export exemption clause...": "Unesite klauzulu o oslobođenju od izvoza...",
188
190
  "tax-clauses.domestic.label": "Zadano / Domaće",
189
191
  "tax-clauses.domestic.description":
@@ -187,9 +187,12 @@ export default {
187
187
  "tax-clauses.intra_eu_b2b.description":
188
188
  "Per vendite a imprese UE con numeri IVA validi. Di solito include una dichiarazione di inversione contabile.",
189
189
  "Enter reverse charge clause...": "Inserisci la clausola di inversione contabile...",
190
- "tax-clauses.export.label": "Esportazione (extra-UE)",
191
- "tax-clauses.export.description":
192
- "Per vendite a clienti extra-UE. Di solito indica l'esenzione IVA per esportazione.",
190
+ "tax-clauses.3w_b2b.label": "Extra-UE B2B (esportazione)",
191
+ "tax-clauses.3w_b2b.description":
192
+ "Per vendite a imprese extra-UE. Di solito indica l'esenzione IVA per esportazione.",
193
+ "tax-clauses.3w_b2c.label": "Extra-UE B2C (esportazione)",
194
+ "tax-clauses.3w_b2c.description":
195
+ "Per vendite a consumatori extra-UE. Di solito indica l'esenzione IVA per esportazione.",
193
196
  "Enter export exemption clause...": "Inserisci la clausola di esenzione per esportazione...",
194
197
  "tax-clauses.domestic.label": "Predefinito / Nazionale",
195
198
  "tax-clauses.domestic.description":
@@ -184,9 +184,12 @@ export default {
184
184
  "tax-clauses.intra_eu_b2b.description":
185
185
  "Voor verkopen aan EU-bedrijven met geldige btw-nummers. Bevat meestal een verklaring van verlegging.",
186
186
  "Enter reverse charge clause...": "Voer de verleggingsclausule in...",
187
- "tax-clauses.export.label": "Export (buiten EU)",
188
- "tax-clauses.export.description":
189
- "Voor verkopen aan klanten buiten de EU. Vermeldt meestal de btw-vrijstelling voor export.",
187
+ "tax-clauses.3w_b2b.label": "Niet-EU B2B (export)",
188
+ "tax-clauses.3w_b2b.description":
189
+ "Voor verkopen aan bedrijven buiten de EU. Vermeldt meestal de btw-vrijstelling voor export.",
190
+ "tax-clauses.3w_b2c.label": "Niet-EU B2C (export)",
191
+ "tax-clauses.3w_b2c.description":
192
+ "Voor verkopen aan consumenten buiten de EU. Vermeldt meestal de btw-vrijstelling voor export.",
190
193
  "Enter export exemption clause...": "Voer de exportvrijstellingsclausule in...",
191
194
  "tax-clauses.domestic.label": "Standaard / Binnenlands",
192
195
  "tax-clauses.domestic.description":
@@ -186,8 +186,12 @@ export default {
186
186
  "tax-clauses.intra_eu_b2b.description":
187
187
  "Dla sprzedaży firmom w UE z ważnymi numerami VAT. Zwykle zawiera oświadczenie o odwrotnym obciążeniu.",
188
188
  "Enter reverse charge clause...": "Wprowadź klauzulę odwrotnego obciążenia...",
189
- "tax-clauses.export.label": "Eksport (poza UE)",
190
- "tax-clauses.export.description": "Dla sprzedaży klientom poza UE. Zwykle wskazuje zwolnienie z VAT na eksport.",
189
+ "tax-clauses.3w_b2b.label": "Poza UE B2B (eksport)",
190
+ "tax-clauses.3w_b2b.description":
191
+ "Dla sprzedaży do przedsiębiorstw spoza UE. Zwykle wskazuje zwolnienie z VAT na eksport.",
192
+ "tax-clauses.3w_b2c.label": "Poza UE B2C (eksport)",
193
+ "tax-clauses.3w_b2c.description":
194
+ "Dla sprzedaży do konsumentów spoza UE. Zwykle wskazuje zwolnienie z VAT na eksport.",
191
195
  "Enter export exemption clause...": "Wprowadź klauzulę zwolnienia eksportowego...",
192
196
  "tax-clauses.domestic.label": "Domyślne / Krajowe",
193
197
  "tax-clauses.domestic.description":
@@ -186,9 +186,12 @@ export default {
186
186
  "tax-clauses.intra_eu_b2b.description":
187
187
  "Para vendas a empresas da UE com números de IVA válidos. Normalmente inclui uma declaração de autoliquidação.",
188
188
  "Enter reverse charge clause...": "Introduza a cláusula de autoliquidação...",
189
- "tax-clauses.export.label": "Exportação (fora da UE)",
190
- "tax-clauses.export.description":
191
- "Para vendas a clientes fora da UE. Normalmente indica a isenção de IVA por exportação.",
189
+ "tax-clauses.3w_b2b.label": "Fora da UE B2B (exportação)",
190
+ "tax-clauses.3w_b2b.description":
191
+ "Para vendas a empresas fora da UE. Normalmente indica a isenção de IVA por exportação.",
192
+ "tax-clauses.3w_b2c.label": "Fora da UE B2C (exportação)",
193
+ "tax-clauses.3w_b2c.description":
194
+ "Para vendas a consumidores fora da UE. Normalmente indica a isenção de IVA por exportação.",
192
195
  "Enter export exemption clause...": "Introduza a cláusula de isenção de exportação...",
193
196
  "tax-clauses.domestic.label": "Predefinido / Nacional",
194
197
  "tax-clauses.domestic.description":
@@ -181,8 +181,10 @@ export default {
181
181
  "tax-clauses.intra_eu_b2b.description":
182
182
  "Za prodajo EU podjetjem z veljavnimi DDV številkami. Običajno vključuje izjavo o obrnjeni davčni obveznosti.",
183
183
  "Enter reverse charge clause...": "Vnesite klavzulo za samoobdavčitev...",
184
- "tax-clauses.export.label": "Izvoz (izven EU)",
185
- "tax-clauses.export.description": "Za prodajo strankam izven EU. Običajno navaja oprostitev DDV za izvoz.",
184
+ "tax-clauses.3w_b2b.label": "Izven EU B2B (izvoz)",
185
+ "tax-clauses.3w_b2b.description": "Za prodajo podjetjem izven EU. Običajno navaja oprostitev DDV za izvoz.",
186
+ "tax-clauses.3w_b2c.label": "Izven EU B2C (izvoz)",
187
+ "tax-clauses.3w_b2c.description": "Za prodajo potrošnikom izven EU. Običajno navaja oprostitev DDV za izvoz.",
186
188
  "Enter export exemption clause...": "Vnesite klavzulo za izvozno oprostitev...",
187
189
  "tax-clauses.domestic.label": "Privzeto / Domače",
188
190
  "tax-clauses.domestic.description":
@@ -1,10 +1,5 @@
1
1
  import { zodResolver } from "@hookform/resolvers/zod";
2
- import type {
3
- Entity,
4
- EntitySettings,
5
- EntitySettingsTaxClauseDefaults,
6
- TaxRules,
7
- } from "@spaceinvoices/js-sdk";
2
+ import type { Entity, EntitySettings, EntitySettingsTaxClauseDefaults, TaxRules } from "@spaceinvoices/js-sdk";
8
3
  import { ChevronDown, Globe, MessageSquareText } from "lucide-react";
9
4
  import type { ReactNode } from "react";
10
5
  import { useState } from "react";
@@ -39,7 +34,8 @@ const taxRulesSettingsSchema = z.object({
39
34
  require_gross_prices: z.boolean(),
40
35
  // Tax clause defaults per transaction type
41
36
  tax_clause_intra_eu_b2b: z.string().optional(),
42
- tax_clause_export: z.string().optional(),
37
+ tax_clause_3w_b2b: z.string().optional(),
38
+ tax_clause_3w_b2c: z.string().optional(),
43
39
  tax_clause_domestic: z.string().optional(),
44
40
  tax_clause_intra_eu_b2c: z.string().optional(),
45
41
  });
@@ -92,7 +88,8 @@ export function TaxRulesSettingsForm({
92
88
  auto_remove_tax_export: currentTaxRules.auto_remove_tax_export ?? false,
93
89
  require_gross_prices: currentTaxRules.require_gross_prices ?? false,
94
90
  tax_clause_intra_eu_b2b: currentTaxClauseDefaults.intra_eu_b2b ?? "",
95
- tax_clause_export: currentTaxClauseDefaults.export ?? "",
91
+ tax_clause_3w_b2b: (currentTaxClauseDefaults as any)["3w_b2b"] ?? currentTaxClauseDefaults.export ?? "",
92
+ tax_clause_3w_b2c: (currentTaxClauseDefaults as any)["3w_b2c"] ?? currentTaxClauseDefaults.export ?? "",
96
93
  tax_clause_domestic: currentTaxClauseDefaults.domestic ?? "",
97
94
  tax_clause_intra_eu_b2c: currentTaxClauseDefaults.intra_eu_b2c ?? "",
98
95
  },
@@ -127,7 +124,8 @@ export function TaxRulesSettingsForm({
127
124
  },
128
125
  tax_clause_defaults: {
129
126
  intra_eu_b2b: values.tax_clause_intra_eu_b2b || null,
130
- export: values.tax_clause_export || null,
127
+ "3w_b2b": values.tax_clause_3w_b2b || null,
128
+ "3w_b2c": values.tax_clause_3w_b2c || null,
131
129
  domestic: values.tax_clause_domestic || null,
132
130
  intra_eu_b2c: values.tax_clause_intra_eu_b2c || null,
133
131
  },
@@ -288,14 +286,34 @@ export function TaxRulesSettingsForm({
288
286
  )}
289
287
  />
290
288
 
291
- {/* Export */}
289
+ {/* 3W B2B (non-EU business) */}
292
290
  <FormField
293
291
  control={form.control}
294
- name="tax_clause_export"
292
+ name="tax_clause_3w_b2b"
295
293
  render={({ field }) => (
296
294
  <FormItem className="rounded-lg border p-4">
297
- <FormLabel>{t("tax-clauses.export.label")}</FormLabel>
298
- <FormDescription>{t("tax-clauses.export.description")}</FormDescription>
295
+ <FormLabel>{t("tax-clauses.3w_b2b.label")}</FormLabel>
296
+ <FormDescription>{t("tax-clauses.3w_b2b.description")}</FormDescription>
297
+ <FormControl>
298
+ <Textarea
299
+ placeholder={t("Enter export exemption clause...")}
300
+ className="min-h-[80px] resize-y"
301
+ {...field}
302
+ />
303
+ </FormControl>
304
+ <FormMessage />
305
+ </FormItem>
306
+ )}
307
+ />
308
+
309
+ {/* 3W B2C (non-EU consumer) */}
310
+ <FormField
311
+ control={form.control}
312
+ name="tax_clause_3w_b2c"
313
+ render={({ field }) => (
314
+ <FormItem className="rounded-lg border p-4">
315
+ <FormLabel>{t("tax-clauses.3w_b2c.label")}</FormLabel>
316
+ <FormDescription>{t("tax-clauses.3w_b2c.description")}</FormDescription>
299
317
  <FormControl>
300
318
  <Textarea
301
319
  placeholder={t("Enter export exemption clause...")}
@@ -11,7 +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
+ import { useTransactionTypeCheck } from "@/ui/hooks/use-transaction-type-check";
15
15
  import type { ComponentTranslationProps } from "@/ui/lib/translation";
16
16
  import { createTranslation } from "@/ui/lib/translation";
17
17
  import { useEntities } from "@/ui/providers/entities-context";
@@ -259,12 +259,13 @@ export default function CreateEstimateForm({
259
259
  transactionType,
260
260
  isFetching: isViesFetching,
261
261
  warning: viesWarning,
262
- } = useViesCheck({
262
+ } = useTransactionTypeCheck({
263
263
  issuerCountryCode: activeEntity?.country_code,
264
264
  isTaxSubject: activeEntity?.is_tax_subject ?? true,
265
265
  customerCountry: formValues.customer?.country,
266
266
  customerCountryCode: formValues.customer?.country_code,
267
267
  customerTaxNumber: formValues.customer?.tax_number,
268
+ customerIsEndConsumer: (formValues.customer as any)?.is_end_consumer,
268
269
  enabled: !!activeEntity,
269
270
  });
270
271
 
@@ -73,7 +73,8 @@ export default {
73
73
  Domestic: "Inland",
74
74
  "EU B2B": "EU B2B",
75
75
  "EU B2C": "EU B2C",
76
- Export: "Export",
76
+ "3W B2B": "3W B2B",
77
+ "3W B2C": "3W B2C",
77
78
  "Determining transaction type...": "Transaktionstyp wird ermittelt...",
78
79
  "This invoice will not be fiscalized (non-domestic transaction)":
79
80
  "Diese Rechnung wird nicht fiskalisiert (nicht-inländische Transaktion)",
@@ -66,7 +66,8 @@ export default {
66
66
  Domestic: "Nacional",
67
67
  "EU B2B": "EU B2B",
68
68
  "EU B2C": "EU B2C",
69
- Export: "Exportación",
69
+ "3W B2B": "3W B2B",
70
+ "3W B2C": "3W B2C",
70
71
  "Determining transaction type...": "Determinando tipo de transacción...",
71
72
  "This invoice will not be fiscalized (non-domestic transaction)":
72
73
  "Esta factura no será fiscalizada (transacción no nacional)",
@@ -67,7 +67,8 @@ export default {
67
67
  Domestic: "Nationale",
68
68
  "EU B2B": "EU B2B",
69
69
  "EU B2C": "EU B2C",
70
- Export: "Exportation",
70
+ "3W B2B": "3W B2B",
71
+ "3W B2C": "3W B2C",
71
72
  "Determining transaction type...": "Détermination du type de transaction...",
72
73
  "This invoice will not be fiscalized (non-domestic transaction)":
73
74
  "Cette facture ne sera pas fiscalisée (transaction non nationale)",
@@ -66,7 +66,8 @@ export default {
66
66
  Domestic: "Domaća",
67
67
  "EU B2B": "EU B2B",
68
68
  "EU B2C": "EU B2C",
69
- Export: "Izvoz",
69
+ "3W B2B": "3W B2B",
70
+ "3W B2C": "3W B2C",
70
71
  "Determining transaction type...": "Određivanje vrste transakcije...",
71
72
  "This invoice will not be fiscalized (non-domestic transaction)":
72
73
  "Ovaj račun neće biti fiskaliziran (nedomaća transakcija)",
@@ -66,7 +66,8 @@ export default {
66
66
  Domestic: "Nazionale",
67
67
  "EU B2B": "EU B2B",
68
68
  "EU B2C": "EU B2C",
69
- Export: "Esportazione",
69
+ "3W B2B": "3W B2B",
70
+ "3W B2C": "3W B2C",
70
71
  "Determining transaction type...": "Determinazione tipo di transazione...",
71
72
  "This invoice will not be fiscalized (non-domestic transaction)":
72
73
  "Questa fattura non sarà fiscalizzata (transazione non nazionale)",
@@ -67,7 +67,8 @@ export default {
67
67
  Domestic: "Binnenland",
68
68
  "EU B2B": "EU B2B",
69
69
  "EU B2C": "EU B2C",
70
- Export: "Export",
70
+ "3W B2B": "3W B2B",
71
+ "3W B2C": "3W B2C",
71
72
  "Determining transaction type...": "Transactietype bepalen...",
72
73
  "This invoice will not be fiscalized (non-domestic transaction)":
73
74
  "Deze factuur wordt niet gefiscaliseerd (niet-binnenlandse transactie)",
@@ -66,7 +66,8 @@ export default {
66
66
  Domestic: "Krajowa",
67
67
  "EU B2B": "EU B2B",
68
68
  "EU B2C": "EU B2C",
69
- Export: "Eksport",
69
+ "3W B2B": "3W B2B",
70
+ "3W B2C": "3W B2C",
70
71
  "Determining transaction type...": "Określanie typu transakcji...",
71
72
  "This invoice will not be fiscalized (non-domestic transaction)":
72
73
  "Ta faktura nie będzie fiskalizowana (transakcja niekrajowa)",
@@ -67,7 +67,8 @@ export default {
67
67
  Domestic: "Nacional",
68
68
  "EU B2B": "EU B2B",
69
69
  "EU B2C": "EU B2C",
70
- Export: "Exportação",
70
+ "3W B2B": "3W B2B",
71
+ "3W B2C": "3W B2C",
71
72
  "Determining transaction type...": "Determinando tipo de transação...",
72
73
  "This invoice will not be fiscalized (non-domestic transaction)":
73
74
  "Esta fatura não será fiscalizada (transação não nacional)",
@@ -72,7 +72,8 @@ export default {
72
72
  Domestic: "Domača",
73
73
  "EU B2B": "EU B2B",
74
74
  "EU B2C": "EU B2C",
75
- Export: "Izvoz",
75
+ "3W B2B": "3W B2B",
76
+ "3W B2C": "3W B2C",
76
77
  "Determining transaction type...": "Določanje vrste transakcije...",
77
78
  "This invoice will not be fiscalized (non-domestic transaction)":
78
79
  "Ta račun ne bo fiskaliziran (nedomača transakcija)",
@@ -13,7 +13,7 @@ import { Form } from "@/ui/components/ui/form";
13
13
  import { Skeleton } from "@/ui/components/ui/skeleton";
14
14
  import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/ui/components/ui/tooltip";
15
15
  import { createInvoiceSchema } from "@/ui/generated/schemas";
16
- import { useViesCheck } from "@/ui/hooks/use-vies-check";
16
+ import { useTransactionTypeCheck } from "@/ui/hooks/use-transaction-type-check";
17
17
  import type { ComponentTranslationProps } from "@/ui/lib/translation";
18
18
  import { createTranslation } from "@/ui/lib/translation";
19
19
  import { cn } from "@/ui/lib/utils";
@@ -416,14 +416,56 @@ export default function CreateInvoiceForm({
416
416
  const isFinaActive =
417
417
  isFinaEnabled && hasFinaPremises && selectedFinaBusinessPremiseName && selectedFinaElectronicDeviceName;
418
418
 
419
+ // ============================================================================
420
+ // VIES Check - determine transaction type early (needed for number preview)
421
+ // ============================================================================
422
+ const customerCountry = useWatch({ control: form.control, name: "customer.country" });
423
+ const customerCountryCode = useWatch({ control: form.control, name: "customer.country_code" });
424
+ const customerTaxNumber = useWatch({ control: form.control, name: "customer.tax_number" });
425
+ const customerIsEndConsumerWatch =
426
+ useWatch({ control: form.control, name: "customer.is_end_consumer" as any }) === true;
427
+
428
+ const {
429
+ reverseChargeApplies,
430
+ transactionType,
431
+ isFetching: isViesFetching,
432
+ warning: viesWarning,
433
+ } = useTransactionTypeCheck({
434
+ issuerCountryCode: activeEntity?.country_code,
435
+ isTaxSubject: activeEntity?.is_tax_subject ?? true,
436
+ customerCountry,
437
+ customerCountryCode,
438
+ customerTaxNumber,
439
+ customerIsEndConsumer: customerIsEndConsumerWatch,
440
+ enabled: !!activeEntity,
441
+ });
442
+
443
+ // FINA numbering guard: use FINA numbering for domestic transactions (or all if unified numbering is on)
444
+ const finaUnifiedNumbering = finaSettings?.unified_numbering !== false;
445
+ const useFinaNumbering =
446
+ !!isFinaActive && (finaUnifiedNumbering || transactionType == null || transactionType === "domestic");
447
+ const isFinaNonDomestic = !!isFinaActive && !useFinaNumbering;
448
+
419
449
  // ============================================================================
420
450
  // Next Invoice Number Preview
421
451
  // ============================================================================
422
452
  // Wait for FURS selection to be ready before querying to prevent number flashing
423
453
  // Skip in edit mode - we use the existing document number
454
+ // Use the same premise/device params for both FURS and FINA (an entity is either one, never both)
455
+ const activePremiseName = isFursActive
456
+ ? selectedPremiseName
457
+ : useFinaNumbering
458
+ ? selectedFinaBusinessPremiseName
459
+ : undefined;
460
+ const activeDeviceNameForNumber = isFursActive
461
+ ? selectedDeviceName
462
+ : useFinaNumbering
463
+ ? selectedFinaElectronicDeviceName
464
+ : undefined;
465
+
424
466
  const { data: nextNumberData, isLoading: isNextNumberLoading } = useNextInvoiceNumber(entityId, {
425
- business_premise_name: isFursActive ? selectedPremiseName : undefined,
426
- electronic_device_name: isFursActive ? selectedDeviceName : undefined,
467
+ business_premise_name: activePremiseName,
468
+ electronic_device_name: activeDeviceNameForNumber,
427
469
  enabled:
428
470
  !!entityId && !isFursLoading && isFursSelectionReady && !isFinaLoading && isFinaSelectionReady && !isEditMode,
429
471
  });
@@ -551,11 +593,6 @@ export default function CreateInvoiceForm({
551
593
  }
552
594
  }, [nextNumberData?.number, form]);
553
595
 
554
- // Watch specific fields for VIES check (stable references)
555
- const customerCountry = useWatch({ control: form.control, name: "customer.country" });
556
- const customerCountryCode = useWatch({ control: form.control, name: "customer.country_code" });
557
- const customerTaxNumber = useWatch({ control: form.control, name: "customer.tax_number" });
558
-
559
596
  // Watch fields needed for document note/payment terms preview
560
597
  const watchedNumber = useWatch({ control: form.control, name: "number" });
561
598
  const watchedDate = useWatch({ control: form.control, name: "date" });
@@ -563,29 +600,45 @@ export default function CreateInvoiceForm({
563
600
  const watchedCurrencyCode = useWatch({ control: form.control, name: "currency_code" });
564
601
  const watchedCustomer = useWatch({ control: form.control, name: "customer" });
565
602
 
566
- // ============================================================================
567
- // VIES Check - determine if reverse charge applies
568
- // ============================================================================
569
- const {
570
- reverseChargeApplies,
571
- transactionType,
572
- customerCountryCode: viesCustomerCountryCode,
573
- isFetching: isViesFetching,
574
- warning: viesWarning,
575
- } = useViesCheck({
576
- issuerCountryCode: activeEntity?.country_code,
577
- isTaxSubject: activeEntity?.is_tax_subject ?? true,
578
- customerCountry,
579
- customerCountryCode,
580
- customerTaxNumber,
581
- enabled: !!activeEntity,
582
- });
603
+ // Croatian invoice validation:
604
+ // - Domestic/3w B2C requires FINA
605
+ // - Domestic B2B (customer has tax number and NOT end consumer) is blocked
606
+ const isCroatianEntity = activeEntity?.country_code === "HR";
607
+ const customerHasTaxNumber = !!customerTaxNumber?.trim();
608
+ const isDomesticTransaction = transactionType === "domestic";
609
+ const requiresFinaFiscalization = isDomesticTransaction || transactionType === "3w_b2c";
610
+ const is3wTransaction = transactionType === "3w_b2b" || transactionType === "3w_b2c";
611
+
612
+ // Auto-toggle is_end_consumer based on tax number for Croatian domestic/3w customers
613
+ // Default: checked (end consumer). When tax number is entered: uncheck (business). User can override.
614
+ const prevAutoSetTaxRef = useRef<string | undefined>(undefined);
615
+ useEffect(() => {
616
+ if (!isCroatianEntity || !(isDomesticTransaction || is3wTransaction)) return;
617
+ const hasTaxNumber = !!customerTaxNumber?.trim();
618
+ const hadTaxNumber = !!prevAutoSetTaxRef.current?.trim();
619
+ prevAutoSetTaxRef.current = customerTaxNumber ?? undefined;
620
+
621
+ // Auto-uncheck when tax number goes from empty to filled (likely a business)
622
+ if (hasTaxNumber && !hadTaxNumber && customerIsEndConsumerWatch) {
623
+ form.setValue("customer.is_end_consumer" as any, false);
624
+ }
625
+ // Auto-check when tax number goes from filled to empty (likely an individual)
626
+ if (!hasTaxNumber && hadTaxNumber && !customerIsEndConsumerWatch) {
627
+ form.setValue("customer.is_end_consumer" as any, true);
628
+ }
629
+ }, [customerTaxNumber, isCroatianEntity, isDomesticTransaction, is3wTransaction, customerIsEndConsumerWatch, form]);
583
630
 
584
- // FINA non-domestic guard: hide FINA selectors for non-domestic transactions
585
- // When unified_numbering is enabled (default: true), show FINA selectors for all transactions
586
- const finaUnifiedNumbering = finaSettings?.unified_numbering !== false;
587
- const isFinaNonDomestic =
588
- isFinaEnabled && !finaUnifiedNumbering && viesCustomerCountryCode != null && viesCustomerCountryCode !== "HR";
631
+ const finaValidationError = (() => {
632
+ if (!isCroatianEntity || !requiresFinaFiscalization) return undefined;
633
+ // Domestic B2B is always blocked (3w B2B never reaches here since requiresFinaFiscalization is false for 3w B2B)
634
+ if (isDomesticTransaction && customerHasTaxNumber && !customerIsEndConsumerWatch) {
635
+ return t("Domestic B2B invoicing in Croatia is not supported");
636
+ }
637
+ if (!isFinaEnabled) {
638
+ return t("FINA fiscalization must be enabled for domestic invoices");
639
+ }
640
+ return undefined;
641
+ })();
589
642
 
590
643
  // Auto-populate tax_clause from entity settings when transaction type changes
591
644
  const effectiveTransactionType = transactionType ?? "domestic";
@@ -658,6 +711,9 @@ export default function CreateInvoiceForm({
658
711
  // Shared submit logic for both regular save and save as draft
659
712
  const submitInvoice = useCallback(
660
713
  (values: CreateInvoiceFormValues, isDraft: boolean) => {
714
+ // Block Croatian domestic B2B and domestic B2C without FINA
715
+ if (finaValidationError) return;
716
+
661
717
  // Skip e-SLOG validation for drafts and edit mode
662
718
  if (!isDraft && !isEditMode && eslogValidationEnabled) {
663
719
  const validationErrors = validateEslogForm(values as any, activeEntity);
@@ -691,8 +747,7 @@ export default function CreateInvoiceForm({
691
747
  const finaOptions =
692
748
  !isDraft &&
693
749
  !isEditMode &&
694
- isFinaEnabled &&
695
- !isFinaNonDomestic &&
750
+ useFinaNumbering &&
696
751
  selectedFinaBusinessPremiseName &&
697
752
  selectedFinaElectronicDeviceName
698
753
  ? {
@@ -740,13 +795,13 @@ export default function CreateInvoiceForm({
740
795
  updateInvoice,
741
796
  documentId,
742
797
  eslogValidationEnabled,
798
+ finaValidationError,
743
799
  forceLinkedDocuments,
744
800
  form,
745
801
  isEditMode,
746
802
  isEslogAvailable,
747
803
  isFursEnabled,
748
- isFinaEnabled,
749
- isFinaNonDomestic,
804
+ useFinaNumbering,
750
805
  markAsPaid,
751
806
  originalCustomer,
752
807
  paymentTypes,
@@ -916,25 +971,36 @@ export default function CreateInvoiceForm({
916
971
  <div className="flex w-full flex-col md:flex-row md:gap-6">
917
972
  {/* Recipient section skeleton */}
918
973
  <div className="flex-1 space-y-4">
919
- <Skeleton className="h-7 w-24" /> {/* "Recipient" title */}
920
- <Skeleton className="h-10 w-full" /> {/* Customer autocomplete */}
974
+ <Skeleton className="h-7 w-24" />
975
+ <Skeleton className="h-10 w-full" />
921
976
  </div>
922
- {/* Details section skeleton */}
923
- <div className="flex-1 space-y-4">
924
- <Skeleton className="h-7 w-20" /> {/* "Details" title */}
925
- <Skeleton className="h-5 w-16" /> {/* "Number *" label */}
926
- <Skeleton className="h-10 w-full" /> {/* Number field */}
927
- <Skeleton className="h-5 w-12" /> {/* "Date *" label */}
928
- <Skeleton className="h-10 w-full" /> {/* Date picker */}
929
- <Skeleton className="h-5 w-16" /> {/* "Due Date" label */}
930
- <Skeleton className="h-10 w-full" /> {/* Due date picker */}
931
- <Skeleton className="h-5 w-20" /> {/* "Currency *" label */}
932
- <Skeleton className="h-10 w-full" /> {/* Currency select */}
933
- {/* Mark as paid section */}
977
+ {/* Details section skeleton — inline label rows */}
978
+ <div className="flex-1 space-y-3">
979
+ <Skeleton className="h-7 w-20" />
980
+ <div className="flex items-center gap-3">
981
+ <Skeleton className="h-5 w-[6.5rem] shrink-0" />
982
+ <Skeleton className="h-10 flex-1" />
983
+ </div>
984
+ <div className="flex items-center gap-3">
985
+ <Skeleton className="h-5 w-[6.5rem] shrink-0" />
986
+ <Skeleton className="h-10 flex-1" />
987
+ </div>
988
+ <div className="flex items-center gap-3">
989
+ <Skeleton className="h-5 w-[6.5rem] shrink-0" />
990
+ <Skeleton className="h-10 flex-1" />
991
+ </div>
992
+ <div className="flex items-center gap-3">
993
+ <Skeleton className="h-5 w-[6.5rem] shrink-0" />
994
+ <Skeleton className="h-10 flex-1" />
995
+ </div>
996
+ <div className="flex items-center gap-3">
997
+ <Skeleton className="h-5 w-[6.5rem] shrink-0" />
998
+ <Skeleton className="h-10 flex-1" />
999
+ </div>
934
1000
  <div className="space-y-3 rounded-md border p-4">
935
1001
  <div className="flex items-center gap-3">
936
- <Skeleton className="h-4 w-4 rounded" /> {/* Checkbox */}
937
- <Skeleton className="h-5 w-28" /> {/* "Mark as Paid" */}
1002
+ <Skeleton className="h-4 w-4 rounded" />
1003
+ <Skeleton className="h-5 w-28" />
938
1004
  </div>
939
1005
  </div>
940
1006
  </div>
@@ -942,25 +1008,22 @@ export default function CreateInvoiceForm({
942
1008
 
943
1009
  {/* Items section skeleton */}
944
1010
  <div className="space-y-4">
945
- <Skeleton className="h-7 w-16" /> {/* "Items" title */}
1011
+ <Skeleton className="h-7 w-16" />
946
1012
  <div className="space-y-4 rounded-lg border p-4">
947
- <Skeleton className="h-10 w-full" /> {/* Item name */}
1013
+ <Skeleton className="h-10 w-full" />
948
1014
  <div className="flex gap-4">
949
- <Skeleton className="h-10 w-24" /> {/* Quantity */}
950
- <Skeleton className="h-10 flex-1" /> {/* Price */}
1015
+ <Skeleton className="h-10 w-24" />
1016
+ <Skeleton className="h-10 flex-1" />
951
1017
  </div>
952
1018
  </div>
953
- <Skeleton className="h-9 w-24" /> {/* Add item button */}
1019
+ <Skeleton className="h-9 w-24" />
954
1020
  </div>
955
1021
 
956
1022
  {/* Note field skeleton */}
957
1023
  <div className="space-y-2">
958
- <Skeleton className="h-5 w-12" /> {/* "Note" label */}
959
- <Skeleton className="h-24 w-full" /> {/* Textarea */}
1024
+ <Skeleton className="h-5 w-12" />
1025
+ <Skeleton className="h-24 w-full" />
960
1026
  </div>
961
-
962
- {/* Save button skeleton */}
963
- <Skeleton className="h-10 w-24" />
964
1027
  </div>
965
1028
  );
966
1029
  }
@@ -968,6 +1031,14 @@ export default function CreateInvoiceForm({
968
1031
  return (
969
1032
  <Form {...form}>
970
1033
  <form id="create-invoice-form" onSubmit={form.handleSubmit(onSubmit)} className="space-y-8">
1034
+ {/* Croatian domestic invoice validation errors */}
1035
+ {finaValidationError && (
1036
+ <Alert variant="destructive">
1037
+ <AlertCircle className="h-4 w-4" />
1038
+ <AlertTitle>{finaValidationError}</AlertTitle>
1039
+ </Alert>
1040
+ )}
1041
+
971
1042
  {/* e-SLOG entity-level validation errors */}
972
1043
  {eslogEntityErrors.length > 0 && (
973
1044
  <Alert variant="destructive">
@@ -996,6 +1067,7 @@ export default function CreateInvoiceForm({
996
1067
  shouldFocusName={shouldFocusName}
997
1068
  selectedCustomerId={selectedCustomerId}
998
1069
  initialCustomerName={initialCustomerName}
1070
+ showEndConsumerToggle={isCroatianEntity && (isDomesticTransaction || is3wTransaction)}
999
1071
  t={t}
1000
1072
  />
1001
1073
  <DocumentDetailsSection
@@ -1017,7 +1089,7 @@ export default function CreateInvoiceForm({
1017
1089
  : undefined
1018
1090
  }
1019
1091
  finaInline={
1020
- !isEditMode && isFinaEnabled && hasFinaPremises && !isFinaNonDomestic
1092
+ !isEditMode && useFinaNumbering
1021
1093
  ? {
1022
1094
  premises: activeFinaPremises.map((p: any) => ({
1023
1095
  id: p.id,
@@ -1052,7 +1124,7 @@ export default function CreateInvoiceForm({
1052
1124
  paymentTypes={paymentTypes}
1053
1125
  onPaymentTypesChange={setPaymentTypes}
1054
1126
  t={t}
1055
- alwaysShowPaymentType={!!isFinaActive}
1127
+ alwaysShowPaymentType={!!isFinaActive && requiresFinaFiscalization}
1056
1128
  />
1057
1129
  )}
1058
1130
  </DocumentDetailsSection>
@@ -146,7 +146,8 @@ export default {
146
146
  Domestic: "Inland",
147
147
  "EU B2B": "EU B2B",
148
148
  "EU B2C": "EU B2C",
149
- Export: "Export",
149
+ "3W B2B": "3W B2B",
150
+ "3W B2C": "3W B2C",
150
151
  "Determining transaction type...": "Transaktionstyp wird ermittelt...",
151
152
  "This invoice will not be fiscalized (non-domestic transaction)":
152
153
  "Diese Rechnung wird nicht fiskalisiert (nicht-inländische Transaktion)",
@@ -154,4 +155,10 @@ export default {
154
155
  "Add tax clause...": "Steuerklausel hinzufügen...",
155
156
  Footer: "Fußzeile",
156
157
  "Add document footer...": "Dokumentfußzeile hinzufügen...",
158
+ // Croatian domestic invoice validation
159
+ "End consumer": "Endverbraucher",
160
+ "Domestic B2B invoicing in Croatia is not supported":
161
+ "Inländische B2B-Rechnungsstellung in Kroatien wird nicht unterstützt. Kroatien erfordert die Einhaltung von Fiskalisierung 2.0 für B2B-Rechnungsstellung.",
162
+ "FINA fiscalization must be enabled for domestic invoices":
163
+ "FINA-Fiskalisierung muss für inländische Rechnungen aktiviert sein. Aktivieren Sie sie in den Unternehmenseinstellungen.",
157
164
  } as const;