@spaceinvoices/react-ui 0.4.4 → 0.4.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cli/dist/index.js +1 -1
- package/package.json +1 -1
- package/src/components/advance-invoices/create/create-advance-invoice-form.tsx +61 -11
- package/src/components/advance-invoices/create/locales/de.ts +15 -0
- package/src/components/advance-invoices/create/locales/es.ts +15 -0
- package/src/components/advance-invoices/create/locales/fr.ts +15 -0
- package/src/components/advance-invoices/create/locales/hr.ts +15 -0
- package/src/components/advance-invoices/create/locales/it.ts +15 -0
- package/src/components/advance-invoices/create/locales/nl.ts +15 -0
- package/src/components/advance-invoices/create/locales/pl.ts +15 -0
- package/src/components/advance-invoices/create/locales/pt.ts +15 -0
- package/src/components/advance-invoices/create/locales/sl.ts +15 -0
- package/src/components/advance-invoices/list/list-row-actions.tsx +48 -1
- package/src/components/advance-invoices/list/list-table.tsx +21 -1
- package/src/components/advance-invoices/list/locales/de.ts +4 -0
- package/src/components/advance-invoices/list/locales/en.ts +3 -0
- package/src/components/advance-invoices/list/locales/es.ts +4 -0
- package/src/components/advance-invoices/list/locales/fr.ts +4 -0
- package/src/components/advance-invoices/list/locales/hr.ts +3 -0
- package/src/components/advance-invoices/list/locales/it.ts +4 -0
- package/src/components/advance-invoices/list/locales/nl.ts +4 -0
- package/src/components/advance-invoices/list/locales/pl.ts +3 -0
- package/src/components/advance-invoices/list/locales/pt.ts +4 -0
- package/src/components/advance-invoices/list/locales/sl.ts +3 -0
- package/src/components/credit-notes/create/create-credit-note-form.tsx +71 -8
- package/src/components/credit-notes/create/locales/de.ts +16 -0
- package/src/components/credit-notes/create/locales/es.ts +16 -0
- package/src/components/credit-notes/create/locales/fr.ts +16 -0
- package/src/components/credit-notes/create/locales/hr.ts +16 -0
- package/src/components/credit-notes/create/locales/it.ts +16 -0
- package/src/components/credit-notes/create/locales/nl.ts +16 -0
- package/src/components/credit-notes/create/locales/pl.ts +16 -0
- package/src/components/credit-notes/create/locales/pt.ts +16 -0
- package/src/components/credit-notes/create/locales/sl.ts +17 -1
- package/src/components/credit-notes/list/list-row-actions.tsx +44 -1
- package/src/components/credit-notes/list/list-table.tsx +16 -2
- package/src/components/credit-notes/list/locales/de.ts +2 -0
- package/src/components/credit-notes/list/locales/en.ts +2 -0
- package/src/components/credit-notes/list/locales/es.ts +2 -0
- package/src/components/credit-notes/list/locales/fr.ts +2 -0
- package/src/components/credit-notes/list/locales/hr.ts +2 -0
- package/src/components/credit-notes/list/locales/it.ts +2 -0
- package/src/components/credit-notes/list/locales/nl.ts +2 -0
- package/src/components/credit-notes/list/locales/pl.ts +2 -0
- package/src/components/credit-notes/list/locales/pt.ts +2 -0
- package/src/components/credit-notes/list/locales/sl.ts +2 -0
- package/src/components/dashboard/collection-rate-card/use-collection-rate.ts +48 -9
- package/src/components/dashboard/revenue-trend-chart/use-revenue-trend.ts +77 -48
- package/src/components/dashboard/shared/use-revenue-data.ts +77 -9
- package/src/components/delivery-notes/create/create-delivery-note-form.tsx +67 -7
- package/src/components/delivery-notes/create/locales/de.ts +16 -0
- package/src/components/delivery-notes/create/locales/es.ts +16 -0
- package/src/components/delivery-notes/create/locales/fr.ts +16 -0
- package/src/components/delivery-notes/create/locales/hr.ts +16 -0
- package/src/components/delivery-notes/create/locales/it.ts +16 -0
- package/src/components/delivery-notes/create/locales/nl.ts +16 -0
- package/src/components/delivery-notes/create/locales/pl.ts +16 -0
- package/src/components/delivery-notes/create/locales/pt.ts +16 -0
- package/src/components/delivery-notes/create/locales/sl.ts +17 -1
- package/src/components/delivery-notes/list/list-table.tsx +17 -8
- package/src/components/delivery-notes/list/locales/de.ts +32 -1
- package/src/components/delivery-notes/list/locales/en.ts +31 -0
- package/src/components/delivery-notes/list/locales/es.ts +32 -1
- package/src/components/delivery-notes/list/locales/fr.ts +32 -1
- package/src/components/delivery-notes/list/locales/hr.ts +32 -1
- package/src/components/delivery-notes/list/locales/it.ts +32 -1
- package/src/components/delivery-notes/list/locales/nl.ts +32 -1
- package/src/components/delivery-notes/list/locales/pl.ts +32 -1
- package/src/components/delivery-notes/list/locales/pt.ts +32 -1
- package/src/components/delivery-notes/list/locales/sl.ts +32 -1
- package/src/components/documents/create/document-add-item-form.tsx +70 -0
- package/src/components/documents/create/document-details-section.tsx +122 -2
- package/src/components/documents/create/document-items-section.tsx +21 -4
- package/src/components/documents/create/live-preview.tsx +24 -4
- package/src/components/documents/create/mark-as-paid-section.tsx +29 -20
- package/src/components/documents/create/prepare-document-submission.ts +19 -7
- package/src/components/documents/shared/document-preview-display.tsx +3 -4
- package/src/components/documents/view/document-actions-bar.tsx +7 -27
- package/src/components/documents/view/document-details-card.tsx +26 -9
- package/src/components/documents/view/locales/de.ts +2 -0
- package/src/components/documents/view/locales/es.ts +2 -0
- package/src/components/documents/view/locales/fr.ts +2 -0
- package/src/components/documents/view/locales/hr.ts +2 -0
- package/src/components/documents/view/locales/it.ts +2 -0
- package/src/components/documents/view/locales/nl.ts +2 -0
- package/src/components/documents/view/locales/pl.ts +2 -0
- package/src/components/documents/view/locales/pt.ts +2 -0
- package/src/components/documents/view/locales/sl.ts +3 -1
- package/src/components/documents/view/use-document-download.ts +6 -3
- package/src/components/entities/create-entity-form.tsx +2 -1
- package/src/components/entities/entity-settings-form/input-with-preview.tsx +30 -2
- package/src/components/entities/entity-settings-form/locales/de.ts +1 -0
- package/src/components/entities/entity-settings-form/locales/es.ts +1 -0
- package/src/components/entities/entity-settings-form/locales/fr.ts +1 -0
- package/src/components/entities/entity-settings-form/locales/hr.ts +1 -0
- package/src/components/entities/entity-settings-form/locales/it.ts +1 -0
- package/src/components/entities/entity-settings-form/locales/nl.ts +1 -0
- package/src/components/entities/entity-settings-form/locales/pl.ts +1 -0
- package/src/components/entities/entity-settings-form/locales/pt.ts +1 -0
- package/src/components/entities/entity-settings-form/locales/sl.ts +1 -0
- package/src/components/entities/settings/company-settings-form.tsx +173 -20
- package/src/components/estimates/create/create-estimate-form.tsx +64 -6
- package/src/components/estimates/create/locales/de.ts +16 -0
- package/src/components/estimates/create/locales/es.ts +16 -0
- package/src/components/estimates/create/locales/fr.ts +16 -0
- package/src/components/estimates/create/locales/hr.ts +16 -0
- package/src/components/estimates/create/locales/it.ts +16 -0
- package/src/components/estimates/create/locales/nl.ts +16 -0
- package/src/components/estimates/create/locales/pl.ts +16 -0
- package/src/components/estimates/create/locales/pt.ts +16 -0
- package/src/components/estimates/create/locales/sl.ts +17 -1
- package/src/components/estimates/list/list-table.tsx +11 -2
- package/src/components/estimates/list/locales/de.ts +1 -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/export/document-export-form.tsx +46 -12
- package/src/components/invoices/create/create-invoice-form.tsx +58 -10
- package/src/components/invoices/create/locales/de.ts +15 -0
- package/src/components/invoices/create/locales/es.ts +15 -0
- package/src/components/invoices/create/locales/fr.ts +15 -0
- package/src/components/invoices/create/locales/hr.ts +15 -0
- package/src/components/invoices/create/locales/it.ts +15 -0
- package/src/components/invoices/create/locales/nl.ts +15 -0
- package/src/components/invoices/create/locales/pl.ts +15 -0
- package/src/components/invoices/create/locales/pt.ts +15 -0
- package/src/components/invoices/create/locales/sl.ts +16 -1
- package/src/components/invoices/view/fiscalization-status-card.tsx +10 -8
- package/src/components/table/search-input.tsx +17 -0
- package/src/generated/schemas/advanceinvoice.ts +4 -2
- package/src/generated/schemas/creditnote.ts +2 -1
- package/src/generated/schemas/deliverynote.ts +2 -1
- package/src/generated/schemas/estimate.ts +4 -2
- package/src/generated/schemas/invoice.ts +2 -1
- package/src/generated/schemas/renderadvanceinvoicepreview_body.ts +17 -23
- package/src/generated/schemas/rendercreditnotepreview_body.ts +17 -23
- package/src/generated/schemas/renderdeliverynotepreview_body.ts +17 -23
- package/src/generated/schemas/renderestimatepreview_body.ts +17 -23
- package/src/generated/schemas/renderinvoicepreview_body.ts +19 -23
- package/src/hooks/use-duplicate-document.ts +16 -9
- package/src/hooks/use-vies-check.ts +3 -0
|
@@ -36,7 +36,8 @@ export default {
|
|
|
36
36
|
"Valid until": "Veljavno do",
|
|
37
37
|
Customer: "Stranka",
|
|
38
38
|
Subtotal: "Vmesni seštevek",
|
|
39
|
-
Tax: "
|
|
39
|
+
Tax: "DDV",
|
|
40
|
+
of: "od",
|
|
40
41
|
Total: "Skupaj",
|
|
41
42
|
Paid: "Plačano",
|
|
42
43
|
Due: "Za plačilo",
|
|
@@ -107,6 +108,7 @@ export default {
|
|
|
107
108
|
Pending: "V čakanju",
|
|
108
109
|
Failed: "Neuspešno",
|
|
109
110
|
Skipped: "Preskočeno",
|
|
111
|
+
"Skipped by user": "Uporabnik preskočil",
|
|
110
112
|
"Retry fiscalization": "Ponovi fiskalizacijo",
|
|
111
113
|
|
|
112
114
|
// PDF language names
|
|
@@ -36,9 +36,11 @@ export function useDocumentDownload({
|
|
|
36
36
|
const [isDownloadingEslog, setIsDownloadingEslog] = useState(false);
|
|
37
37
|
|
|
38
38
|
/**
|
|
39
|
-
* Download PDF
|
|
39
|
+
* Download PDF with optional language override for translations
|
|
40
|
+
* Formatting (decimal separators, dates) always uses entity locale.
|
|
41
|
+
* The language param only changes labels/translations on the PDF.
|
|
40
42
|
*/
|
|
41
|
-
const downloadPdf = async (document: Document, documentType: DocumentType,
|
|
43
|
+
const downloadPdf = async (document: Document, documentType: DocumentType, language?: string) => {
|
|
42
44
|
if (!sdk || !activeEntity?.id) {
|
|
43
45
|
onDownloadError?.("Download failed");
|
|
44
46
|
return;
|
|
@@ -51,7 +53,8 @@ export function useDocumentDownload({
|
|
|
51
53
|
// SDK signature: renderPdf(id, params?, SDKMethodOptions?)
|
|
52
54
|
// entity_id goes in SDKMethodOptions (last arg), not params
|
|
53
55
|
// Note: renderPdf is on invoices module but works with any document ID via /documents/{id}/pdf
|
|
54
|
-
|
|
56
|
+
// Don't send locale — entity locale drives formatting. Send language for translation override.
|
|
57
|
+
const params = language ? { language } : {};
|
|
55
58
|
const blob = await sdk.invoices.renderPdf(document.id, params, { entity_id: activeEntity.id });
|
|
56
59
|
const downloadUrl = window.URL.createObjectURL(blob);
|
|
57
60
|
const link = window.document.createElement("a");
|
|
@@ -99,10 +99,11 @@ export function CreateEntityForm({
|
|
|
99
99
|
useEffect(() => {
|
|
100
100
|
if (countryValue !== autoFilledCountryRef.current) {
|
|
101
101
|
setActiveCountryCode(undefined);
|
|
102
|
+
form.setValue("country_code", "");
|
|
102
103
|
} else {
|
|
103
104
|
setActiveCountryCode(countryCode);
|
|
104
105
|
}
|
|
105
|
-
}, [countryValue, countryCode]);
|
|
106
|
+
}, [countryValue, countryCode, form]);
|
|
106
107
|
|
|
107
108
|
const handleCompanySelect = (company: CompanyRegistryResult) => {
|
|
108
109
|
form.setValue("name", company.name);
|
|
@@ -17,9 +17,10 @@ interface InputWithPreviewProps {
|
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
function formatVariableName(varName: string): string {
|
|
20
|
-
// Convert snake_case to Title Case with spaces
|
|
21
|
-
// e.g., "document_number" -> "Document Number"
|
|
20
|
+
// Convert snake_case (with optional dot notation) to Title Case with spaces
|
|
21
|
+
// e.g., "document_number" -> "Document Number", "bank_account.iban" -> "Bank Account Iban"
|
|
22
22
|
return varName
|
|
23
|
+
.replace(/\./g, "_")
|
|
23
24
|
.split("_")
|
|
24
25
|
.map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
|
|
25
26
|
.join(" ");
|
|
@@ -78,6 +79,33 @@ function getVariableValue(varName: string, entity: Entity, document?: Invoice |
|
|
|
78
79
|
}
|
|
79
80
|
}
|
|
80
81
|
|
|
82
|
+
// Bank account variables (from entity settings)
|
|
83
|
+
const bankAccounts = (entity.settings as any)?.bank_accounts as
|
|
84
|
+
| Array<{
|
|
85
|
+
iban?: string;
|
|
86
|
+
bank_name?: string;
|
|
87
|
+
bic?: string;
|
|
88
|
+
account_number?: string;
|
|
89
|
+
routing_number?: string;
|
|
90
|
+
sort_code?: string;
|
|
91
|
+
is_default?: boolean;
|
|
92
|
+
}>
|
|
93
|
+
| undefined;
|
|
94
|
+
const bankAccount = bankAccounts?.find((acc) => acc.is_default) ?? bankAccounts?.[0];
|
|
95
|
+
|
|
96
|
+
if (varName === "bank_account" && bankAccount) {
|
|
97
|
+
const lines: string[] = [];
|
|
98
|
+
if (bankAccount.bank_name) lines.push(bankAccount.bank_name);
|
|
99
|
+
if (bankAccount.iban) lines.push(`IBAN: ${bankAccount.iban}`);
|
|
100
|
+
else if (bankAccount.account_number) lines.push(`Account: ${bankAccount.account_number}`);
|
|
101
|
+
if (bankAccount.bic) lines.push(`BIC: ${bankAccount.bic}`);
|
|
102
|
+
return lines.join(", ") || null;
|
|
103
|
+
}
|
|
104
|
+
if (varName === "bank_account.iban") return bankAccount?.iban || null;
|
|
105
|
+
if (varName === "bank_account.bank_name") return bankAccount?.bank_name || null;
|
|
106
|
+
if (varName === "bank_account.bic") return bankAccount?.bic || null;
|
|
107
|
+
if (varName === "bank_account.account_number") return bankAccount?.account_number || null;
|
|
108
|
+
|
|
81
109
|
// Return null for unavailable variables - they will show as placeholders
|
|
82
110
|
return null;
|
|
83
111
|
}
|
|
@@ -2,6 +2,7 @@ import { zodResolver } from "@hookform/resolvers/zod";
|
|
|
2
2
|
import type { Entity } from "@spaceinvoices/js-sdk";
|
|
3
3
|
import { useForm } from "react-hook-form";
|
|
4
4
|
import { z } from "zod";
|
|
5
|
+
import { Checkbox } from "@/ui/components/ui/checkbox";
|
|
5
6
|
import {
|
|
6
7
|
Form,
|
|
7
8
|
FormControl,
|
|
@@ -24,12 +25,23 @@ const translations = { sl, de } as const;
|
|
|
24
25
|
const companySettingsSchema = z.object({
|
|
25
26
|
name: z.string().min(1, "Name is required"),
|
|
26
27
|
tax_number: z.union([z.string(), z.null()]).optional(),
|
|
28
|
+
is_tax_subject: z.boolean(),
|
|
27
29
|
tax_number_2: z.union([z.string(), z.null()]).optional(),
|
|
28
30
|
address: z.union([z.string(), z.null()]).optional(),
|
|
29
31
|
address_2: z.union([z.string(), z.null()]).optional(),
|
|
30
32
|
post_code: z.union([z.string(), z.null()]).optional(),
|
|
31
33
|
city: z.union([z.string(), z.null()]).optional(),
|
|
32
34
|
state: z.union([z.string(), z.null()]).optional(),
|
|
35
|
+
// Bank account fields (stored in settings.bank_accounts array)
|
|
36
|
+
bank_account_iban: z
|
|
37
|
+
.union([z.string(), z.null()])
|
|
38
|
+
.refine((val) => !val || /^[A-Z]{2}[0-9A-Z]{2,32}$/.test(val.replace(/\s/g, "")), {
|
|
39
|
+
message: "Must be a valid IBAN",
|
|
40
|
+
})
|
|
41
|
+
.optional(),
|
|
42
|
+
bank_account_name: z.union([z.string(), z.null()]).optional(),
|
|
43
|
+
bank_account_bank_name: z.union([z.string(), z.null()]).optional(),
|
|
44
|
+
bank_account_bic: z.union([z.string(), z.null()]).optional(),
|
|
33
45
|
});
|
|
34
46
|
|
|
35
47
|
type CompanySettingsSchema = z.infer<typeof companySettingsSchema>;
|
|
@@ -50,17 +62,24 @@ export function CompanySettingsForm({
|
|
|
50
62
|
}: CompanySettingsFormProps) {
|
|
51
63
|
const t = createTranslation({ t: translateProp, namespace, locale, translations });
|
|
52
64
|
|
|
65
|
+
const currentSettings = (entity.settings as any) || {};
|
|
66
|
+
|
|
53
67
|
const form = useForm<CompanySettingsSchema>({
|
|
54
68
|
resolver: zodResolver(companySettingsSchema),
|
|
55
69
|
defaultValues: {
|
|
56
70
|
name: entity.name || "",
|
|
57
71
|
tax_number: (entity as any).tax_number || null,
|
|
72
|
+
is_tax_subject: entity.is_tax_subject ?? true,
|
|
58
73
|
tax_number_2: (entity as any).tax_number_2 || null,
|
|
59
74
|
address: (entity as any).address || null,
|
|
60
75
|
address_2: (entity as any).address_2 || null,
|
|
61
76
|
post_code: (entity as any).post_code || null,
|
|
62
77
|
city: (entity as any).city || null,
|
|
63
78
|
state: (entity as any).state || null,
|
|
79
|
+
bank_account_iban: currentSettings.bank_accounts?.[0]?.iban || null,
|
|
80
|
+
bank_account_name: currentSettings.bank_accounts?.[0]?.name || null,
|
|
81
|
+
bank_account_bank_name: currentSettings.bank_accounts?.[0]?.bank_name || null,
|
|
82
|
+
bank_account_bic: currentSettings.bank_accounts?.[0]?.bic || null,
|
|
64
83
|
},
|
|
65
84
|
});
|
|
66
85
|
|
|
@@ -85,6 +104,7 @@ export function CompanySettingsForm({
|
|
|
85
104
|
|
|
86
105
|
if (values.name !== entity.name) updatePayload.name = values.name;
|
|
87
106
|
if (values.tax_number !== (entity as any).tax_number) updatePayload.tax_number = values.tax_number;
|
|
107
|
+
if (values.is_tax_subject !== entity.is_tax_subject) updatePayload.is_tax_subject = values.is_tax_subject;
|
|
88
108
|
if (values.tax_number_2 !== (entity as any).tax_number_2) updatePayload.tax_number_2 = values.tax_number_2;
|
|
89
109
|
if (values.address !== (entity as any).address) updatePayload.address = values.address;
|
|
90
110
|
if (values.address_2 !== (entity as any).address_2) updatePayload.address_2 = values.address_2;
|
|
@@ -92,6 +112,37 @@ export function CompanySettingsForm({
|
|
|
92
112
|
if (values.city !== (entity as any).city) updatePayload.city = values.city;
|
|
93
113
|
if (values.state !== (entity as any).state) updatePayload.state = values.state;
|
|
94
114
|
|
|
115
|
+
// Check if bank account fields changed
|
|
116
|
+
const currentIban = currentSettings.bank_accounts?.[0]?.iban || null;
|
|
117
|
+
const currentBankName = currentSettings.bank_accounts?.[0]?.name || null;
|
|
118
|
+
const currentBankBankName = currentSettings.bank_accounts?.[0]?.bank_name || null;
|
|
119
|
+
const currentBic = currentSettings.bank_accounts?.[0]?.bic || null;
|
|
120
|
+
|
|
121
|
+
const bankChanged =
|
|
122
|
+
values.bank_account_iban !== currentIban ||
|
|
123
|
+
values.bank_account_name !== currentBankName ||
|
|
124
|
+
values.bank_account_bank_name !== currentBankBankName ||
|
|
125
|
+
values.bank_account_bic !== currentBic;
|
|
126
|
+
|
|
127
|
+
if (bankChanged) {
|
|
128
|
+
updatePayload.settings = {
|
|
129
|
+
...currentSettings,
|
|
130
|
+
bank_accounts: values.bank_account_iban
|
|
131
|
+
? [
|
|
132
|
+
{
|
|
133
|
+
type: "iban" as const,
|
|
134
|
+
iban: values.bank_account_iban,
|
|
135
|
+
name: values.bank_account_name || undefined,
|
|
136
|
+
bank_name: values.bank_account_bank_name || undefined,
|
|
137
|
+
bic: values.bank_account_bic || undefined,
|
|
138
|
+
is_default: true,
|
|
139
|
+
},
|
|
140
|
+
...(currentSettings.bank_accounts?.slice(1) || []),
|
|
141
|
+
]
|
|
142
|
+
: currentSettings.bank_accounts || undefined,
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
95
146
|
if (Object.keys(updatePayload).length > 0) {
|
|
96
147
|
updateEntity({ id: entity.id, data: updatePayload });
|
|
97
148
|
} else {
|
|
@@ -117,26 +168,40 @@ export function CompanySettingsForm({
|
|
|
117
168
|
)}
|
|
118
169
|
/>
|
|
119
170
|
|
|
120
|
-
<
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
<
|
|
126
|
-
|
|
127
|
-
<
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
171
|
+
<div className="grid grid-cols-[1fr_auto] items-end gap-4">
|
|
172
|
+
<FormField
|
|
173
|
+
control={form.control}
|
|
174
|
+
name="tax_number"
|
|
175
|
+
render={({ field }) => (
|
|
176
|
+
<FormItem className="max-w-xs">
|
|
177
|
+
<FormLabel className="font-medium text-base">{t("Tax ID")}</FormLabel>
|
|
178
|
+
<FormControl>
|
|
179
|
+
<Input
|
|
180
|
+
{...field}
|
|
181
|
+
value={field.value || ""}
|
|
182
|
+
onChange={(e) => field.onChange(e.target.value || null)}
|
|
183
|
+
placeholder="12-3456789"
|
|
184
|
+
className="h-10"
|
|
185
|
+
/>
|
|
186
|
+
</FormControl>
|
|
187
|
+
<FormDescription className="text-xs">{t("Tax identification number (optional)")}</FormDescription>
|
|
188
|
+
<FormMessage />
|
|
189
|
+
</FormItem>
|
|
190
|
+
)}
|
|
191
|
+
/>
|
|
192
|
+
<FormField
|
|
193
|
+
control={form.control}
|
|
194
|
+
name="is_tax_subject"
|
|
195
|
+
render={({ field }) => (
|
|
196
|
+
<FormItem className="flex flex-row items-center space-x-2 space-y-0 pb-7">
|
|
197
|
+
<FormControl>
|
|
198
|
+
<Checkbox checked={field.value} onCheckedChange={field.onChange} />
|
|
199
|
+
</FormControl>
|
|
200
|
+
<FormLabel className="font-normal">{t("Tax subject")}</FormLabel>
|
|
201
|
+
</FormItem>
|
|
202
|
+
)}
|
|
203
|
+
/>
|
|
204
|
+
</div>
|
|
140
205
|
|
|
141
206
|
{(entity as any).country_rules?.features?.includes("tax_number_2") && (
|
|
142
207
|
<FormField
|
|
@@ -278,6 +343,94 @@ export function CompanySettingsForm({
|
|
|
278
343
|
<FormDescription className="text-xs">{t("Country cannot be changed")}</FormDescription>
|
|
279
344
|
</FormItem>
|
|
280
345
|
</div>
|
|
346
|
+
|
|
347
|
+
<div className="border-t pt-6">
|
|
348
|
+
<p className="mb-4 font-medium text-base">{t("Bank Account")}</p>
|
|
349
|
+
|
|
350
|
+
<div className="space-y-4">
|
|
351
|
+
<FormField
|
|
352
|
+
control={form.control}
|
|
353
|
+
name="bank_account_iban"
|
|
354
|
+
render={({ field }) => (
|
|
355
|
+
<FormItem>
|
|
356
|
+
<FormLabel>{t("IBAN")}</FormLabel>
|
|
357
|
+
<FormControl>
|
|
358
|
+
<Input
|
|
359
|
+
{...field}
|
|
360
|
+
value={field.value || ""}
|
|
361
|
+
onChange={(e) => field.onChange(e.target.value.toUpperCase().replace(/\s/g, "") || null)}
|
|
362
|
+
placeholder="SI56 0123 4567 8901 234"
|
|
363
|
+
className="h-10 font-mono"
|
|
364
|
+
/>
|
|
365
|
+
</FormControl>
|
|
366
|
+
<FormMessage />
|
|
367
|
+
</FormItem>
|
|
368
|
+
)}
|
|
369
|
+
/>
|
|
370
|
+
|
|
371
|
+
<FormField
|
|
372
|
+
control={form.control}
|
|
373
|
+
name="bank_account_name"
|
|
374
|
+
render={({ field }) => (
|
|
375
|
+
<FormItem>
|
|
376
|
+
<FormLabel>{t("Account Name")}</FormLabel>
|
|
377
|
+
<FormControl>
|
|
378
|
+
<Input
|
|
379
|
+
{...field}
|
|
380
|
+
value={field.value || ""}
|
|
381
|
+
onChange={(e) => field.onChange(e.target.value || null)}
|
|
382
|
+
placeholder={t("Main Business Account")}
|
|
383
|
+
className="h-10"
|
|
384
|
+
/>
|
|
385
|
+
</FormControl>
|
|
386
|
+
<FormMessage />
|
|
387
|
+
</FormItem>
|
|
388
|
+
)}
|
|
389
|
+
/>
|
|
390
|
+
|
|
391
|
+
<div className="grid gap-4 md:grid-cols-2">
|
|
392
|
+
<FormField
|
|
393
|
+
control={form.control}
|
|
394
|
+
name="bank_account_bank_name"
|
|
395
|
+
render={({ field }) => (
|
|
396
|
+
<FormItem>
|
|
397
|
+
<FormLabel>{t("Bank Name")}</FormLabel>
|
|
398
|
+
<FormControl>
|
|
399
|
+
<Input
|
|
400
|
+
{...field}
|
|
401
|
+
value={field.value || ""}
|
|
402
|
+
onChange={(e) => field.onChange(e.target.value || null)}
|
|
403
|
+
placeholder="NLB d.d."
|
|
404
|
+
className="h-10"
|
|
405
|
+
/>
|
|
406
|
+
</FormControl>
|
|
407
|
+
<FormMessage />
|
|
408
|
+
</FormItem>
|
|
409
|
+
)}
|
|
410
|
+
/>
|
|
411
|
+
|
|
412
|
+
<FormField
|
|
413
|
+
control={form.control}
|
|
414
|
+
name="bank_account_bic"
|
|
415
|
+
render={({ field }) => (
|
|
416
|
+
<FormItem>
|
|
417
|
+
<FormLabel>{t("BIC/SWIFT")}</FormLabel>
|
|
418
|
+
<FormControl>
|
|
419
|
+
<Input
|
|
420
|
+
{...field}
|
|
421
|
+
value={field.value || ""}
|
|
422
|
+
onChange={(e) => field.onChange(e.target.value.toUpperCase() || null)}
|
|
423
|
+
placeholder="LJBASI2X"
|
|
424
|
+
className="h-10 font-mono"
|
|
425
|
+
/>
|
|
426
|
+
</FormControl>
|
|
427
|
+
<FormMessage />
|
|
428
|
+
</FormItem>
|
|
429
|
+
)}
|
|
430
|
+
/>
|
|
431
|
+
</div>
|
|
432
|
+
</div>
|
|
433
|
+
</div>
|
|
281
434
|
</form>
|
|
282
435
|
</Form>
|
|
283
436
|
);
|
|
@@ -11,6 +11,7 @@ import { Form } from "@/ui/components/ui/form";
|
|
|
11
11
|
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/ui/components/ui/tooltip";
|
|
12
12
|
import { createEstimateSchema } from "@/ui/generated/schemas";
|
|
13
13
|
import { useNextDocumentNumber } from "@/ui/hooks/use-next-document-number";
|
|
14
|
+
import { useViesCheck } from "@/ui/hooks/use-vies-check";
|
|
14
15
|
import type { ComponentTranslationProps } from "@/ui/lib/translation";
|
|
15
16
|
import { createTranslation } from "@/ui/lib/translation";
|
|
16
17
|
import { useEntities } from "@/ui/providers/entities-context";
|
|
@@ -20,6 +21,7 @@ import {
|
|
|
20
21
|
DocumentDetailsSection,
|
|
21
22
|
DocumentNoteField,
|
|
22
23
|
DocumentPaymentTermsField,
|
|
24
|
+
DocumentTaxClauseField,
|
|
23
25
|
} from "../../documents/create/document-details-section";
|
|
24
26
|
import { DocumentItemsSection, type PriceModesMap } from "../../documents/create/document-items-section";
|
|
25
27
|
import { DocumentRecipientSection } from "../../documents/create/document-recipient-section";
|
|
@@ -126,13 +128,18 @@ export default function CreateEstimateForm({
|
|
|
126
128
|
// Cast customer to form schema type (API type may have additional fields)
|
|
127
129
|
customer: (initialValues?.customer as CreateEstimateFormValues["customer"]) ?? undefined,
|
|
128
130
|
items: initialValues?.items?.length
|
|
129
|
-
? initialValues.items.map((item) => ({
|
|
131
|
+
? initialValues.items.map((item: any) => ({
|
|
132
|
+
type: item.type,
|
|
130
133
|
name: item.name || "",
|
|
131
134
|
description: item.description || "",
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
135
|
+
...(item.type !== "separator"
|
|
136
|
+
? {
|
|
137
|
+
quantity: item.quantity ?? 1,
|
|
138
|
+
// Use gross_price if set, otherwise use price
|
|
139
|
+
price: item.gross_price ?? item.price,
|
|
140
|
+
taxes: item.taxes || [],
|
|
141
|
+
}
|
|
142
|
+
: {}),
|
|
136
143
|
}))
|
|
137
144
|
: [
|
|
138
145
|
{
|
|
@@ -145,6 +152,7 @@ export default function CreateEstimateForm({
|
|
|
145
152
|
],
|
|
146
153
|
currency_code: initialValues?.currency_code || activeEntity?.currency_code || "EUR",
|
|
147
154
|
note: initialValues?.note ?? defaultEstimateNote,
|
|
155
|
+
tax_clause: (initialValues as any)?.tax_clause ?? "",
|
|
148
156
|
payment_terms: initialValues?.payment_terms ?? defaultPaymentTerms,
|
|
149
157
|
date_valid_till:
|
|
150
158
|
initialValues?.date_valid_till ||
|
|
@@ -234,6 +242,37 @@ export default function CreateEstimateForm({
|
|
|
234
242
|
control: form.control,
|
|
235
243
|
});
|
|
236
244
|
|
|
245
|
+
// ============================================================================
|
|
246
|
+
// VIES Check - determine if reverse charge applies
|
|
247
|
+
// ============================================================================
|
|
248
|
+
const {
|
|
249
|
+
reverseChargeApplies,
|
|
250
|
+
transactionType,
|
|
251
|
+
isFetching: isViesFetching,
|
|
252
|
+
warning: viesWarning,
|
|
253
|
+
} = useViesCheck({
|
|
254
|
+
issuerCountryCode: activeEntity?.country_code,
|
|
255
|
+
isTaxSubject: activeEntity?.is_tax_subject ?? true,
|
|
256
|
+
customerCountry: formValues.customer?.country,
|
|
257
|
+
customerCountryCode: formValues.customer?.country_code,
|
|
258
|
+
customerTaxNumber: formValues.customer?.tax_number,
|
|
259
|
+
enabled: !!activeEntity,
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
// Auto-populate tax_clause from entity settings when transaction type changes
|
|
263
|
+
const effectiveTransactionType = transactionType ?? "domestic";
|
|
264
|
+
const prevTransactionTypeRef = useRef<string | undefined>(undefined);
|
|
265
|
+
useEffect(() => {
|
|
266
|
+
if (effectiveTransactionType === prevTransactionTypeRef.current) return;
|
|
267
|
+
prevTransactionTypeRef.current = effectiveTransactionType;
|
|
268
|
+
|
|
269
|
+
const taxClauseDefaults = (activeEntity?.settings as any)?.tax_clause_defaults;
|
|
270
|
+
if (!taxClauseDefaults) return;
|
|
271
|
+
|
|
272
|
+
const clause = taxClauseDefaults[effectiveTransactionType] ?? "";
|
|
273
|
+
form.setValue("tax_clause", clause);
|
|
274
|
+
}, [effectiveTransactionType, activeEntity, form]);
|
|
275
|
+
|
|
237
276
|
// Extract customer management logic into a custom hook
|
|
238
277
|
const {
|
|
239
278
|
originalCustomer,
|
|
@@ -304,7 +343,7 @@ export default function CreateEstimateForm({
|
|
|
304
343
|
useFormFooterRegistration({
|
|
305
344
|
formId: "create-estimate-form",
|
|
306
345
|
isPending,
|
|
307
|
-
isDirty: form.formState.isDirty,
|
|
346
|
+
isDirty: form.formState.isDirty || !!initialValues,
|
|
308
347
|
label: titleType === "estimate" ? t("Create Estimate") : t("Create Quote"),
|
|
309
348
|
secondaryAction,
|
|
310
349
|
});
|
|
@@ -392,6 +431,10 @@ export default function CreateEstimateForm({
|
|
|
392
431
|
maxTaxesPerItem={activeEntity?.country_rules?.max_taxes_per_item}
|
|
393
432
|
priceModesRef={priceModesRef}
|
|
394
433
|
initialPriceModes={initialPriceModes}
|
|
434
|
+
taxesDisabled={reverseChargeApplies}
|
|
435
|
+
taxesDisabledMessage={
|
|
436
|
+
reverseChargeApplies ? t("Reverse charge - tax exempt EU B2B sale") : viesWarning ? viesWarning : undefined
|
|
437
|
+
}
|
|
395
438
|
/>
|
|
396
439
|
|
|
397
440
|
<DocumentNoteField
|
|
@@ -407,6 +450,21 @@ export default function CreateEstimateForm({
|
|
|
407
450
|
}}
|
|
408
451
|
/>
|
|
409
452
|
|
|
453
|
+
<DocumentTaxClauseField
|
|
454
|
+
control={form.control}
|
|
455
|
+
t={t}
|
|
456
|
+
entity={activeEntity}
|
|
457
|
+
document={{
|
|
458
|
+
number: formValues.number,
|
|
459
|
+
date: formValues.date,
|
|
460
|
+
date_valid_till: formValues.date_valid_till,
|
|
461
|
+
currency_code: formValues.currency_code,
|
|
462
|
+
customer: formValues.customer as any,
|
|
463
|
+
}}
|
|
464
|
+
transactionType={transactionType}
|
|
465
|
+
isTransactionTypeFetching={isViesFetching}
|
|
466
|
+
/>
|
|
467
|
+
|
|
410
468
|
<DocumentPaymentTermsField
|
|
411
469
|
control={form.control}
|
|
412
470
|
t={t}
|
|
@@ -61,4 +61,20 @@ export default {
|
|
|
61
61
|
"Net price": "Nettopreis",
|
|
62
62
|
"Gross price (tax included)": "Bruttopreis (inkl. MwSt.)",
|
|
63
63
|
"Net price (before tax)": "Nettopreis (exkl. MwSt.)",
|
|
64
|
+
// Separator items
|
|
65
|
+
"Add separator": "Trennzeile hinzufügen",
|
|
66
|
+
"Section header": "Abschnittsüberschrift",
|
|
67
|
+
"Section title...": "Abschnittstitel...",
|
|
68
|
+
// Transaction type
|
|
69
|
+
"Transaction type": "Transaktionstyp",
|
|
70
|
+
Domestic: "Inland",
|
|
71
|
+
"EU B2B": "EU B2B",
|
|
72
|
+
"EU B2C": "EU B2C",
|
|
73
|
+
Export: "Export",
|
|
74
|
+
"Determining transaction type...": "Transaktionstyp wird ermittelt...",
|
|
75
|
+
"This invoice will not be fiscalized (non-domestic transaction)":
|
|
76
|
+
"Diese Rechnung wird nicht fiskalisiert (nicht-inländische Transaktion)",
|
|
77
|
+
"Tax Clause": "Steuerklausel",
|
|
78
|
+
"Add tax clause...": "Steuerklausel hinzufügen...",
|
|
79
|
+
"Reverse charge - tax exempt EU B2B sale": "Umkehrung der Steuerschuldnerschaft - steuerbefreiter EU B2B Verkauf",
|
|
64
80
|
} as const;
|
|
@@ -54,4 +54,20 @@ export default {
|
|
|
54
54
|
"Net price": "Precio neto",
|
|
55
55
|
"Gross price (tax included)": "Precio bruto (impuestos incluidos)",
|
|
56
56
|
"Net price (before tax)": "Precio neto (antes de impuestos)",
|
|
57
|
+
// Separator items
|
|
58
|
+
"Add separator": "Añadir separador",
|
|
59
|
+
"Section header": "Encabezado de sección",
|
|
60
|
+
"Section title...": "Título de sección...",
|
|
61
|
+
// Transaction type
|
|
62
|
+
"Transaction type": "Tipo de transacción",
|
|
63
|
+
Domestic: "Nacional",
|
|
64
|
+
"EU B2B": "EU B2B",
|
|
65
|
+
"EU B2C": "EU B2C",
|
|
66
|
+
Export: "Exportación",
|
|
67
|
+
"Determining transaction type...": "Determinando tipo de transacción...",
|
|
68
|
+
"This invoice will not be fiscalized (non-domestic transaction)":
|
|
69
|
+
"Esta factura no será fiscalizada (transacción no nacional)",
|
|
70
|
+
"Tax Clause": "Cláusula fiscal",
|
|
71
|
+
"Add tax clause...": "Agregar cláusula fiscal...",
|
|
72
|
+
"Reverse charge - tax exempt EU B2B sale": "Inversión del sujeto pasivo - venta EU B2B exenta de impuestos",
|
|
57
73
|
} as const;
|
|
@@ -55,4 +55,20 @@ export default {
|
|
|
55
55
|
"Net price": "Prix net",
|
|
56
56
|
"Gross price (tax included)": "Prix brut (taxes incluses)",
|
|
57
57
|
"Net price (before tax)": "Prix net (avant taxes)",
|
|
58
|
+
// Separator items
|
|
59
|
+
"Add separator": "Ajouter un séparateur",
|
|
60
|
+
"Section header": "En-tête de section",
|
|
61
|
+
"Section title...": "Titre de section...",
|
|
62
|
+
// Transaction type
|
|
63
|
+
"Transaction type": "Type de transaction",
|
|
64
|
+
Domestic: "Nationale",
|
|
65
|
+
"EU B2B": "EU B2B",
|
|
66
|
+
"EU B2C": "EU B2C",
|
|
67
|
+
Export: "Exportation",
|
|
68
|
+
"Determining transaction type...": "Détermination du type de transaction...",
|
|
69
|
+
"This invoice will not be fiscalized (non-domestic transaction)":
|
|
70
|
+
"Cette facture ne sera pas fiscalisée (transaction non nationale)",
|
|
71
|
+
"Tax Clause": "Clause fiscale",
|
|
72
|
+
"Add tax clause...": "Ajouter une clause fiscale...",
|
|
73
|
+
"Reverse charge - tax exempt EU B2B sale": "Autoliquidation - vente B2B UE exonérée de taxe",
|
|
58
74
|
} as const;
|
|
@@ -54,4 +54,20 @@ export default {
|
|
|
54
54
|
"Net price": "Neto cijena",
|
|
55
55
|
"Gross price (tax included)": "Bruto cijena (s porezom)",
|
|
56
56
|
"Net price (before tax)": "Neto cijena (prije poreza)",
|
|
57
|
+
// Separator items
|
|
58
|
+
"Add separator": "Dodaj separator",
|
|
59
|
+
"Section header": "Naslov odjeljka",
|
|
60
|
+
"Section title...": "Naslov odjeljka...",
|
|
61
|
+
// Transaction type
|
|
62
|
+
"Transaction type": "Vrsta transakcije",
|
|
63
|
+
Domestic: "Domaća",
|
|
64
|
+
"EU B2B": "EU B2B",
|
|
65
|
+
"EU B2C": "EU B2C",
|
|
66
|
+
Export: "Izvoz",
|
|
67
|
+
"Determining transaction type...": "Određivanje vrste transakcije...",
|
|
68
|
+
"This invoice will not be fiscalized (non-domestic transaction)":
|
|
69
|
+
"Ovaj račun neće biti fiskaliziran (nedomaća transakcija)",
|
|
70
|
+
"Tax Clause": "Porezna klauzula",
|
|
71
|
+
"Add tax clause...": "Dodajte poreznu klauzulu...",
|
|
72
|
+
"Reverse charge - tax exempt EU B2B sale": "Prijenos porezne obveze - porezno oslobođena EU B2B prodaja",
|
|
57
73
|
} as const;
|