@spaceinvoices/react-ui 0.4.6 → 0.4.7

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 (55) 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/credit-notes/create/create-credit-note-form.tsx +52 -42
  5. package/src/components/dashboard/collection-rate-card/use-collection-rate.ts +48 -92
  6. package/src/components/dashboard/invoice-status-chart/use-invoice-status.ts +48 -82
  7. package/src/components/dashboard/payment-methods-chart/use-payment-methods.ts +22 -31
  8. package/src/components/dashboard/payment-trend-chart/use-payment-trend.ts +33 -48
  9. package/src/components/dashboard/revenue-trend-chart/use-revenue-trend.ts +56 -76
  10. package/src/components/dashboard/shared/index.ts +1 -1
  11. package/src/components/dashboard/shared/use-revenue-data.ts +106 -182
  12. package/src/components/dashboard/shared/use-stats-counts.ts +18 -68
  13. package/src/components/dashboard/shared/use-stats-query.ts +35 -5
  14. package/src/components/dashboard/tax-collected-card/use-tax-collected.ts +57 -75
  15. package/src/components/dashboard/top-customers-chart/use-top-customers.ts +38 -49
  16. package/src/components/delivery-notes/create/create-delivery-note-form.tsx +3 -2
  17. package/src/components/documents/create/document-details-section.tsx +6 -4
  18. package/src/components/documents/create/document-recipient-section.tsx +30 -1
  19. package/src/components/documents/create/live-preview.tsx +15 -28
  20. package/src/components/documents/create/prepare-document-submission.ts +1 -0
  21. package/src/components/documents/create/use-document-customer-form.ts +4 -0
  22. package/src/components/documents/shared/document-preview-skeleton.tsx +63 -0
  23. package/src/components/documents/shared/index.ts +1 -0
  24. package/src/components/documents/view/document-actions-bar.tsx +29 -7
  25. package/src/components/entities/settings/tax-rules-settings-form.tsx +31 -13
  26. package/src/components/estimates/create/create-estimate-form.tsx +3 -2
  27. package/src/components/invoices/create/create-invoice-form.tsx +134 -62
  28. package/src/components/invoices/create/locales/de.ts +6 -0
  29. package/src/components/invoices/create/locales/es.ts +6 -0
  30. package/src/components/invoices/create/locales/fr.ts +6 -0
  31. package/src/components/invoices/create/locales/hr.ts +6 -0
  32. package/src/components/invoices/create/locales/it.ts +6 -0
  33. package/src/components/invoices/create/locales/nl.ts +6 -0
  34. package/src/components/invoices/create/locales/pl.ts +6 -0
  35. package/src/components/invoices/create/locales/pt.ts +6 -0
  36. package/src/components/invoices/create/locales/sl.ts +6 -0
  37. package/src/components/invoices/invoices.hooks.ts +1 -1
  38. package/src/components/ui/progress.tsx +27 -0
  39. package/src/generate-schemas.ts +15 -2
  40. package/src/generated/schemas/advanceinvoice.ts +2 -0
  41. package/src/generated/schemas/creditnote.ts +2 -0
  42. package/src/generated/schemas/customer.ts +2 -0
  43. package/src/generated/schemas/deliverynote.ts +2 -0
  44. package/src/generated/schemas/entity.ts +10 -0
  45. package/src/generated/schemas/estimate.ts +2 -0
  46. package/src/generated/schemas/finasettings.ts +4 -3
  47. package/src/generated/schemas/invoice.ts +2 -0
  48. package/src/generated/schemas/renderadvanceinvoicepreview_body.ts +16 -10
  49. package/src/generated/schemas/rendercreditnotepreview_body.ts +16 -10
  50. package/src/generated/schemas/renderdeliverynotepreview_body.ts +14 -7
  51. package/src/generated/schemas/renderestimatepreview_body.ts +14 -7
  52. package/src/generated/schemas/renderinvoicepreview_body.ts +16 -10
  53. package/src/generated/schemas/startpdfexport_body.ts +12 -17
  54. package/src/hooks/use-transaction-type-check.ts +152 -0
  55. package/src/hooks/use-vies-check.ts +7 -131
@@ -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>
@@ -154,4 +154,10 @@ export default {
154
154
  "Add tax clause...": "Steuerklausel hinzufügen...",
155
155
  Footer: "Fußzeile",
156
156
  "Add document footer...": "Dokumentfußzeile hinzufügen...",
157
+ // Croatian domestic invoice validation
158
+ "End consumer": "Endverbraucher",
159
+ "Domestic B2B invoicing in Croatia is not supported":
160
+ "Inländische B2B-Rechnungsstellung in Kroatien wird nicht unterstützt. Kroatien erfordert die Einhaltung von Fiskalisierung 2.0 für B2B-Rechnungsstellung.",
161
+ "FINA fiscalization must be enabled for domestic invoices":
162
+ "FINA-Fiskalisierung muss für inländische Rechnungen aktiviert sein. Aktivieren Sie sie in den Unternehmenseinstellungen.",
157
163
  } as const;
@@ -139,4 +139,10 @@ export default {
139
139
  "Add tax clause...": "Agregar cláusula fiscal...",
140
140
  Footer: "Pie de página",
141
141
  "Add document footer...": "Añadir pie de página del documento...",
142
+ // Croatian domestic invoice validation
143
+ "End consumer": "Consumidor final",
144
+ "Domestic B2B invoicing in Croatia is not supported":
145
+ "La facturación B2B doméstica en Croacia no está soportada. Croacia requiere conformidad con Fiskalizacija 2.0 para facturación B2B.",
146
+ "FINA fiscalization must be enabled for domestic invoices":
147
+ "La fiscalización FINA debe estar habilitada para facturas domésticas. Habilítela en la configuración de la entidad.",
142
148
  } as const;
@@ -141,4 +141,10 @@ export default {
141
141
  "Add tax clause...": "Ajouter une clause fiscale...",
142
142
  Footer: "Pied de page",
143
143
  "Add document footer...": "Ajouter un pied de page...",
144
+ // Croatian domestic invoice validation
145
+ "End consumer": "Consommateur final",
146
+ "Domestic B2B invoicing in Croatia is not supported":
147
+ "La facturation B2B domestique en Croatie n'est pas prise en charge. La Croatie exige la conformité à Fiskalizacija 2.0 pour la facturation B2B.",
148
+ "FINA fiscalization must be enabled for domestic invoices":
149
+ "La fiscalisation FINA doit être activée pour les factures domestiques. Activez-la dans les paramètres de l'entité.",
144
150
  } as const;
@@ -138,4 +138,10 @@ export default {
138
138
  "Add tax clause...": "Dodajte poreznu klauzulu...",
139
139
  Footer: "Podnožje",
140
140
  "Add document footer...": "Dodajte podnožje dokumenta...",
141
+ // Croatian domestic invoice validation
142
+ "End consumer": "Krajnji potrošač",
143
+ "Domestic B2B invoicing in Croatia is not supported":
144
+ "Domaće B2B fakturiranje u Hrvatskoj nije podržano. Hrvatska zahtijeva usklađenost s Fiskalizacijom 2.0 za B2B fakturiranje.",
145
+ "FINA fiscalization must be enabled for domestic invoices":
146
+ "FINA fiskalizacija mora biti omogućena za domaće račune. Omogućite je u postavkama tvrtke.",
141
147
  } as const;
@@ -141,4 +141,10 @@ export default {
141
141
  "Add tax clause...": "Aggiungi clausola fiscale...",
142
142
  Footer: "Piè di pagina",
143
143
  "Add document footer...": "Aggiungi piè di pagina del documento...",
144
+ // Croatian domestic invoice validation
145
+ "End consumer": "Consumatore finale",
146
+ "Domestic B2B invoicing in Croatia is not supported":
147
+ "La fatturazione B2B domestica in Croazia non è supportata. La Croazia richiede la conformità a Fiskalizacija 2.0 per la fatturazione B2B.",
148
+ "FINA fiscalization must be enabled for domestic invoices":
149
+ "La fiscalizzazione FINA deve essere abilitata per le fatture domestiche. Abilitarla nelle impostazioni dell'entità.",
144
150
  } as const;
@@ -140,4 +140,10 @@ export default {
140
140
  "Add tax clause...": "Belastingclausule toevoegen...",
141
141
  Footer: "Voettekst",
142
142
  "Add document footer...": "Documentvoettekst toevoegen...",
143
+ // Croatian domestic invoice validation
144
+ "End consumer": "Eindconsument",
145
+ "Domestic B2B invoicing in Croatia is not supported":
146
+ "Binnenlandse B2B-facturering in Kroatië wordt niet ondersteund. Kroatië vereist naleving van Fiskalizacija 2.0 voor B2B-facturering.",
147
+ "FINA fiscalization must be enabled for domestic invoices":
148
+ "FINA-fiscalisering moet zijn ingeschakeld voor binnenlandse facturen. Schakel deze in via de bedrijfsinstellingen.",
143
149
  } as const;
@@ -139,4 +139,10 @@ export default {
139
139
  "Add tax clause...": "Dodaj klauzulę podatkową...",
140
140
  Footer: "Stopka",
141
141
  "Add document footer...": "Dodaj stopkę dokumentu...",
142
+ // Croatian domestic invoice validation
143
+ "End consumer": "Konsument końcowy",
144
+ "Domestic B2B invoicing in Croatia is not supported":
145
+ "Krajowe fakturowanie B2B w Chorwacji nie jest obsługiwane. Chorwacja wymaga zgodności z Fiskalizacija 2.0 dla fakturowania B2B.",
146
+ "FINA fiscalization must be enabled for domestic invoices":
147
+ "Fiskalizacja FINA musi być włączona dla faktur krajowych. Włącz ją w ustawieniach podmiotu.",
142
148
  } as const;
@@ -140,4 +140,10 @@ export default {
140
140
  "Add tax clause...": "Adicionar cláusula fiscal...",
141
141
  Footer: "Rodapé",
142
142
  "Add document footer...": "Adicionar rodapé do documento...",
143
+ // Croatian domestic invoice validation
144
+ "End consumer": "Consumidor final",
145
+ "Domestic B2B invoicing in Croatia is not supported":
146
+ "A faturação B2B doméstica na Croácia não é suportada. A Croácia exige conformidade com Fiskalizacija 2.0 para faturação B2B.",
147
+ "FINA fiscalization must be enabled for domestic invoices":
148
+ "A fiscalização FINA deve estar ativada para faturas domésticas. Ative-a nas configurações da entidade.",
143
149
  } as const;
@@ -149,4 +149,10 @@ export default {
149
149
  "Add tax clause...": "Dodajte davčno klavzulo...",
150
150
  Footer: "Noga dokumenta",
151
151
  "Add document footer...": "Dodajte nogo dokumenta...",
152
+ // Croatian domestic invoice validation
153
+ "End consumer": "Končni potrošnik",
154
+ "Domestic B2B invoicing in Croatia is not supported":
155
+ "Domače B2B fakturiranje na Hrvaškem ni podprto. Hrvška zahteva skladnost s Fiskalizacijo 2.0 za B2B fakturiranje.",
156
+ "FINA fiscalization must be enabled for domestic invoices":
157
+ "FINA fiskalizacija mora biti omogočena za domače račune. Omogočite jo v nastavitvah podjetja.",
152
158
  } as const;
@@ -66,7 +66,7 @@ export function useNextInvoiceNumber(
66
66
  return response as NextInvoiceNumberResponse;
67
67
  },
68
68
  enabled: options?.enabled !== false && !!entityId && !!sdk?.documents,
69
- staleTime: 5000, // 5 seconds - short to catch concurrent creates
69
+ staleTime: 0, // Always refetch when form opens or params change
70
70
  });
71
71
  }
72
72
 
@@ -0,0 +1,27 @@
1
+ import { Progress as ProgressPrimitive } from "@base-ui/react/progress"
2
+
3
+ import { cn } from "@/ui/lib/utils"
4
+
5
+ function Progress({
6
+ value,
7
+ className,
8
+ ...props
9
+ }: ProgressPrimitive.Root.Props & { className?: string }) {
10
+ return (
11
+ <ProgressPrimitive.Root
12
+ data-slot="progress"
13
+ value={value}
14
+ className={cn("relative w-full overflow-hidden rounded-full bg-primary/20", className)}
15
+ {...props}
16
+ >
17
+ <ProgressPrimitive.Track>
18
+ <ProgressPrimitive.Indicator
19
+ className="h-full bg-primary transition-all duration-500 ease-in-out"
20
+ style={{ width: `${value ?? 0}%` }}
21
+ />
22
+ </ProgressPrimitive.Track>
23
+ </ProgressPrimitive.Root>
24
+ )
25
+ }
26
+
27
+ export { Progress }
@@ -26,9 +26,21 @@ async function main() {
26
26
  await fs.mkdir(SCHEMAS_DIR, { recursive: true });
27
27
  await fs.mkdir(GENERATED_DIR, { recursive: true });
28
28
 
29
- // Generate initial schemas with openapi-zod-client
29
+ // Fetch OpenAPI spec from running API and generate schemas
30
+ const API_URL = "http://localhost:3000/openapi.json";
31
+ const openApiPath = path.resolve(GENERATED_DIR, "openapi.json");
32
+
33
+ try {
34
+ const res = await fetch(API_URL);
35
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
36
+ await fs.writeFile(openApiPath, await res.text());
37
+ console.log(`Fetched OpenAPI spec from ${API_URL}`);
38
+ } catch (_error) {
39
+ console.error(`Failed to fetch OpenAPI spec from ${API_URL}. Is the API running?`);
40
+ process.exit(1);
41
+ }
42
+
30
43
  try {
31
- const openApiPath = path.resolve(__dirname, "../../../apps/api/openapi.json");
32
44
  execSync(
33
45
  `bunx openapi-zod-client ${openApiPath} ` +
34
46
  "--output " +
@@ -299,6 +311,7 @@ export { createInvoiceSchema as createCreditNoteSchema, type CreateInvoiceSchema
299
311
 
300
312
  // Clean up temporary files
301
313
  await fs.unlink(`${GENERATED_DIR}/schemas.ts`);
314
+ await fs.unlink(`${GENERATED_DIR}/openapi.json`);
302
315
  }
303
316
 
304
317
  main().catch(console.error);
@@ -97,6 +97,8 @@ const createAdvanceInvoiceSchemaDefinition = z.object({
97
97
  note: z.union([z.string(), z.null()]).optional(),
98
98
  tax_clause: z.union([z.string(), z.null()]).optional(),
99
99
  footer: z.union([z.string(), z.null()]).optional(),
100
+ signature: z.union([z.string(), z.null()]).optional(),
101
+ reference: z.union([z.string(), z.null()]).optional(),
100
102
  currency_code: z.string().max(3).optional(),
101
103
  metadata: z.union([z.record(z.string(), z.any()), z.null()]).optional(),
102
104
  date_due: z.union([z.string(), z.null()]).optional(),
@@ -111,6 +111,8 @@ const updateCreditNoteSchemaDefinition = z
111
111
  .min(1),
112
112
  note: z.union([z.string(), z.null()]),
113
113
  footer: z.union([z.string(), z.null()]),
114
+ signature: z.union([z.string(), z.null()]),
115
+ reference: z.union([z.string(), z.null()]),
114
116
  payment_terms: z.union([z.string(), z.null()]),
115
117
  currency_code: z.string(),
116
118
  metadata: z.union([z.object({}).partial().passthrough(), z.null()]),
@@ -22,6 +22,7 @@ const createCustomerSchemaDefinition = z.object({
22
22
  company_number: z.union([z.string(), z.null()]).optional(),
23
23
  email: z.union([z.string(), z.null()]).optional(),
24
24
  is_tax_subject: z.boolean().optional(),
25
+ is_end_consumer: z.union([z.boolean(), z.null()]).optional(),
25
26
  metadata: z.union([z.record(z.string(), z.any()), z.null()]).optional(),
26
27
  });
27
28
 
@@ -44,6 +45,7 @@ const updateCustomerSchemaDefinition = z
44
45
  company_number: z.union([z.string(), z.null()]),
45
46
  email: z.union([z.string(), z.null()]),
46
47
  is_tax_subject: z.boolean(),
48
+ is_end_consumer: z.union([z.boolean(), z.null()]),
47
49
  metadata: z.union([z.record(z.string(), z.any()), z.null()]),
48
50
  })
49
51
  .partial();
@@ -85,6 +85,8 @@ const createDeliveryNoteSchemaDefinition = z.object({
85
85
  payment_terms: z.union([z.string(), z.null()]).optional(),
86
86
  tax_clause: z.union([z.string(), z.null()]).optional(),
87
87
  footer: z.union([z.string(), z.null()]).optional(),
88
+ signature: z.union([z.string(), z.null()]).optional(),
89
+ reference: z.union([z.string(), z.null()]).optional(),
88
90
  currency_code: z.string().max(3).optional(),
89
91
  metadata: z.union([z.record(z.string(), z.any()), z.null()]).optional(),
90
92
  hide_prices: z.boolean().optional(),
@@ -70,6 +70,7 @@ const createEntitySchemaDefinition = z.object({
70
70
  default_credit_note_note: z.union([z.string(), z.null()]),
71
71
  default_credit_note_payment_terms: z.union([z.string(), z.null()]),
72
72
  document_footer: z.union([z.string(), z.null()]),
73
+ default_document_signature: z.union([z.string(), z.null()]),
73
74
  furs: z.union([
74
75
  z
75
76
  .object({
@@ -78,6 +79,7 @@ const createEntitySchemaDefinition = z.object({
78
79
  operator_tax_number: z.string(),
79
80
  operator_label: z.string(),
80
81
  foreign_operator: z.boolean(),
82
+ environment: z.enum(["test", "production"]),
81
83
  })
82
84
  .partial()
83
85
  .passthrough(),
@@ -91,6 +93,7 @@ const createEntitySchemaDefinition = z.object({
91
93
  operator_label: z.string(),
92
94
  u_sust_pdv: z.boolean().default(true),
93
95
  numbering_sequence: z.enum(["N", "P"]).default("P"),
96
+ unified_numbering: z.union([z.boolean(), z.null()]),
94
97
  certificate_expiry: z.string(),
95
98
  })
96
99
  .partial()
@@ -144,6 +147,8 @@ const createEntitySchemaDefinition = z.object({
144
147
  domestic: z.union([z.string(), z.null()]),
145
148
  intra_eu_b2b: z.union([z.string(), z.null()]),
146
149
  intra_eu_b2c: z.union([z.string(), z.null()]),
150
+ "3w_b2b": z.union([z.string(), z.null()]),
151
+ "3w_b2c": z.union([z.string(), z.null()]),
147
152
  export: z.union([z.string(), z.null()]),
148
153
  })
149
154
  .partial()
@@ -221,6 +226,7 @@ const patchEntitySchemaDefinition = z
221
226
  default_credit_note_note: z.union([z.string(), z.null()]),
222
227
  default_credit_note_payment_terms: z.union([z.string(), z.null()]),
223
228
  document_footer: z.union([z.string(), z.null()]),
229
+ default_document_signature: z.union([z.string(), z.null()]),
224
230
  furs: z.union([
225
231
  z
226
232
  .object({
@@ -229,6 +235,7 @@ const patchEntitySchemaDefinition = z
229
235
  operator_tax_number: z.string(),
230
236
  operator_label: z.string(),
231
237
  foreign_operator: z.boolean(),
238
+ environment: z.enum(["test", "production"]),
232
239
  })
233
240
  .partial()
234
241
  .passthrough(),
@@ -242,6 +249,7 @@ const patchEntitySchemaDefinition = z
242
249
  operator_label: z.string(),
243
250
  u_sust_pdv: z.boolean().default(true),
244
251
  numbering_sequence: z.enum(["N", "P"]).default("P"),
252
+ unified_numbering: z.union([z.boolean(), z.null()]),
245
253
  certificate_expiry: z.string(),
246
254
  })
247
255
  .partial()
@@ -295,6 +303,8 @@ const patchEntitySchemaDefinition = z
295
303
  domestic: z.union([z.string(), z.null()]),
296
304
  intra_eu_b2b: z.union([z.string(), z.null()]),
297
305
  intra_eu_b2c: z.union([z.string(), z.null()]),
306
+ "3w_b2b": z.union([z.string(), z.null()]),
307
+ "3w_b2c": z.union([z.string(), z.null()]),
298
308
  export: z.union([z.string(), z.null()]),
299
309
  })
300
310
  .partial()
@@ -87,6 +87,8 @@ const createEstimateSchemaDefinition = z.object({
87
87
  payment_terms: z.union([z.string(), z.null()]).optional(),
88
88
  tax_clause: z.union([z.string(), z.null()]).optional(),
89
89
  footer: z.union([z.string(), z.null()]).optional(),
90
+ signature: z.union([z.string(), z.null()]).optional(),
91
+ reference: z.union([z.string(), z.null()]).optional(),
90
92
  currency_code: z.string().max(3).optional(),
91
93
  metadata: z.union([z.record(z.string(), z.any()), z.null()]).optional(),
92
94
  date_valid_till: z.union([z.string(), z.null()]).optional(),
@@ -11,11 +11,12 @@ import { z } from 'zod';
11
11
  // Schema for update finasettings operation
12
12
  const updateFinaSettingsSchemaDefinition = z
13
13
  .object({
14
- enabled: z.boolean(),
14
+ enabled: z.boolean().default(false),
15
15
  operator_oib: z.string().min(11).max(11),
16
16
  operator_label: z.string(),
17
- u_sust_pdv: z.boolean(),
18
- numbering_sequence: z.enum(["N", "P"]),
17
+ u_sust_pdv: z.boolean().default(true),
18
+ numbering_sequence: z.enum(["N", "P"]).default("P"),
19
+ unified_numbering: z.union([z.boolean(), z.null()]),
19
20
  })
20
21
  .partial();
21
22
 
@@ -98,6 +98,8 @@ const createInvoiceSchemaDefinition = z.object({
98
98
  payment_terms: z.union([z.string(), z.null()]).optional(),
99
99
  tax_clause: z.union([z.string(), z.null()]).optional(),
100
100
  footer: z.union([z.string(), z.null()]).optional(),
101
+ signature: z.union([z.string(), z.null()]).optional(),
102
+ reference: z.union([z.string(), z.null()]).optional(),
101
103
  currency_code: z.string().max(3).optional(),
102
104
  metadata: z.union([z.record(z.string(), z.any()), z.null()]).optional(),
103
105
  date_due: z.union([z.string(), z.null()]).optional(),