@spaceinvoices/react-ui 0.1.1
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/LICENSE +21 -0
- package/README.md +340 -0
- package/cli/dist/index.js +922 -0
- package/package.json +87 -0
- package/registry.json +600 -0
- package/spaceinvoices.schema.json +47 -0
- package/src/app.tsx +25 -0
- package/src/common/autocomplete.tsx +135 -0
- package/src/components/activities/activity-timeline.tsx +160 -0
- package/src/components/activities/index.ts +1 -0
- package/src/components/activities/locales/de.ts +30 -0
- package/src/components/activities/locales/sl.ts +30 -0
- package/src/components/advance-invoices/advance-invoices.hooks.ts +75 -0
- package/src/components/advance-invoices/create/create-advance-invoice-form.tsx +702 -0
- package/src/components/advance-invoices/create/locales/de.ts +29 -0
- package/src/components/advance-invoices/create/locales/sl.ts +25 -0
- package/src/components/advance-invoices/create/prepare-advance-invoice-submission.ts +74 -0
- package/src/components/advance-invoices/index.ts +5 -0
- package/src/components/advance-invoices/list/index.ts +3 -0
- package/src/components/advance-invoices/list/list-row-actions.tsx +119 -0
- package/src/components/advance-invoices/list/list-table.tsx +178 -0
- package/src/components/advance-invoices/list/locales/de.ts +32 -0
- package/src/components/advance-invoices/list/locales/sl.ts +32 -0
- package/src/components/advance-invoices/list/use-advance-invoice-download.ts +63 -0
- package/src/components/button-loader.tsx +11 -0
- package/src/components/combobox.tsx +96 -0
- package/src/components/company-registry/company-registry-autocomplete.tsx +151 -0
- package/src/components/company-registry/company-registry.hooks.ts +67 -0
- package/src/components/company-registry/index.ts +7 -0
- package/src/components/credit-notes/create/create-credit-note-form.tsx +332 -0
- package/src/components/credit-notes/create/index.ts +1 -0
- package/src/components/credit-notes/create/locales/de.ts +69 -0
- package/src/components/credit-notes/create/locales/sl.ts +67 -0
- package/src/components/credit-notes/credit-notes.hooks.ts +22 -0
- package/src/components/credit-notes/index.ts +10 -0
- package/src/components/credit-notes/list/index.ts +3 -0
- package/src/components/credit-notes/list/list-row-actions.tsx +116 -0
- package/src/components/credit-notes/list/list-table.tsx +183 -0
- package/src/components/credit-notes/list/locales/de.ts +33 -0
- package/src/components/credit-notes/list/locales/sl.ts +33 -0
- package/src/components/credit-notes/list/use-credit-note-download.ts +65 -0
- package/src/components/customers/create-customer-form/create-customer-form.tsx +134 -0
- package/src/components/customers/create-customer-form/locales/de.ts +20 -0
- package/src/components/customers/create-customer-form/locales/sl.ts +20 -0
- package/src/components/customers/customer-autocomplete.tsx +173 -0
- package/src/components/customers/customer-combobox.tsx +130 -0
- package/src/components/customers/customer-list-table/customer-list-row-actions.tsx +48 -0
- package/src/components/customers/customer-list-table/customer-list-table.tsx +124 -0
- package/src/components/customers/customer-list-table/index.ts +2 -0
- package/src/components/customers/customer-list-table/locales/de.ts +16 -0
- package/src/components/customers/customer-list-table/locales/sl.ts +16 -0
- package/src/components/customers/customers.hooks.test.ts +348 -0
- package/src/components/customers/customers.hooks.ts +57 -0
- package/src/components/customers/index.ts +5 -0
- package/src/components/dashboard/chart-empty-state.tsx +29 -0
- package/src/components/dashboard/collection-rate-card/collection-rate-card.tsx +80 -0
- package/src/components/dashboard/collection-rate-card/index.ts +4 -0
- package/src/components/dashboard/collection-rate-card/locales/sl.ts +3 -0
- package/src/components/dashboard/collection-rate-card/use-collection-rate.ts +74 -0
- package/src/components/dashboard/index.ts +54 -0
- package/src/components/dashboard/invoice-status-chart/index.ts +4 -0
- package/src/components/dashboard/invoice-status-chart/invoice-status-chart.tsx +130 -0
- package/src/components/dashboard/invoice-status-chart/locales/sl.ts +9 -0
- package/src/components/dashboard/invoice-status-chart/use-invoice-status.ts +105 -0
- package/src/components/dashboard/loading-card.tsx +19 -0
- package/src/components/dashboard/payment-methods-chart/index.ts +4 -0
- package/src/components/dashboard/payment-methods-chart/locales/sl.ts +12 -0
- package/src/components/dashboard/payment-methods-chart/payment-methods-chart.tsx +152 -0
- package/src/components/dashboard/payment-methods-chart/use-payment-methods.ts +50 -0
- package/src/components/dashboard/payment-trend-chart/index.ts +4 -0
- package/src/components/dashboard/payment-trend-chart/locales/sl.ts +5 -0
- package/src/components/dashboard/payment-trend-chart/payment-trend-chart.tsx +137 -0
- package/src/components/dashboard/payment-trend-chart/use-payment-trend.ts +92 -0
- package/src/components/dashboard/revenue-card.tsx +49 -0
- package/src/components/dashboard/revenue-trend-chart/index.ts +4 -0
- package/src/components/dashboard/revenue-trend-chart/locales/sl.ts +5 -0
- package/src/components/dashboard/revenue-trend-chart/revenue-trend-chart.tsx +137 -0
- package/src/components/dashboard/revenue-trend-chart/use-revenue-trend.ts +93 -0
- package/src/components/dashboard/shared/index.ts +5 -0
- package/src/components/dashboard/shared/use-revenue-data.ts +160 -0
- package/src/components/dashboard/shared/use-stats-counts.ts +89 -0
- package/src/components/dashboard/shared/use-stats-query.ts +38 -0
- package/src/components/dashboard/stat-card.tsx +41 -0
- package/src/components/dashboard/tax-collected-card/index.ts +2 -0
- package/src/components/dashboard/tax-collected-card/tax-collected-card.tsx +77 -0
- package/src/components/dashboard/tax-collected-card/use-tax-collected.ts +145 -0
- package/src/components/dashboard/top-customers-chart/index.ts +4 -0
- package/src/components/dashboard/top-customers-chart/locales/sl.ts +5 -0
- package/src/components/dashboard/top-customers-chart/top-customers-chart.tsx +130 -0
- package/src/components/dashboard/top-customers-chart/use-top-customers.ts +72 -0
- package/src/components/documents/create/document-add-item-form.tsx +379 -0
- package/src/components/documents/create/document-add-item-tax-rate-field.tsx +120 -0
- package/src/components/documents/create/document-details-section.tsx +597 -0
- package/src/components/documents/create/document-items-section.tsx +133 -0
- package/src/components/documents/create/document-recipient-section.tsx +101 -0
- package/src/components/documents/create/form-types.ts +36 -0
- package/src/components/documents/create/index.ts +9 -0
- package/src/components/documents/create/live-preview.tsx +235 -0
- package/src/components/documents/create/mark-as-paid-section.tsx +82 -0
- package/src/components/documents/create/prepare-document-submission.test.ts +132 -0
- package/src/components/documents/create/prepare-document-submission.ts +187 -0
- package/src/components/documents/create/prepare-preview-data.test.ts +155 -0
- package/src/components/documents/create/prepare-preview-data.ts +16 -0
- package/src/components/documents/create/smart-code-insert-button.tsx +139 -0
- package/src/components/documents/create/use-document-customer-form.ts +161 -0
- package/src/components/documents/document-preview.tsx +13 -0
- package/src/components/documents/documents.hooks.ts +146 -0
- package/src/components/documents/index.ts +23 -0
- package/src/components/documents/shared/document-preview-display.tsx +172 -0
- package/src/components/documents/shared/index.ts +3 -0
- package/src/components/documents/shared/scaled-document-preview.tsx +70 -0
- package/src/components/documents/shared/use-a4-scaling.ts +62 -0
- package/src/components/documents/types.ts +61 -0
- package/src/components/documents/view/document-actions-bar.tsx +328 -0
- package/src/components/documents/view/document-details-card.tsx +179 -0
- package/src/components/documents/view/document-payments-list.tsx +256 -0
- package/src/components/documents/view/index.ts +4 -0
- package/src/components/documents/view/locales/de.ts +85 -0
- package/src/components/documents/view/locales/sl.ts +84 -0
- package/src/components/documents/view/use-document-download.ts +125 -0
- package/src/components/entities/create-entity-form.tsx +105 -0
- package/src/components/entities/entities.hooks.ts +50 -0
- package/src/components/entities/entity-settings-form/email-template-variables-info.tsx +103 -0
- package/src/components/entities/entity-settings-form/entity-settings-form.tsx +1326 -0
- package/src/components/entities/entity-settings-form/image-upload-with-crop.tsx +222 -0
- package/src/components/entities/entity-settings-form/index.ts +2 -0
- package/src/components/entities/entity-settings-form/input-with-preview.tsx +190 -0
- package/src/components/entities/entity-settings-form/locales/de.ts +192 -0
- package/src/components/entities/entity-settings-form/locales/sl.ts +188 -0
- package/src/components/entities/furs-settings-form/furs-settings-form.tsx +410 -0
- package/src/components/entities/furs-settings-form/furs-settings.hooks.ts +320 -0
- package/src/components/entities/furs-settings-form/index.ts +3 -0
- package/src/components/entities/furs-settings-form/locales/de.ts +233 -0
- package/src/components/entities/furs-settings-form/locales/en.ts +194 -0
- package/src/components/entities/furs-settings-form/locales/sl.ts +196 -0
- package/src/components/entities/furs-settings-form/sections/certificate-settings-section.tsx +242 -0
- package/src/components/entities/furs-settings-form/sections/enable-fiscalization-section.tsx +139 -0
- package/src/components/entities/furs-settings-form/sections/general-settings-section.tsx +252 -0
- package/src/components/entities/furs-settings-form/sections/premises-management-section.tsx +370 -0
- package/src/components/entities/furs-settings-form/sections/register-premise-dialog.tsx +420 -0
- package/src/components/entities/keys.ts +2 -0
- package/src/components/entities/settings/branding-settings-form.tsx +274 -0
- package/src/components/entities/settings/company-settings-form.tsx +256 -0
- package/src/components/entities/settings/defaults-settings-form.tsx +501 -0
- package/src/components/entities/settings/email-settings-form.tsx +288 -0
- package/src/components/entities/settings/eslog-settings-form.tsx +113 -0
- package/src/components/entities/settings/index.ts +8 -0
- package/src/components/entities/settings/number-format-settings-form.tsx +244 -0
- package/src/components/entities/settings/pdf-template-selector/demo-invoice-data.ts +164 -0
- package/src/components/entities/settings/pdf-template-selector/index.ts +2 -0
- package/src/components/entities/settings/pdf-template-selector/locales/de.ts +18 -0
- package/src/components/entities/settings/pdf-template-selector/locales/sl.ts +18 -0
- package/src/components/entities/settings/pdf-template-selector/pdf-template-cards.tsx +49 -0
- package/src/components/entities/settings/settings-footer.tsx +16 -0
- package/src/components/entities/settings/tax-rules-settings-form.tsx +346 -0
- package/src/components/estimates/create/create-estimate-form.tsx +384 -0
- package/src/components/estimates/create/locales/de.ts +64 -0
- package/src/components/estimates/create/locales/sl.ts +63 -0
- package/src/components/estimates/create/prepare-estimate-submission.ts +39 -0
- package/src/components/estimates/create/use-estimate-customer-form.ts +5 -0
- package/src/components/estimates/estimates.hooks.ts +15 -0
- package/src/components/estimates/index.ts +6 -0
- package/src/components/estimates/list/index.ts +3 -0
- package/src/components/estimates/list/list-row-actions.tsx +103 -0
- package/src/components/estimates/list/list-table.tsx +171 -0
- package/src/components/estimates/list/locales/de.ts +26 -0
- package/src/components/estimates/list/locales/sl.ts +26 -0
- package/src/components/estimates/list/use-estimate-download.ts +63 -0
- package/src/components/export/document-export-form.tsx +288 -0
- package/src/components/export/index.ts +2 -0
- package/src/components/form/form-input.tsx +89 -0
- package/src/components/form/index.ts +1 -0
- package/src/components/invoices/create/create-invoice-form.tsx +852 -0
- package/src/components/invoices/create/eslog-validation.test.ts +242 -0
- package/src/components/invoices/create/eslog-validation.ts +208 -0
- package/src/components/invoices/create/locales/de.ts +118 -0
- package/src/components/invoices/create/locales/sl.ts +114 -0
- package/src/components/invoices/create/prepare-invoice-submission.test.ts +777 -0
- package/src/components/invoices/create/prepare-invoice-submission.ts +79 -0
- package/src/components/invoices/create/use-invoice-customer-form.ts +5 -0
- package/src/components/invoices/index.ts +9 -0
- package/src/components/invoices/invoices-furs.hooks.ts +28 -0
- package/src/components/invoices/invoices.hooks.ts +110 -0
- package/src/components/invoices/list/index.ts +3 -0
- package/src/components/invoices/list/list-row-actions.tsx +132 -0
- package/src/components/invoices/list/list-table.tsx +165 -0
- package/src/components/invoices/list/locales/de.ts +33 -0
- package/src/components/invoices/list/locales/sl.ts +33 -0
- package/src/components/invoices/list/use-invoice-download.ts +62 -0
- package/src/components/invoices/send-email-dialog/index.ts +1 -0
- package/src/components/invoices/send-email-dialog/locales/de.ts +18 -0
- package/src/components/invoices/send-email-dialog/locales/sl.ts +17 -0
- package/src/components/invoices/send-email-dialog/send-email-dialog.tsx +289 -0
- package/src/components/invoices/send-email-dialog.tsx +2 -0
- package/src/components/invoices/shared/index.ts +2 -0
- package/src/components/invoices/shared/scaled-document-preview.tsx +32 -0
- package/src/components/invoices/shared/use-a4-scaling.tsx +39 -0
- package/src/components/invoices/view/eslog-info-display.tsx +160 -0
- package/src/components/invoices/view/furs-info-display.tsx +213 -0
- package/src/components/items/create-item-form/create-item-form.tsx +155 -0
- package/src/components/items/create-item-form/locales/de.ts +14 -0
- package/src/components/items/create-item-form/locales/en.ts +9 -0
- package/src/components/items/create-item-form/locales/sl.ts +14 -0
- package/src/components/items/item-combobox.tsx +147 -0
- package/src/components/items/item-list-table/item-list-header.tsx +33 -0
- package/src/components/items/item-list-table/item-list-row-actions.tsx +48 -0
- package/src/components/items/item-list-table/item-list-row.tsx +32 -0
- package/src/components/items/item-list-table/item-list-table.tsx +76 -0
- package/src/components/items/item-list-table/locales/de.ts +10 -0
- package/src/components/items/item-list-table/locales/en.ts +10 -0
- package/src/components/items/item-list-table/locales/sl.ts +10 -0
- package/src/components/items/items.hooks.ts +63 -0
- package/src/components/loading-spinner.tsx +24 -0
- package/src/components/payments/create-payment-form/create-payment-form.tsx +222 -0
- package/src/components/payments/create-payment-form/locales/de.ts +20 -0
- package/src/components/payments/create-payment-form/locales/sl.ts +20 -0
- package/src/components/payments/edit-payment-form/edit-payment-form.tsx +230 -0
- package/src/components/payments/edit-payment-form/index.ts +1 -0
- package/src/components/payments/edit-payment-form/locales/de.ts +20 -0
- package/src/components/payments/edit-payment-form/locales/sl.ts +20 -0
- package/src/components/payments/index.ts +4 -0
- package/src/components/payments/list/index.ts +2 -0
- package/src/components/payments/list/list-row-actions.tsx +98 -0
- package/src/components/payments/list/list-table.tsx +186 -0
- package/src/components/payments/list/locales/de.ts +19 -0
- package/src/components/payments/list/locales/sl.ts +19 -0
- package/src/components/payments/payments.hooks.ts +15 -0
- package/src/components/request-logs/index.ts +3 -0
- package/src/components/request-logs/request-log-detail.tsx +242 -0
- package/src/components/request-logs/request-log-list-table.tsx +266 -0
- package/src/components/request-logs/request-logs-page.tsx +10 -0
- package/src/components/table/README.md +410 -0
- package/src/components/table/data-table.tsx +251 -0
- package/src/components/table/date-cell.tsx +35 -0
- package/src/components/table/filter-bar.tsx +114 -0
- package/src/components/table/filter-panel.tsx +407 -0
- package/src/components/table/hooks/use-table-fetch.ts +17 -0
- package/src/components/table/hooks/use-table-query.ts +36 -0
- package/src/components/table/hooks/use-table-state.ts +293 -0
- package/src/components/table/index.ts +35 -0
- package/src/components/table/search-input.tsx +85 -0
- package/src/components/table/sortable-header.tsx +56 -0
- package/src/components/table/table-empty-state.tsx +40 -0
- package/src/components/table/table-no-results.tsx +41 -0
- package/src/components/table/table-pagination.tsx +42 -0
- package/src/components/table/table-skeleton.tsx +54 -0
- package/src/components/table/types.ts +136 -0
- package/src/components/tax-reports/index.ts +1 -0
- package/src/components/tax-reports/kir-export-form.tsx +172 -0
- package/src/components/taxes/create-tax-form/create-tax-form.tsx +112 -0
- package/src/components/taxes/create-tax-form/locales/de.ts +8 -0
- package/src/components/taxes/create-tax-form/locales/en.ts +7 -0
- package/src/components/taxes/create-tax-form/locales/sl.ts +8 -0
- package/src/components/taxes/tax-list-table/locales/de.ts +11 -0
- package/src/components/taxes/tax-list-table/locales/en.ts +10 -0
- package/src/components/taxes/tax-list-table/locales/sl.ts +11 -0
- package/src/components/taxes/tax-list-table/tax-list-header.tsx +29 -0
- package/src/components/taxes/tax-list-table/tax-list-row-actions.tsx +43 -0
- package/src/components/taxes/tax-list-table/tax-list-row.tsx +46 -0
- package/src/components/taxes/tax-list-table/tax-list-table.tsx +59 -0
- package/src/components/taxes/taxes.hooks.ts +35 -0
- package/src/components/ui/alert-dialog.tsx +61 -0
- package/src/components/ui/alert.tsx +72 -0
- package/src/components/ui/badge.tsx +48 -0
- package/src/components/ui/breadcrumb.tsx +132 -0
- package/src/components/ui/button.tsx +61 -0
- package/src/components/ui/calendar.tsx +213 -0
- package/src/components/ui/card.tsx +94 -0
- package/src/components/ui/chart.tsx +380 -0
- package/src/components/ui/checkbox.tsx +27 -0
- package/src/components/ui/collapsible.tsx +56 -0
- package/src/components/ui/command.tsx +187 -0
- package/src/components/ui/dialog.tsx +187 -0
- package/src/components/ui/drawer.tsx +123 -0
- package/src/components/ui/dropdown-menu.tsx +291 -0
- package/src/components/ui/form.tsx +166 -0
- package/src/components/ui/input-group.tsx +149 -0
- package/src/components/ui/input.tsx +20 -0
- package/src/components/ui/label.tsx +18 -0
- package/src/components/ui/loading-spinner.tsx +16 -0
- package/src/components/ui/popover.tsx +108 -0
- package/src/components/ui/radio-group.tsx +37 -0
- package/src/components/ui/select.tsx +200 -0
- package/src/components/ui/separator.tsx +23 -0
- package/src/components/ui/sheet.tsx +145 -0
- package/src/components/ui/sidebar.tsx +771 -0
- package/src/components/ui/skeleton.tsx +13 -0
- package/src/components/ui/sonner.tsx +60 -0
- package/src/components/ui/spinner.tsx +10 -0
- package/src/components/ui/sticky-form-footer.tsx +55 -0
- package/src/components/ui/switch.tsx +30 -0
- package/src/components/ui/table.tsx +101 -0
- package/src/components/ui/tabs.tsx +80 -0
- package/src/components/ui/textarea.tsx +18 -0
- package/src/components/ui/tooltip.tsx +89 -0
- package/src/components/wl-subscription/index.ts +2 -0
- package/src/components/wl-subscription/locked-feature.tsx +173 -0
- package/src/components/wl-subscription/upgrade-modal.tsx +209 -0
- package/src/frontend.tsx +28 -0
- package/src/generate-schemas.ts +265 -0
- package/src/generated/schemas/advanceinvoice.ts +177 -0
- package/src/generated/schemas/creditnote.ts +187 -0
- package/src/generated/schemas/customer.ts +29 -0
- package/src/generated/schemas/entity.ts +252 -0
- package/src/generated/schemas/estimate.ts +159 -0
- package/src/generated/schemas/furssettings.ts +25 -0
- package/src/generated/schemas/index.ts +24 -0
- package/src/generated/schemas/invoice.ts +167 -0
- package/src/generated/schemas/item.ts +38 -0
- package/src/generated/schemas/payment.ts +44 -0
- package/src/generated/schemas/previewadvanceinvoice_body.ts +354 -0
- package/src/generated/schemas/previewestimate_body.ts +309 -0
- package/src/generated/schemas/registerfursmovablepremise_body.ts +22 -0
- package/src/generated/schemas/registerfursrealestatepremise_body.ts +32 -0
- package/src/generated/schemas/renderdocument_body.ts +594 -0
- package/src/generated/schemas/sendemail_body.ts +26 -0
- package/src/generated/schemas/startpdfexport_body.ts +20 -0
- package/src/generated/schemas/tax.ts +48 -0
- package/src/generated/schemas/uploadfile_body.ts +23 -0
- package/src/generated/schemas/uploadfurscertificate_body.ts +20 -0
- package/src/generated/schemas/userfurssettings.ts +19 -0
- package/src/hooks/create-resource-hooks.test.ts +483 -0
- package/src/hooks/create-resource-hooks.ts +300 -0
- package/src/hooks/use-debounce.ts +12 -0
- package/src/hooks/use-duplicate-document.ts +185 -0
- package/src/hooks/use-media-query.tsx +19 -0
- package/src/hooks/use-mobile.ts +39 -0
- package/src/hooks/use-next-document-number.ts +57 -0
- package/src/hooks/use-resource-mutation.ts +118 -0
- package/src/hooks/use-vies-check.ts +130 -0
- package/src/index.css +11 -0
- package/src/index.html +13 -0
- package/src/index.tsx +12 -0
- package/src/lib/auth.ts +4 -0
- package/src/lib/browser-cookies.ts +70 -0
- package/src/lib/constants.ts +287 -0
- package/src/lib/cookies.ts +36 -0
- package/src/lib/schemas/advance-invoice.ts +43 -0
- package/src/lib/schemas/credit-note.ts +32 -0
- package/src/lib/schemas/estimate.ts +31 -0
- package/src/lib/schemas/index.ts +18 -0
- package/src/lib/schemas/invoice.ts +43 -0
- package/src/lib/schemas/shared.ts +79 -0
- package/src/lib/translation.ts +38 -0
- package/src/lib/utils.ts +6 -0
- package/src/providers/entities-context.tsx +41 -0
- package/src/providers/entities-provider.tsx +201 -0
- package/src/providers/form-footer-context.tsx +72 -0
- package/src/providers/sdk-provider.tsx +164 -0
- package/src/providers/white-label-provider.tsx +91 -0
- package/src/providers/wl-subscription-provider.tsx +277 -0
- package/src/utils/string-helpers.ts +111 -0
|
@@ -0,0 +1,702 @@
|
|
|
1
|
+
import { zodResolver } from "@hookform/resolvers/zod";
|
|
2
|
+
import type { AdvanceInvoice, CreateAdvanceInvoiceRequest } from "@spaceinvoices/js-sdk";
|
|
3
|
+
import { AlertCircle, Check, FileCode2, X } from "lucide-react";
|
|
4
|
+
import type { ReactNode } from "react";
|
|
5
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
6
|
+
import type { Resolver } from "react-hook-form";
|
|
7
|
+
import { useForm, useWatch } from "react-hook-form";
|
|
8
|
+
import { Alert, AlertDescription, AlertTitle } from "@/ui/components/ui/alert";
|
|
9
|
+
import { Button } from "@/ui/components/ui/button";
|
|
10
|
+
import { Form } from "@/ui/components/ui/form";
|
|
11
|
+
import { Skeleton } from "@/ui/components/ui/skeleton";
|
|
12
|
+
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/ui/components/ui/tooltip";
|
|
13
|
+
import type { CreateAdvanceInvoiceSchema } from "@/ui/generated/schemas";
|
|
14
|
+
import { createAdvanceInvoiceSchema } from "@/ui/generated/schemas";
|
|
15
|
+
import { useNextDocumentNumber } from "@/ui/hooks/use-next-document-number";
|
|
16
|
+
import { useViesCheck } from "@/ui/hooks/use-vies-check";
|
|
17
|
+
import type { ComponentTranslationProps } from "@/ui/lib/translation";
|
|
18
|
+
import { createTranslation } from "@/ui/lib/translation";
|
|
19
|
+
import { cn } from "@/ui/lib/utils";
|
|
20
|
+
import { useEntities } from "@/ui/providers/entities-context";
|
|
21
|
+
import { useFormFooterRegistration } from "@/ui/providers/form-footer-context";
|
|
22
|
+
import { DocumentDetailsSection, DocumentNoteField } from "../../documents/create/document-details-section";
|
|
23
|
+
import { DocumentItemsSection, type PriceModesMap } from "../../documents/create/document-items-section";
|
|
24
|
+
import { DocumentRecipientSection } from "../../documents/create/document-recipient-section";
|
|
25
|
+
import { MarkAsPaidSection } from "../../documents/create/mark-as-paid-section";
|
|
26
|
+
import { useDocumentCustomerForm } from "../../documents/create/use-document-customer-form";
|
|
27
|
+
import type { DocumentTypes } from "../../documents/types";
|
|
28
|
+
import { useFursPremises, useFursSettings } from "../../entities/furs-settings-form/furs-settings.hooks";
|
|
29
|
+
import { getEntityErrors, getFormFieldErrors, validateEslogForm } from "../../invoices/create/eslog-validation";
|
|
30
|
+
import { getLastUsedFursCombo, setLastUsedFursCombo, useCreateAdvanceInvoice } from "../advance-invoices.hooks";
|
|
31
|
+
import de from "./locales/de";
|
|
32
|
+
import sl from "./locales/sl";
|
|
33
|
+
import { prepareAdvanceInvoiceSubmission } from "./prepare-advance-invoice-submission";
|
|
34
|
+
|
|
35
|
+
const translations = {
|
|
36
|
+
sl,
|
|
37
|
+
de,
|
|
38
|
+
} as const;
|
|
39
|
+
|
|
40
|
+
// Form values: extend schema with local-only fields (number is for display, not sent to API)
|
|
41
|
+
type CreateAdvanceInvoiceFormValues = CreateAdvanceInvoiceSchema & {
|
|
42
|
+
number?: string;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
/** Preview payload extends request with display-only fields */
|
|
46
|
+
type AdvanceInvoicePreviewPayload = Partial<CreateAdvanceInvoiceRequest> & { number?: string };
|
|
47
|
+
|
|
48
|
+
type CreateAdvanceInvoiceFormProps = {
|
|
49
|
+
type: DocumentTypes;
|
|
50
|
+
entityId: string;
|
|
51
|
+
onSuccess?: (data: AdvanceInvoice) => void;
|
|
52
|
+
onError?: (error: unknown) => void;
|
|
53
|
+
onChange?: (data: AdvanceInvoicePreviewPayload) => void;
|
|
54
|
+
onAddNewTax?: () => void;
|
|
55
|
+
onHeaderActionChange?: (action: ReactNode) => void;
|
|
56
|
+
/** Initial values for form fields (used for document duplication) */
|
|
57
|
+
initialValues?: Partial<CreateAdvanceInvoiceRequest>;
|
|
58
|
+
} & ComponentTranslationProps;
|
|
59
|
+
|
|
60
|
+
export default function CreateAdvanceInvoiceForm({
|
|
61
|
+
type: _type,
|
|
62
|
+
entityId,
|
|
63
|
+
onSuccess,
|
|
64
|
+
onError,
|
|
65
|
+
onChange,
|
|
66
|
+
onAddNewTax,
|
|
67
|
+
onHeaderActionChange,
|
|
68
|
+
initialValues,
|
|
69
|
+
t: translateProp,
|
|
70
|
+
namespace,
|
|
71
|
+
locale,
|
|
72
|
+
}: CreateAdvanceInvoiceFormProps) {
|
|
73
|
+
const t = createTranslation({
|
|
74
|
+
t: translateProp,
|
|
75
|
+
namespace,
|
|
76
|
+
locale,
|
|
77
|
+
translations,
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
const { activeEntity } = useEntities();
|
|
81
|
+
|
|
82
|
+
// Get default note from entity settings (use invoice defaults)
|
|
83
|
+
// Note: Advance invoices don't have payment terms - they are documents requesting payment
|
|
84
|
+
const defaultNote = (activeEntity?.settings as any)?.default_invoice_note || "";
|
|
85
|
+
|
|
86
|
+
// ============================================================================
|
|
87
|
+
// FURS Settings & Premises
|
|
88
|
+
// ============================================================================
|
|
89
|
+
const { data: fursSettings, isLoading: isFursSettingsLoading } = useFursSettings(entityId);
|
|
90
|
+
const { data: fursPremises, isLoading: isFursPremisesLoading } = useFursPremises(entityId, {
|
|
91
|
+
enabled: fursSettings?.enabled === true,
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
// Loading state for FURS - don't render form until we know if FURS is active
|
|
95
|
+
const isFursLoading = isFursSettingsLoading || (fursSettings?.enabled && isFursPremisesLoading);
|
|
96
|
+
|
|
97
|
+
// Check if FURS is enabled and has active premises
|
|
98
|
+
const isFursEnabled = fursSettings?.enabled === true;
|
|
99
|
+
const activePremises = useMemo(() => fursPremises?.filter((p) => p.is_active) || [], [fursPremises]);
|
|
100
|
+
const hasFursPremises = activePremises.length > 0;
|
|
101
|
+
|
|
102
|
+
// FURS premise/device selection state
|
|
103
|
+
const [selectedPremiseName, setSelectedPremiseName] = useState<string | undefined>();
|
|
104
|
+
const [selectedDeviceName, setSelectedDeviceName] = useState<string | undefined>();
|
|
105
|
+
const [skipFiscalization, setSkipFiscalization] = useState(false);
|
|
106
|
+
|
|
107
|
+
// UI-only state (not part of API schema)
|
|
108
|
+
const [markAsPaid, setMarkAsPaid] = useState(false);
|
|
109
|
+
const [paymentType, setPaymentType] = useState("bank_transfer");
|
|
110
|
+
const [isDraftPending, setIsDraftPending] = useState(false);
|
|
111
|
+
|
|
112
|
+
// Price modes per item (gross vs net) - collected from component state at submit
|
|
113
|
+
const initialPriceModes = useMemo(() => {
|
|
114
|
+
if (!initialValues?.items) return {};
|
|
115
|
+
return initialValues.items.reduce((acc, item, index) => {
|
|
116
|
+
acc[index] = item.gross_price != null;
|
|
117
|
+
return acc;
|
|
118
|
+
}, {} as PriceModesMap);
|
|
119
|
+
}, [initialValues?.items]);
|
|
120
|
+
const priceModesRef = useRef<PriceModesMap>(initialPriceModes);
|
|
121
|
+
|
|
122
|
+
// ============================================================================
|
|
123
|
+
// e-SLOG Settings (Slovenian e-Invoice)
|
|
124
|
+
// ============================================================================
|
|
125
|
+
const isSlovenianEntity = activeEntity?.country_code === "SI";
|
|
126
|
+
const entityEslogEnabled = !!(activeEntity?.settings as any)?.eslog_validation_enabled;
|
|
127
|
+
const isEslogAvailable = isSlovenianEntity && entityEslogEnabled;
|
|
128
|
+
|
|
129
|
+
// e-SLOG validation state - defaults to entity setting
|
|
130
|
+
const [eslogValidationEnabled, setEslogValidationEnabled] = useState<boolean | undefined>(undefined);
|
|
131
|
+
// e-SLOG entity-level errors (require settings update, can't be fixed in form)
|
|
132
|
+
const [eslogEntityErrors, setEslogEntityErrors] = useState<Array<{ field: string; message: string }>>([]);
|
|
133
|
+
|
|
134
|
+
// Initialize e-SLOG state from entity settings
|
|
135
|
+
useEffect(() => {
|
|
136
|
+
if (isEslogAvailable && eslogValidationEnabled === undefined) {
|
|
137
|
+
setEslogValidationEnabled(true);
|
|
138
|
+
}
|
|
139
|
+
}, [isEslogAvailable, eslogValidationEnabled]);
|
|
140
|
+
|
|
141
|
+
// Clear entity errors when eslog validation is disabled
|
|
142
|
+
useEffect(() => {
|
|
143
|
+
if (!eslogValidationEnabled) {
|
|
144
|
+
setEslogEntityErrors([]);
|
|
145
|
+
}
|
|
146
|
+
}, [eslogValidationEnabled]);
|
|
147
|
+
|
|
148
|
+
// Get active devices for selected premise
|
|
149
|
+
const activeDevices = useMemo(() => {
|
|
150
|
+
if (!selectedPremiseName) return [];
|
|
151
|
+
const premise = activePremises.find((p) => p.business_premise_name === selectedPremiseName);
|
|
152
|
+
return premise?.Devices?.filter((d) => d.is_active) || [];
|
|
153
|
+
}, [activePremises, selectedPremiseName]);
|
|
154
|
+
|
|
155
|
+
// Initialize FURS selection from localStorage or first active combo
|
|
156
|
+
useEffect(() => {
|
|
157
|
+
if (!isFursEnabled || !hasFursPremises || selectedPremiseName) return;
|
|
158
|
+
|
|
159
|
+
const lastUsed = getLastUsedFursCombo(entityId);
|
|
160
|
+
if (lastUsed) {
|
|
161
|
+
// Verify the last-used combo is still valid (premise/device still exist and active)
|
|
162
|
+
const premise = activePremises.find((p) => p.business_premise_name === lastUsed.business_premise_name);
|
|
163
|
+
const device = premise?.Devices?.find(
|
|
164
|
+
(d) => d.electronic_device_name === lastUsed.electronic_device_name && d.is_active,
|
|
165
|
+
);
|
|
166
|
+
if (premise && device) {
|
|
167
|
+
setSelectedPremiseName(lastUsed.business_premise_name);
|
|
168
|
+
setSelectedDeviceName(lastUsed.electronic_device_name);
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Fall back to first active premise/device
|
|
174
|
+
const firstPremise = activePremises[0];
|
|
175
|
+
const firstDevice = firstPremise?.Devices?.find((d) => d.is_active);
|
|
176
|
+
if (firstPremise && firstDevice) {
|
|
177
|
+
setSelectedPremiseName(firstPremise.business_premise_name);
|
|
178
|
+
setSelectedDeviceName(firstDevice.electronic_device_name);
|
|
179
|
+
}
|
|
180
|
+
}, [isFursEnabled, hasFursPremises, activePremises, entityId, selectedPremiseName]);
|
|
181
|
+
|
|
182
|
+
// When premise changes, select first active device
|
|
183
|
+
useEffect(() => {
|
|
184
|
+
if (!selectedPremiseName) return;
|
|
185
|
+
const premise = activePremises.find((p) => p.business_premise_name === selectedPremiseName);
|
|
186
|
+
const firstDevice = premise?.Devices?.find((d) => d.is_active);
|
|
187
|
+
if (firstDevice && selectedDeviceName !== firstDevice.electronic_device_name) {
|
|
188
|
+
// Only update if the current device is not in this premise
|
|
189
|
+
const currentDeviceInPremise = premise?.Devices?.find(
|
|
190
|
+
(d) => d.electronic_device_name === selectedDeviceName && d.is_active,
|
|
191
|
+
);
|
|
192
|
+
if (!currentDeviceInPremise) {
|
|
193
|
+
setSelectedDeviceName(firstDevice.electronic_device_name);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}, [selectedPremiseName, activePremises, selectedDeviceName]);
|
|
197
|
+
|
|
198
|
+
const form = useForm<CreateAdvanceInvoiceFormValues>({
|
|
199
|
+
// Cast resolver to accept extended form type (includes UI-only fields)
|
|
200
|
+
resolver: zodResolver(createAdvanceInvoiceSchema) as Resolver<CreateAdvanceInvoiceFormValues>,
|
|
201
|
+
defaultValues: {
|
|
202
|
+
number: "", // Will be set by useNextAdvanceInvoiceNumber
|
|
203
|
+
date: initialValues?.date || new Date().toISOString(),
|
|
204
|
+
customer_id: initialValues?.customer_id ?? undefined,
|
|
205
|
+
// Cast customer to form schema type (API type may have additional fields)
|
|
206
|
+
customer: (initialValues?.customer as CreateAdvanceInvoiceFormValues["customer"]) ?? undefined,
|
|
207
|
+
items: initialValues?.items?.length
|
|
208
|
+
? initialValues.items.map((item) => ({
|
|
209
|
+
name: item.name || "",
|
|
210
|
+
description: item.description || "",
|
|
211
|
+
quantity: item.quantity ?? 1,
|
|
212
|
+
// Use gross_price if set, otherwise use price
|
|
213
|
+
price: item.gross_price ?? item.price,
|
|
214
|
+
taxes: item.taxes || [],
|
|
215
|
+
}))
|
|
216
|
+
: [
|
|
217
|
+
{
|
|
218
|
+
name: "",
|
|
219
|
+
description: "",
|
|
220
|
+
quantity: 1,
|
|
221
|
+
price: undefined,
|
|
222
|
+
taxes: [],
|
|
223
|
+
},
|
|
224
|
+
],
|
|
225
|
+
currency_code: initialValues?.currency_code || activeEntity?.currency_code || "EUR",
|
|
226
|
+
note: initialValues?.note ?? defaultNote,
|
|
227
|
+
},
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
// Skip fiscalization is only allowed for bank transfers or unpaid invoices
|
|
231
|
+
const canSkipFiscalization = !markAsPaid || paymentType === "bank_transfer";
|
|
232
|
+
|
|
233
|
+
// Auto-disable skip when it becomes invalid (e.g., user changes payment type to cash)
|
|
234
|
+
useEffect(() => {
|
|
235
|
+
if (!canSkipFiscalization && skipFiscalization) {
|
|
236
|
+
setSkipFiscalization(false);
|
|
237
|
+
}
|
|
238
|
+
}, [canSkipFiscalization, skipFiscalization]);
|
|
239
|
+
|
|
240
|
+
// Check if FURS selection is ready (needed to prevent number flashing)
|
|
241
|
+
const isFursSelectionReady = !isFursEnabled || !hasFursPremises || (!!selectedPremiseName && !!selectedDeviceName);
|
|
242
|
+
|
|
243
|
+
// FURS is "active" for this advance invoice if enabled and we have a valid selection (and not skipped)
|
|
244
|
+
const isFursActive =
|
|
245
|
+
isFursEnabled && hasFursPremises && selectedPremiseName && selectedDeviceName && !skipFiscalization;
|
|
246
|
+
|
|
247
|
+
// ============================================================================
|
|
248
|
+
// Next Advance Invoice Number Preview
|
|
249
|
+
// ============================================================================
|
|
250
|
+
const { data: nextNumberData, isLoading: isNextNumberLoading } = useNextDocumentNumber(entityId, "advance_invoice", {
|
|
251
|
+
businessPremiseName: isFursActive ? selectedPremiseName : undefined,
|
|
252
|
+
electronicDeviceName: isFursActive ? selectedDeviceName : undefined,
|
|
253
|
+
enabled: !!entityId && !isFursLoading && isFursSelectionReady,
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
// Overall loading state
|
|
257
|
+
const isFormDataLoading = isFursLoading || !isFursSelectionReady || isNextNumberLoading;
|
|
258
|
+
|
|
259
|
+
// Update header action with FURS and e-SLOG toggle buttons
|
|
260
|
+
useEffect(() => {
|
|
261
|
+
if (!onHeaderActionChange) return;
|
|
262
|
+
|
|
263
|
+
if (isFursLoading) {
|
|
264
|
+
onHeaderActionChange(null);
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
const showFursToggle = isFursEnabled && hasFursPremises;
|
|
269
|
+
const showEslogToggle = isEslogAvailable;
|
|
270
|
+
|
|
271
|
+
if (showFursToggle || showEslogToggle) {
|
|
272
|
+
const isFursChecked = !skipFiscalization;
|
|
273
|
+
const isEslogChecked = eslogValidationEnabled === true;
|
|
274
|
+
|
|
275
|
+
onHeaderActionChange(
|
|
276
|
+
<div className="flex items-center gap-2">
|
|
277
|
+
{/* e-SLOG toggle */}
|
|
278
|
+
{showEslogToggle && (
|
|
279
|
+
<TooltipProvider>
|
|
280
|
+
<Tooltip>
|
|
281
|
+
<TooltipTrigger asChild>
|
|
282
|
+
<Button
|
|
283
|
+
type="button"
|
|
284
|
+
variant={isEslogChecked ? "outline" : "ghost"}
|
|
285
|
+
size="sm"
|
|
286
|
+
className={cn("h-8 cursor-pointer gap-2", !isEslogChecked && "text-muted-foreground")}
|
|
287
|
+
onClick={() => setEslogValidationEnabled(!eslogValidationEnabled)}
|
|
288
|
+
>
|
|
289
|
+
<div
|
|
290
|
+
className={cn(
|
|
291
|
+
"flex size-4 items-center justify-center rounded border",
|
|
292
|
+
isEslogChecked
|
|
293
|
+
? "border-primary bg-primary text-primary-foreground"
|
|
294
|
+
: "border-muted-foreground bg-background text-muted-foreground",
|
|
295
|
+
)}
|
|
296
|
+
>
|
|
297
|
+
{isEslogChecked ? <Check className="size-3" /> : <FileCode2 className="size-3" />}
|
|
298
|
+
</div>
|
|
299
|
+
<span>{t("e-SLOG")}</span>
|
|
300
|
+
</Button>
|
|
301
|
+
</TooltipTrigger>
|
|
302
|
+
<TooltipContent side="bottom" className="max-w-xs">
|
|
303
|
+
{isEslogChecked
|
|
304
|
+
? t("Click to skip e-SLOG validation for this advance invoice")
|
|
305
|
+
: t("Click to enable e-SLOG validation")}
|
|
306
|
+
</TooltipContent>
|
|
307
|
+
</Tooltip>
|
|
308
|
+
</TooltipProvider>
|
|
309
|
+
)}
|
|
310
|
+
|
|
311
|
+
{/* FURS toggle */}
|
|
312
|
+
{showFursToggle && (
|
|
313
|
+
<TooltipProvider>
|
|
314
|
+
<Tooltip>
|
|
315
|
+
<TooltipTrigger asChild>
|
|
316
|
+
<Button
|
|
317
|
+
type="button"
|
|
318
|
+
variant={isFursChecked ? "outline" : "ghost"}
|
|
319
|
+
size="sm"
|
|
320
|
+
className={cn(
|
|
321
|
+
"h-8 cursor-pointer gap-2",
|
|
322
|
+
!canSkipFiscalization && "cursor-not-allowed opacity-50",
|
|
323
|
+
!isFursChecked && "text-destructive hover:text-destructive",
|
|
324
|
+
)}
|
|
325
|
+
onClick={() => canSkipFiscalization && setSkipFiscalization(!skipFiscalization)}
|
|
326
|
+
>
|
|
327
|
+
<div
|
|
328
|
+
className={cn(
|
|
329
|
+
"flex size-4 items-center justify-center rounded border",
|
|
330
|
+
isFursChecked
|
|
331
|
+
? "border-primary bg-primary text-primary-foreground"
|
|
332
|
+
: "border-destructive bg-destructive text-destructive-foreground",
|
|
333
|
+
)}
|
|
334
|
+
>
|
|
335
|
+
{isFursChecked ? <Check className="size-3" /> : <X className="size-3" />}
|
|
336
|
+
</div>
|
|
337
|
+
<span>{t("Fiscally verify")}</span>
|
|
338
|
+
</Button>
|
|
339
|
+
</TooltipTrigger>
|
|
340
|
+
<TooltipContent side="bottom" className="max-w-xs">
|
|
341
|
+
{canSkipFiscalization
|
|
342
|
+
? isFursChecked
|
|
343
|
+
? t("Click to skip fiscalization for this advance invoice")
|
|
344
|
+
: t("Click to enable fiscalization")
|
|
345
|
+
: t("Cannot skip fiscalization for cash payments")}
|
|
346
|
+
</TooltipContent>
|
|
347
|
+
</Tooltip>
|
|
348
|
+
</TooltipProvider>
|
|
349
|
+
)}
|
|
350
|
+
</div>,
|
|
351
|
+
);
|
|
352
|
+
} else {
|
|
353
|
+
onHeaderActionChange(null);
|
|
354
|
+
}
|
|
355
|
+
}, [
|
|
356
|
+
isFursLoading,
|
|
357
|
+
isFursEnabled,
|
|
358
|
+
hasFursPremises,
|
|
359
|
+
skipFiscalization,
|
|
360
|
+
canSkipFiscalization,
|
|
361
|
+
isEslogAvailable,
|
|
362
|
+
eslogValidationEnabled,
|
|
363
|
+
onHeaderActionChange,
|
|
364
|
+
t,
|
|
365
|
+
]);
|
|
366
|
+
|
|
367
|
+
// Pre-fill advance invoice number from preview
|
|
368
|
+
useEffect(() => {
|
|
369
|
+
if (nextNumberData?.number) {
|
|
370
|
+
form.setValue("number", nextNumberData.number);
|
|
371
|
+
}
|
|
372
|
+
}, [nextNumberData?.number, form]);
|
|
373
|
+
|
|
374
|
+
const formValues = useWatch({
|
|
375
|
+
control: form.control,
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
// ============================================================================
|
|
379
|
+
// VIES Check - determine if reverse charge applies
|
|
380
|
+
// ============================================================================
|
|
381
|
+
const { reverseChargeApplies, warning: viesWarning } = useViesCheck({
|
|
382
|
+
issuerCountryCode: activeEntity?.country_code,
|
|
383
|
+
isTaxSubject: activeEntity?.is_tax_subject ?? true,
|
|
384
|
+
customerCountry: formValues.customer?.country,
|
|
385
|
+
customerCountryCode: formValues.customer?.country_code,
|
|
386
|
+
customerTaxNumber: formValues.customer?.tax_number,
|
|
387
|
+
enabled: !!activeEntity,
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
// Customer form management
|
|
391
|
+
const {
|
|
392
|
+
originalCustomer,
|
|
393
|
+
showCustomerForm,
|
|
394
|
+
shouldFocusName,
|
|
395
|
+
selectedCustomerId,
|
|
396
|
+
initialCustomerName,
|
|
397
|
+
handleCustomerSelect,
|
|
398
|
+
handleCustomerClear,
|
|
399
|
+
} = useDocumentCustomerForm(form as any);
|
|
400
|
+
|
|
401
|
+
const { mutate: createAdvanceInvoice, isPending } = useCreateAdvanceInvoice({
|
|
402
|
+
entityId,
|
|
403
|
+
onSuccess: (data) => {
|
|
404
|
+
// Save FURS combo to localStorage on successful creation
|
|
405
|
+
if (isFursActive && selectedPremiseName && selectedDeviceName) {
|
|
406
|
+
setLastUsedFursCombo(entityId, {
|
|
407
|
+
business_premise_name: selectedPremiseName,
|
|
408
|
+
electronic_device_name: selectedDeviceName,
|
|
409
|
+
});
|
|
410
|
+
}
|
|
411
|
+
onSuccess?.(data);
|
|
412
|
+
},
|
|
413
|
+
onError,
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
// Shared submit logic for both regular save and save as draft
|
|
417
|
+
const submitAdvanceInvoice = useCallback(
|
|
418
|
+
(values: CreateAdvanceInvoiceFormValues, isDraft: boolean) => {
|
|
419
|
+
// Skip e-SLOG and FURS validation for drafts
|
|
420
|
+
if (!isDraft && eslogValidationEnabled) {
|
|
421
|
+
const validationErrors = validateEslogForm(values, activeEntity);
|
|
422
|
+
|
|
423
|
+
if (validationErrors.length > 0) {
|
|
424
|
+
const entityErrors = getEntityErrors(validationErrors);
|
|
425
|
+
const formErrors = getFormFieldErrors(validationErrors);
|
|
426
|
+
setEslogEntityErrors(entityErrors);
|
|
427
|
+
for (const error of formErrors) {
|
|
428
|
+
form.setError(error.field as any, {
|
|
429
|
+
type: "eslog",
|
|
430
|
+
message: error.message,
|
|
431
|
+
});
|
|
432
|
+
}
|
|
433
|
+
return;
|
|
434
|
+
}
|
|
435
|
+
setEslogEntityErrors([]);
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// Build FURS options (skip for drafts)
|
|
439
|
+
const fursOptions =
|
|
440
|
+
!isDraft && isFursEnabled
|
|
441
|
+
? skipFiscalization
|
|
442
|
+
? { skip: true }
|
|
443
|
+
: selectedPremiseName && selectedDeviceName
|
|
444
|
+
? { business_premise_name: selectedPremiseName, electronic_device_name: selectedDeviceName }
|
|
445
|
+
: undefined
|
|
446
|
+
: undefined;
|
|
447
|
+
|
|
448
|
+
// Build e-SLOG options (skip for drafts)
|
|
449
|
+
const eslogOptions =
|
|
450
|
+
!isDraft && isEslogAvailable ? { validation_enabled: eslogValidationEnabled === true } : undefined;
|
|
451
|
+
|
|
452
|
+
const payload = prepareAdvanceInvoiceSubmission(values, {
|
|
453
|
+
originalCustomer,
|
|
454
|
+
wasCustomerFormShown: showCustomerForm,
|
|
455
|
+
markAsPaid: isDraft ? false : markAsPaid,
|
|
456
|
+
paymentType,
|
|
457
|
+
furs: fursOptions,
|
|
458
|
+
eslog: eslogOptions,
|
|
459
|
+
priceModes: priceModesRef.current,
|
|
460
|
+
isDraft,
|
|
461
|
+
});
|
|
462
|
+
|
|
463
|
+
createAdvanceInvoice(payload);
|
|
464
|
+
},
|
|
465
|
+
[
|
|
466
|
+
activeEntity,
|
|
467
|
+
createAdvanceInvoice,
|
|
468
|
+
eslogValidationEnabled,
|
|
469
|
+
form,
|
|
470
|
+
isEslogAvailable,
|
|
471
|
+
isFursEnabled,
|
|
472
|
+
markAsPaid,
|
|
473
|
+
originalCustomer,
|
|
474
|
+
paymentType,
|
|
475
|
+
selectedDeviceName,
|
|
476
|
+
selectedPremiseName,
|
|
477
|
+
showCustomerForm,
|
|
478
|
+
skipFiscalization,
|
|
479
|
+
],
|
|
480
|
+
);
|
|
481
|
+
|
|
482
|
+
// Handle save as draft
|
|
483
|
+
const handleSaveAsDraft = useCallback(async () => {
|
|
484
|
+
setIsDraftPending(true);
|
|
485
|
+
try {
|
|
486
|
+
const isValid = await form.trigger();
|
|
487
|
+
if (isValid) {
|
|
488
|
+
const values = form.getValues();
|
|
489
|
+
submitAdvanceInvoice(values, true);
|
|
490
|
+
}
|
|
491
|
+
} finally {
|
|
492
|
+
setIsDraftPending(false);
|
|
493
|
+
}
|
|
494
|
+
}, [form, submitAdvanceInvoice]);
|
|
495
|
+
|
|
496
|
+
useFormFooterRegistration({
|
|
497
|
+
formId: "create-advance-invoice-form",
|
|
498
|
+
isPending,
|
|
499
|
+
isDirty: form.formState.isDirty,
|
|
500
|
+
label: t("Save"),
|
|
501
|
+
secondaryAction: {
|
|
502
|
+
label: t("Save as Draft"),
|
|
503
|
+
onClick: handleSaveAsDraft,
|
|
504
|
+
isPending: isDraftPending,
|
|
505
|
+
},
|
|
506
|
+
});
|
|
507
|
+
|
|
508
|
+
// Set default note from entity settings (advance invoices don't have payment terms)
|
|
509
|
+
useEffect(() => {
|
|
510
|
+
const entityDefaultNote = (activeEntity?.settings as any)?.default_invoice_note;
|
|
511
|
+
if (entityDefaultNote && !form.getValues("note")) {
|
|
512
|
+
form.setValue("note", entityDefaultNote);
|
|
513
|
+
}
|
|
514
|
+
}, [activeEntity, form]);
|
|
515
|
+
|
|
516
|
+
// Auto-add tax field for tax subject entities
|
|
517
|
+
useEffect(() => {
|
|
518
|
+
if (activeEntity?.is_tax_subject) {
|
|
519
|
+
const items = form.getValues("items") || [];
|
|
520
|
+
if (items.length > 0 && (!items[0].taxes || items[0].taxes.length === 0)) {
|
|
521
|
+
form.setValue("items.0.taxes", [{ tax_id: undefined }]);
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
}, [activeEntity?.is_tax_subject, form]);
|
|
525
|
+
|
|
526
|
+
useEffect(() => {
|
|
527
|
+
if (onChange) {
|
|
528
|
+
const currentItems = form.getValues("items") || [];
|
|
529
|
+
|
|
530
|
+
// Transform items to use gross_price when price mode is gross
|
|
531
|
+
const transformedItems = currentItems.map((item: any, index: number) => {
|
|
532
|
+
const { price, ...rest } = item;
|
|
533
|
+
const isGross = priceModesRef.current[index] ?? false;
|
|
534
|
+
if (isGross) {
|
|
535
|
+
return { ...rest, gross_price: price };
|
|
536
|
+
}
|
|
537
|
+
return { ...rest, price };
|
|
538
|
+
});
|
|
539
|
+
|
|
540
|
+
const payload: AdvanceInvoicePreviewPayload = {
|
|
541
|
+
number: formValues.number,
|
|
542
|
+
date: formValues.date,
|
|
543
|
+
customer_id: formValues.customer_id,
|
|
544
|
+
customer: formValues.customer,
|
|
545
|
+
items: transformedItems,
|
|
546
|
+
currency_code: formValues.currency_code,
|
|
547
|
+
note: formValues.note,
|
|
548
|
+
};
|
|
549
|
+
onChange(payload);
|
|
550
|
+
}
|
|
551
|
+
}, [formValues, onChange, form]);
|
|
552
|
+
|
|
553
|
+
const onSubmit = (values: CreateAdvanceInvoiceFormValues) => {
|
|
554
|
+
submitAdvanceInvoice(values, false);
|
|
555
|
+
};
|
|
556
|
+
|
|
557
|
+
// Show skeleton while loading
|
|
558
|
+
if (isFormDataLoading) {
|
|
559
|
+
return (
|
|
560
|
+
<div className="space-y-8">
|
|
561
|
+
<div className="flex w-full flex-col md:flex-row md:gap-6">
|
|
562
|
+
<div className="flex-1 space-y-4">
|
|
563
|
+
<Skeleton className="h-7 w-24" />
|
|
564
|
+
<Skeleton className="h-10 w-full" />
|
|
565
|
+
</div>
|
|
566
|
+
<div className="flex-1 space-y-4">
|
|
567
|
+
<Skeleton className="h-7 w-20" />
|
|
568
|
+
<Skeleton className="h-5 w-16" />
|
|
569
|
+
<Skeleton className="h-10 w-full" />
|
|
570
|
+
<Skeleton className="h-5 w-12" />
|
|
571
|
+
<Skeleton className="h-10 w-full" />
|
|
572
|
+
<Skeleton className="h-5 w-16" />
|
|
573
|
+
<Skeleton className="h-10 w-full" />
|
|
574
|
+
<Skeleton className="h-5 w-20" />
|
|
575
|
+
<Skeleton className="h-10 w-full" />
|
|
576
|
+
<div className="space-y-3 rounded-md border p-4">
|
|
577
|
+
<div className="flex items-center gap-3">
|
|
578
|
+
<Skeleton className="h-4 w-4 rounded" />
|
|
579
|
+
<Skeleton className="h-5 w-28" />
|
|
580
|
+
</div>
|
|
581
|
+
</div>
|
|
582
|
+
</div>
|
|
583
|
+
</div>
|
|
584
|
+
|
|
585
|
+
<div className="space-y-4">
|
|
586
|
+
<Skeleton className="h-7 w-16" />
|
|
587
|
+
<div className="space-y-4 rounded-lg border p-4">
|
|
588
|
+
<Skeleton className="h-10 w-full" />
|
|
589
|
+
<div className="flex gap-4">
|
|
590
|
+
<Skeleton className="h-10 w-24" />
|
|
591
|
+
<Skeleton className="h-10 flex-1" />
|
|
592
|
+
</div>
|
|
593
|
+
</div>
|
|
594
|
+
<Skeleton className="h-9 w-24" />
|
|
595
|
+
</div>
|
|
596
|
+
|
|
597
|
+
<div className="space-y-2">
|
|
598
|
+
<Skeleton className="h-5 w-12" />
|
|
599
|
+
<Skeleton className="h-24 w-full" />
|
|
600
|
+
</div>
|
|
601
|
+
|
|
602
|
+
<Skeleton className="h-10 w-24" />
|
|
603
|
+
</div>
|
|
604
|
+
);
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
return (
|
|
608
|
+
<Form {...form}>
|
|
609
|
+
<form id="create-advance-invoice-form" onSubmit={form.handleSubmit(onSubmit)} className="space-y-8">
|
|
610
|
+
{/* e-SLOG entity-level validation errors */}
|
|
611
|
+
{eslogEntityErrors.length > 0 && (
|
|
612
|
+
<Alert variant="destructive">
|
|
613
|
+
<AlertCircle className="h-4 w-4" />
|
|
614
|
+
<AlertTitle>{t("e-SLOG Validation Failed")}</AlertTitle>
|
|
615
|
+
<AlertDescription>
|
|
616
|
+
<p className="mb-2">{t("The following entity settings need to be updated:")}</p>
|
|
617
|
+
<ul className="list-disc space-y-1 pl-4">
|
|
618
|
+
{eslogEntityErrors.map((error) => (
|
|
619
|
+
<li key={error.field} className="text-sm">
|
|
620
|
+
{error.message}
|
|
621
|
+
</li>
|
|
622
|
+
))}
|
|
623
|
+
</ul>
|
|
624
|
+
</AlertDescription>
|
|
625
|
+
</Alert>
|
|
626
|
+
)}
|
|
627
|
+
|
|
628
|
+
<div className="flex w-full flex-col md:flex-row md:gap-6">
|
|
629
|
+
<DocumentRecipientSection
|
|
630
|
+
control={form.control}
|
|
631
|
+
entityId={entityId}
|
|
632
|
+
onCustomerSelect={handleCustomerSelect}
|
|
633
|
+
onCustomerClear={handleCustomerClear}
|
|
634
|
+
showCustomerForm={showCustomerForm}
|
|
635
|
+
shouldFocusName={shouldFocusName}
|
|
636
|
+
selectedCustomerId={selectedCustomerId}
|
|
637
|
+
initialCustomerName={initialCustomerName}
|
|
638
|
+
t={t}
|
|
639
|
+
/>
|
|
640
|
+
<DocumentDetailsSection
|
|
641
|
+
control={form.control}
|
|
642
|
+
documentType={_type}
|
|
643
|
+
t={t}
|
|
644
|
+
fursInline={
|
|
645
|
+
isFursEnabled && hasFursPremises
|
|
646
|
+
? {
|
|
647
|
+
premises: activePremises.map((p) => ({ id: p.id, business_premise_name: p.business_premise_name })),
|
|
648
|
+
devices: activeDevices.map((d) => ({ id: d.id, electronic_device_name: d.electronic_device_name })),
|
|
649
|
+
selectedPremise: selectedPremiseName,
|
|
650
|
+
selectedDevice: selectedDeviceName,
|
|
651
|
+
onPremiseChange: setSelectedPremiseName,
|
|
652
|
+
onDeviceChange: setSelectedDeviceName,
|
|
653
|
+
isSkipped: skipFiscalization,
|
|
654
|
+
}
|
|
655
|
+
: undefined
|
|
656
|
+
}
|
|
657
|
+
>
|
|
658
|
+
{/* Mark as paid section (UI-only state, not in form schema) */}
|
|
659
|
+
<MarkAsPaidSection
|
|
660
|
+
checked={markAsPaid}
|
|
661
|
+
onCheckedChange={setMarkAsPaid}
|
|
662
|
+
paymentType={paymentType}
|
|
663
|
+
onPaymentTypeChange={setPaymentType}
|
|
664
|
+
t={t}
|
|
665
|
+
/>
|
|
666
|
+
</DocumentDetailsSection>
|
|
667
|
+
</div>
|
|
668
|
+
|
|
669
|
+
<DocumentItemsSection
|
|
670
|
+
control={form.control}
|
|
671
|
+
watch={form.watch}
|
|
672
|
+
setValue={form.setValue}
|
|
673
|
+
getValues={form.getValues}
|
|
674
|
+
entityId={entityId}
|
|
675
|
+
currencyCode={activeEntity?.currency_code ?? undefined}
|
|
676
|
+
onAddNewTax={onAddNewTax}
|
|
677
|
+
t={t}
|
|
678
|
+
taxesDisabled={reverseChargeApplies}
|
|
679
|
+
taxesDisabledMessage={
|
|
680
|
+
reverseChargeApplies ? t("Reverse charge - tax exempt EU B2B sale") : viesWarning ? viesWarning : undefined
|
|
681
|
+
}
|
|
682
|
+
maxTaxesPerItem={activeEntity?.country_rules?.max_taxes_per_item}
|
|
683
|
+
priceModesRef={priceModesRef}
|
|
684
|
+
initialPriceModes={initialPriceModes}
|
|
685
|
+
/>
|
|
686
|
+
|
|
687
|
+
<DocumentNoteField
|
|
688
|
+
control={form.control}
|
|
689
|
+
t={t}
|
|
690
|
+
entity={activeEntity}
|
|
691
|
+
document={{
|
|
692
|
+
number: formValues.number,
|
|
693
|
+
date: formValues.date,
|
|
694
|
+
date_due: formValues.date_due,
|
|
695
|
+
currency_code: formValues.currency_code,
|
|
696
|
+
customer: formValues.customer as any,
|
|
697
|
+
}}
|
|
698
|
+
/>
|
|
699
|
+
</form>
|
|
700
|
+
</Form>
|
|
701
|
+
);
|
|
702
|
+
}
|