feat(vat)!: remove VAT entirely from offers, orders and issued orders
Accounting rule: nabidky, objednavky (incl. confirmation PDF) and objednavky
vydane are NOT tax documents - VAT belongs only on invoices. Per user
decision this is a FULL removal including DB columns.
- migration: DROP orders.vat_rate/apply_vat, issued_orders.vat_rate/apply_vat,
issued_order_items.vat_rate, quotations.vat_rate/apply_vat (applied to
dev + test DBs)
- services: net-only totals everywhere (computeIssuedOrderTotals(items) ->
{total}; enrichOrder/enrichQuotation net; per-currency list totals net)
- PDFs (offers, order confirmation, issued order): no VAT columns/summary,
single 'Celkem bez DPH' / 'Total excl. VAT' total, note under totals:
'Ceny jsou uvedeny bez DPH. DPH bude uctovano dle platnych predpisu.';
duplicate Cena/Celkem column merged (desc width 56%); orders-pdf render
extracted as exported renderOrderConfirmationHtml for testability
- frontend: Uplatnit DPH checkboxes, VAT selects and per-item VAT columns
removed from OfferDetail/OrderDetail/IssuedOrderDetail/
OrderConfirmationModal/ReceivedOrders manual-create; list footers read
'Celkem bez DPH'; invoice-from-order prefill now takes the company default
VAT (invoice decides its own VAT)
- tests: suites reworked to net math; new pdf-vat-note.test.ts pins the
exact cs+en note text and VAT-free layout on all three PDFs
Invoices and received invoices keep their VAT handling unchanged.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -787,13 +787,12 @@ export default function InvoiceDetail() {
|
||||
}));
|
||||
}
|
||||
|
||||
// Pre-fill from order
|
||||
// Pre-fill from order. Orders no longer carry VAT (not tax documents) —
|
||||
// the invoice decides its own VAT: default rate from company settings,
|
||||
// apply_vat on.
|
||||
if (fromOrderId && orderDataQuery.data) {
|
||||
const order = orderDataQuery.data;
|
||||
const vatRate = numberOr(
|
||||
order.vat_rate,
|
||||
companySettings?.default_vat_rate ?? 21,
|
||||
);
|
||||
const vatRate = numberOr(companySettings?.default_vat_rate, 21);
|
||||
setForm((prev) => ({
|
||||
...prev,
|
||||
customer_id: order.customer_id as number,
|
||||
@@ -803,7 +802,7 @@ export default function InvoiceDetail() {
|
||||
(order.currency as string) ||
|
||||
companySettings?.default_currency ||
|
||||
"CZK",
|
||||
apply_vat: Number(order.apply_vat) || 0,
|
||||
apply_vat: 1,
|
||||
vat_rate: vatRate,
|
||||
}));
|
||||
const orderItems = order.items as Record<string, unknown>[] | undefined;
|
||||
|
||||
@@ -57,7 +57,6 @@ import {
|
||||
DateField,
|
||||
Field,
|
||||
StatusChip,
|
||||
CheckboxField,
|
||||
ConfirmDialog,
|
||||
LoadingState,
|
||||
PageEnter,
|
||||
@@ -85,11 +84,6 @@ const TRANSITION_LABELS: Record<string, string> = {
|
||||
cancelled: "Stornovat",
|
||||
};
|
||||
|
||||
const VAT_OPTIONS = [0, 10, 12, 15, 21].map((v) => ({
|
||||
value: String(v),
|
||||
label: `${v}%`,
|
||||
}));
|
||||
|
||||
const CURRENCY_FALLBACK = ["CZK", "EUR", "USD", "GBP"];
|
||||
|
||||
const BackIcon = (
|
||||
@@ -151,20 +145,16 @@ interface OrderItem {
|
||||
item_description: string;
|
||||
// Held as the raw typed string while editing so the field can be cleared
|
||||
// (empty renders fine in a type="number" input). Coerced via Number(x) || 0
|
||||
// only where used (live totals + save payload). vat_rate stays numeric: it
|
||||
// is edited via a Select, never a free-text number input.
|
||||
// only where used (live totals + save payload).
|
||||
quantity: string | number;
|
||||
unit: string;
|
||||
unit_price: string | number;
|
||||
vat_rate: number;
|
||||
}
|
||||
|
||||
interface OrderForm {
|
||||
supplier_id: number | null;
|
||||
supplier_name: string;
|
||||
currency: string;
|
||||
apply_vat: boolean;
|
||||
vat_rate: number;
|
||||
order_date: string;
|
||||
delivery_date: string;
|
||||
language: string;
|
||||
@@ -182,7 +172,6 @@ function SortableOrderRow({
|
||||
item,
|
||||
index,
|
||||
currency,
|
||||
apply_vat,
|
||||
readOnly,
|
||||
onUpdate,
|
||||
onRemove,
|
||||
@@ -191,7 +180,6 @@ function SortableOrderRow({
|
||||
item: OrderItem;
|
||||
index: number;
|
||||
currency: string;
|
||||
apply_vat: boolean;
|
||||
readOnly: boolean;
|
||||
onUpdate: (
|
||||
index: number,
|
||||
@@ -290,7 +278,7 @@ function SortableOrderRow({
|
||||
<Box
|
||||
sx={{
|
||||
display: "grid",
|
||||
gridTemplateColumns: "repeat(2, minmax(0, 1fr))",
|
||||
gridTemplateColumns: "repeat(3, minmax(0, 1fr))",
|
||||
gap: 1,
|
||||
}}
|
||||
>
|
||||
@@ -317,21 +305,6 @@ function SortableOrderRow({
|
||||
slotProps={{ htmlInput: { step: "any" } }}
|
||||
InputProps={{ readOnly }}
|
||||
/>
|
||||
{apply_vat &&
|
||||
(readOnly ? (
|
||||
<TextField
|
||||
label="DPH"
|
||||
value={`${Number(item.vat_rate)}%`}
|
||||
InputProps={{ readOnly: true }}
|
||||
/>
|
||||
) : (
|
||||
<Select
|
||||
label="DPH"
|
||||
value={String(item.vat_rate)}
|
||||
onChange={(val) => onUpdate(index, "vat_rate", Number(val))}
|
||||
options={VAT_OPTIONS}
|
||||
/>
|
||||
))}
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
@@ -436,20 +409,6 @@ function SortableOrderRow({
|
||||
sx={{ "& input": { textAlign: "right" } }}
|
||||
/>
|
||||
</TableCell>
|
||||
{apply_vat ? (
|
||||
<TableCell>
|
||||
{readOnly ? (
|
||||
<Box sx={{ textAlign: "center" }}>{Number(item.vat_rate)}%</Box>
|
||||
) : (
|
||||
<Select
|
||||
value={String(item.vat_rate)}
|
||||
onChange={(val) => onUpdate(index, "vat_rate", Number(val))}
|
||||
sx={{ minWidth: "4.5rem" }}
|
||||
options={VAT_OPTIONS}
|
||||
/>
|
||||
)}
|
||||
</TableCell>
|
||||
) : null}
|
||||
<TableCell
|
||||
align="right"
|
||||
sx={{
|
||||
@@ -496,7 +455,6 @@ export default function IssuedOrderDetail() {
|
||||
quantity: 1,
|
||||
unit: "ks",
|
||||
unit_price: 0,
|
||||
vat_rate: 21,
|
||||
}),
|
||||
[],
|
||||
);
|
||||
@@ -513,8 +471,6 @@ export default function IssuedOrderDetail() {
|
||||
supplier_id: null,
|
||||
supplier_name: "",
|
||||
currency: "CZK",
|
||||
apply_vat: true,
|
||||
vat_rate: 21,
|
||||
order_date: todayLocalStr(),
|
||||
delivery_date: "",
|
||||
language: "cs",
|
||||
@@ -534,7 +490,6 @@ export default function IssuedOrderDetail() {
|
||||
quantity: 1,
|
||||
unit: "ks",
|
||||
unit_price: 0,
|
||||
vat_rate: 21,
|
||||
},
|
||||
]);
|
||||
const [errors, setErrors] = useState<Record<string, string>>({});
|
||||
@@ -615,8 +570,6 @@ export default function IssuedOrderDetail() {
|
||||
supplier_id: d.supplier_id ?? null,
|
||||
supplier_name: d.supplier_name ?? "",
|
||||
currency: d.currency || "CZK",
|
||||
apply_vat: d.apply_vat !== false,
|
||||
vat_rate: numberOr(d.vat_rate, 21),
|
||||
order_date: normalizeDateStr(d.order_date),
|
||||
delivery_date: normalizeDateStr(d.delivery_date),
|
||||
language: d.language || "cs",
|
||||
@@ -640,7 +593,6 @@ export default function IssuedOrderDetail() {
|
||||
quantity: numberOr(it.quantity, 1),
|
||||
unit: it.unit || "",
|
||||
unit_price: Number(it.unit_price) || 0,
|
||||
vat_rate: numberOr(it.vat_rate, numberOr(d.vat_rate, 21)),
|
||||
}))
|
||||
: [];
|
||||
if (mapped.length > 0) setItems(mapped);
|
||||
@@ -683,21 +635,14 @@ export default function IssuedOrderDetail() {
|
||||
const editable = !isEdit || form.status === "draft" || form.status === "sent";
|
||||
const canExport = hasPermission("orders.view");
|
||||
|
||||
// ─── Totals (live) ───
|
||||
// ─── Totals (live, NET only — issued orders carry no VAT) ───
|
||||
const totals = useMemo(() => {
|
||||
let subtotal = 0;
|
||||
const vatByRate: Record<number, number> = {};
|
||||
let total = 0;
|
||||
items.forEach((it) => {
|
||||
const line = (Number(it.quantity) || 0) * (Number(it.unit_price) || 0);
|
||||
subtotal += line;
|
||||
if (form.apply_vat) {
|
||||
const rate = Number(it.vat_rate) || 0;
|
||||
vatByRate[rate] = (vatByRate[rate] || 0) + (line * rate) / 100;
|
||||
}
|
||||
total += (Number(it.quantity) || 0) * (Number(it.unit_price) || 0);
|
||||
});
|
||||
const totalVat = Object.values(vatByRate).reduce((s, v) => s + v, 0);
|
||||
return { subtotal, vatByRate, totalVat, total: subtotal + totalVat };
|
||||
}, [items, form.apply_vat]);
|
||||
return { total };
|
||||
}, [items]);
|
||||
|
||||
// ─── Mutations ───
|
||||
const saveMutation = useApiMutation<Record<string, unknown>, { id: number }>({
|
||||
@@ -774,8 +719,6 @@ export default function IssuedOrderDetail() {
|
||||
const payload: Record<string, unknown> = {
|
||||
supplier_id: form.supplier_id,
|
||||
currency: form.currency,
|
||||
vat_rate: form.vat_rate,
|
||||
apply_vat: form.apply_vat,
|
||||
order_date: form.order_date,
|
||||
delivery_date: form.delivery_date || null,
|
||||
language: form.language,
|
||||
@@ -795,7 +738,6 @@ export default function IssuedOrderDetail() {
|
||||
quantity: Number(it.quantity) || 0,
|
||||
unit: it.unit,
|
||||
unit_price: Number(it.unit_price) || 0,
|
||||
vat_rate: it.vat_rate,
|
||||
position: i,
|
||||
})),
|
||||
};
|
||||
@@ -1175,7 +1117,7 @@ export default function IssuedOrderDetail() {
|
||||
<Box
|
||||
sx={{
|
||||
display: "grid",
|
||||
gridTemplateColumns: { xs: "1fr", md: "1fr 1fr 1fr 1fr" },
|
||||
gridTemplateColumns: { xs: "1fr", md: "1fr 1fr" },
|
||||
gap: 2,
|
||||
}}
|
||||
>
|
||||
@@ -1202,31 +1144,6 @@ export default function IssuedOrderDetail() {
|
||||
]}
|
||||
/>
|
||||
</Field>
|
||||
<Field label="Sazba DPH">
|
||||
<Select
|
||||
value={String(form.vat_rate)}
|
||||
disabled={!editable || !form.apply_vat}
|
||||
onChange={(val) =>
|
||||
setForm((prev) => ({ ...prev, vat_rate: Number(val) }))
|
||||
}
|
||||
options={VAT_OPTIONS}
|
||||
/>
|
||||
</Field>
|
||||
<Field label="DPH">
|
||||
<Box sx={{ display: "flex", alignItems: "center", height: 40 }}>
|
||||
<CheckboxField
|
||||
label="Uplatnit DPH"
|
||||
checked={form.apply_vat}
|
||||
disabled={!editable}
|
||||
onChange={(v) =>
|
||||
setForm((prev) => ({
|
||||
...prev,
|
||||
apply_vat: v,
|
||||
}))
|
||||
}
|
||||
/>
|
||||
</Box>
|
||||
</Field>
|
||||
</Box>
|
||||
|
||||
<Field label="Vystavil">
|
||||
@@ -1291,7 +1208,6 @@ export default function IssuedOrderDetail() {
|
||||
item={item}
|
||||
index={index}
|
||||
currency={form.currency}
|
||||
apply_vat={form.apply_vat}
|
||||
readOnly={!editable}
|
||||
onUpdate={updateItem}
|
||||
onRemove={removeItem}
|
||||
@@ -1324,11 +1240,6 @@ export default function IssuedOrderDetail() {
|
||||
<TableCell sx={{ width: "8rem" }} align="center">
|
||||
Jedn. cena
|
||||
</TableCell>
|
||||
{form.apply_vat ? (
|
||||
<TableCell sx={{ width: "5rem" }} align="center">
|
||||
DPH
|
||||
</TableCell>
|
||||
) : null}
|
||||
<TableCell sx={{ width: "8rem" }} align="right">
|
||||
Celkem
|
||||
</TableCell>
|
||||
@@ -1342,7 +1253,6 @@ export default function IssuedOrderDetail() {
|
||||
item={item}
|
||||
index={index}
|
||||
currency={form.currency}
|
||||
apply_vat={form.apply_vat}
|
||||
readOnly={!editable}
|
||||
onUpdate={updateItem}
|
||||
onRemove={removeItem}
|
||||
@@ -1356,7 +1266,7 @@ export default function IssuedOrderDetail() {
|
||||
</SortableContext>
|
||||
</DndContext>
|
||||
|
||||
{/* Totals */}
|
||||
{/* Totals (NET only — issued orders are not tax documents) */}
|
||||
<Box
|
||||
sx={{
|
||||
mt: 2,
|
||||
@@ -1367,34 +1277,6 @@ export default function IssuedOrderDetail() {
|
||||
gap: 0.5,
|
||||
}}
|
||||
>
|
||||
<Box sx={{ display: "flex", justifyContent: "space-between" }}>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
Mezisoučet:
|
||||
</Typography>
|
||||
<Typography
|
||||
variant="body2"
|
||||
sx={{ fontFamily: "'DM Mono', Menlo, monospace" }}
|
||||
>
|
||||
{formatCurrency(totals.subtotal, form.currency)}
|
||||
</Typography>
|
||||
</Box>
|
||||
{form.apply_vat &&
|
||||
Object.entries(totals.vatByRate).map(([rate, amount]) => (
|
||||
<Box
|
||||
key={rate}
|
||||
sx={{ display: "flex", justifyContent: "space-between" }}
|
||||
>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
DPH {rate}%:
|
||||
</Typography>
|
||||
<Typography
|
||||
variant="body2"
|
||||
sx={{ fontFamily: "'DM Mono', Menlo, monospace" }}
|
||||
>
|
||||
{formatCurrency(amount, form.currency)}
|
||||
</Typography>
|
||||
</Box>
|
||||
))}
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
@@ -1406,7 +1288,7 @@ export default function IssuedOrderDetail() {
|
||||
}}
|
||||
>
|
||||
<Typography variant="body2" sx={{ fontWeight: 700 }}>
|
||||
Celkem:
|
||||
Celkem bez DPH:
|
||||
</Typography>
|
||||
<Typography
|
||||
variant="body2"
|
||||
|
||||
@@ -392,7 +392,7 @@ export default function IssuedOrders({ month, year }: IssuedOrdersProps) {
|
||||
fontSize: "0.9rem",
|
||||
}}
|
||||
>
|
||||
<span>Celkem:</span>
|
||||
<span>Celkem bez DPH:</span>
|
||||
<Box
|
||||
component="span"
|
||||
sx={{ fontWeight: 700, color: "text.primary" }}
|
||||
|
||||
@@ -118,8 +118,6 @@ interface OfferForm {
|
||||
valid_until: string;
|
||||
currency: string;
|
||||
language: string;
|
||||
vat_rate: number;
|
||||
apply_vat: boolean;
|
||||
}
|
||||
|
||||
const emptyForm: OfferForm = {
|
||||
@@ -131,8 +129,6 @@ const emptyForm: OfferForm = {
|
||||
valid_until: "",
|
||||
currency: "CZK",
|
||||
language: "EN",
|
||||
vat_rate: 21,
|
||||
apply_vat: false,
|
||||
};
|
||||
|
||||
const emptyScopeSection = (): ScopeSection => ({
|
||||
@@ -585,10 +581,6 @@ export default function OfferDetail() {
|
||||
prev.currency === "CZK"
|
||||
? companySettings.default_currency || "CZK"
|
||||
: prev.currency,
|
||||
vat_rate:
|
||||
prev.vat_rate === 21
|
||||
? (companySettings.default_vat_rate ?? 21)
|
||||
: prev.vat_rate,
|
||||
}));
|
||||
}
|
||||
}, [companySettings, isEdit]);
|
||||
@@ -639,8 +631,6 @@ export default function OfferDetail() {
|
||||
valid_until: d.valid_until ? d.valid_until.substring(0, 10) : "",
|
||||
currency: d.currency || companySettings?.default_currency || "CZK",
|
||||
language: d.language || "EN",
|
||||
vat_rate: d.vat_rate ?? companySettings?.default_vat_rate ?? 21,
|
||||
apply_vat: !!d.apply_vat,
|
||||
};
|
||||
setForm(formData);
|
||||
const mappedItems =
|
||||
@@ -790,7 +780,8 @@ export default function OfferDetail() {
|
||||
setItems((prev) => prev.filter((_, i) => i !== index));
|
||||
};
|
||||
|
||||
const subtotal = items.reduce((sum, item) => {
|
||||
// NET only — offers are not tax documents (no VAT on them).
|
||||
const total = items.reduce((sum, item) => {
|
||||
if (item.is_included_in_total) {
|
||||
return (
|
||||
sum + (Number(item.quantity) || 0) * (Number(item.unit_price) || 0)
|
||||
@@ -798,8 +789,6 @@ export default function OfferDetail() {
|
||||
}
|
||||
return sum;
|
||||
}, 0);
|
||||
const vatAmount = form.apply_vat ? subtotal * (form.vat_rate / 100) : 0;
|
||||
const total = subtotal + vatAmount;
|
||||
|
||||
const handleSave = async (targetStatus?: string) => {
|
||||
const newErrors: Record<string, string> = {};
|
||||
@@ -1389,44 +1378,6 @@ export default function OfferDetail() {
|
||||
/>
|
||||
</Field>
|
||||
</Box>
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
display: "grid",
|
||||
gridTemplateColumns: { xs: "1fr", md: "1fr 1fr 1fr" },
|
||||
gap: 2,
|
||||
}}
|
||||
>
|
||||
<Field label="Sazba DPH">
|
||||
<Box sx={{ display: "flex", gap: 1, alignItems: "center" }}>
|
||||
<Select
|
||||
value={String(form.vat_rate)}
|
||||
onChange={(val) => updateForm("vat_rate", parseFloat(val) || 0)}
|
||||
disabled={readOnly}
|
||||
sx={{ flex: 1 }}
|
||||
options={(
|
||||
companySettings?.available_vat_rates || [0, 10, 12, 15, 21]
|
||||
).map((r) => ({ value: String(r), label: `${r}%` }))}
|
||||
/>
|
||||
<Box
|
||||
component="label"
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
whiteSpace: "nowrap",
|
||||
cursor: readOnly ? "default" : "pointer",
|
||||
}}
|
||||
>
|
||||
<Checkbox
|
||||
checked={form.apply_vat}
|
||||
onChange={(e) => updateForm("apply_vat", e.target.checked)}
|
||||
disabled={readOnly}
|
||||
/>
|
||||
<Box component="span">Uplatnit DPH</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
</Field>
|
||||
</Box>
|
||||
</Card>
|
||||
|
||||
{/* Items Section with drag-and-drop */}
|
||||
@@ -1596,7 +1547,7 @@ export default function OfferDetail() {
|
||||
</SortableContext>
|
||||
</DndContext>
|
||||
|
||||
{/* Totals */}
|
||||
{/* Totals (NET only — offers are not tax documents) */}
|
||||
<Box
|
||||
sx={{
|
||||
mt: 2,
|
||||
@@ -1607,30 +1558,6 @@ export default function OfferDetail() {
|
||||
gap: 0.5,
|
||||
}}
|
||||
>
|
||||
<Box sx={{ display: "flex", justifyContent: "space-between" }}>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
Mezisoučet:
|
||||
</Typography>
|
||||
<Typography
|
||||
variant="body2"
|
||||
sx={{ fontFamily: "'DM Mono', Menlo, monospace" }}
|
||||
>
|
||||
{formatCurrency(subtotal, form.currency)}
|
||||
</Typography>
|
||||
</Box>
|
||||
{form.apply_vat && (
|
||||
<Box sx={{ display: "flex", justifyContent: "space-between" }}>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
DPH ({form.vat_rate}%):
|
||||
</Typography>
|
||||
<Typography
|
||||
variant="body2"
|
||||
sx={{ fontFamily: "'DM Mono', Menlo, monospace" }}
|
||||
>
|
||||
{formatCurrency(vatAmount, form.currency)}
|
||||
</Typography>
|
||||
</Box>
|
||||
)}
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
@@ -1642,7 +1569,7 @@ export default function OfferDetail() {
|
||||
}}
|
||||
>
|
||||
<Typography variant="body2" sx={{ fontWeight: 700 }}>
|
||||
Celkem:
|
||||
Celkem bez DPH:
|
||||
</Typography>
|
||||
<Typography
|
||||
variant="body2"
|
||||
|
||||
@@ -906,7 +906,7 @@ export default function Offers() {
|
||||
fontSize: "0.9rem",
|
||||
}}
|
||||
>
|
||||
<span>Celkem:</span>
|
||||
<span>Celkem bez DPH:</span>
|
||||
<Box
|
||||
component="span"
|
||||
sx={{ fontWeight: 700, color: "text.primary" }}
|
||||
|
||||
@@ -144,9 +144,10 @@ export default function OrderDetail() {
|
||||
return () => window.removeEventListener("beforeunload", handler);
|
||||
}, [isDirty]);
|
||||
|
||||
// NET only — received orders are not tax documents (no VAT on them).
|
||||
const totals = useMemo(() => {
|
||||
if (!order?.items) return { subtotal: 0, vatAmount: 0, total: 0 };
|
||||
const subtotal = order.items.reduce((sum, item) => {
|
||||
if (!order?.items) return { total: 0 };
|
||||
const total = order.items.reduce((sum, item) => {
|
||||
if (Number(item.is_included_in_total)) {
|
||||
return (
|
||||
sum + (Number(item.quantity) || 0) * (Number(item.unit_price) || 0)
|
||||
@@ -154,10 +155,7 @@ export default function OrderDetail() {
|
||||
}
|
||||
return sum;
|
||||
}, 0);
|
||||
const vatAmount = Number(order.apply_vat)
|
||||
? subtotal * ((Number(order.vat_rate) || 0) / 100)
|
||||
: 0;
|
||||
return { subtotal, vatAmount, total: subtotal + vatAmount };
|
||||
return { total };
|
||||
}, [order]);
|
||||
|
||||
const statusMutation = useApiMutation<{ status: string }, unknown>({
|
||||
@@ -236,14 +234,12 @@ export default function OrderDetail() {
|
||||
|
||||
const handleGenerateConfirmation = async (
|
||||
lang: string,
|
||||
applyVat: boolean,
|
||||
customItems?: Array<{
|
||||
description: string;
|
||||
quantity: number;
|
||||
unit: string;
|
||||
unit_price: number;
|
||||
is_included_in_total: boolean;
|
||||
vat_rate: number;
|
||||
}>,
|
||||
) => {
|
||||
setConfirmationLoading(true);
|
||||
@@ -253,7 +249,7 @@ export default function OrderDetail() {
|
||||
{
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ lang, applyVat, items: customItems }),
|
||||
body: JSON.stringify({ lang, items: customItems }),
|
||||
},
|
||||
);
|
||||
if (!response.ok) {
|
||||
@@ -625,7 +621,7 @@ export default function OrderDetail() {
|
||||
empty={<EmptyState title="Žádné položky." />}
|
||||
/>
|
||||
|
||||
{/* Totals */}
|
||||
{/* Totals (NET only — orders are not tax documents) */}
|
||||
<Box
|
||||
sx={{
|
||||
mt: 2,
|
||||
@@ -636,30 +632,6 @@ export default function OrderDetail() {
|
||||
gap: 0.5,
|
||||
}}
|
||||
>
|
||||
<Box sx={{ display: "flex", justifyContent: "space-between" }}>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
Mezisoučet:
|
||||
</Typography>
|
||||
<Typography
|
||||
variant="body2"
|
||||
sx={{ fontFamily: "'DM Mono', Menlo, monospace" }}
|
||||
>
|
||||
{formatCurrency(totals.subtotal, order.currency)}
|
||||
</Typography>
|
||||
</Box>
|
||||
{Number(order.apply_vat) > 0 && (
|
||||
<Box sx={{ display: "flex", justifyContent: "space-between" }}>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
DPH ({order.vat_rate}%):
|
||||
</Typography>
|
||||
<Typography
|
||||
variant="body2"
|
||||
sx={{ fontFamily: "'DM Mono', Menlo, monospace" }}
|
||||
>
|
||||
{formatCurrency(totals.vatAmount, order.currency)}
|
||||
</Typography>
|
||||
</Box>
|
||||
)}
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
@@ -671,7 +643,7 @@ export default function OrderDetail() {
|
||||
}}
|
||||
>
|
||||
<Typography variant="body2" sx={{ fontWeight: 700 }}>
|
||||
Celkem k úhradě:
|
||||
Celkem bez DPH:
|
||||
</Typography>
|
||||
<Typography
|
||||
variant="body2"
|
||||
@@ -829,11 +801,8 @@ export default function OrderDetail() {
|
||||
unit: it.unit || "",
|
||||
unit_price: Number(it.unit_price) || 0,
|
||||
is_included_in_total: Number(it.is_included_in_total) !== 0,
|
||||
vat_rate: Number(order.vat_rate) || 21,
|
||||
}))}
|
||||
orderNumber={order.order_number}
|
||||
defaultVatRate={Number(order.vat_rate) || 21}
|
||||
applyVat={!!order.apply_vat}
|
||||
/>
|
||||
)}
|
||||
</PageEnter>
|
||||
|
||||
@@ -184,8 +184,6 @@ export default function OrdersReceived({
|
||||
customer_id: "",
|
||||
customer_order_number: "",
|
||||
currency: "CZK",
|
||||
vat_rate: "21",
|
||||
apply_vat: true,
|
||||
scope_title: "",
|
||||
scope_description: "",
|
||||
notes: "",
|
||||
@@ -219,8 +217,6 @@ export default function OrdersReceived({
|
||||
customer_id: "",
|
||||
customer_order_number: "",
|
||||
currency: "CZK",
|
||||
vat_rate: "21",
|
||||
apply_vat: true,
|
||||
scope_title: "",
|
||||
scope_description: "",
|
||||
notes: "",
|
||||
@@ -255,8 +251,6 @@ export default function OrdersReceived({
|
||||
fd.append("customer_id", createForm.customer_id);
|
||||
fd.append("customer_order_number", createForm.customer_order_number);
|
||||
fd.append("currency", createForm.currency);
|
||||
fd.append("vat_rate", createForm.vat_rate);
|
||||
fd.append("apply_vat", createForm.apply_vat ? "1" : "0");
|
||||
fd.append("scope_title", createForm.scope_title);
|
||||
fd.append("scope_description", createForm.scope_description);
|
||||
fd.append("notes", createForm.notes);
|
||||
@@ -274,8 +268,6 @@ export default function OrdersReceived({
|
||||
: null,
|
||||
customer_order_number: createForm.customer_order_number,
|
||||
currency: createForm.currency,
|
||||
vat_rate: createForm.vat_rate,
|
||||
apply_vat: createForm.apply_vat,
|
||||
scope_title: createForm.scope_title,
|
||||
scope_description: createForm.scope_description,
|
||||
notes: createForm.notes,
|
||||
@@ -571,7 +563,7 @@ export default function OrdersReceived({
|
||||
fontSize: "0.9rem",
|
||||
}}
|
||||
>
|
||||
<span>Celkem:</span>
|
||||
<span>Celkem bez DPH:</span>
|
||||
<Box
|
||||
component="span"
|
||||
sx={{ fontWeight: 700, color: "text.primary" }}
|
||||
@@ -669,19 +661,6 @@ export default function OrdersReceived({
|
||||
/>
|
||||
</Field>
|
||||
</Box>
|
||||
<Box sx={{ flex: "1 1 200px" }}>
|
||||
<Field label="Sazba DPH (%)">
|
||||
<TextField
|
||||
type="number"
|
||||
inputMode="numeric"
|
||||
value={createForm.vat_rate}
|
||||
onChange={(e) =>
|
||||
setCreateForm({ ...createForm, vat_rate: e.target.value })
|
||||
}
|
||||
slotProps={{ htmlInput: { min: 0 } }}
|
||||
/>
|
||||
</Field>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<Box sx={{ display: "flex", gap: 2, flexWrap: "wrap" }}>
|
||||
@@ -757,12 +736,6 @@ export default function OrdersReceived({
|
||||
/>
|
||||
</Field>
|
||||
|
||||
<CheckboxField
|
||||
label="Účtovat DPH"
|
||||
checked={createForm.apply_vat}
|
||||
onChange={(v) => setCreateForm({ ...createForm, apply_vat: v })}
|
||||
/>
|
||||
|
||||
<CheckboxField
|
||||
label="Vytvořit propojený projekt"
|
||||
checked={createForm.create_project}
|
||||
|
||||
Reference in New Issue
Block a user