@spaceinvoices/react-ui 0.4.8 → 0.4.11
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.
- package/README.md +24 -8
- package/cli/dist/index.js +89 -26
- package/package.json +4 -1
- package/spaceinvoices.schema.json +6 -1
- package/src/common/autocomplete.tsx +69 -6
- package/src/components/advance-invoices/create/create-advance-invoice-form.tsx +124 -285
- package/src/components/advance-invoices/list/list-table.tsx +10 -3
- package/src/components/advance-invoices/list/locales/de.ts +2 -0
- package/src/components/advance-invoices/list/locales/en.ts +1 -0
- package/src/components/advance-invoices/list/locales/es.ts +1 -0
- package/src/components/advance-invoices/list/locales/fr.ts +1 -0
- package/src/components/advance-invoices/list/locales/hr.ts +1 -0
- package/src/components/advance-invoices/list/locales/it.ts +1 -0
- package/src/components/advance-invoices/list/locales/nl.ts +1 -0
- package/src/components/advance-invoices/list/locales/pl.ts +1 -0
- package/src/components/advance-invoices/list/locales/pt.ts +1 -0
- package/src/components/advance-invoices/list/locales/sl.ts +1 -0
- package/src/components/advance-invoices/list/use-advance-invoice-download.ts +1 -12
- package/src/components/credit-notes/create/create-credit-note-form.tsx +116 -238
- package/src/components/credit-notes/list/list-table.tsx +6 -3
- package/src/components/credit-notes/list/use-credit-note-download.ts +1 -12
- package/src/components/customers/customer-autocomplete.tsx +64 -11
- package/src/components/customers/customer-list-table/customer-list-table.tsx +3 -2
- package/src/components/dashboard/collection-rate-card/collection-rate-card.tsx +9 -1
- package/src/components/dashboard/collection-rate-card/locales/bg.ts +3 -0
- package/src/components/dashboard/collection-rate-card/locales/cs.ts +3 -0
- package/src/components/dashboard/collection-rate-card/locales/et.ts +3 -0
- package/src/components/dashboard/collection-rate-card/locales/fi.ts +3 -0
- package/src/components/dashboard/collection-rate-card/locales/is.ts +3 -0
- package/src/components/dashboard/collection-rate-card/locales/nb.ts +3 -0
- package/src/components/dashboard/collection-rate-card/locales/sk.ts +3 -0
- package/src/components/dashboard/collection-rate-card/locales/sv.ts +3 -0
- package/src/components/dashboard/invoice-status-chart/invoice-status-chart.tsx +10 -2
- package/src/components/dashboard/invoice-status-chart/locales/bg.ts +10 -0
- package/src/components/dashboard/invoice-status-chart/locales/cs.ts +10 -0
- package/src/components/dashboard/invoice-status-chart/locales/de.ts +1 -0
- package/src/components/dashboard/invoice-status-chart/locales/es.ts +1 -0
- package/src/components/dashboard/invoice-status-chart/locales/et.ts +10 -0
- package/src/components/dashboard/invoice-status-chart/locales/fi.ts +10 -0
- package/src/components/dashboard/invoice-status-chart/locales/fr.ts +1 -0
- package/src/components/dashboard/invoice-status-chart/locales/hr.ts +1 -0
- package/src/components/dashboard/invoice-status-chart/locales/is.ts +10 -0
- package/src/components/dashboard/invoice-status-chart/locales/it.ts +1 -0
- package/src/components/dashboard/invoice-status-chart/locales/nb.ts +10 -0
- package/src/components/dashboard/invoice-status-chart/locales/nl.ts +1 -0
- package/src/components/dashboard/invoice-status-chart/locales/pl.ts +1 -0
- package/src/components/dashboard/invoice-status-chart/locales/pt.ts +1 -0
- package/src/components/dashboard/invoice-status-chart/locales/sk.ts +10 -0
- package/src/components/dashboard/invoice-status-chart/locales/sl.ts +1 -0
- package/src/components/dashboard/invoice-status-chart/locales/sv.ts +10 -0
- package/src/components/dashboard/payment-methods-chart/locales/bg.ts +12 -0
- package/src/components/dashboard/payment-methods-chart/locales/cs.ts +12 -0
- package/src/components/dashboard/payment-methods-chart/locales/et.ts +12 -0
- package/src/components/dashboard/payment-methods-chart/locales/fi.ts +12 -0
- package/src/components/dashboard/payment-methods-chart/locales/is.ts +12 -0
- package/src/components/dashboard/payment-methods-chart/locales/nb.ts +12 -0
- package/src/components/dashboard/payment-methods-chart/locales/sk.ts +12 -0
- package/src/components/dashboard/payment-methods-chart/locales/sv.ts +12 -0
- package/src/components/dashboard/payment-methods-chart/payment-methods-chart.tsx +9 -1
- package/src/components/dashboard/payment-trend-chart/locales/bg.ts +6 -0
- package/src/components/dashboard/payment-trend-chart/locales/cs.ts +6 -0
- package/src/components/dashboard/payment-trend-chart/locales/de.ts +1 -0
- package/src/components/dashboard/payment-trend-chart/locales/es.ts +1 -0
- package/src/components/dashboard/payment-trend-chart/locales/et.ts +6 -0
- package/src/components/dashboard/payment-trend-chart/locales/fi.ts +6 -0
- package/src/components/dashboard/payment-trend-chart/locales/fr.ts +1 -0
- package/src/components/dashboard/payment-trend-chart/locales/hr.ts +1 -0
- package/src/components/dashboard/payment-trend-chart/locales/is.ts +6 -0
- package/src/components/dashboard/payment-trend-chart/locales/it.ts +1 -0
- package/src/components/dashboard/payment-trend-chart/locales/nb.ts +6 -0
- package/src/components/dashboard/payment-trend-chart/locales/nl.ts +1 -0
- package/src/components/dashboard/payment-trend-chart/locales/pl.ts +1 -0
- package/src/components/dashboard/payment-trend-chart/locales/pt.ts +1 -0
- package/src/components/dashboard/payment-trend-chart/locales/sk.ts +6 -0
- package/src/components/dashboard/payment-trend-chart/locales/sl.ts +1 -0
- package/src/components/dashboard/payment-trend-chart/locales/sv.ts +6 -0
- package/src/components/dashboard/payment-trend-chart/payment-trend-chart.tsx +15 -8
- package/src/components/dashboard/revenue-trend-chart/locales/bg.ts +6 -0
- package/src/components/dashboard/revenue-trend-chart/locales/cs.ts +6 -0
- package/src/components/dashboard/revenue-trend-chart/locales/de.ts +1 -0
- package/src/components/dashboard/revenue-trend-chart/locales/es.ts +1 -0
- package/src/components/dashboard/revenue-trend-chart/locales/et.ts +6 -0
- package/src/components/dashboard/revenue-trend-chart/locales/fi.ts +6 -0
- package/src/components/dashboard/revenue-trend-chart/locales/fr.ts +1 -0
- package/src/components/dashboard/revenue-trend-chart/locales/hr.ts +1 -0
- package/src/components/dashboard/revenue-trend-chart/locales/is.ts +6 -0
- package/src/components/dashboard/revenue-trend-chart/locales/it.ts +1 -0
- package/src/components/dashboard/revenue-trend-chart/locales/nb.ts +6 -0
- package/src/components/dashboard/revenue-trend-chart/locales/nl.ts +1 -0
- package/src/components/dashboard/revenue-trend-chart/locales/pl.ts +1 -0
- package/src/components/dashboard/revenue-trend-chart/locales/pt.ts +1 -0
- package/src/components/dashboard/revenue-trend-chart/locales/sk.ts +6 -0
- package/src/components/dashboard/revenue-trend-chart/locales/sl.ts +1 -0
- package/src/components/dashboard/revenue-trend-chart/locales/sv.ts +6 -0
- package/src/components/dashboard/revenue-trend-chart/revenue-trend-chart.tsx +15 -8
- package/src/components/dashboard/tax-collected-card/locales.ts +110 -0
- package/src/components/dashboard/tax-collected-card/tax-collected-card.tsx +8 -2
- package/src/components/dashboard/tax-collected-card/use-tax-collected.ts +4 -4
- package/src/components/dashboard/top-customers-chart/locales/bg.ts +7 -0
- package/src/components/dashboard/top-customers-chart/locales/cs.ts +7 -0
- package/src/components/dashboard/top-customers-chart/locales/de.ts +2 -0
- package/src/components/dashboard/top-customers-chart/locales/es.ts +2 -0
- package/src/components/dashboard/top-customers-chart/locales/et.ts +7 -0
- package/src/components/dashboard/top-customers-chart/locales/fi.ts +7 -0
- package/src/components/dashboard/top-customers-chart/locales/fr.ts +2 -0
- package/src/components/dashboard/top-customers-chart/locales/hr.ts +2 -0
- package/src/components/dashboard/top-customers-chart/locales/is.ts +7 -0
- package/src/components/dashboard/top-customers-chart/locales/it.ts +2 -0
- package/src/components/dashboard/top-customers-chart/locales/nb.ts +7 -0
- package/src/components/dashboard/top-customers-chart/locales/nl.ts +2 -0
- package/src/components/dashboard/top-customers-chart/locales/pl.ts +2 -0
- package/src/components/dashboard/top-customers-chart/locales/pt.ts +2 -0
- package/src/components/dashboard/top-customers-chart/locales/sk.ts +7 -0
- package/src/components/dashboard/top-customers-chart/locales/sl.ts +2 -0
- package/src/components/dashboard/top-customers-chart/locales/sv.ts +7 -0
- package/src/components/dashboard/top-customers-chart/top-customers-chart.tsx +23 -12
- package/src/components/delivery-notes/create/create-delivery-note-form.tsx +33 -20
- package/src/components/delivery-notes/list/list-table.tsx +22 -13
- package/src/components/delivery-notes/list/locales/de.ts +2 -0
- package/src/components/delivery-notes/list/locales/en.ts +1 -0
- package/src/components/delivery-notes/list/locales/es.ts +1 -0
- package/src/components/delivery-notes/list/locales/fr.ts +1 -0
- package/src/components/delivery-notes/list/locales/hr.ts +1 -0
- package/src/components/delivery-notes/list/locales/it.ts +1 -0
- package/src/components/delivery-notes/list/locales/nl.ts +1 -0
- package/src/components/delivery-notes/list/locales/pl.ts +1 -0
- package/src/components/delivery-notes/list/locales/pt.ts +1 -0
- package/src/components/delivery-notes/list/locales/sl.ts +1 -0
- package/src/components/delivery-notes/list/use-delivery-note-download.ts +1 -12
- package/src/components/documents/create/document-add-item-form.tsx +28 -16
- package/src/components/documents/create/document-add-item-tax-rate-field.tsx +12 -2
- package/src/components/documents/create/document-items-section.tsx +70 -39
- package/src/components/documents/create/document-recipient-section.tsx +10 -1
- package/src/components/documents/create/live-preview.tsx +113 -15
- package/src/components/documents/create/prepare-document-submission.ts +35 -16
- package/src/components/documents/create/use-document-customer-form.ts +14 -3
- package/src/components/documents/documents.hooks.ts +7 -2
- package/src/components/documents/shared/document-preview-display.tsx +136 -67
- package/src/components/documents/shared/scaled-document-preview.tsx +45 -5
- package/src/components/documents/view/document-actions-bar.tsx +284 -182
- package/src/components/documents/view/document-activities-list.tsx +3 -0
- package/src/components/documents/view/document-payments-list.tsx +3 -0
- package/src/components/documents/view/locales/de.ts +8 -0
- package/src/components/documents/view/locales/es.ts +8 -0
- package/src/components/documents/view/locales/fr.ts +8 -0
- package/src/components/documents/view/locales/hr.ts +8 -0
- package/src/components/documents/view/locales/it.ts +8 -0
- package/src/components/documents/view/locales/nl.ts +8 -0
- package/src/components/documents/view/locales/pl.ts +8 -0
- package/src/components/documents/view/locales/pt.ts +8 -0
- package/src/components/documents/view/locales/sl.ts +8 -0
- package/src/components/documents/view/use-document-download.ts +14 -25
- package/src/components/entities/create-entity-form.tsx +101 -16
- package/src/components/entities/entity-settings-form/entity-settings-form.tsx +10 -10
- package/src/components/entities/entity-settings-form/locales/de.ts +10 -0
- package/src/components/entities/entity-settings-form/locales/es.ts +10 -0
- package/src/components/entities/entity-settings-form/locales/fr.ts +10 -0
- package/src/components/entities/entity-settings-form/locales/hr.ts +10 -0
- package/src/components/entities/entity-settings-form/locales/it.ts +10 -0
- package/src/components/entities/entity-settings-form/locales/nl.ts +10 -0
- package/src/components/entities/entity-settings-form/locales/pl.ts +10 -0
- package/src/components/entities/entity-settings-form/locales/pt.ts +10 -0
- package/src/components/entities/entity-settings-form/locales/sl.ts +10 -0
- package/src/components/entities/fina-settings-form/fina-operator-required-dialog.tsx +3 -3
- package/src/components/entities/fina-settings-form/fina-settings-form.tsx +78 -124
- package/src/components/entities/fina-settings-form/sections/certificate-settings-section.tsx +8 -1
- package/src/components/entities/fina-settings-form/sections/premises-management-section.tsx +14 -2
- package/src/components/entities/fina-settings-form/sections/register-premise-dialog.tsx +7 -2
- package/src/components/entities/furs-settings-form/furs-settings-form.tsx +56 -130
- package/src/components/entities/furs-settings-form/sections/certificate-settings-section.tsx +8 -1
- package/src/components/entities/furs-settings-form/sections/enable-fiscalization-section.tsx +1 -0
- package/src/components/entities/furs-settings-form/sections/general-settings-section.tsx +15 -2
- package/src/components/entities/furs-settings-form/sections/premises-management-section.tsx +20 -3
- package/src/components/entities/furs-settings-form/sections/register-premise-dialog.tsx +38 -12
- package/src/components/entities/settings/defaults-settings-form.tsx +6 -6
- package/src/components/entities/settings/eslog-settings-form.tsx +13 -1
- package/src/components/entities/settings/pdf-template-selector/demo-invoice-data.ts +3 -22
- package/src/components/entities/shared/fiscalization-step-flow.ts +77 -0
- package/src/components/entities/shared/fiscalization-step-tabs.tsx +71 -0
- package/src/components/estimates/create/create-estimate-form.tsx +34 -21
- package/src/components/estimates/list/list-table.tsx +23 -14
- package/src/components/estimates/list/locales/de.ts +2 -0
- package/src/components/estimates/list/locales/en.ts +1 -0
- package/src/components/estimates/list/locales/es.ts +1 -0
- package/src/components/estimates/list/locales/fr.ts +1 -0
- package/src/components/estimates/list/locales/hr.ts +1 -0
- package/src/components/estimates/list/locales/it.ts +1 -0
- package/src/components/estimates/list/locales/nl.ts +1 -0
- package/src/components/estimates/list/locales/pl.ts +1 -0
- package/src/components/estimates/list/locales/pt.ts +1 -0
- package/src/components/estimates/list/locales/sl.ts +1 -0
- package/src/components/estimates/list/use-estimate-download.ts +1 -12
- package/src/components/export/document-export-form.tsx +33 -7
- package/src/components/export/sales-per-item-export-form.tsx +23 -7
- package/src/components/invoices/create/create-invoice-form.tsx +295 -329
- package/src/components/invoices/create/prepare-invoice-submission.ts +0 -8
- package/src/components/invoices/list/list-table.tsx +7 -4
- package/src/components/invoices/list/use-invoice-download.ts +1 -11
- package/src/components/invoices/send-email-dialog/locales/de.ts +20 -0
- package/src/components/invoices/send-email-dialog/locales/es.ts +20 -0
- package/src/components/invoices/send-email-dialog/locales/fr.ts +20 -0
- package/src/components/invoices/send-email-dialog/locales/hr.ts +20 -0
- package/src/components/invoices/send-email-dialog/locales/it.ts +20 -0
- package/src/components/invoices/send-email-dialog/locales/nl.ts +20 -0
- package/src/components/invoices/send-email-dialog/locales/pl.ts +20 -0
- package/src/components/invoices/send-email-dialog/locales/pt.ts +20 -0
- package/src/components/invoices/send-email-dialog/locales/sl.ts +20 -0
- package/src/components/invoices/send-email-dialog/send-email-dialog.tsx +77 -8
- package/src/components/invoices/view/eslog-info-display.tsx +17 -1
- package/src/components/invoices/view/fiscalization-status-card.tsx +7 -3
- package/src/components/items/item-combobox.tsx +26 -6
- package/src/components/items/item-list-table/item-list-table.tsx +5 -2
- package/src/components/payments/create-payment-form/index.ts +1 -0
- package/src/components/payments/list/list-table.tsx +14 -4
- package/src/components/recurring-invoices/list/list-table.tsx +7 -4
- package/src/components/request-logs/locales.ts +412 -0
- package/src/components/request-logs/request-log-detail.tsx +37 -21
- package/src/components/request-logs/request-log-list-table.tsx +57 -11
- package/src/components/table/data-table.tsx +5 -2
- package/src/components/table/date-cell.tsx +3 -1
- package/src/components/table/filter-bar.tsx +14 -2
- package/src/components/table/hooks/use-table-query.ts +1 -1
- package/src/components/table/locales.ts +1116 -0
- package/src/components/table/search-input.tsx +12 -3
- package/src/components/table/selection-toolbar.tsx +23 -6
- package/src/components/table/table-empty-state.tsx +43 -3
- package/src/components/table/table-no-results.tsx +3 -3
- package/src/components/table/table-pagination.tsx +4 -3
- package/src/components/table/types.ts +1 -0
- package/src/components/tax-reports/index.ts +1 -0
- package/src/components/tax-reports/kir-export-form.tsx +46 -8
- package/src/components/tax-reports/slovenia-tax-profile-step.tsx +191 -0
- package/src/components/tax-reports/slovenia-yearly-export-form.tsx +509 -0
- package/src/components/tax-reports/slovenia-yearly-review-step.tsx +253 -0
- package/src/components/tax-reports/slovenia-yearly-summary.tsx +19 -0
- package/src/components/taxes/tax-list-table/tax-list-table.tsx +3 -2
- package/src/components/ui/sidebar.tsx +3 -2
- package/src/components/ui/sticky-form-footer.tsx +7 -1
- package/src/components/webhook-logs/index.ts +6 -0
- package/src/components/webhook-logs/locales.ts +392 -0
- package/src/components/webhook-logs/webhook-delivery-detail.tsx +255 -0
- package/src/components/webhook-logs/webhook-delivery-list-table.tsx +278 -0
- package/src/components/wl-subscription/index.ts +1 -0
- package/src/components/wl-subscription/locked-feature.tsx +1 -0
- package/src/components/wl-subscription/paywall.tsx +193 -0
- package/src/components/wl-subscription/upgrade-modal.tsx +93 -29
- package/src/generate-schemas.ts +12 -7
- package/src/generated/schemas/customer.ts +2 -0
- package/src/generated/schemas/entity.ts +134 -0
- package/src/generated/schemas/exportsloveniayearlynormiranireport_body.ts +27 -0
- package/src/generated/schemas/index.ts +2 -0
- package/src/generated/schemas/me.ts +20 -1
- package/src/generated/schemas/renderadvanceinvoicepreview_body.ts +40 -34
- package/src/generated/schemas/rendercreditnotepreview_body.ts +42 -36
- package/src/generated/schemas/renderdeliverynotepreview_body.ts +23 -13
- package/src/generated/schemas/renderestimatepreview_body.ts +23 -13
- package/src/generated/schemas/renderinvoicepreview_body.ts +40 -34
- package/src/generated/schemas/sendemail_body.ts +44 -0
- package/src/generated/schemas/sloveniataxprofile.ts +42 -0
- package/src/generated/schemas/startpdfexport_body.ts +91 -1
- package/src/generated/schemas/webhook.ts +10 -0
- package/src/hooks/use-duplicate-document.ts +51 -13
- package/src/hooks/use-eslog-validation.ts +59 -0
- package/src/hooks/use-premise-selection.ts +186 -0
- package/src/lib/browser-cookies.ts +4 -4
- package/src/lib/date-fns-locale.ts +48 -0
- package/src/lib/fiscalization-options.ts +81 -0
- package/src/lib/locale.ts +38 -0
- package/src/lib/template-variables.tsx +1 -1
- package/src/lib/translation.ts +14 -3
- package/src/providers/entities-context.tsx +1 -0
- package/src/providers/entities-provider.tsx +102 -3
- package/src/providers/form-footer-context.tsx +37 -4
- package/src/providers/sdk-provider.tsx +7 -2
- package/src/providers/white-label-provider.tsx +4 -1
- package/src/providers/wl-subscription-provider.tsx +90 -3
|
@@ -13,7 +13,10 @@ 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 { useEslogValidation } from "@/ui/hooks/use-eslog-validation";
|
|
17
|
+
import { usePremiseSelection } from "@/ui/hooks/use-premise-selection";
|
|
16
18
|
import { useTransactionTypeCheck } from "@/ui/hooks/use-transaction-type-check";
|
|
19
|
+
import { buildEslogOptions, buildFinaOptions, buildFursOptions } from "@/ui/lib/fiscalization-options";
|
|
17
20
|
import type { ComponentTranslationProps } from "@/ui/lib/translation";
|
|
18
21
|
import { createTranslation } from "@/ui/lib/translation";
|
|
19
22
|
import { cn } from "@/ui/lib/utils";
|
|
@@ -33,17 +36,7 @@ import { DocumentRecipientSection } from "../../documents/create/document-recipi
|
|
|
33
36
|
import { type LinkedDocumentSummary, LinkedDocumentsInfo } from "../../documents/create/linked-documents-info";
|
|
34
37
|
import { MarkAsPaidSection } from "../../documents/create/mark-as-paid-section";
|
|
35
38
|
import type { DocumentTypes } from "../../documents/types";
|
|
36
|
-
import {
|
|
37
|
-
import { useFursPremises, useFursSettings } from "../../entities/furs-settings-form/furs-settings.hooks";
|
|
38
|
-
import {
|
|
39
|
-
getLastUsedFinaCombo,
|
|
40
|
-
getLastUsedFursCombo,
|
|
41
|
-
setLastUsedFinaCombo,
|
|
42
|
-
setLastUsedFursCombo,
|
|
43
|
-
useCreateInvoice,
|
|
44
|
-
useNextInvoiceNumber,
|
|
45
|
-
useUpdateInvoice,
|
|
46
|
-
} from "../invoices.hooks";
|
|
39
|
+
import { useCreateInvoice, useNextInvoiceNumber, useUpdateInvoice } from "../invoices.hooks";
|
|
47
40
|
import { getEntityErrors, getFormFieldErrors, validateEslogForm } from "./eslog-validation";
|
|
48
41
|
import de from "./locales/de";
|
|
49
42
|
import es from "./locales/es";
|
|
@@ -77,6 +70,13 @@ const translations = {
|
|
|
77
70
|
hr,
|
|
78
71
|
} as const;
|
|
79
72
|
|
|
73
|
+
const DUPLICATE_PREVIEW_SETTLE_MS = 120;
|
|
74
|
+
const DUPLICATE_PREVIEW_MIN_DELAY_MS = 260;
|
|
75
|
+
|
|
76
|
+
function emitInvoiceCreateDebug(_detail: Record<string, unknown>) {
|
|
77
|
+
if (!import.meta.env.DEV || typeof window === "undefined") return;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
80
|
// Form values: extend schema with local-only fields (number is for display, not sent to API)
|
|
81
81
|
type CreateInvoiceFormValues = z.infer<typeof createInvoiceSchema> & {
|
|
82
82
|
number?: string;
|
|
@@ -105,6 +105,62 @@ type DocumentAddFormProps = {
|
|
|
105
105
|
documentId?: string;
|
|
106
106
|
} & ComponentTranslationProps;
|
|
107
107
|
|
|
108
|
+
function buildInvoiceFormValues({
|
|
109
|
+
initialValues,
|
|
110
|
+
currencyCode,
|
|
111
|
+
defaultInvoiceNote,
|
|
112
|
+
defaultPaymentTerms,
|
|
113
|
+
defaultFooter,
|
|
114
|
+
defaultInvoiceDueDays,
|
|
115
|
+
}: {
|
|
116
|
+
initialValues?: Partial<CreateInvoiceRequest> & { number?: string };
|
|
117
|
+
currencyCode?: string;
|
|
118
|
+
defaultInvoiceNote: string;
|
|
119
|
+
defaultPaymentTerms: string;
|
|
120
|
+
defaultFooter: string;
|
|
121
|
+
defaultInvoiceDueDays: number;
|
|
122
|
+
}): CreateInvoiceFormValues {
|
|
123
|
+
return {
|
|
124
|
+
number: initialValues?.number || "",
|
|
125
|
+
date: initialValues?.date || new Date().toISOString(),
|
|
126
|
+
customer_id: initialValues?.customer_id ?? undefined,
|
|
127
|
+
customer: (initialValues?.customer as CreateInvoiceFormValues["customer"]) ?? undefined,
|
|
128
|
+
items: initialValues?.items?.length
|
|
129
|
+
? initialValues.items.map((item: any) => ({
|
|
130
|
+
type: item.type,
|
|
131
|
+
name: item.name || "",
|
|
132
|
+
description: item.description || "",
|
|
133
|
+
...(item.type !== "separator"
|
|
134
|
+
? {
|
|
135
|
+
quantity: item.quantity ?? 1,
|
|
136
|
+
price: item.gross_price ?? item.price,
|
|
137
|
+
taxes: item.taxes || [],
|
|
138
|
+
}
|
|
139
|
+
: {}),
|
|
140
|
+
}))
|
|
141
|
+
: [
|
|
142
|
+
{
|
|
143
|
+
name: "",
|
|
144
|
+
description: "",
|
|
145
|
+
quantity: 1,
|
|
146
|
+
price: undefined,
|
|
147
|
+
taxes: [],
|
|
148
|
+
},
|
|
149
|
+
],
|
|
150
|
+
currency_code: initialValues?.currency_code || currencyCode || "EUR",
|
|
151
|
+
reference: (initialValues as any)?.reference ?? "",
|
|
152
|
+
note: initialValues?.note ?? defaultInvoiceNote,
|
|
153
|
+
tax_clause: (initialValues as any)?.tax_clause ?? "",
|
|
154
|
+
payment_terms: initialValues?.payment_terms ?? defaultPaymentTerms,
|
|
155
|
+
footer: (initialValues as any)?.footer ?? defaultFooter,
|
|
156
|
+
date_due:
|
|
157
|
+
initialValues?.date_due ||
|
|
158
|
+
calculateDueDate(initialValues?.date || new Date().toISOString(), defaultInvoiceDueDays),
|
|
159
|
+
date_service: (initialValues as any)?.date_service || new Date().toISOString(),
|
|
160
|
+
linked_documents: (initialValues as any)?.linked_documents,
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
|
|
108
164
|
export default function CreateInvoiceForm({
|
|
109
165
|
type: _type,
|
|
110
166
|
entityId,
|
|
@@ -140,42 +196,16 @@ export default function CreateInvoiceForm({
|
|
|
140
196
|
const defaultInvoiceDueDays = (activeEntity?.settings as any)?.default_invoice_due_days ?? 30;
|
|
141
197
|
|
|
142
198
|
// ============================================================================
|
|
143
|
-
// FURS
|
|
199
|
+
// FURS & FINA Premise Selection (shared hook)
|
|
144
200
|
// ============================================================================
|
|
145
|
-
const {
|
|
146
|
-
const {
|
|
147
|
-
enabled: fursSettings?.enabled === true,
|
|
148
|
-
});
|
|
149
|
-
|
|
150
|
-
// Loading state for FURS - don't render form until we know if FURS is active
|
|
151
|
-
const isFursLoading = isFursSettingsLoading || (fursSettings?.enabled && isFursPremisesLoading);
|
|
152
|
-
|
|
153
|
-
// Check if FURS is enabled and has active premises
|
|
154
|
-
const isFursEnabled = fursSettings?.enabled === true;
|
|
155
|
-
const activePremises = useMemo(() => fursPremises?.filter((p) => p.is_active) || [], [fursPremises]);
|
|
156
|
-
const hasFursPremises = activePremises.length > 0;
|
|
157
|
-
|
|
158
|
-
// FURS premise/device selection state
|
|
159
|
-
const [selectedPremiseName, setSelectedPremiseName] = useState<string | undefined>();
|
|
160
|
-
const [selectedDeviceName, setSelectedDeviceName] = useState<string | undefined>();
|
|
201
|
+
const furs = usePremiseSelection({ entityId, type: "furs" });
|
|
202
|
+
const fina = usePremiseSelection({ entityId, type: "fina" });
|
|
161
203
|
const [skipFiscalization, setSkipFiscalization] = useState(false);
|
|
162
204
|
|
|
163
205
|
// ============================================================================
|
|
164
|
-
//
|
|
206
|
+
// e-SLOG Validation (shared hook)
|
|
165
207
|
// ============================================================================
|
|
166
|
-
const
|
|
167
|
-
const { data: finaPremises, isLoading: isFinaPremisesLoading } = useFinaPremises(entityId, {
|
|
168
|
-
enabled: finaSettings?.enabled === true,
|
|
169
|
-
});
|
|
170
|
-
|
|
171
|
-
const isFinaLoading = isFinaSettingsLoading || (finaSettings?.enabled && isFinaPremisesLoading);
|
|
172
|
-
const isFinaEnabled = finaSettings?.enabled === true;
|
|
173
|
-
const activeFinaPremises = useMemo(() => finaPremises?.filter((p: any) => p.is_active) || [], [finaPremises]);
|
|
174
|
-
const hasFinaPremises = activeFinaPremises.length > 0;
|
|
175
|
-
|
|
176
|
-
// FINA premise/device selection state (no skip - all FINA invoices must be fiscalized)
|
|
177
|
-
const [selectedFinaBusinessPremiseName, setSelectedFinaBusinessPremiseName] = useState<string | undefined>();
|
|
178
|
-
const [selectedFinaElectronicDeviceName, setSelectedFinaElectronicDeviceName] = useState<string | undefined>();
|
|
208
|
+
const eslog = useEslogValidation(activeEntity);
|
|
179
209
|
|
|
180
210
|
// UI-only state (not part of API schema)
|
|
181
211
|
const [markAsPaid, setMarkAsPaid] = useState(false);
|
|
@@ -183,7 +213,9 @@ export default function CreateInvoiceForm({
|
|
|
183
213
|
const [isDraftPending, setIsDraftPending] = useState(false);
|
|
184
214
|
|
|
185
215
|
// Service date type state (single date or range)
|
|
186
|
-
const [serviceDateType, setServiceDateType] = useState<"single" | "range">(
|
|
216
|
+
const [serviceDateType, setServiceDateType] = useState<"single" | "range">(
|
|
217
|
+
initialValues && (initialValues as any).date_service_to ? "range" : "single",
|
|
218
|
+
);
|
|
187
219
|
|
|
188
220
|
// Due days type state for invoice due date selector
|
|
189
221
|
const [dueDaysType, setDueDaysType] = useState<number | "custom">(() => {
|
|
@@ -202,173 +234,30 @@ export default function CreateInvoiceForm({
|
|
|
202
234
|
}, [initialValues?.items]);
|
|
203
235
|
const priceModesRef = useRef<PriceModesMap>(initialPriceModes);
|
|
204
236
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
// Clear entity errors when eslog validation is disabled
|
|
225
|
-
useEffect(() => {
|
|
226
|
-
if (!eslogValidationEnabled) {
|
|
227
|
-
setEslogEntityErrors([]);
|
|
228
|
-
}
|
|
229
|
-
}, [eslogValidationEnabled]);
|
|
230
|
-
|
|
231
|
-
// Get active devices for selected premise
|
|
232
|
-
const activeDevices = useMemo(() => {
|
|
233
|
-
if (!selectedPremiseName) return [];
|
|
234
|
-
const premise = activePremises.find((p) => p.business_premise_name === selectedPremiseName);
|
|
235
|
-
return premise?.Devices?.filter((d) => d.is_active) || [];
|
|
236
|
-
}, [activePremises, selectedPremiseName]);
|
|
237
|
-
|
|
238
|
-
// Initialize FURS selection from localStorage or first active combo
|
|
239
|
-
useEffect(() => {
|
|
240
|
-
if (!isFursEnabled || !hasFursPremises || selectedPremiseName) return;
|
|
241
|
-
|
|
242
|
-
const lastUsed = getLastUsedFursCombo(entityId);
|
|
243
|
-
if (lastUsed) {
|
|
244
|
-
// Verify the last-used combo is still valid (premise/device still exist and active)
|
|
245
|
-
const premise = activePremises.find((p) => p.business_premise_name === lastUsed.business_premise_name);
|
|
246
|
-
const device = premise?.Devices?.find(
|
|
247
|
-
(d) => d.electronic_device_name === lastUsed.electronic_device_name && d.is_active,
|
|
248
|
-
);
|
|
249
|
-
if (premise && device) {
|
|
250
|
-
setSelectedPremiseName(lastUsed.business_premise_name);
|
|
251
|
-
setSelectedDeviceName(lastUsed.electronic_device_name);
|
|
252
|
-
return;
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
// Fall back to first active premise/device
|
|
257
|
-
const firstPremise = activePremises[0];
|
|
258
|
-
const firstDevice = firstPremise?.Devices?.find((d) => d.is_active);
|
|
259
|
-
if (firstPremise && firstDevice) {
|
|
260
|
-
setSelectedPremiseName(firstPremise.business_premise_name);
|
|
261
|
-
setSelectedDeviceName(firstDevice.electronic_device_name);
|
|
262
|
-
}
|
|
263
|
-
}, [isFursEnabled, hasFursPremises, activePremises, entityId, selectedPremiseName]);
|
|
264
|
-
|
|
265
|
-
// When premise changes, select first active device
|
|
266
|
-
useEffect(() => {
|
|
267
|
-
if (!selectedPremiseName) return;
|
|
268
|
-
const premise = activePremises.find((p) => p.business_premise_name === selectedPremiseName);
|
|
269
|
-
const firstDevice = premise?.Devices?.find((d) => d.is_active);
|
|
270
|
-
if (firstDevice && selectedDeviceName !== firstDevice.electronic_device_name) {
|
|
271
|
-
// Only update if the current device is not in this premise
|
|
272
|
-
const currentDeviceInPremise = premise?.Devices?.find(
|
|
273
|
-
(d) => d.electronic_device_name === selectedDeviceName && d.is_active,
|
|
274
|
-
);
|
|
275
|
-
if (!currentDeviceInPremise) {
|
|
276
|
-
setSelectedDeviceName(firstDevice.electronic_device_name);
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
}, [selectedPremiseName, activePremises, selectedDeviceName]);
|
|
280
|
-
|
|
281
|
-
// Get active FINA devices for selected premise
|
|
282
|
-
const activeFinaDevices = useMemo(() => {
|
|
283
|
-
if (!selectedFinaBusinessPremiseName) return [];
|
|
284
|
-
const premise = activeFinaPremises.find((p: any) => p.business_premise_name === selectedFinaBusinessPremiseName);
|
|
285
|
-
return premise?.Devices?.filter((d: any) => d.is_active) || [];
|
|
286
|
-
}, [activeFinaPremises, selectedFinaBusinessPremiseName]);
|
|
287
|
-
|
|
288
|
-
// Initialize FINA selection from localStorage or first active combo
|
|
289
|
-
useEffect(() => {
|
|
290
|
-
if (!isFinaEnabled || !hasFinaPremises || selectedFinaBusinessPremiseName) return;
|
|
291
|
-
|
|
292
|
-
const lastUsed = getLastUsedFinaCombo(entityId);
|
|
293
|
-
if (lastUsed) {
|
|
294
|
-
const premise = activeFinaPremises.find((p: any) => p.business_premise_name === lastUsed.business_premise_name);
|
|
295
|
-
const device = premise?.Devices?.find(
|
|
296
|
-
(d: any) => d.electronic_device_name === lastUsed.electronic_device_name && d.is_active,
|
|
297
|
-
);
|
|
298
|
-
if (premise && device) {
|
|
299
|
-
setSelectedFinaBusinessPremiseName(lastUsed.business_premise_name);
|
|
300
|
-
setSelectedFinaElectronicDeviceName(lastUsed.electronic_device_name);
|
|
301
|
-
return;
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
const firstPremise = activeFinaPremises[0];
|
|
306
|
-
const firstDevice = firstPremise?.Devices?.find((d: any) => d.is_active);
|
|
307
|
-
if (firstPremise && firstDevice) {
|
|
308
|
-
setSelectedFinaBusinessPremiseName(firstPremise.business_premise_name);
|
|
309
|
-
setSelectedFinaElectronicDeviceName(firstDevice.electronic_device_name);
|
|
310
|
-
}
|
|
311
|
-
}, [isFinaEnabled, hasFinaPremises, activeFinaPremises, entityId, selectedFinaBusinessPremiseName]);
|
|
312
|
-
|
|
313
|
-
// When FINA premise changes, select first active device
|
|
314
|
-
useEffect(() => {
|
|
315
|
-
if (!selectedFinaBusinessPremiseName) return;
|
|
316
|
-
const premise = activeFinaPremises.find((p: any) => p.business_premise_name === selectedFinaBusinessPremiseName);
|
|
317
|
-
const firstDevice = premise?.Devices?.find((d: any) => d.is_active);
|
|
318
|
-
if (firstDevice && selectedFinaElectronicDeviceName !== firstDevice.electronic_device_name) {
|
|
319
|
-
const currentDeviceInPremise = premise?.Devices?.find(
|
|
320
|
-
(d: any) => d.electronic_device_name === selectedFinaElectronicDeviceName && d.is_active,
|
|
321
|
-
);
|
|
322
|
-
if (!currentDeviceInPremise) {
|
|
323
|
-
setSelectedFinaElectronicDeviceName(firstDevice.electronic_device_name);
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
}, [selectedFinaBusinessPremiseName, activeFinaPremises, selectedFinaElectronicDeviceName]);
|
|
237
|
+
const formDefaultValues = useMemo(
|
|
238
|
+
() =>
|
|
239
|
+
buildInvoiceFormValues({
|
|
240
|
+
initialValues,
|
|
241
|
+
currencyCode: activeEntity?.currency_code ?? undefined,
|
|
242
|
+
defaultInvoiceNote,
|
|
243
|
+
defaultPaymentTerms,
|
|
244
|
+
defaultFooter,
|
|
245
|
+
defaultInvoiceDueDays,
|
|
246
|
+
}),
|
|
247
|
+
[
|
|
248
|
+
activeEntity?.currency_code,
|
|
249
|
+
defaultFooter,
|
|
250
|
+
defaultInvoiceDueDays,
|
|
251
|
+
defaultInvoiceNote,
|
|
252
|
+
defaultPaymentTerms,
|
|
253
|
+
initialValues,
|
|
254
|
+
],
|
|
255
|
+
);
|
|
327
256
|
|
|
328
257
|
const form = useForm<CreateInvoiceFormValues>({
|
|
329
258
|
// Cast resolver to accept extended form type (includes UI-only fields)
|
|
330
259
|
resolver: zodResolver(createInvoiceSchema) as Resolver<CreateInvoiceFormValues>,
|
|
331
|
-
defaultValues:
|
|
332
|
-
number: initialValues?.number || "", // Edit mode uses initialValues, create mode uses useNextInvoiceNumber
|
|
333
|
-
date: initialValues?.date || new Date().toISOString(),
|
|
334
|
-
customer_id: initialValues?.customer_id ?? undefined,
|
|
335
|
-
// Cast customer to form schema type (API type may have additional fields)
|
|
336
|
-
customer: (initialValues?.customer as CreateInvoiceFormValues["customer"]) ?? undefined,
|
|
337
|
-
items: initialValues?.items?.length
|
|
338
|
-
? initialValues.items.map((item: any) => ({
|
|
339
|
-
type: item.type,
|
|
340
|
-
name: item.name || "",
|
|
341
|
-
description: item.description || "",
|
|
342
|
-
...(item.type !== "separator"
|
|
343
|
-
? {
|
|
344
|
-
quantity: item.quantity ?? 1,
|
|
345
|
-
// Use gross_price if set, otherwise use price
|
|
346
|
-
price: item.gross_price ?? item.price,
|
|
347
|
-
taxes: item.taxes || [],
|
|
348
|
-
}
|
|
349
|
-
: {}),
|
|
350
|
-
}))
|
|
351
|
-
: [
|
|
352
|
-
{
|
|
353
|
-
name: "",
|
|
354
|
-
description: "",
|
|
355
|
-
quantity: 1,
|
|
356
|
-
price: undefined,
|
|
357
|
-
taxes: [],
|
|
358
|
-
},
|
|
359
|
-
],
|
|
360
|
-
currency_code: initialValues?.currency_code || activeEntity?.currency_code || "EUR",
|
|
361
|
-
reference: (initialValues as any)?.reference ?? "",
|
|
362
|
-
note: initialValues?.note ?? defaultInvoiceNote,
|
|
363
|
-
tax_clause: (initialValues as any)?.tax_clause ?? "",
|
|
364
|
-
payment_terms: initialValues?.payment_terms ?? defaultPaymentTerms,
|
|
365
|
-
footer: (initialValues as any)?.footer ?? defaultFooter,
|
|
366
|
-
date_due:
|
|
367
|
-
initialValues?.date_due ||
|
|
368
|
-
calculateDueDate(initialValues?.date || new Date().toISOString(), defaultInvoiceDueDays),
|
|
369
|
-
date_service: new Date().toISOString(),
|
|
370
|
-
linked_documents: (initialValues as any)?.linked_documents,
|
|
371
|
-
},
|
|
260
|
+
defaultValues: formDefaultValues,
|
|
372
261
|
});
|
|
373
262
|
|
|
374
263
|
// Skip fiscalization is only allowed for bank transfers or unpaid invoices
|
|
@@ -402,19 +291,8 @@ export default function CreateInvoiceForm({
|
|
|
402
291
|
[form],
|
|
403
292
|
);
|
|
404
293
|
|
|
405
|
-
// Check if FURS selection is ready (needed to prevent number flashing)
|
|
406
|
-
// Selection is ready when: FURS not enabled, OR no premises, OR we have a valid selection
|
|
407
|
-
const isFursSelectionReady = !isFursEnabled || !hasFursPremises || (!!selectedPremiseName && !!selectedDeviceName);
|
|
408
|
-
|
|
409
294
|
// FURS is "active" for this invoice if enabled and we have a valid selection (and not skipped)
|
|
410
|
-
const isFursActive =
|
|
411
|
-
isFursEnabled && hasFursPremises && selectedPremiseName && selectedDeviceName && !skipFiscalization;
|
|
412
|
-
|
|
413
|
-
// FINA selection ready and active checks
|
|
414
|
-
const isFinaSelectionReady =
|
|
415
|
-
!isFinaEnabled || !hasFinaPremises || (!!selectedFinaBusinessPremiseName && !!selectedFinaElectronicDeviceName);
|
|
416
|
-
const isFinaActive =
|
|
417
|
-
isFinaEnabled && hasFinaPremises && selectedFinaBusinessPremiseName && selectedFinaElectronicDeviceName;
|
|
295
|
+
const isFursActive = furs.isActive && !skipFiscalization;
|
|
418
296
|
|
|
419
297
|
// ============================================================================
|
|
420
298
|
// VIES Check - determine transaction type early (needed for number preview)
|
|
@@ -441,10 +319,10 @@ export default function CreateInvoiceForm({
|
|
|
441
319
|
});
|
|
442
320
|
|
|
443
321
|
// FINA numbering guard: use FINA numbering for domestic transactions (or all if unified numbering is on)
|
|
444
|
-
const finaUnifiedNumbering =
|
|
322
|
+
const finaUnifiedNumbering = fina.settings?.unified_numbering !== false;
|
|
445
323
|
const useFinaNumbering =
|
|
446
|
-
!!
|
|
447
|
-
const isFinaNonDomestic = !!
|
|
324
|
+
!!fina.isActive && (finaUnifiedNumbering || transactionType == null || transactionType === "domestic");
|
|
325
|
+
const isFinaNonDomestic = !!fina.isActive && !useFinaNumbering;
|
|
448
326
|
|
|
449
327
|
// ============================================================================
|
|
450
328
|
// Next Invoice Number Preview
|
|
@@ -453,44 +331,44 @@ export default function CreateInvoiceForm({
|
|
|
453
331
|
// Skip in edit mode - we use the existing document number
|
|
454
332
|
// Use the same premise/device params for both FURS and FINA (an entity is either one, never both)
|
|
455
333
|
const activePremiseName = isFursActive
|
|
456
|
-
? selectedPremiseName
|
|
334
|
+
? furs.selectedPremiseName
|
|
457
335
|
: useFinaNumbering
|
|
458
|
-
?
|
|
336
|
+
? fina.selectedPremiseName
|
|
459
337
|
: undefined;
|
|
460
338
|
const activeDeviceNameForNumber = isFursActive
|
|
461
|
-
? selectedDeviceName
|
|
339
|
+
? furs.selectedDeviceName
|
|
462
340
|
: useFinaNumbering
|
|
463
|
-
?
|
|
341
|
+
? fina.selectedDeviceName
|
|
464
342
|
: undefined;
|
|
465
343
|
|
|
466
344
|
const { data: nextNumberData, isLoading: isNextNumberLoading } = useNextInvoiceNumber(entityId, {
|
|
467
345
|
business_premise_name: activePremiseName,
|
|
468
346
|
electronic_device_name: activeDeviceNameForNumber,
|
|
469
347
|
enabled:
|
|
470
|
-
!!entityId && !
|
|
348
|
+
!!entityId && !furs.isLoading && furs.isSelectionReady && !fina.isLoading && fina.isSelectionReady && !isEditMode,
|
|
471
349
|
});
|
|
472
350
|
|
|
473
351
|
// Overall loading state - wait until we have FURS/FINA data, selection ready, and next number (only in create mode)
|
|
474
352
|
const isFormDataLoading = isEditMode
|
|
475
353
|
? false // In edit mode, don't wait for next number
|
|
476
|
-
:
|
|
354
|
+
: furs.isLoading || !furs.isSelectionReady || fina.isLoading || !fina.isSelectionReady || isNextNumberLoading;
|
|
477
355
|
|
|
478
356
|
// Update header action with FURS and e-SLOG toggle buttons
|
|
479
357
|
useEffect(() => {
|
|
480
358
|
if (!onHeaderActionChange) return;
|
|
481
359
|
|
|
482
360
|
// Don't set header action while loading or in edit mode (FURS/FINA/e-SLOG not editable)
|
|
483
|
-
if (
|
|
361
|
+
if (furs.isLoading || fina.isLoading || isEditMode) {
|
|
484
362
|
onHeaderActionChange(null);
|
|
485
363
|
return;
|
|
486
364
|
}
|
|
487
365
|
|
|
488
|
-
const showFursToggle =
|
|
489
|
-
const showEslogToggle =
|
|
366
|
+
const showFursToggle = furs.isEnabled && furs.hasPremises;
|
|
367
|
+
const showEslogToggle = eslog.isAvailable;
|
|
490
368
|
|
|
491
369
|
if (showFursToggle || showEslogToggle) {
|
|
492
370
|
const isFursChecked = !skipFiscalization;
|
|
493
|
-
const isEslogChecked =
|
|
371
|
+
const isEslogChecked = eslog.isEnabled === true;
|
|
494
372
|
|
|
495
373
|
onHeaderActionChange(
|
|
496
374
|
<div className="flex items-center gap-2">
|
|
@@ -504,7 +382,7 @@ export default function CreateInvoiceForm({
|
|
|
504
382
|
variant={isEslogChecked ? "outline" : "ghost"}
|
|
505
383
|
size="sm"
|
|
506
384
|
className={cn("h-8 cursor-pointer gap-2", !isEslogChecked && "text-muted-foreground")}
|
|
507
|
-
onClick={() =>
|
|
385
|
+
onClick={() => eslog.setEnabled(!eslog.isEnabled)}
|
|
508
386
|
>
|
|
509
387
|
<div
|
|
510
388
|
className={cn(
|
|
@@ -573,14 +451,15 @@ export default function CreateInvoiceForm({
|
|
|
573
451
|
onHeaderActionChange(null);
|
|
574
452
|
}
|
|
575
453
|
}, [
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
454
|
+
furs.isLoading,
|
|
455
|
+
fina.isLoading,
|
|
456
|
+
furs.isEnabled,
|
|
457
|
+
furs.hasPremises,
|
|
580
458
|
skipFiscalization,
|
|
581
459
|
canSkipFiscalization,
|
|
582
|
-
|
|
583
|
-
|
|
460
|
+
eslog.isAvailable,
|
|
461
|
+
eslog.isEnabled,
|
|
462
|
+
eslog.setEnabled,
|
|
584
463
|
isEditMode,
|
|
585
464
|
onHeaderActionChange,
|
|
586
465
|
t,
|
|
@@ -634,7 +513,7 @@ export default function CreateInvoiceForm({
|
|
|
634
513
|
if (isDomesticTransaction && customerHasTaxNumber && !customerIsEndConsumerWatch) {
|
|
635
514
|
return t("Domestic B2B invoicing in Croatia is not supported");
|
|
636
515
|
}
|
|
637
|
-
if (!
|
|
516
|
+
if (!fina.isEnabled) {
|
|
638
517
|
return t("FINA fiscalization must be enabled for domestic invoices");
|
|
639
518
|
}
|
|
640
519
|
return undefined;
|
|
@@ -668,20 +547,9 @@ export default function CreateInvoiceForm({
|
|
|
668
547
|
const { mutate: createInvoice, isPending: isCreatePending } = useCreateInvoice({
|
|
669
548
|
entityId,
|
|
670
549
|
onSuccess: (data) => {
|
|
671
|
-
// Save FURS
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
business_premise_name: selectedPremiseName,
|
|
675
|
-
electronic_device_name: selectedDeviceName,
|
|
676
|
-
});
|
|
677
|
-
}
|
|
678
|
-
// Save FINA combo to localStorage on successful creation
|
|
679
|
-
if (isFinaActive && selectedFinaBusinessPremiseName && selectedFinaElectronicDeviceName) {
|
|
680
|
-
setLastUsedFinaCombo(entityId, {
|
|
681
|
-
business_premise_name: selectedFinaBusinessPremiseName,
|
|
682
|
-
electronic_device_name: selectedFinaElectronicDeviceName,
|
|
683
|
-
});
|
|
684
|
-
}
|
|
550
|
+
// Save FURS/FINA combos to localStorage on successful creation
|
|
551
|
+
furs.saveCombo();
|
|
552
|
+
fina.saveCombo();
|
|
685
553
|
// Invalidate customers cache when a customer was created/linked
|
|
686
554
|
// This ensures the new customer appears in autocomplete for future documents
|
|
687
555
|
if (data.customer_id) {
|
|
@@ -715,13 +583,13 @@ export default function CreateInvoiceForm({
|
|
|
715
583
|
if (finaValidationError) return;
|
|
716
584
|
|
|
717
585
|
// Skip e-SLOG validation for drafts and edit mode
|
|
718
|
-
if (!isDraft && !isEditMode &&
|
|
586
|
+
if (!isDraft && !isEditMode && eslog.isEnabled) {
|
|
719
587
|
const validationErrors = validateEslogForm(values as any, activeEntity);
|
|
720
588
|
|
|
721
589
|
if (validationErrors.length > 0) {
|
|
722
590
|
const entityErrors = getEntityErrors(validationErrors);
|
|
723
591
|
const formErrors = getFormFieldErrors(validationErrors);
|
|
724
|
-
|
|
592
|
+
eslog.setEntityErrors(entityErrors);
|
|
725
593
|
for (const error of formErrors) {
|
|
726
594
|
form.setError(error.field as any, {
|
|
727
595
|
type: "eslog",
|
|
@@ -730,38 +598,36 @@ export default function CreateInvoiceForm({
|
|
|
730
598
|
}
|
|
731
599
|
return;
|
|
732
600
|
}
|
|
733
|
-
|
|
601
|
+
eslog.setEntityErrors([]);
|
|
734
602
|
}
|
|
735
603
|
|
|
736
604
|
// Build FURS options (skip for drafts and edit mode)
|
|
737
|
-
const fursOptions =
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
605
|
+
const fursOptions = buildFursOptions({
|
|
606
|
+
isDraft,
|
|
607
|
+
isEnabled: furs.isEnabled,
|
|
608
|
+
isEditMode,
|
|
609
|
+
skipFiscalization,
|
|
610
|
+
premiseName: furs.selectedPremiseName,
|
|
611
|
+
deviceName: furs.selectedDeviceName,
|
|
612
|
+
});
|
|
745
613
|
|
|
746
614
|
// Build FINA options (skip for drafts, edit mode, and non-domestic transactions)
|
|
747
|
-
const finaOptions =
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
electronic_device_name: selectedFinaElectronicDeviceName,
|
|
756
|
-
payment_type: paymentTypes[0],
|
|
757
|
-
}
|
|
758
|
-
: undefined;
|
|
615
|
+
const finaOptions = buildFinaOptions({
|
|
616
|
+
isDraft,
|
|
617
|
+
useFinaNumbering,
|
|
618
|
+
isEditMode,
|
|
619
|
+
premiseName: fina.selectedPremiseName,
|
|
620
|
+
deviceName: fina.selectedDeviceName,
|
|
621
|
+
paymentType: paymentTypes[0],
|
|
622
|
+
});
|
|
759
623
|
|
|
760
624
|
// Build e-SLOG options (skip for drafts and edit mode)
|
|
761
|
-
const eslogOptions =
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
625
|
+
const eslogOptions = buildEslogOptions({
|
|
626
|
+
isDraft,
|
|
627
|
+
isEditMode,
|
|
628
|
+
isAvailable: eslog.isAvailable,
|
|
629
|
+
isEnabled: eslog.isEnabled,
|
|
630
|
+
});
|
|
765
631
|
|
|
766
632
|
const payload = prepareInvoiceSubmission(values as any, {
|
|
767
633
|
originalCustomer,
|
|
@@ -794,21 +660,17 @@ export default function CreateInvoiceForm({
|
|
|
794
660
|
createInvoice,
|
|
795
661
|
updateInvoice,
|
|
796
662
|
documentId,
|
|
797
|
-
|
|
663
|
+
eslog,
|
|
798
664
|
finaValidationError,
|
|
665
|
+
fina,
|
|
799
666
|
forceLinkedDocuments,
|
|
800
667
|
form,
|
|
668
|
+
furs,
|
|
801
669
|
isEditMode,
|
|
802
|
-
isEslogAvailable,
|
|
803
|
-
isFursEnabled,
|
|
804
670
|
useFinaNumbering,
|
|
805
671
|
markAsPaid,
|
|
806
672
|
originalCustomer,
|
|
807
673
|
paymentTypes,
|
|
808
|
-
selectedDeviceName,
|
|
809
|
-
selectedPremiseName,
|
|
810
|
-
selectedFinaBusinessPremiseName,
|
|
811
|
-
selectedFinaElectronicDeviceName,
|
|
812
674
|
showCustomerForm,
|
|
813
675
|
skipFiscalization,
|
|
814
676
|
],
|
|
@@ -858,6 +720,24 @@ export default function CreateInvoiceForm({
|
|
|
858
720
|
|
|
859
721
|
// Track if initial setup has been done
|
|
860
722
|
const initialSetupDoneRef = useRef(false);
|
|
723
|
+
const hasInitialValues = !!initialValues;
|
|
724
|
+
const duplicateHydrationStartedAtRef = useRef<number | null>(hasInitialValues ? performance.now() : null);
|
|
725
|
+
const duplicateHydrationLoggedRef = useRef(false);
|
|
726
|
+
const appliedInitialValuesSignatureRef = useRef<string | null>(null);
|
|
727
|
+
|
|
728
|
+
useEffect(() => {
|
|
729
|
+
if (!hasInitialValues) return;
|
|
730
|
+
|
|
731
|
+
const nextSignature = JSON.stringify(formDefaultValues);
|
|
732
|
+
if (appliedInitialValuesSignatureRef.current === nextSignature) return;
|
|
733
|
+
|
|
734
|
+
appliedInitialValuesSignatureRef.current = nextSignature;
|
|
735
|
+
initialSetupDoneRef.current = false;
|
|
736
|
+
duplicateHydrationStartedAtRef.current = performance.now();
|
|
737
|
+
duplicateHydrationLoggedRef.current = false;
|
|
738
|
+
form.reset(formDefaultValues);
|
|
739
|
+
priceModesRef.current = initialPriceModes;
|
|
740
|
+
}, [form, formDefaultValues, hasInitialValues, initialPriceModes]);
|
|
861
741
|
|
|
862
742
|
// Set default note and payment terms from entity settings when entity data is available
|
|
863
743
|
// This handles the case where activeEntity loads asynchronously
|
|
@@ -901,7 +781,15 @@ export default function CreateInvoiceForm({
|
|
|
901
781
|
}
|
|
902
782
|
|
|
903
783
|
initialSetupDoneRef.current = true;
|
|
904
|
-
|
|
784
|
+
if (hasInitialValues && duplicateHydrationStartedAtRef.current && !duplicateHydrationLoggedRef.current) {
|
|
785
|
+
duplicateHydrationLoggedRef.current = true;
|
|
786
|
+
emitInvoiceCreateDebug({
|
|
787
|
+
stage: "entity_defaults_applied",
|
|
788
|
+
hasInitialValues: true,
|
|
789
|
+
elapsedMs: Number((performance.now() - duplicateHydrationStartedAtRef.current).toFixed(1)),
|
|
790
|
+
});
|
|
791
|
+
}
|
|
792
|
+
}, [activeEntity, form, hasInitialValues, isEditMode, initialValues]);
|
|
905
793
|
|
|
906
794
|
// Recalculate due date when document date changes (skip in edit mode and custom due days)
|
|
907
795
|
const prevDateRef = useRef(form.getValues("date"));
|
|
@@ -916,48 +804,119 @@ export default function CreateInvoiceForm({
|
|
|
916
804
|
|
|
917
805
|
// Use form.watch subscription for onChange callback (avoids re-render loops)
|
|
918
806
|
const prevPayloadRef = useRef<string>("");
|
|
807
|
+
const initialPreviewTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
808
|
+
const hasEmittedSettledInitialPreviewRef = useRef(!hasInitialValues);
|
|
809
|
+
const pendingInitialPreviewPayloadRef = useRef<InvoicePreviewPayload | null>(null);
|
|
810
|
+
|
|
811
|
+
useEffect(() => {
|
|
812
|
+
if (!hasInitialValues) return;
|
|
813
|
+
|
|
814
|
+
hasEmittedSettledInitialPreviewRef.current = false;
|
|
815
|
+
prevPayloadRef.current = "";
|
|
816
|
+
pendingInitialPreviewPayloadRef.current = null;
|
|
817
|
+
if (initialPreviewTimeoutRef.current) {
|
|
818
|
+
clearTimeout(initialPreviewTimeoutRef.current);
|
|
819
|
+
initialPreviewTimeoutRef.current = null;
|
|
820
|
+
}
|
|
821
|
+
}, [hasInitialValues]);
|
|
822
|
+
|
|
823
|
+
const buildPreviewPayload = useCallback((formValues: any): InvoicePreviewPayload => {
|
|
824
|
+
const currentItems = formValues.items || [];
|
|
825
|
+
const transformedItems = currentItems.map((item: any, index: number) => {
|
|
826
|
+
const { price, ...rest } = item;
|
|
827
|
+
const isGross = priceModesRef.current[index] ?? false;
|
|
828
|
+
return isGross ? { ...rest, gross_price: price } : { ...rest, price };
|
|
829
|
+
});
|
|
830
|
+
return {
|
|
831
|
+
number: formValues.number,
|
|
832
|
+
date: formValues.date,
|
|
833
|
+
date_service: formValues.date_service,
|
|
834
|
+
date_service_to: formValues.date_service_to,
|
|
835
|
+
customer_id: formValues.customer_id,
|
|
836
|
+
customer: formValues.customer,
|
|
837
|
+
items: transformedItems,
|
|
838
|
+
currency_code: formValues.currency_code,
|
|
839
|
+
reference: formValues.reference,
|
|
840
|
+
note: formValues.note,
|
|
841
|
+
payment_terms: formValues.payment_terms,
|
|
842
|
+
signature: formValues.signature,
|
|
843
|
+
};
|
|
844
|
+
}, []);
|
|
845
|
+
|
|
846
|
+
const emitCurrentPreviewPayload = useCallback(() => {
|
|
847
|
+
if (!onChange) return;
|
|
848
|
+
onChange(buildPreviewPayload(form.getValues()));
|
|
849
|
+
}, [buildPreviewPayload, form, onChange]);
|
|
919
850
|
|
|
920
851
|
useEffect(() => {
|
|
921
852
|
if (!onChange) return;
|
|
922
853
|
|
|
923
|
-
const
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
854
|
+
const emitSettledInitialPreview = (payload: InvoicePreviewPayload) => {
|
|
855
|
+
pendingInitialPreviewPayloadRef.current = payload;
|
|
856
|
+
|
|
857
|
+
if (initialPreviewTimeoutRef.current) return;
|
|
858
|
+
|
|
859
|
+
const hydrationStartedAt = duplicateHydrationStartedAtRef.current ?? performance.now();
|
|
860
|
+
const elapsedMs = performance.now() - hydrationStartedAt;
|
|
861
|
+
const delayMs = Math.max(DUPLICATE_PREVIEW_SETTLE_MS, DUPLICATE_PREVIEW_MIN_DELAY_MS - elapsedMs);
|
|
862
|
+
initialPreviewTimeoutRef.current = setTimeout(() => {
|
|
863
|
+
initialPreviewTimeoutRef.current = null;
|
|
864
|
+
hasEmittedSettledInitialPreviewRef.current = true;
|
|
865
|
+
const settledPayload = pendingInitialPreviewPayloadRef.current ?? payload;
|
|
866
|
+
if (hasInitialValues && duplicateHydrationStartedAtRef.current && !duplicateHydrationLoggedRef.current) {
|
|
867
|
+
duplicateHydrationLoggedRef.current = true;
|
|
868
|
+
emitInvoiceCreateDebug({
|
|
869
|
+
stage: "initial_payload_emitted",
|
|
870
|
+
hasInitialValues: true,
|
|
871
|
+
itemCount: settledPayload.items?.length ?? 0,
|
|
872
|
+
elapsedMs: Number((performance.now() - duplicateHydrationStartedAtRef.current).toFixed(1)),
|
|
873
|
+
});
|
|
874
|
+
}
|
|
875
|
+
onChange(settledPayload);
|
|
876
|
+
pendingInitialPreviewPayloadRef.current = null;
|
|
877
|
+
}, delayMs);
|
|
942
878
|
};
|
|
943
879
|
|
|
944
880
|
// Initial call
|
|
945
|
-
const initialPayload =
|
|
881
|
+
const initialPayload = buildPreviewPayload(form.getValues());
|
|
946
882
|
prevPayloadRef.current = JSON.stringify(initialPayload);
|
|
947
|
-
|
|
883
|
+
if (hasInitialValues) {
|
|
884
|
+
emitSettledInitialPreview(initialPayload);
|
|
885
|
+
} else {
|
|
886
|
+
onChange(initialPayload);
|
|
887
|
+
}
|
|
948
888
|
|
|
949
889
|
// Subscribe to changes
|
|
950
890
|
const subscription = form.watch((formValues) => {
|
|
951
|
-
const payload =
|
|
891
|
+
const payload = buildPreviewPayload(formValues);
|
|
952
892
|
const payloadStr = JSON.stringify(payload);
|
|
953
893
|
if (payloadStr !== prevPayloadRef.current) {
|
|
954
894
|
prevPayloadRef.current = payloadStr;
|
|
955
|
-
|
|
895
|
+
if (hasInitialValues && duplicateHydrationStartedAtRef.current) {
|
|
896
|
+
emitInvoiceCreateDebug({
|
|
897
|
+
stage: "payload_changed",
|
|
898
|
+
hasInitialValues: true,
|
|
899
|
+
itemCount: payload.items?.length ?? 0,
|
|
900
|
+
elapsedMs: Number((performance.now() - duplicateHydrationStartedAtRef.current).toFixed(1)),
|
|
901
|
+
});
|
|
902
|
+
}
|
|
903
|
+
if (hasInitialValues && !hasEmittedSettledInitialPreviewRef.current) {
|
|
904
|
+
emitSettledInitialPreview(payload);
|
|
905
|
+
} else {
|
|
906
|
+
onChange(payload);
|
|
907
|
+
}
|
|
956
908
|
}
|
|
957
909
|
});
|
|
958
910
|
|
|
959
|
-
return () =>
|
|
960
|
-
|
|
911
|
+
return () => {
|
|
912
|
+
subscription.unsubscribe();
|
|
913
|
+
if (initialPreviewTimeoutRef.current) {
|
|
914
|
+
clearTimeout(initialPreviewTimeoutRef.current);
|
|
915
|
+
initialPreviewTimeoutRef.current = null;
|
|
916
|
+
}
|
|
917
|
+
pendingInitialPreviewPayloadRef.current = null;
|
|
918
|
+
};
|
|
919
|
+
}, [buildPreviewPayload, form, hasInitialValues, onChange]);
|
|
961
920
|
|
|
962
921
|
const onSubmit = (values: CreateInvoiceFormValues) => {
|
|
963
922
|
submitInvoice(values, false);
|
|
@@ -1040,14 +999,14 @@ export default function CreateInvoiceForm({
|
|
|
1040
999
|
)}
|
|
1041
1000
|
|
|
1042
1001
|
{/* e-SLOG entity-level validation errors */}
|
|
1043
|
-
{
|
|
1002
|
+
{eslog.entityErrors.length > 0 && (
|
|
1044
1003
|
<Alert variant="destructive">
|
|
1045
1004
|
<AlertCircle className="h-4 w-4" />
|
|
1046
1005
|
<AlertTitle>{t("e-SLOG Validation Failed")}</AlertTitle>
|
|
1047
1006
|
<AlertDescription>
|
|
1048
1007
|
<p className="mb-2">{t("The following entity settings need to be updated:")}</p>
|
|
1049
1008
|
<ul className="list-disc space-y-1 pl-4">
|
|
1050
|
-
{
|
|
1009
|
+
{eslog.entityErrors.map((error) => (
|
|
1051
1010
|
<li key={error.field} className="text-sm">
|
|
1052
1011
|
{error.message}
|
|
1053
1012
|
</li>
|
|
@@ -1076,14 +1035,20 @@ export default function CreateInvoiceForm({
|
|
|
1076
1035
|
t={t}
|
|
1077
1036
|
fursInline={
|
|
1078
1037
|
// Hide FURS selector in edit mode - fiscalization is set at creation only
|
|
1079
|
-
!isEditMode &&
|
|
1038
|
+
!isEditMode && furs.isEnabled && furs.hasPremises
|
|
1080
1039
|
? {
|
|
1081
|
-
premises: activePremises.map((p) => ({
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1040
|
+
premises: furs.activePremises.map((p) => ({
|
|
1041
|
+
id: p.id,
|
|
1042
|
+
business_premise_name: p.business_premise_name,
|
|
1043
|
+
})),
|
|
1044
|
+
devices: furs.activeDevices.map((d: any) => ({
|
|
1045
|
+
id: d.id,
|
|
1046
|
+
electronic_device_name: d.electronic_device_name,
|
|
1047
|
+
})),
|
|
1048
|
+
selectedPremise: furs.selectedPremiseName,
|
|
1049
|
+
selectedDevice: furs.selectedDeviceName,
|
|
1050
|
+
onPremiseChange: furs.setSelectedPremiseName,
|
|
1051
|
+
onDeviceChange: furs.setSelectedDeviceName,
|
|
1087
1052
|
isSkipped: skipFiscalization,
|
|
1088
1053
|
}
|
|
1089
1054
|
: undefined
|
|
@@ -1091,18 +1056,18 @@ export default function CreateInvoiceForm({
|
|
|
1091
1056
|
finaInline={
|
|
1092
1057
|
!isEditMode && useFinaNumbering
|
|
1093
1058
|
? {
|
|
1094
|
-
premises:
|
|
1059
|
+
premises: fina.activePremises.map((p: any) => ({
|
|
1095
1060
|
id: p.id,
|
|
1096
1061
|
business_premise_name: p.business_premise_name,
|
|
1097
1062
|
})),
|
|
1098
|
-
devices:
|
|
1063
|
+
devices: fina.activeDevices.map((d: any) => ({
|
|
1099
1064
|
id: d.id,
|
|
1100
1065
|
electronic_device_name: d.electronic_device_name,
|
|
1101
1066
|
})),
|
|
1102
|
-
selectedPremise:
|
|
1103
|
-
selectedDevice:
|
|
1104
|
-
onPremiseChange:
|
|
1105
|
-
onDeviceChange:
|
|
1067
|
+
selectedPremise: fina.selectedPremiseName,
|
|
1068
|
+
selectedDevice: fina.selectedDeviceName,
|
|
1069
|
+
onPremiseChange: fina.setSelectedPremiseName,
|
|
1070
|
+
onDeviceChange: fina.setSelectedDeviceName,
|
|
1106
1071
|
}
|
|
1107
1072
|
: undefined
|
|
1108
1073
|
}
|
|
@@ -1124,7 +1089,7 @@ export default function CreateInvoiceForm({
|
|
|
1124
1089
|
paymentTypes={paymentTypes}
|
|
1125
1090
|
onPaymentTypesChange={setPaymentTypes}
|
|
1126
1091
|
t={t}
|
|
1127
|
-
alwaysShowPaymentType={!!
|
|
1092
|
+
alwaysShowPaymentType={!!fina.isActive && requiresFinaFiscalization}
|
|
1128
1093
|
/>
|
|
1129
1094
|
)}
|
|
1130
1095
|
</DocumentDetailsSection>
|
|
@@ -1146,6 +1111,7 @@ export default function CreateInvoiceForm({
|
|
|
1146
1111
|
maxTaxesPerItem={activeEntity?.country_rules?.max_taxes_per_item}
|
|
1147
1112
|
priceModesRef={priceModesRef}
|
|
1148
1113
|
initialPriceModes={initialPriceModes}
|
|
1114
|
+
onItemsStateChange={emitCurrentPreviewPayload}
|
|
1149
1115
|
/>
|
|
1150
1116
|
|
|
1151
1117
|
<DocumentNoteField
|