@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
|
@@ -20,7 +20,7 @@ export function useCollectionRateData(entityId: string | undefined) {
|
|
|
20
20
|
|
|
21
21
|
const queries = useQueries({
|
|
22
22
|
queries: [
|
|
23
|
-
// Total invoiced (
|
|
23
|
+
// Total invoiced (including voided — counter credit notes cancel them out)
|
|
24
24
|
{
|
|
25
25
|
queryKey: [STATS_QUERY_CACHE_KEY, entityId, "total-invoiced"],
|
|
26
26
|
queryFn: async () => {
|
|
@@ -29,7 +29,7 @@ export function useCollectionRateData(entityId: string | undefined) {
|
|
|
29
29
|
{
|
|
30
30
|
metrics: [{ type: "sum", field: "total_with_tax", alias: "total" }],
|
|
31
31
|
table: "invoices",
|
|
32
|
-
filters: { is_draft: false
|
|
32
|
+
filters: { is_draft: false },
|
|
33
33
|
},
|
|
34
34
|
{ entity_id: entityId },
|
|
35
35
|
);
|
|
@@ -37,15 +37,50 @@ export function useCollectionRateData(entityId: string | undefined) {
|
|
|
37
37
|
enabled: !!entityId && !!sdk,
|
|
38
38
|
staleTime: 120_000,
|
|
39
39
|
},
|
|
40
|
-
//
|
|
40
|
+
// Invoice payments (credit_note_id IS NULL)
|
|
41
41
|
{
|
|
42
|
-
queryKey: [STATS_QUERY_CACHE_KEY, entityId, "
|
|
42
|
+
queryKey: [STATS_QUERY_CACHE_KEY, entityId, "invoice-payments"],
|
|
43
43
|
queryFn: async () => {
|
|
44
44
|
if (!entityId || !sdk) throw new Error("Missing entity or SDK");
|
|
45
45
|
return sdk.entityStats.queryEntityStats(
|
|
46
46
|
{
|
|
47
47
|
metrics: [{ type: "sum", field: "amount", alias: "total" }],
|
|
48
48
|
table: "payments",
|
|
49
|
+
filters: { credit_note_id: null },
|
|
50
|
+
},
|
|
51
|
+
{ entity_id: entityId },
|
|
52
|
+
);
|
|
53
|
+
},
|
|
54
|
+
enabled: !!entityId && !!sdk,
|
|
55
|
+
staleTime: 120_000,
|
|
56
|
+
},
|
|
57
|
+
// Credit note payments / refunds (credit_note_id IS NOT NULL)
|
|
58
|
+
{
|
|
59
|
+
queryKey: [STATS_QUERY_CACHE_KEY, entityId, "cn-payments"],
|
|
60
|
+
queryFn: async () => {
|
|
61
|
+
if (!entityId || !sdk) throw new Error("Missing entity or SDK");
|
|
62
|
+
return sdk.entityStats.queryEntityStats(
|
|
63
|
+
{
|
|
64
|
+
metrics: [{ type: "sum", field: "amount", alias: "total" }],
|
|
65
|
+
table: "payments",
|
|
66
|
+
filters: { credit_note_id: { not: null } },
|
|
67
|
+
},
|
|
68
|
+
{ entity_id: entityId },
|
|
69
|
+
);
|
|
70
|
+
},
|
|
71
|
+
enabled: !!entityId && !!sdk,
|
|
72
|
+
staleTime: 120_000,
|
|
73
|
+
},
|
|
74
|
+
// Credit notes total (subtracted from invoiced to get net revenue)
|
|
75
|
+
{
|
|
76
|
+
queryKey: [STATS_QUERY_CACHE_KEY, entityId, "cn-total"],
|
|
77
|
+
queryFn: async () => {
|
|
78
|
+
if (!entityId || !sdk) throw new Error("Missing entity or SDK");
|
|
79
|
+
return sdk.entityStats.queryEntityStats(
|
|
80
|
+
{
|
|
81
|
+
metrics: [{ type: "sum", field: "total_with_tax", alias: "total" }],
|
|
82
|
+
table: "credit_notes",
|
|
83
|
+
filters: { is_draft: false },
|
|
49
84
|
},
|
|
50
85
|
{ entity_id: entityId },
|
|
51
86
|
);
|
|
@@ -56,17 +91,21 @@ export function useCollectionRateData(entityId: string | undefined) {
|
|
|
56
91
|
],
|
|
57
92
|
});
|
|
58
93
|
|
|
59
|
-
const [invoicedQuery,
|
|
94
|
+
const [invoicedQuery, invoicePaymentsQuery, cnPaymentsQuery, cnQuery] = queries;
|
|
60
95
|
|
|
61
96
|
const totalInvoiced = Number(invoicedQuery.data?.data?.[0]?.total) || 0;
|
|
62
|
-
const
|
|
63
|
-
const
|
|
97
|
+
const invoicePayments = Number(invoicePaymentsQuery.data?.data?.[0]?.total) || 0;
|
|
98
|
+
const cnPayments = Number(cnPaymentsQuery.data?.data?.[0]?.total) || 0;
|
|
99
|
+
const cnTotal = Number(cnQuery.data?.data?.[0]?.total) || 0;
|
|
100
|
+
const netInvoiced = totalInvoiced - cnTotal;
|
|
101
|
+
const netCollected = invoicePayments - cnPayments;
|
|
102
|
+
const collectionRate = netInvoiced > 0 ? (netCollected / netInvoiced) * 100 : 0;
|
|
64
103
|
|
|
65
104
|
return {
|
|
66
105
|
data: {
|
|
67
106
|
collectionRate,
|
|
68
|
-
totalCollected,
|
|
69
|
-
totalInvoiced,
|
|
107
|
+
totalCollected: netCollected,
|
|
108
|
+
totalInvoiced: netInvoiced,
|
|
70
109
|
currency: "EUR", // TODO: Get from entity settings
|
|
71
110
|
} as CollectionRateData,
|
|
72
111
|
isLoading: queries.some((q) => q.isLoading),
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Server-side aggregation by month for accurate trend data.
|
|
4
4
|
*/
|
|
5
5
|
import type { StatsQueryDataItem } from "@spaceinvoices/js-sdk";
|
|
6
|
-
import {
|
|
6
|
+
import { useQueries } from "@tanstack/react-query";
|
|
7
7
|
import { useSDK } from "@/ui/providers/sdk-provider";
|
|
8
8
|
import { STATS_QUERY_CACHE_KEY } from "../shared/use-stats-query";
|
|
9
9
|
|
|
@@ -37,57 +37,86 @@ export function useRevenueTrendData(entityId: string | undefined) {
|
|
|
37
37
|
|
|
38
38
|
const { months, startDate, endDate } = getLastMonths(6);
|
|
39
39
|
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
40
|
+
const sharedQueryParams = {
|
|
41
|
+
date_from: startDate,
|
|
42
|
+
date_to: endDate,
|
|
43
|
+
filters: { is_draft: false },
|
|
44
|
+
group_by: ["month", "quote_currency"],
|
|
45
|
+
order_by: [{ field: "month", direction: "asc" as const }],
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const queries = useQueries({
|
|
49
|
+
queries: [
|
|
50
|
+
{
|
|
51
|
+
queryKey: [STATS_QUERY_CACHE_KEY, entityId, "revenue-trend", startDate, endDate],
|
|
52
|
+
queryFn: async () => {
|
|
53
|
+
if (!entityId || !sdk) throw new Error("Missing entity or SDK");
|
|
54
|
+
return sdk.entityStats.queryEntityStats(
|
|
55
|
+
{
|
|
56
|
+
metrics: [{ type: "sum", field: "total_with_tax_converted", alias: "revenue" }],
|
|
57
|
+
table: "invoices",
|
|
58
|
+
...sharedQueryParams,
|
|
59
|
+
},
|
|
60
|
+
{ entity_id: entityId },
|
|
61
|
+
);
|
|
62
|
+
},
|
|
63
|
+
enabled: !!entityId && !!sdk,
|
|
64
|
+
staleTime: 120_000,
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
queryKey: [STATS_QUERY_CACHE_KEY, entityId, "cn-revenue-trend", startDate, endDate],
|
|
68
|
+
queryFn: async () => {
|
|
69
|
+
if (!entityId || !sdk) throw new Error("Missing entity or SDK");
|
|
70
|
+
return sdk.entityStats.queryEntityStats(
|
|
71
|
+
{
|
|
72
|
+
metrics: [{ type: "sum", field: "total_with_tax_converted", alias: "revenue" }],
|
|
73
|
+
table: "credit_notes",
|
|
74
|
+
...sharedQueryParams,
|
|
75
|
+
},
|
|
76
|
+
{ entity_id: entityId },
|
|
77
|
+
);
|
|
53
78
|
},
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
select: (response) => {
|
|
60
|
-
// Build a map of all months with 0 revenue
|
|
61
|
-
const monthMap: Record<string, number> = {};
|
|
62
|
-
for (const month of months) {
|
|
63
|
-
monthMap[month] = 0;
|
|
64
|
-
}
|
|
79
|
+
enabled: !!entityId && !!sdk,
|
|
80
|
+
staleTime: 120_000,
|
|
81
|
+
},
|
|
82
|
+
],
|
|
83
|
+
});
|
|
65
84
|
|
|
66
|
-
|
|
67
|
-
// Sum up revenues per month (in case of multiple rows due to quote_currency grouping)
|
|
68
|
-
const data = response.data || [];
|
|
69
|
-
let currency = "EUR";
|
|
70
|
-
for (const row of data as StatsQueryDataItem[]) {
|
|
71
|
-
const month = String(row.month);
|
|
72
|
-
if (month in monthMap) {
|
|
73
|
-
monthMap[month] += Number(row.revenue) || 0;
|
|
74
|
-
}
|
|
75
|
-
// Get currency from first row with data
|
|
76
|
-
if (row.quote_currency && currency === "EUR") {
|
|
77
|
-
currency = String(row.quote_currency);
|
|
78
|
-
}
|
|
79
|
-
}
|
|
85
|
+
const [invoiceQuery, cnQuery] = queries;
|
|
80
86
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
+
// Build month maps
|
|
88
|
+
const monthMap: Record<string, number> = {};
|
|
89
|
+
const cnMonthMap: Record<string, number> = {};
|
|
90
|
+
for (const month of months) {
|
|
91
|
+
monthMap[month] = 0;
|
|
92
|
+
cnMonthMap[month] = 0;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Fill invoice revenue per month
|
|
96
|
+
const invoiceData = (invoiceQuery.data?.data || []) as StatsQueryDataItem[];
|
|
97
|
+
let currency = "EUR";
|
|
98
|
+
for (const row of invoiceData) {
|
|
99
|
+
const month = String(row.month);
|
|
100
|
+
if (month in monthMap) {
|
|
101
|
+
monthMap[month] += Number(row.revenue) || 0;
|
|
102
|
+
}
|
|
103
|
+
if (row.quote_currency && currency === "EUR") {
|
|
104
|
+
currency = String(row.quote_currency);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Fill credit note revenue per month
|
|
109
|
+
const cnData = (cnQuery.data?.data || []) as StatsQueryDataItem[];
|
|
110
|
+
for (const row of cnData) {
|
|
111
|
+
const month = String(row.month);
|
|
112
|
+
if (month in cnMonthMap) {
|
|
113
|
+
cnMonthMap[month] += Number(row.revenue) || 0;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
87
116
|
|
|
88
117
|
return {
|
|
89
|
-
data:
|
|
90
|
-
currency
|
|
91
|
-
isLoading:
|
|
118
|
+
data: months.map((month) => ({ month, revenue: monthMap[month] - cnMonthMap[month] })),
|
|
119
|
+
currency,
|
|
120
|
+
isLoading: queries.some((q) => q.isLoading),
|
|
92
121
|
};
|
|
93
122
|
}
|
|
@@ -54,7 +54,7 @@ export function useRevenueData(entityId: string | undefined) {
|
|
|
54
54
|
table: "invoices",
|
|
55
55
|
date_from: monthRange.from,
|
|
56
56
|
date_to: monthRange.to,
|
|
57
|
-
filters: { is_draft: false
|
|
57
|
+
filters: { is_draft: false },
|
|
58
58
|
group_by: ["quote_currency"], // Get the currency for display
|
|
59
59
|
},
|
|
60
60
|
{ entity_id: entityId },
|
|
@@ -74,7 +74,7 @@ export function useRevenueData(entityId: string | undefined) {
|
|
|
74
74
|
table: "invoices",
|
|
75
75
|
date_from: yearRange.from,
|
|
76
76
|
date_to: yearRange.to,
|
|
77
|
-
filters: { is_draft: false
|
|
77
|
+
filters: { is_draft: false },
|
|
78
78
|
},
|
|
79
79
|
{ entity_id: entityId },
|
|
80
80
|
);
|
|
@@ -91,7 +91,7 @@ export function useRevenueData(entityId: string | undefined) {
|
|
|
91
91
|
{
|
|
92
92
|
metrics: [{ type: "sum", field: "total_due", alias: "outstanding" }],
|
|
93
93
|
table: "invoices",
|
|
94
|
-
filters: { is_draft: false,
|
|
94
|
+
filters: { is_draft: false, paid_in_full: false },
|
|
95
95
|
},
|
|
96
96
|
{ entity_id: entityId },
|
|
97
97
|
);
|
|
@@ -111,7 +111,7 @@ export function useRevenueData(entityId: string | undefined) {
|
|
|
111
111
|
{ type: "count", alias: "count" },
|
|
112
112
|
],
|
|
113
113
|
table: "invoices",
|
|
114
|
-
filters: { is_draft: false,
|
|
114
|
+
filters: { is_draft: false, paid_in_full: false },
|
|
115
115
|
group_by: ["overdue_bucket"],
|
|
116
116
|
},
|
|
117
117
|
{ entity_id: entityId },
|
|
@@ -120,10 +120,73 @@ export function useRevenueData(entityId: string | undefined) {
|
|
|
120
120
|
enabled: !!entityId && !!sdk,
|
|
121
121
|
staleTime: 120_000,
|
|
122
122
|
},
|
|
123
|
+
// Credit notes: this month
|
|
124
|
+
{
|
|
125
|
+
queryKey: [STATS_QUERY_CACHE_KEY, entityId, "cn-this-month", monthRange.from],
|
|
126
|
+
queryFn: async () => {
|
|
127
|
+
if (!entityId || !sdk) throw new Error("Missing entity or SDK");
|
|
128
|
+
return sdk.entityStats.queryEntityStats(
|
|
129
|
+
{
|
|
130
|
+
metrics: [{ type: "sum", field: "total_with_tax_converted", alias: "revenue" }],
|
|
131
|
+
table: "credit_notes",
|
|
132
|
+
date_from: monthRange.from,
|
|
133
|
+
date_to: monthRange.to,
|
|
134
|
+
filters: { is_draft: false },
|
|
135
|
+
},
|
|
136
|
+
{ entity_id: entityId },
|
|
137
|
+
);
|
|
138
|
+
},
|
|
139
|
+
enabled: !!entityId && !!sdk,
|
|
140
|
+
staleTime: 120_000,
|
|
141
|
+
},
|
|
142
|
+
// Credit notes: this year
|
|
143
|
+
{
|
|
144
|
+
queryKey: [STATS_QUERY_CACHE_KEY, entityId, "cn-this-year", yearRange.from],
|
|
145
|
+
queryFn: async () => {
|
|
146
|
+
if (!entityId || !sdk) throw new Error("Missing entity or SDK");
|
|
147
|
+
return sdk.entityStats.queryEntityStats(
|
|
148
|
+
{
|
|
149
|
+
metrics: [{ type: "sum", field: "total_with_tax_converted", alias: "revenue" }],
|
|
150
|
+
table: "credit_notes",
|
|
151
|
+
date_from: yearRange.from,
|
|
152
|
+
date_to: yearRange.to,
|
|
153
|
+
filters: { is_draft: false },
|
|
154
|
+
},
|
|
155
|
+
{ entity_id: entityId },
|
|
156
|
+
);
|
|
157
|
+
},
|
|
158
|
+
enabled: !!entityId && !!sdk,
|
|
159
|
+
staleTime: 120_000,
|
|
160
|
+
},
|
|
161
|
+
// Credit notes: outstanding
|
|
162
|
+
{
|
|
163
|
+
queryKey: [STATS_QUERY_CACHE_KEY, entityId, "cn-outstanding"],
|
|
164
|
+
queryFn: async () => {
|
|
165
|
+
if (!entityId || !sdk) throw new Error("Missing entity or SDK");
|
|
166
|
+
return sdk.entityStats.queryEntityStats(
|
|
167
|
+
{
|
|
168
|
+
metrics: [{ type: "sum", field: "total_due", alias: "outstanding" }],
|
|
169
|
+
table: "credit_notes",
|
|
170
|
+
filters: { is_draft: false, paid_in_full: false },
|
|
171
|
+
},
|
|
172
|
+
{ entity_id: entityId },
|
|
173
|
+
);
|
|
174
|
+
},
|
|
175
|
+
enabled: !!entityId && !!sdk,
|
|
176
|
+
staleTime: 120_000,
|
|
177
|
+
},
|
|
123
178
|
],
|
|
124
179
|
});
|
|
125
180
|
|
|
126
|
-
const [
|
|
181
|
+
const [
|
|
182
|
+
thisMonthQuery,
|
|
183
|
+
thisYearQuery,
|
|
184
|
+
outstandingQuery,
|
|
185
|
+
overdueQuery,
|
|
186
|
+
cnThisMonthQuery,
|
|
187
|
+
cnThisYearQuery,
|
|
188
|
+
cnOutstandingQuery,
|
|
189
|
+
] = queries;
|
|
127
190
|
|
|
128
191
|
// Extract this month revenue and currency (may have multiple rows if grouped by quote_currency)
|
|
129
192
|
const thisMonthData = thisMonthQuery.data?.data || [];
|
|
@@ -134,7 +197,12 @@ export function useRevenueData(entityId: string | undefined) {
|
|
|
134
197
|
// Get currency from first row with data
|
|
135
198
|
const currency = (thisMonthData[0]?.quote_currency as string) || "EUR";
|
|
136
199
|
|
|
137
|
-
//
|
|
200
|
+
// Credit note totals
|
|
201
|
+
const cnThisMonth = Number(cnThisMonthQuery.data?.data?.[0]?.revenue) || 0;
|
|
202
|
+
const cnThisYear = Number(cnThisYearQuery.data?.data?.[0]?.revenue) || 0;
|
|
203
|
+
const cnOutstanding = Number(cnOutstandingQuery.data?.data?.[0]?.outstanding) || 0;
|
|
204
|
+
|
|
205
|
+
// Extract overdue data (buckets other than "current") — stays invoice-only
|
|
138
206
|
const overdueData = overdueQuery.data?.data || [];
|
|
139
207
|
const overdueBuckets = overdueData.filter((row: StatsQueryDataItem) => row.overdue_bucket !== "current");
|
|
140
208
|
const totalOverdue = overdueBuckets.reduce(
|
|
@@ -148,9 +216,9 @@ export function useRevenueData(entityId: string | undefined) {
|
|
|
148
216
|
|
|
149
217
|
return {
|
|
150
218
|
data: {
|
|
151
|
-
thisMonth: thisMonthRevenue,
|
|
152
|
-
thisYear: Number(thisYearQuery.data?.data?.[0]?.revenue) || 0,
|
|
153
|
-
outstanding: Number(outstandingQuery.data?.data?.[0]?.outstanding) || 0,
|
|
219
|
+
thisMonth: thisMonthRevenue - cnThisMonth,
|
|
220
|
+
thisYear: (Number(thisYearQuery.data?.data?.[0]?.revenue) || 0) - cnThisYear,
|
|
221
|
+
outstanding: (Number(outstandingQuery.data?.data?.[0]?.outstanding) || 0) - cnOutstanding,
|
|
154
222
|
overdue: totalOverdue,
|
|
155
223
|
overdueCount,
|
|
156
224
|
currency, // Currency from document data
|
|
@@ -10,12 +10,17 @@ import { Form } from "@/ui/components/ui/form";
|
|
|
10
10
|
import { Label } from "@/ui/components/ui/label";
|
|
11
11
|
import { createDeliveryNoteSchema } from "@/ui/generated/schemas";
|
|
12
12
|
import { useNextDocumentNumber } from "@/ui/hooks/use-next-document-number";
|
|
13
|
+
import { useViesCheck } from "@/ui/hooks/use-vies-check";
|
|
13
14
|
import type { ComponentTranslationProps } from "@/ui/lib/translation";
|
|
14
15
|
import { createTranslation } from "@/ui/lib/translation";
|
|
15
16
|
import { useEntities } from "@/ui/providers/entities-context";
|
|
16
17
|
import { useFormFooterRegistration } from "@/ui/providers/form-footer-context";
|
|
17
18
|
import { CUSTOMERS_CACHE_KEY } from "../../customers/customers.hooks";
|
|
18
|
-
import {
|
|
19
|
+
import {
|
|
20
|
+
DocumentDetailsSection,
|
|
21
|
+
DocumentNoteField,
|
|
22
|
+
DocumentTaxClauseField,
|
|
23
|
+
} from "../../documents/create/document-details-section";
|
|
19
24
|
import { DocumentItemsSection, type PriceModesMap } from "../../documents/create/document-items-section";
|
|
20
25
|
import { DocumentRecipientSection } from "../../documents/create/document-recipient-section";
|
|
21
26
|
import type { DocumentTypes } from "../../documents/types";
|
|
@@ -106,13 +111,18 @@ export default function CreateDeliveryNoteForm({
|
|
|
106
111
|
// Cast customer to form schema type (API type may have additional fields)
|
|
107
112
|
customer: (initialValues?.customer as CreateDeliveryNoteFormValues["customer"]) ?? undefined,
|
|
108
113
|
items: initialValues?.items?.length
|
|
109
|
-
? initialValues.items.map((item) => ({
|
|
114
|
+
? initialValues.items.map((item: any) => ({
|
|
115
|
+
type: item.type,
|
|
110
116
|
name: item.name || "",
|
|
111
117
|
description: item.description || "",
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
118
|
+
...(item.type !== "separator"
|
|
119
|
+
? {
|
|
120
|
+
quantity: item.quantity ?? 1,
|
|
121
|
+
// Use gross_price if set, otherwise use price
|
|
122
|
+
price: item.gross_price ?? item.price,
|
|
123
|
+
taxes: item.taxes || [],
|
|
124
|
+
}
|
|
125
|
+
: {}),
|
|
116
126
|
}))
|
|
117
127
|
: [
|
|
118
128
|
{
|
|
@@ -125,6 +135,7 @@ export default function CreateDeliveryNoteForm({
|
|
|
125
135
|
],
|
|
126
136
|
currency_code: initialValues?.currency_code || activeEntity?.currency_code || "EUR",
|
|
127
137
|
note: initialValues?.note ?? "",
|
|
138
|
+
tax_clause: (initialValues as any)?.tax_clause ?? "",
|
|
128
139
|
},
|
|
129
140
|
});
|
|
130
141
|
|
|
@@ -162,6 +173,37 @@ export default function CreateDeliveryNoteForm({
|
|
|
162
173
|
control: form.control,
|
|
163
174
|
});
|
|
164
175
|
|
|
176
|
+
// ============================================================================
|
|
177
|
+
// VIES Check - determine if reverse charge applies
|
|
178
|
+
// ============================================================================
|
|
179
|
+
const {
|
|
180
|
+
reverseChargeApplies,
|
|
181
|
+
transactionType,
|
|
182
|
+
isFetching: isViesFetching,
|
|
183
|
+
warning: viesWarning,
|
|
184
|
+
} = useViesCheck({
|
|
185
|
+
issuerCountryCode: activeEntity?.country_code,
|
|
186
|
+
isTaxSubject: activeEntity?.is_tax_subject ?? true,
|
|
187
|
+
customerCountry: formValues.customer?.country,
|
|
188
|
+
customerCountryCode: formValues.customer?.country_code,
|
|
189
|
+
customerTaxNumber: formValues.customer?.tax_number,
|
|
190
|
+
enabled: !!activeEntity,
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
// Auto-populate tax_clause from entity settings when transaction type changes
|
|
194
|
+
const effectiveTransactionType = transactionType ?? "domestic";
|
|
195
|
+
const prevTransactionTypeRef = useRef<string | undefined>(undefined);
|
|
196
|
+
useEffect(() => {
|
|
197
|
+
if (effectiveTransactionType === prevTransactionTypeRef.current) return;
|
|
198
|
+
prevTransactionTypeRef.current = effectiveTransactionType;
|
|
199
|
+
|
|
200
|
+
const taxClauseDefaults = (activeEntity?.settings as any)?.tax_clause_defaults;
|
|
201
|
+
if (!taxClauseDefaults) return;
|
|
202
|
+
|
|
203
|
+
const clause = taxClauseDefaults[effectiveTransactionType] ?? "";
|
|
204
|
+
form.setValue("tax_clause", clause);
|
|
205
|
+
}, [effectiveTransactionType, activeEntity, form]);
|
|
206
|
+
|
|
165
207
|
// Extract customer management logic into a custom hook
|
|
166
208
|
const {
|
|
167
209
|
originalCustomer,
|
|
@@ -232,7 +274,7 @@ export default function CreateDeliveryNoteForm({
|
|
|
232
274
|
useFormFooterRegistration({
|
|
233
275
|
formId: "create-delivery-note-form",
|
|
234
276
|
isPending,
|
|
235
|
-
isDirty: form.formState.isDirty,
|
|
277
|
+
isDirty: form.formState.isDirty || !!initialValues,
|
|
236
278
|
label: t("Create Delivery Note"),
|
|
237
279
|
secondaryAction,
|
|
238
280
|
});
|
|
@@ -313,6 +355,10 @@ export default function CreateDeliveryNoteForm({
|
|
|
313
355
|
maxTaxesPerItem={activeEntity?.country_rules?.max_taxes_per_item}
|
|
314
356
|
priceModesRef={priceModesRef}
|
|
315
357
|
initialPriceModes={initialPriceModes}
|
|
358
|
+
taxesDisabled={reverseChargeApplies}
|
|
359
|
+
taxesDisabledMessage={
|
|
360
|
+
reverseChargeApplies ? t("Reverse charge - tax exempt EU B2B sale") : viesWarning ? viesWarning : undefined
|
|
361
|
+
}
|
|
316
362
|
/>
|
|
317
363
|
|
|
318
364
|
<DocumentNoteField
|
|
@@ -326,6 +372,20 @@ export default function CreateDeliveryNoteForm({
|
|
|
326
372
|
customer: formValues.customer as any,
|
|
327
373
|
}}
|
|
328
374
|
/>
|
|
375
|
+
|
|
376
|
+
<DocumentTaxClauseField
|
|
377
|
+
control={form.control}
|
|
378
|
+
t={t}
|
|
379
|
+
entity={activeEntity}
|
|
380
|
+
document={{
|
|
381
|
+
number: formValues.number,
|
|
382
|
+
date: formValues.date,
|
|
383
|
+
currency_code: formValues.currency_code,
|
|
384
|
+
customer: formValues.customer as any,
|
|
385
|
+
}}
|
|
386
|
+
transactionType={transactionType}
|
|
387
|
+
isTransactionTypeFetching={isViesFetching}
|
|
388
|
+
/>
|
|
329
389
|
</form>
|
|
330
390
|
</Form>
|
|
331
391
|
);
|
|
@@ -47,4 +47,20 @@ export default {
|
|
|
47
47
|
"Gross price (tax included)": "Bruttopreis (inkl. MwSt.)",
|
|
48
48
|
"Net price (before tax)": "Nettopreis (exkl. MwSt.)",
|
|
49
49
|
"Hide prices": "Preise ausblenden",
|
|
50
|
+
// Separator items
|
|
51
|
+
"Add separator": "Trennzeile hinzufügen",
|
|
52
|
+
"Section header": "Abschnittsüberschrift",
|
|
53
|
+
"Section title...": "Abschnittstitel...",
|
|
54
|
+
// Transaction type
|
|
55
|
+
"Transaction type": "Transaktionstyp",
|
|
56
|
+
Domestic: "Inland",
|
|
57
|
+
"EU B2B": "EU B2B",
|
|
58
|
+
"EU B2C": "EU B2C",
|
|
59
|
+
Export: "Export",
|
|
60
|
+
"Determining transaction type...": "Transaktionstyp wird ermittelt...",
|
|
61
|
+
"This invoice will not be fiscalized (non-domestic transaction)":
|
|
62
|
+
"Diese Rechnung wird nicht fiskalisiert (nicht-inländische Transaktion)",
|
|
63
|
+
"Tax Clause": "Steuerklausel",
|
|
64
|
+
"Add tax clause...": "Steuerklausel hinzufügen...",
|
|
65
|
+
"Reverse charge - tax exempt EU B2B sale": "Umkehrung der Steuerschuldnerschaft - steuerbefreiter EU B2B Verkauf",
|
|
50
66
|
} as const;
|
|
@@ -46,4 +46,20 @@ export default {
|
|
|
46
46
|
"Gross price (tax included)": "Precio bruto (impuestos incluidos)",
|
|
47
47
|
"Net price (before tax)": "Precio neto (antes de impuestos)",
|
|
48
48
|
"Hide prices": "Ocultar precios",
|
|
49
|
+
// Separator items
|
|
50
|
+
"Add separator": "Añadir separador",
|
|
51
|
+
"Section header": "Encabezado de sección",
|
|
52
|
+
"Section title...": "Título de sección...",
|
|
53
|
+
// Transaction type
|
|
54
|
+
"Transaction type": "Tipo de transacción",
|
|
55
|
+
Domestic: "Nacional",
|
|
56
|
+
"EU B2B": "EU B2B",
|
|
57
|
+
"EU B2C": "EU B2C",
|
|
58
|
+
Export: "Exportación",
|
|
59
|
+
"Determining transaction type...": "Determinando tipo de transacción...",
|
|
60
|
+
"This invoice will not be fiscalized (non-domestic transaction)":
|
|
61
|
+
"Esta factura no será fiscalizada (transacción no nacional)",
|
|
62
|
+
"Tax Clause": "Cláusula fiscal",
|
|
63
|
+
"Add tax clause...": "Agregar cláusula fiscal...",
|
|
64
|
+
"Reverse charge - tax exempt EU B2B sale": "Inversión del sujeto pasivo - venta EU B2B exenta de impuestos",
|
|
49
65
|
} as const;
|
|
@@ -47,4 +47,20 @@ export default {
|
|
|
47
47
|
"Gross price (tax included)": "Prix brut (taxes incluses)",
|
|
48
48
|
"Net price (before tax)": "Prix net (avant taxes)",
|
|
49
49
|
"Hide prices": "Masquer les prix",
|
|
50
|
+
// Separator items
|
|
51
|
+
"Add separator": "Ajouter un séparateur",
|
|
52
|
+
"Section header": "En-tête de section",
|
|
53
|
+
"Section title...": "Titre de section...",
|
|
54
|
+
// Transaction type
|
|
55
|
+
"Transaction type": "Type de transaction",
|
|
56
|
+
Domestic: "Nationale",
|
|
57
|
+
"EU B2B": "EU B2B",
|
|
58
|
+
"EU B2C": "EU B2C",
|
|
59
|
+
Export: "Exportation",
|
|
60
|
+
"Determining transaction type...": "Détermination du type de transaction...",
|
|
61
|
+
"This invoice will not be fiscalized (non-domestic transaction)":
|
|
62
|
+
"Cette facture ne sera pas fiscalisée (transaction non nationale)",
|
|
63
|
+
"Tax Clause": "Clause fiscale",
|
|
64
|
+
"Add tax clause...": "Ajouter une clause fiscale...",
|
|
65
|
+
"Reverse charge - tax exempt EU B2B sale": "Autoliquidation - vente B2B UE exonérée de taxe",
|
|
50
66
|
} as const;
|
|
@@ -46,4 +46,20 @@ export default {
|
|
|
46
46
|
"Gross price (tax included)": "Bruto cijena (s porezom)",
|
|
47
47
|
"Net price (before tax)": "Neto cijena (prije poreza)",
|
|
48
48
|
"Hide prices": "Sakrij cijene",
|
|
49
|
+
// Separator items
|
|
50
|
+
"Add separator": "Dodaj separator",
|
|
51
|
+
"Section header": "Naslov odjeljka",
|
|
52
|
+
"Section title...": "Naslov odjeljka...",
|
|
53
|
+
// Transaction type
|
|
54
|
+
"Transaction type": "Vrsta transakcije",
|
|
55
|
+
Domestic: "Domaća",
|
|
56
|
+
"EU B2B": "EU B2B",
|
|
57
|
+
"EU B2C": "EU B2C",
|
|
58
|
+
Export: "Izvoz",
|
|
59
|
+
"Determining transaction type...": "Određivanje vrste transakcije...",
|
|
60
|
+
"This invoice will not be fiscalized (non-domestic transaction)":
|
|
61
|
+
"Ovaj račun neće biti fiskaliziran (nedomaća transakcija)",
|
|
62
|
+
"Tax Clause": "Porezna klauzula",
|
|
63
|
+
"Add tax clause...": "Dodajte poreznu klauzulu...",
|
|
64
|
+
"Reverse charge - tax exempt EU B2B sale": "Prijenos porezne obveze - porezno oslobođena EU B2B prodaja",
|
|
49
65
|
} as const;
|
|
@@ -46,4 +46,20 @@ export default {
|
|
|
46
46
|
"Gross price (tax included)": "Prezzo lordo (imposte incluse)",
|
|
47
47
|
"Net price (before tax)": "Prezzo netto (prima delle imposte)",
|
|
48
48
|
"Hide prices": "Nascondi prezzi",
|
|
49
|
+
// Separator items
|
|
50
|
+
"Add separator": "Aggiungi separatore",
|
|
51
|
+
"Section header": "Intestazione sezione",
|
|
52
|
+
"Section title...": "Titolo sezione...",
|
|
53
|
+
// Transaction type
|
|
54
|
+
"Transaction type": "Tipo di transazione",
|
|
55
|
+
Domestic: "Nazionale",
|
|
56
|
+
"EU B2B": "EU B2B",
|
|
57
|
+
"EU B2C": "EU B2C",
|
|
58
|
+
Export: "Esportazione",
|
|
59
|
+
"Determining transaction type...": "Determinazione tipo di transazione...",
|
|
60
|
+
"This invoice will not be fiscalized (non-domestic transaction)":
|
|
61
|
+
"Questa fattura non sarà fiscalizzata (transazione non nazionale)",
|
|
62
|
+
"Tax Clause": "Clausola fiscale",
|
|
63
|
+
"Add tax clause...": "Aggiungi clausola fiscale...",
|
|
64
|
+
"Reverse charge - tax exempt EU B2B sale": "Inversione contabile - vendita B2B UE esente da imposta",
|
|
49
65
|
} as const;
|
|
@@ -47,4 +47,20 @@ export default {
|
|
|
47
47
|
"Gross price (tax included)": "Brutoprijs (inclusief belasting)",
|
|
48
48
|
"Net price (before tax)": "Nettoprijs (exclusief belasting)",
|
|
49
49
|
"Hide prices": "Prijzen verbergen",
|
|
50
|
+
// Separator items
|
|
51
|
+
"Add separator": "Scheidingslijn toevoegen",
|
|
52
|
+
"Section header": "Sectiekop",
|
|
53
|
+
"Section title...": "Sectietitel...",
|
|
54
|
+
// Transaction type
|
|
55
|
+
"Transaction type": "Transactietype",
|
|
56
|
+
Domestic: "Binnenland",
|
|
57
|
+
"EU B2B": "EU B2B",
|
|
58
|
+
"EU B2C": "EU B2C",
|
|
59
|
+
Export: "Export",
|
|
60
|
+
"Determining transaction type...": "Transactietype bepalen...",
|
|
61
|
+
"This invoice will not be fiscalized (non-domestic transaction)":
|
|
62
|
+
"Deze factuur wordt niet gefiscaliseerd (niet-binnenlandse transactie)",
|
|
63
|
+
"Tax Clause": "Belastingclausule",
|
|
64
|
+
"Add tax clause...": "Belastingclausule toevoegen...",
|
|
65
|
+
"Reverse charge - tax exempt EU B2B sale": "Verlegging - belastingvrijgestelde EU B2B verkoop",
|
|
50
66
|
} as const;
|
|
@@ -46,4 +46,20 @@ export default {
|
|
|
46
46
|
"Gross price (tax included)": "Cena brutto (z podatkiem)",
|
|
47
47
|
"Net price (before tax)": "Cena netto (przed podatkiem)",
|
|
48
48
|
"Hide prices": "Ukryj ceny",
|
|
49
|
+
// Separator items
|
|
50
|
+
"Add separator": "Dodaj separator",
|
|
51
|
+
"Section header": "Nagłówek sekcji",
|
|
52
|
+
"Section title...": "Tytuł sekcji...",
|
|
53
|
+
// Transaction type
|
|
54
|
+
"Transaction type": "Typ transakcji",
|
|
55
|
+
Domestic: "Krajowa",
|
|
56
|
+
"EU B2B": "EU B2B",
|
|
57
|
+
"EU B2C": "EU B2C",
|
|
58
|
+
Export: "Eksport",
|
|
59
|
+
"Determining transaction type...": "Określanie typu transakcji...",
|
|
60
|
+
"This invoice will not be fiscalized (non-domestic transaction)":
|
|
61
|
+
"Ta faktura nie będzie fiskalizowana (transakcja niekrajowa)",
|
|
62
|
+
"Tax Clause": "Klauzula podatkowa",
|
|
63
|
+
"Add tax clause...": "Dodaj klauzulę podatkową...",
|
|
64
|
+
"Reverse charge - tax exempt EU B2B sale": "Odwrotne obciążenie - zwolniona z podatku sprzedaż EU B2B",
|
|
49
65
|
} as const;
|