@spaceinvoices/react-ui 0.4.4 → 0.4.6
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/advance-invoices.hooks.ts +2 -2
- package/src/components/advance-invoices/create/create-advance-invoice-form.tsx +151 -45
- package/src/components/advance-invoices/create/locales/de.ts +20 -0
- package/src/components/advance-invoices/create/locales/es.ts +20 -0
- package/src/components/advance-invoices/create/locales/fr.ts +20 -0
- package/src/components/advance-invoices/create/locales/hr.ts +20 -0
- package/src/components/advance-invoices/create/locales/it.ts +20 -0
- package/src/components/advance-invoices/create/locales/nl.ts +20 -0
- package/src/components/advance-invoices/create/locales/pl.ts +20 -0
- package/src/components/advance-invoices/create/locales/pt.ts +20 -0
- package/src/components/advance-invoices/create/locales/sl.ts +20 -0
- package/src/components/advance-invoices/create/prepare-advance-invoice-submission.ts +5 -5
- 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 +161 -42
- package/src/components/credit-notes/create/locales/de.ts +21 -0
- package/src/components/credit-notes/create/locales/es.ts +21 -0
- package/src/components/credit-notes/create/locales/fr.ts +21 -0
- package/src/components/credit-notes/create/locales/hr.ts +21 -0
- package/src/components/credit-notes/create/locales/it.ts +21 -0
- package/src/components/credit-notes/create/locales/nl.ts +21 -0
- package/src/components/credit-notes/create/locales/pl.ts +21 -0
- package/src/components/credit-notes/create/locales/pt.ts +21 -0
- package/src/components/credit-notes/create/locales/sl.ts +22 -1
- package/src/components/credit-notes/credit-notes.hooks.ts +2 -2
- 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 +114 -7
- package/src/components/delivery-notes/create/locales/de.ts +21 -0
- package/src/components/delivery-notes/create/locales/es.ts +21 -0
- package/src/components/delivery-notes/create/locales/fr.ts +21 -0
- package/src/components/delivery-notes/create/locales/hr.ts +21 -0
- package/src/components/delivery-notes/create/locales/it.ts +21 -0
- package/src/components/delivery-notes/create/locales/nl.ts +21 -0
- package/src/components/delivery-notes/create/locales/pl.ts +21 -0
- package/src/components/delivery-notes/create/locales/pt.ts +21 -0
- package/src/components/delivery-notes/create/locales/sl.ts +22 -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 +590 -344
- 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 +22 -8
- package/src/components/documents/create/smart-code-insert-button.tsx +6 -0
- 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 +32 -9
- package/src/components/documents/view/locales/de.ts +3 -0
- package/src/components/documents/view/locales/es.ts +3 -0
- package/src/components/documents/view/locales/fr.ts +3 -0
- package/src/components/documents/view/locales/hr.ts +3 -0
- package/src/components/documents/view/locales/it.ts +3 -0
- package/src/components/documents/view/locales/nl.ts +3 -0
- package/src/components/documents/view/locales/pl.ts +3 -0
- package/src/components/documents/view/locales/pt.ts +3 -0
- package/src/components/documents/view/locales/sl.ts +4 -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/email-template-variables-info.tsx +6 -0
- package/src/components/entities/entity-settings-form/input-with-preview.tsx +2 -117
- package/src/components/entities/entity-settings-form/locales/de.ts +5 -0
- package/src/components/entities/entity-settings-form/locales/es.ts +5 -0
- package/src/components/entities/entity-settings-form/locales/fr.ts +5 -0
- package/src/components/entities/entity-settings-form/locales/hr.ts +5 -0
- package/src/components/entities/entity-settings-form/locales/it.ts +5 -0
- package/src/components/entities/entity-settings-form/locales/nl.ts +5 -0
- package/src/components/entities/entity-settings-form/locales/pl.ts +5 -0
- package/src/components/entities/entity-settings-form/locales/pt.ts +5 -0
- package/src/components/entities/entity-settings-form/locales/sl.ts +5 -0
- package/src/components/entities/fina-settings-form/fina-settings-form.tsx +15 -0
- package/src/components/entities/fina-settings-form/fina-settings.hooks.ts +5 -1
- package/src/components/entities/fina-settings-form/locales/de.ts +3 -0
- package/src/components/entities/fina-settings-form/locales/en.ts +3 -0
- package/src/components/entities/fina-settings-form/locales/es.ts +3 -0
- package/src/components/entities/fina-settings-form/locales/fr.ts +3 -0
- package/src/components/entities/fina-settings-form/locales/hr.ts +3 -0
- package/src/components/entities/fina-settings-form/locales/it.ts +3 -0
- package/src/components/entities/fina-settings-form/locales/nl.ts +3 -0
- package/src/components/entities/fina-settings-form/locales/pl.ts +3 -0
- package/src/components/entities/fina-settings-form/locales/pt.ts +3 -0
- package/src/components/entities/fina-settings-form/locales/sl.ts +3 -0
- package/src/components/entities/fina-settings-form/sections/premises-management-section.tsx +4 -4
- package/src/components/entities/fina-settings-form/sections/register-premise-dialog.tsx +3 -3
- package/src/components/entities/settings/company-settings-form.tsx +173 -20
- package/src/components/entities/settings/defaults-settings-form.tsx +38 -1
- package/src/components/entities/settings/tax-rules-settings-form.tsx +1 -2
- package/src/components/estimates/create/create-estimate-form.tsx +107 -8
- package/src/components/estimates/create/locales/de.ts +21 -0
- package/src/components/estimates/create/locales/es.ts +21 -0
- package/src/components/estimates/create/locales/fr.ts +21 -0
- package/src/components/estimates/create/locales/hr.ts +21 -0
- package/src/components/estimates/create/locales/it.ts +21 -0
- package/src/components/estimates/create/locales/nl.ts +21 -0
- package/src/components/estimates/create/locales/pl.ts +21 -0
- package/src/components/estimates/create/locales/pt.ts +21 -0
- package/src/components/estimates/create/locales/sl.ts +22 -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 +186 -48
- package/src/components/invoices/create/locales/de.ts +28 -0
- package/src/components/invoices/create/locales/es.ts +28 -0
- package/src/components/invoices/create/locales/fr.ts +28 -0
- package/src/components/invoices/create/locales/hr.ts +28 -0
- package/src/components/invoices/create/locales/it.ts +28 -0
- package/src/components/invoices/create/locales/nl.ts +28 -0
- package/src/components/invoices/create/locales/pl.ts +28 -0
- package/src/components/invoices/create/locales/pt.ts +28 -0
- package/src/components/invoices/create/locales/sl.ts +29 -1
- package/src/components/invoices/create/prepare-invoice-submission.ts +5 -5
- package/src/components/invoices/invoices.hooks.ts +2 -2
- package/src/components/invoices/view/fiscalization-status-card.tsx +10 -8
- package/src/components/table/search-input.tsx +17 -0
- package/src/components/table/table-pagination.tsx +1 -1
- package/src/generated/schemas/advanceinvoice.ts +6 -2
- package/src/generated/schemas/creditnote.ts +3 -1
- package/src/generated/schemas/deliverynote.ts +3 -1
- package/src/generated/schemas/entity.ts +4 -4
- package/src/generated/schemas/entityapikey.ts +19 -0
- package/src/generated/schemas/estimate.ts +6 -2
- package/src/generated/schemas/index.ts +1 -0
- package/src/generated/schemas/invoice.ts +4 -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/generated/schemas/startpdfexport_body.ts +14 -2
- package/src/generated/schemas/webhook.ts +4 -0
- package/src/hooks/use-duplicate-document.ts +16 -9
- package/src/hooks/use-vies-check.ts +3 -0
- package/src/lib/template-variables.tsx +167 -0
- package/src/providers/entities-context.tsx +2 -2
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Shared document items section for invoices and estimates
|
|
3
3
|
* Handles: item management (add, remove, reorder)
|
|
4
4
|
*/
|
|
5
|
-
import { PlusIcon } from "lucide-react";
|
|
5
|
+
import { PlusIcon, SeparatorHorizontal } from "lucide-react";
|
|
6
6
|
import type { MutableRefObject } from "react";
|
|
7
7
|
import type { UseFormGetValues, UseFormSetValue, UseFormWatch } from "react-hook-form";
|
|
8
8
|
import { Button } from "@/ui/components/ui/button";
|
|
@@ -65,6 +65,18 @@ export function DocumentItemsSection({
|
|
|
65
65
|
]);
|
|
66
66
|
};
|
|
67
67
|
|
|
68
|
+
const addSeparator = () => {
|
|
69
|
+
const currentItems = getValues("items") || [];
|
|
70
|
+
setValue("items", [
|
|
71
|
+
...currentItems,
|
|
72
|
+
{
|
|
73
|
+
type: "separator",
|
|
74
|
+
name: "",
|
|
75
|
+
description: "",
|
|
76
|
+
},
|
|
77
|
+
]);
|
|
78
|
+
};
|
|
79
|
+
|
|
68
80
|
const removeItem = (index: number) => {
|
|
69
81
|
const currentItems = getValues("items") || [];
|
|
70
82
|
setValue(
|
|
@@ -125,9 +137,14 @@ export function DocumentItemsSection({
|
|
|
125
137
|
</div>
|
|
126
138
|
))}
|
|
127
139
|
|
|
128
|
-
<
|
|
129
|
-
<
|
|
130
|
-
|
|
140
|
+
<div className="flex gap-2">
|
|
141
|
+
<Button type="button" variant="outline" onClick={addItem} className="flex-1 cursor-pointer border-dashed">
|
|
142
|
+
<PlusIcon className="mr-2 h-4 w-4" /> {t("Add item")}
|
|
143
|
+
</Button>
|
|
144
|
+
<Button type="button" variant="ghost" onClick={addSeparator} className="cursor-pointer text-muted-foreground">
|
|
145
|
+
<SeparatorHorizontal className="mr-2 h-4 w-4" /> {t("Add separator")}
|
|
146
|
+
</Button>
|
|
147
|
+
</div>
|
|
131
148
|
</div>
|
|
132
149
|
);
|
|
133
150
|
}
|
|
@@ -30,6 +30,12 @@ type LiveInvoicePreviewProps = {
|
|
|
30
30
|
documentTypeLabel?: string;
|
|
31
31
|
/** Document type to determine which render endpoint to use */
|
|
32
32
|
documentType?: DocumentTypes;
|
|
33
|
+
/** QR settings overrides for preview (before saving) */
|
|
34
|
+
qrOverrides?: {
|
|
35
|
+
upn_qr_enabled?: boolean;
|
|
36
|
+
upn_qr_display_mode?: "qr_only" | "full_slip";
|
|
37
|
+
epc_qr_enabled?: boolean;
|
|
38
|
+
};
|
|
33
39
|
};
|
|
34
40
|
|
|
35
41
|
/**
|
|
@@ -49,11 +55,12 @@ export function LiveInvoicePreview({
|
|
|
49
55
|
currency: _currency = "EUR",
|
|
50
56
|
template,
|
|
51
57
|
className,
|
|
52
|
-
locale,
|
|
58
|
+
locale: _locale,
|
|
53
59
|
fixedScale,
|
|
54
60
|
t: tProp,
|
|
55
61
|
documentTypeLabel,
|
|
56
62
|
documentType = "invoice",
|
|
63
|
+
qrOverrides,
|
|
57
64
|
}: LiveInvoicePreviewProps) {
|
|
58
65
|
const t = tProp ?? ((key: string) => key);
|
|
59
66
|
const [previewHtml, setPreviewHtml] = useState<string>("");
|
|
@@ -103,7 +110,7 @@ export function LiveInvoicePreview({
|
|
|
103
110
|
// Filter out unresolved tax_ids (race condition: form may add
|
|
104
111
|
// { tax_id: undefined } before the tax dropdown auto-selects a value)
|
|
105
112
|
items: filterUnresolvedTaxes(invoiceData.items),
|
|
106
|
-
issuer:
|
|
113
|
+
issuer: {
|
|
107
114
|
name: activeEntity.name,
|
|
108
115
|
address: activeEntity.address,
|
|
109
116
|
address_2: activeEntity.address_2,
|
|
@@ -112,11 +119,22 @@ export function LiveInvoicePreview({
|
|
|
112
119
|
state: activeEntity.state,
|
|
113
120
|
country: activeEntity.country,
|
|
114
121
|
tax_number: activeEntity.tax_number,
|
|
122
|
+
...invoiceData.issuer,
|
|
115
123
|
},
|
|
116
124
|
};
|
|
117
125
|
|
|
118
126
|
// Call the render API using the appropriate SDK method for the document type
|
|
119
|
-
|
|
127
|
+
// Don't send locale — let entity locale drive formatting (decimal separators, date format)
|
|
128
|
+
const renderParams: Record<string, any> = { partial: "true" as const, template };
|
|
129
|
+
if (qrOverrides?.upn_qr_enabled !== undefined) {
|
|
130
|
+
renderParams.upn_qr_enabled = qrOverrides.upn_qr_enabled ? "true" : "false";
|
|
131
|
+
if (qrOverrides.upn_qr_display_mode) {
|
|
132
|
+
renderParams.upn_qr_display_mode = qrOverrides.upn_qr_display_mode;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
if (qrOverrides?.epc_qr_enabled !== undefined) {
|
|
136
|
+
renderParams.epc_qr_enabled = qrOverrides.epc_qr_enabled ? "true" : "false";
|
|
137
|
+
}
|
|
120
138
|
const requestOpts = { entity_id: activeEntity.id };
|
|
121
139
|
let html: string;
|
|
122
140
|
switch (documentType) {
|
|
@@ -170,9 +188,11 @@ export function LiveInvoicePreview({
|
|
|
170
188
|
activeEntity?.city,
|
|
171
189
|
activeEntity?.name,
|
|
172
190
|
template,
|
|
173
|
-
locale,
|
|
174
191
|
sdk,
|
|
175
192
|
documentType,
|
|
193
|
+
qrOverrides?.upn_qr_enabled,
|
|
194
|
+
qrOverrides?.upn_qr_display_mode,
|
|
195
|
+
qrOverrides?.epc_qr_enabled,
|
|
176
196
|
],
|
|
177
197
|
);
|
|
178
198
|
|
|
@@ -31,6 +31,8 @@ type MarkAsPaidSectionProps = {
|
|
|
31
31
|
t: (key: string) => string;
|
|
32
32
|
/** Always show payment type selector (e.g. for FINA fiscalization) */
|
|
33
33
|
alwaysShowPaymentType?: boolean;
|
|
34
|
+
/** Force paid state — hides the checkbox and always shows payment selectors */
|
|
35
|
+
forced?: boolean;
|
|
34
36
|
};
|
|
35
37
|
|
|
36
38
|
export function MarkAsPaidSection({
|
|
@@ -40,31 +42,38 @@ export function MarkAsPaidSection({
|
|
|
40
42
|
onPaymentTypesChange,
|
|
41
43
|
t,
|
|
42
44
|
alwaysShowPaymentType,
|
|
45
|
+
forced,
|
|
43
46
|
}: MarkAsPaidSectionProps) {
|
|
44
|
-
const showPaymentTypes = checked || alwaysShowPaymentType;
|
|
47
|
+
const showPaymentTypes = forced || checked || alwaysShowPaymentType;
|
|
45
48
|
|
|
46
49
|
return (
|
|
47
50
|
<div className={cn("flex flex-col gap-4 rounded-md border p-4", showPaymentTypes && "gap-3")}>
|
|
48
|
-
|
|
49
|
-
<
|
|
50
|
-
|
|
51
|
-
<Label>{checked ? t("Paid") : t("Mark as Paid")}</Label>
|
|
52
|
-
{!checked && (
|
|
53
|
-
<Tooltip>
|
|
54
|
-
<TooltipTrigger asChild>
|
|
55
|
-
<button
|
|
56
|
-
type="button"
|
|
57
|
-
className="rounded-full p-1 transition-colors hover:bg-accent"
|
|
58
|
-
onClick={(e) => e.preventDefault()}
|
|
59
|
-
>
|
|
60
|
-
<HelpCircle className="size-4 text-muted-foreground" />
|
|
61
|
-
</button>
|
|
62
|
-
</TooltipTrigger>
|
|
63
|
-
<TooltipContent side="top">{t("Invoice will be marked as fully paid upon creation")}</TooltipContent>
|
|
64
|
-
</Tooltip>
|
|
65
|
-
)}
|
|
51
|
+
{forced ? (
|
|
52
|
+
<div className="flex flex-row items-center space-x-3 space-y-0">
|
|
53
|
+
<Label>{t("Paid")}</Label>
|
|
66
54
|
</div>
|
|
67
|
-
|
|
55
|
+
) : (
|
|
56
|
+
<div className="flex flex-row items-center space-x-3 space-y-0">
|
|
57
|
+
<Checkbox checked={checked} onCheckedChange={(v) => onCheckedChange(v === true)} />
|
|
58
|
+
<div className="flex items-center gap-1 leading-none">
|
|
59
|
+
<Label>{checked ? t("Paid") : t("Mark as Paid")}</Label>
|
|
60
|
+
{!checked && (
|
|
61
|
+
<Tooltip>
|
|
62
|
+
<TooltipTrigger asChild>
|
|
63
|
+
<button
|
|
64
|
+
type="button"
|
|
65
|
+
className="rounded-full p-1 transition-colors hover:bg-accent"
|
|
66
|
+
onClick={(e) => e.preventDefault()}
|
|
67
|
+
>
|
|
68
|
+
<HelpCircle className="size-4 text-muted-foreground" />
|
|
69
|
+
</button>
|
|
70
|
+
</TooltipTrigger>
|
|
71
|
+
<TooltipContent side="top">{t("Invoice will be marked as fully paid upon creation")}</TooltipContent>
|
|
72
|
+
</Tooltip>
|
|
73
|
+
)}
|
|
74
|
+
</div>
|
|
75
|
+
</div>
|
|
76
|
+
)}
|
|
68
77
|
|
|
69
78
|
{showPaymentTypes && (
|
|
70
79
|
<div className="flex flex-col gap-2">
|
|
@@ -127,6 +127,15 @@ export function prepareDocumentSubmission<T extends BaseDocumentValues>(
|
|
|
127
127
|
if (values.items) {
|
|
128
128
|
const priceModes = options.priceModes ?? {};
|
|
129
129
|
values.items = values.items.map((item: any, index: number) => {
|
|
130
|
+
// Separator items — pass through with only type, name, description
|
|
131
|
+
if (item.type === "separator") {
|
|
132
|
+
return {
|
|
133
|
+
type: "separator",
|
|
134
|
+
name: item.name,
|
|
135
|
+
description: item.description || undefined,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
130
139
|
const { price, ...rest } = item;
|
|
131
140
|
|
|
132
141
|
// Transform price based on price mode (from component state, not form)
|
|
@@ -136,25 +145,30 @@ export function prepareDocumentSubmission<T extends BaseDocumentValues>(
|
|
|
136
145
|
return {
|
|
137
146
|
...rest,
|
|
138
147
|
...priceFields,
|
|
139
|
-
taxes: item.taxes
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
148
|
+
taxes: item.taxes
|
|
149
|
+
?.map((tax: any) => {
|
|
150
|
+
if (tax.tax_id) {
|
|
151
|
+
// Only send tax_id, API will resolve the rate
|
|
152
|
+
return { tax_id: tax.tax_id };
|
|
153
|
+
}
|
|
154
|
+
return tax;
|
|
155
|
+
})
|
|
156
|
+
// Filter out empty placeholder taxes (UI adds { tax_id: undefined } for the dropdown)
|
|
157
|
+
.filter((tax: any) => tax.tax_id || tax.rate !== undefined || tax.classification),
|
|
146
158
|
};
|
|
147
159
|
});
|
|
148
160
|
}
|
|
149
161
|
|
|
150
162
|
// Build payload with date conversions
|
|
151
163
|
// Destructure to exclude fields we handle explicitly (number is always server-generated)
|
|
152
|
-
const { number: _number, note, payment_terms, ...restValues } = values as any;
|
|
164
|
+
const { number: _number, note, payment_terms, reference, signature, ...restValues } = values as any;
|
|
153
165
|
const payload: any = {
|
|
154
166
|
...restValues,
|
|
155
167
|
...(note?.trim() && { note: note.trim() }),
|
|
168
|
+
...(reference?.trim() && { reference: reference.trim() }),
|
|
156
169
|
// Advance invoices don't have payment terms - they are documents requesting payment
|
|
157
170
|
...(options.documentType !== "advance_invoice" && payment_terms?.trim() && { payment_terms: payment_terms.trim() }),
|
|
171
|
+
...(signature?.trim() && { signature: signature.trim() }),
|
|
158
172
|
date: values.date ? new Date(values.date) : undefined,
|
|
159
173
|
};
|
|
160
174
|
|
|
@@ -22,6 +22,12 @@ const TEMPLATE_VARIABLES = [
|
|
|
22
22
|
variables: [
|
|
23
23
|
{ code: "{entity_name}", label: "Company name" },
|
|
24
24
|
{ code: "{entity_email}", label: "Email address" },
|
|
25
|
+
{ code: "{entity_address}", label: "Address" },
|
|
26
|
+
{ code: "{entity_post_code}", label: "Post code" },
|
|
27
|
+
{ code: "{entity_city}", label: "City" },
|
|
28
|
+
{ code: "{entity_country}", label: "Country" },
|
|
29
|
+
{ code: "{entity_tax_number}", label: "Tax number" },
|
|
30
|
+
{ code: "{entity_company_number}", label: "Company number" },
|
|
25
31
|
],
|
|
26
32
|
},
|
|
27
33
|
{
|
|
@@ -80,9 +80,7 @@ export function DocumentPreviewDisplay({
|
|
|
80
80
|
try {
|
|
81
81
|
// Determine document type from shareable ID prefix
|
|
82
82
|
const docTypePath = getDocTypePathFromShareableId(shareableId);
|
|
83
|
-
const response = await fetch(
|
|
84
|
-
`${apiBaseUrl}/${docTypePath}/shareable/${shareableId}/html${locale ? `?locale=${locale}` : ""}`,
|
|
85
|
-
);
|
|
83
|
+
const response = await fetch(`${apiBaseUrl}/${docTypePath}/shareable/${shareableId}/html`);
|
|
86
84
|
if (!response.ok) {
|
|
87
85
|
throw new Error("Failed to load preview");
|
|
88
86
|
}
|
|
@@ -108,7 +106,8 @@ export function DocumentPreviewDisplay({
|
|
|
108
106
|
try {
|
|
109
107
|
// Fetch the rendered HTML by document ID using SDK wrapper
|
|
110
108
|
// Document type is auto-detected from ID prefix (inv_, est_, cre_, adv_)
|
|
111
|
-
|
|
109
|
+
// Don't send locale — let entity locale drive formatting (decimal separators, date format)
|
|
110
|
+
const html = await sdk.invoices.renderHtml(document.id, { template }, { entity_id: activeEntity.id });
|
|
112
111
|
|
|
113
112
|
setPreviewHtml(html);
|
|
114
113
|
setError(null);
|
|
@@ -44,7 +44,8 @@ const translations = { sl, de, it, fr, es, pt, nl, pl, hr } as const;
|
|
|
44
44
|
|
|
45
45
|
type Document = Invoice | Estimate | CreditNote | AdvanceInvoice;
|
|
46
46
|
|
|
47
|
-
|
|
47
|
+
/** Language options for PDF label translations (formatting always uses entity locale) */
|
|
48
|
+
const PDF_LANGUAGE_CODES = [
|
|
48
49
|
{ label: "English", code: "en-US" },
|
|
49
50
|
{ label: "German", code: "de-DE" },
|
|
50
51
|
{ label: "Slovenian", code: "sl-SI" },
|
|
@@ -100,22 +101,6 @@ interface DocumentActionsBarProps extends ComponentTranslationProps {
|
|
|
100
101
|
isVoiding?: boolean;
|
|
101
102
|
}
|
|
102
103
|
|
|
103
|
-
function getApiLocale(uiLanguage: string): string {
|
|
104
|
-
const localeMap: Record<string, string> = {
|
|
105
|
-
en: "en-US",
|
|
106
|
-
de: "de-DE",
|
|
107
|
-
sl: "sl-SI",
|
|
108
|
-
it: "it-IT",
|
|
109
|
-
fr: "fr-FR",
|
|
110
|
-
es: "es-ES",
|
|
111
|
-
pt: "pt-PT",
|
|
112
|
-
nl: "nl-NL",
|
|
113
|
-
pl: "pl-PL",
|
|
114
|
-
hr: "hr-HR",
|
|
115
|
-
};
|
|
116
|
-
return localeMap[uiLanguage] || "en-US";
|
|
117
|
-
}
|
|
118
|
-
|
|
119
104
|
export function DocumentActionsBar({
|
|
120
105
|
document,
|
|
121
106
|
documentType,
|
|
@@ -163,7 +148,7 @@ export function DocumentActionsBar({
|
|
|
163
148
|
const shareableId = (document as Invoice).shareable_id;
|
|
164
149
|
const shareUrl = shareableId ? `${window.location.origin}/public/invoices/${shareableId}` : null;
|
|
165
150
|
|
|
166
|
-
const handleDownloadPdf = (
|
|
151
|
+
const handleDownloadPdf = (language?: string) => downloadPdf(document, documentType, language);
|
|
167
152
|
const handleDownloadEslog = () => downloadEslog(document, documentType);
|
|
168
153
|
|
|
169
154
|
const handleCopyShareLink = async () => {
|
|
@@ -178,7 +163,6 @@ export function DocumentActionsBar({
|
|
|
178
163
|
}
|
|
179
164
|
};
|
|
180
165
|
|
|
181
|
-
const apiLocale = getApiLocale(currentLocale);
|
|
182
166
|
const isDraft = (document as any).is_draft === true;
|
|
183
167
|
|
|
184
168
|
return (
|
|
@@ -189,7 +173,7 @@ export function DocumentActionsBar({
|
|
|
189
173
|
variant="outline"
|
|
190
174
|
size="sm"
|
|
191
175
|
disabled={isDownloadingPdf}
|
|
192
|
-
onClick={() => handleDownloadPdf(
|
|
176
|
+
onClick={() => handleDownloadPdf()}
|
|
193
177
|
className="cursor-pointer rounded-r-none"
|
|
194
178
|
>
|
|
195
179
|
{isDownloadingPdf ? <Loader2 className="mr-2 h-4 w-4 animate-spin" /> : <Download className="mr-2 h-4 w-4" />}
|
|
@@ -207,13 +191,9 @@ export function DocumentActionsBar({
|
|
|
207
191
|
</Button>
|
|
208
192
|
</DropdownMenuTrigger>
|
|
209
193
|
<DropdownMenuContent align="end">
|
|
210
|
-
{
|
|
211
|
-
<DropdownMenuItem
|
|
212
|
-
|
|
213
|
-
onClick={() => handleDownloadPdf(locale.code)}
|
|
214
|
-
className="cursor-pointer"
|
|
215
|
-
>
|
|
216
|
-
{t(locale.label)}
|
|
194
|
+
{PDF_LANGUAGE_CODES.map((lang) => (
|
|
195
|
+
<DropdownMenuItem key={lang.code} onClick={() => handleDownloadPdf(lang.code)} className="cursor-pointer">
|
|
196
|
+
{t(lang.label)}
|
|
217
197
|
</DropdownMenuItem>
|
|
218
198
|
))}
|
|
219
199
|
</DropdownMenuContent>
|
|
@@ -90,6 +90,7 @@ export function DocumentDetailsCard({
|
|
|
90
90
|
const t = createTranslation({ translations, locale, ...i18nProps });
|
|
91
91
|
|
|
92
92
|
const currencyCode = document.currency_code;
|
|
93
|
+
const sign = documentType === "credit_note" ? -1 : 1;
|
|
93
94
|
const fmt = (amount: number) => formatCurrency(amount, currencyCode, locale);
|
|
94
95
|
const fmtDate = (date: Date | string | null | undefined) => formatDate(date, locale);
|
|
95
96
|
|
|
@@ -102,9 +103,6 @@ export function DocumentDetailsCard({
|
|
|
102
103
|
// Get customer name
|
|
103
104
|
const customerName = document.customer?.name || "-";
|
|
104
105
|
|
|
105
|
-
// Calculate tax total
|
|
106
|
-
const taxTotal = document.total_with_tax - document.total;
|
|
107
|
-
|
|
108
106
|
const bodyContent = (
|
|
109
107
|
<>
|
|
110
108
|
{/* Document info */}
|
|
@@ -150,17 +148,42 @@ export function DocumentDetailsCard({
|
|
|
150
148
|
|
|
151
149
|
{/* Totals */}
|
|
152
150
|
<div className="space-y-2 text-sm">
|
|
151
|
+
{document.total_discount != null && document.total_discount !== 0 && (
|
|
152
|
+
<div className="flex justify-between">
|
|
153
|
+
<span className="text-muted-foreground">{t("Discount")}</span>
|
|
154
|
+
<span>{fmt(document.total_discount * sign)}</span>
|
|
155
|
+
</div>
|
|
156
|
+
)}
|
|
153
157
|
<div className="flex justify-between">
|
|
154
158
|
<span className="text-muted-foreground">{t("Subtotal")}</span>
|
|
155
|
-
<span>{fmt(document.total)}</span>
|
|
156
|
-
</div>
|
|
157
|
-
<div className="flex justify-between">
|
|
158
|
-
<span className="text-muted-foreground">{t("Tax")}</span>
|
|
159
|
-
<span>{fmt(taxTotal)}</span>
|
|
159
|
+
<span>{fmt(document.total * sign)}</span>
|
|
160
160
|
</div>
|
|
161
|
+
{document.taxes && document.taxes.length > 0 ? (
|
|
162
|
+
document.taxes.map((tax: Document["taxes"][number]) => {
|
|
163
|
+
const taxAmount = tax.amount ?? 0;
|
|
164
|
+
return (
|
|
165
|
+
<div key={String(tax.rate)} className="flex justify-between">
|
|
166
|
+
<span className="text-muted-foreground">
|
|
167
|
+
{t("Tax")} {tax.rate ?? 0}%{" "}
|
|
168
|
+
{tax.base != null && (
|
|
169
|
+
<span className="text-xs">
|
|
170
|
+
{t("of")} {fmt(tax.base * sign)}
|
|
171
|
+
</span>
|
|
172
|
+
)}
|
|
173
|
+
</span>
|
|
174
|
+
<span>{fmt(taxAmount * sign)}</span>
|
|
175
|
+
</div>
|
|
176
|
+
);
|
|
177
|
+
})
|
|
178
|
+
) : (
|
|
179
|
+
<div className="flex justify-between">
|
|
180
|
+
<span className="text-muted-foreground">{t("Tax")}</span>
|
|
181
|
+
<span>{fmt(0)}</span>
|
|
182
|
+
</div>
|
|
183
|
+
)}
|
|
161
184
|
<div className="flex justify-between font-semibold">
|
|
162
185
|
<span>{t("Total")}</span>
|
|
163
|
-
<span>{fmt(document.total_with_tax)}</span>
|
|
186
|
+
<span>{fmt(document.total_with_tax * sign)}</span>
|
|
164
187
|
</div>
|
|
165
188
|
|
|
166
189
|
{/* Payment info for invoices/advance invoices */}
|
|
@@ -36,8 +36,10 @@ export default {
|
|
|
36
36
|
"Service period": "Leistungszeitraum",
|
|
37
37
|
"Valid until": "Gültig bis",
|
|
38
38
|
Customer: "Kunde",
|
|
39
|
+
Discount: "Rabatt",
|
|
39
40
|
Subtotal: "Zwischensumme",
|
|
40
41
|
Tax: "Steuer",
|
|
42
|
+
of: "von",
|
|
41
43
|
Total: "Gesamt",
|
|
42
44
|
Paid: "Bezahlt",
|
|
43
45
|
Due: "Offen",
|
|
@@ -108,6 +110,7 @@ export default {
|
|
|
108
110
|
Pending: "Ausstehend",
|
|
109
111
|
Failed: "Fehlgeschlagen",
|
|
110
112
|
Skipped: "Übersprungen",
|
|
113
|
+
"Skipped by user": "Vom Benutzer übersprungen",
|
|
111
114
|
"Retry fiscalization": "Fiskalisierung wiederholen",
|
|
112
115
|
|
|
113
116
|
// PDF language names
|
|
@@ -35,8 +35,10 @@ export default {
|
|
|
35
35
|
"Service period": "Período de servicio",
|
|
36
36
|
"Valid until": "Válido hasta",
|
|
37
37
|
Customer: "Cliente",
|
|
38
|
+
Discount: "Descuento",
|
|
38
39
|
Subtotal: "Subtotal",
|
|
39
40
|
Tax: "Impuesto",
|
|
41
|
+
of: "de",
|
|
40
42
|
Total: "Total",
|
|
41
43
|
Paid: "Pagado",
|
|
42
44
|
Due: "Pendiente",
|
|
@@ -106,6 +108,7 @@ export default {
|
|
|
106
108
|
Pending: "Pendiente",
|
|
107
109
|
Failed: "Fallido",
|
|
108
110
|
Skipped: "Omitido",
|
|
111
|
+
"Skipped by user": "Omitido por el usuario",
|
|
109
112
|
"Retry fiscalization": "Reintentar fiscalización",
|
|
110
113
|
|
|
111
114
|
// PDF language names
|
|
@@ -35,8 +35,10 @@ export default {
|
|
|
35
35
|
"Service period": "Période de service",
|
|
36
36
|
"Valid until": "Valable jusqu'au",
|
|
37
37
|
Customer: "Client",
|
|
38
|
+
Discount: "Remise",
|
|
38
39
|
Subtotal: "Sous-total",
|
|
39
40
|
Tax: "Taxe",
|
|
41
|
+
of: "de",
|
|
40
42
|
Total: "Total",
|
|
41
43
|
Paid: "Payé",
|
|
42
44
|
Due: "Dû",
|
|
@@ -106,6 +108,7 @@ export default {
|
|
|
106
108
|
Pending: "En attente",
|
|
107
109
|
Failed: "Échoué",
|
|
108
110
|
Skipped: "Ignoré",
|
|
111
|
+
"Skipped by user": "Ignoré par l'utilisateur",
|
|
109
112
|
"Retry fiscalization": "Réessayer la fiscalisation",
|
|
110
113
|
|
|
111
114
|
// PDF language names
|
|
@@ -35,8 +35,10 @@ export default {
|
|
|
35
35
|
"Service period": "Razdoblje usluge",
|
|
36
36
|
"Valid until": "Vrijedi do",
|
|
37
37
|
Customer: "Kupac",
|
|
38
|
+
Discount: "Popust",
|
|
38
39
|
Subtotal: "Međuzbroj",
|
|
39
40
|
Tax: "Porez",
|
|
41
|
+
of: "od",
|
|
40
42
|
Total: "Ukupno",
|
|
41
43
|
Paid: "Plaćeno",
|
|
42
44
|
Due: "Za plaćanje",
|
|
@@ -106,6 +108,7 @@ export default {
|
|
|
106
108
|
Pending: "Na čekanju",
|
|
107
109
|
Failed: "Neuspjelo",
|
|
108
110
|
Skipped: "Preskočeno",
|
|
111
|
+
"Skipped by user": "Korisnik preskočio",
|
|
109
112
|
"Retry fiscalization": "Ponovi fiskalizaciju",
|
|
110
113
|
|
|
111
114
|
// PDF language names
|
|
@@ -35,8 +35,10 @@ export default {
|
|
|
35
35
|
"Service period": "Periodo di servizio",
|
|
36
36
|
"Valid until": "Valido fino al",
|
|
37
37
|
Customer: "Cliente",
|
|
38
|
+
Discount: "Sconto",
|
|
38
39
|
Subtotal: "Subtotale",
|
|
39
40
|
Tax: "Imposta",
|
|
41
|
+
of: "di",
|
|
40
42
|
Total: "Totale",
|
|
41
43
|
Paid: "Pagato",
|
|
42
44
|
Due: "Da pagare",
|
|
@@ -106,6 +108,7 @@ export default {
|
|
|
106
108
|
Pending: "In attesa",
|
|
107
109
|
Failed: "Fallito",
|
|
108
110
|
Skipped: "Saltato",
|
|
111
|
+
"Skipped by user": "Saltato dall'utente",
|
|
109
112
|
"Retry fiscalization": "Riprova fiscalizzazione",
|
|
110
113
|
|
|
111
114
|
// PDF language names
|
|
@@ -35,8 +35,10 @@ export default {
|
|
|
35
35
|
"Service period": "Serviceperiode",
|
|
36
36
|
"Valid until": "Geldig tot",
|
|
37
37
|
Customer: "Klant",
|
|
38
|
+
Discount: "Korting",
|
|
38
39
|
Subtotal: "Subtotaal",
|
|
39
40
|
Tax: "Belasting",
|
|
41
|
+
of: "van",
|
|
40
42
|
Total: "Totaal",
|
|
41
43
|
Paid: "Betaald",
|
|
42
44
|
Due: "Openstaand",
|
|
@@ -107,6 +109,7 @@ export default {
|
|
|
107
109
|
Pending: "In behandeling",
|
|
108
110
|
Failed: "Mislukt",
|
|
109
111
|
Skipped: "Overgeslagen",
|
|
112
|
+
"Skipped by user": "Overgeslagen door gebruiker",
|
|
110
113
|
"Retry fiscalization": "Fiscalisatie opnieuw proberen",
|
|
111
114
|
|
|
112
115
|
// PDF language names
|
|
@@ -35,8 +35,10 @@ export default {
|
|
|
35
35
|
"Service period": "Okres usługi",
|
|
36
36
|
"Valid until": "Ważne do",
|
|
37
37
|
Customer: "Klient",
|
|
38
|
+
Discount: "Rabat",
|
|
38
39
|
Subtotal: "Suma częściowa",
|
|
39
40
|
Tax: "Podatek",
|
|
41
|
+
of: "z",
|
|
40
42
|
Total: "Razem",
|
|
41
43
|
Paid: "Zapłacono",
|
|
42
44
|
Due: "Do zapłaty",
|
|
@@ -106,6 +108,7 @@ export default {
|
|
|
106
108
|
Pending: "Oczekuje",
|
|
107
109
|
Failed: "Niepowodzenie",
|
|
108
110
|
Skipped: "Pominięto",
|
|
111
|
+
"Skipped by user": "Pominięto przez użytkownika",
|
|
109
112
|
"Retry fiscalization": "Ponów fiskalizację",
|
|
110
113
|
|
|
111
114
|
// PDF language names
|
|
@@ -35,8 +35,10 @@ export default {
|
|
|
35
35
|
"Service period": "Período de serviço",
|
|
36
36
|
"Valid until": "Válido até",
|
|
37
37
|
Customer: "Cliente",
|
|
38
|
+
Discount: "Desconto",
|
|
38
39
|
Subtotal: "Subtotal",
|
|
39
40
|
Tax: "Imposto",
|
|
41
|
+
of: "de",
|
|
40
42
|
Total: "Total",
|
|
41
43
|
Paid: "Pago",
|
|
42
44
|
Due: "Em dívida",
|
|
@@ -106,6 +108,7 @@ export default {
|
|
|
106
108
|
Pending: "Pendente",
|
|
107
109
|
Failed: "Falhado",
|
|
108
110
|
Skipped: "Ignorado",
|
|
111
|
+
"Skipped by user": "Ignorado pelo utilizador",
|
|
109
112
|
"Retry fiscalization": "Tentar novamente fiscalização",
|
|
110
113
|
|
|
111
114
|
// PDF language names
|
|
@@ -35,8 +35,10 @@ export default {
|
|
|
35
35
|
"Service period": "Obdobje storitve",
|
|
36
36
|
"Valid until": "Veljavno do",
|
|
37
37
|
Customer: "Stranka",
|
|
38
|
+
Discount: "Popust",
|
|
38
39
|
Subtotal: "Vmesni seštevek",
|
|
39
|
-
Tax: "
|
|
40
|
+
Tax: "DDV",
|
|
41
|
+
of: "od",
|
|
40
42
|
Total: "Skupaj",
|
|
41
43
|
Paid: "Plačano",
|
|
42
44
|
Due: "Za plačilo",
|
|
@@ -107,6 +109,7 @@ export default {
|
|
|
107
109
|
Pending: "V čakanju",
|
|
108
110
|
Failed: "Neuspešno",
|
|
109
111
|
Skipped: "Preskočeno",
|
|
112
|
+
"Skipped by user": "Uporabnik preskočil",
|
|
110
113
|
"Retry fiscalization": "Ponovi fiskalizacijo",
|
|
111
114
|
|
|
112
115
|
// 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);
|
|
@@ -12,6 +12,12 @@ const TEMPLATE_VARIABLES = [
|
|
|
12
12
|
variables: [
|
|
13
13
|
{ name: "{entity_name}", description: "Your company or entity name" },
|
|
14
14
|
{ name: "{entity_email}", description: "Entity email address" },
|
|
15
|
+
{ name: "{entity_address}", description: "Entity address" },
|
|
16
|
+
{ name: "{entity_post_code}", description: "Entity post code" },
|
|
17
|
+
{ name: "{entity_city}", description: "Entity city" },
|
|
18
|
+
{ name: "{entity_country}", description: "Entity country" },
|
|
19
|
+
{ name: "{entity_tax_number}", description: "Entity tax number" },
|
|
20
|
+
{ name: "{entity_company_number}", description: "Entity company number" },
|
|
15
21
|
],
|
|
16
22
|
},
|
|
17
23
|
{
|