",
delivery_terms: null,
payment_terms: null,
@@ -451,7 +437,6 @@ describe("renderIssuedOrderHtml", () => {
quantity: 2,
unit: "ks",
unit_price: 100,
- vat_rate: 21,
},
];
@@ -547,33 +532,29 @@ describe("renderIssuedOrderHtml", () => {
expect(fallback).toContain("Objednáváme si u Vás:");
});
- it("with VAT: keeps the VAT columns and the DPH cell holds ONLY the line VAT", () => {
+ it("never renders VAT columns, totals NET with 'Celkem bez DPH' and the prices notice", () => {
const html = renderIssuedOrderHtml(order, items, null, null, "cs", issuer);
- expect(html).toContain("%DPH");
- expect(html).toContain(">DPH<");
- // 2 × 100 @ 21 %: DPH cell = 42,00 (VAT only), Celkem cell = 242,00.
- expect(html).toContain('
42,00
');
- expect(html).toContain('
242,00
');
- expect(html).not.toContain("Celkem bez DPH");
- });
-
- it("without VAT: hides the VAT columns and labels the total 'Celkem bez DPH'", () => {
- const html = renderIssuedOrderHtml(
- { ...order, apply_vat: false },
- items,
- null,
- null,
- "cs",
- issuer,
- );
+ // No VAT anywhere — the PO is not a tax document.
expect(html).not.toContain("%DPH");
expect(html).not.toContain(">DPH<");
- // No per-line VAT cell, line total = netto.
- expect(html).not.toContain('
42,00
');
+ // Line total = netto (2 × 100).
expect(html).toContain('
200,00
');
expect(html).toContain("Celkem bez DPH");
- // The subtotal detail row is dropped (it would duplicate the grand total).
+ // No Mezisoučet row (it would duplicate the grand total).
expect(html).not.toContain("Mezisoučet");
+ // The fixed prices-excl.-VAT notice.
+ expect(html).toContain(
+ "Ceny jsou uvedeny bez DPH. DPH bude účtováno dle platných předpisů.",
+ );
+ });
+
+ it("renders the English notice and total label for lang=en", () => {
+ const html = renderIssuedOrderHtml(order, items, null, null, "en", issuer);
+ expect(html).toContain("Total excl. VAT");
+ expect(html).toContain(
+ "Prices are exclusive of VAT. VAT will be charged according to applicable regulations.",
+ );
+ expect(html).not.toContain("VAT%");
});
it("footer shows the logged-in user's name, no e-mail, no Schválil column", () => {
diff --git a/src/__tests__/manual-create.test.ts b/src/__tests__/manual-create.test.ts
index a71bb17..b3f8edc 100644
--- a/src/__tests__/manual-create.test.ts
+++ b/src/__tests__/manual-create.test.ts
@@ -47,8 +47,6 @@ const baseOrder = {
status: "prijata" as const,
currency: "CZK",
language: "cs",
- vat_rate: 21,
- apply_vat: true,
exchange_rate: 1,
};
@@ -117,8 +115,6 @@ describe("createOrderFromQuotation (auto-project gets its OWN number)", () => {
status: "active",
currency: "CZK",
language: "cs",
- vat_rate: 21,
- apply_vat: true,
},
});
createdQuotationIds.push(quotation.id);
diff --git a/src/__tests__/offer-invoice-totals.test.ts b/src/__tests__/offer-invoice-totals.test.ts
index 83e03d9..a682017 100644
--- a/src/__tests__/offer-invoice-totals.test.ts
+++ b/src/__tests__/offer-invoice-totals.test.ts
@@ -54,18 +54,16 @@ function amountFor(
}
describe("getOfferTotals per-currency aggregation", () => {
- it("sums offer TOTAL incl. VAT per currency over the full filtered set", async () => {
- // Two CZK offers + one EUR. 21% VAT applied on the whole subtotal
- // (enrichQuotation math).
- // CZK #1: 2 x 1000 = 2000 net -> +21% = 2420
- // CZK #2: 1 x 500 = 500 net -> +21% = 605 => CZK total 3025
- // EUR : 3 x 100 = 300 net -> +21% = 363 => EUR total 363
+ it("sums offer NET total per currency over the full filtered set", async () => {
+ // Two CZK offers + one EUR. NET only (enrichQuotation math — offers are
+ // not tax documents, no VAT anywhere).
+ // CZK #1: 2 x 1000 = 2000
+ // CZK #2: 1 x 500 = 500 => CZK total 2500
+ // EUR : 3 x 100 = 300 => EUR total 300
const mk = async (currency: string, qty: number, price: number) => {
const res = await createOffer({
status: "draft", // draft so no offer number is consumed
currency,
- vat_rate: 21,
- apply_vat: true,
project_code: OFFER_MARKER,
items: [{ description: "X", quantity: qty, unit_price: price }],
});
@@ -79,8 +77,8 @@ describe("getOfferTotals per-currency aggregation", () => {
await mk("EUR", 3, 100);
const { totals } = await getOfferTotals({ search: OFFER_MARKER });
- expect(amountFor(totals, "CZK")).toBe(3025);
- expect(amountFor(totals, "EUR")).toBe(363);
+ expect(amountFor(totals, "CZK")).toBe(2500);
+ expect(amountFor(totals, "EUR")).toBe(300);
});
it("respects the where: a status filter narrows the result", async () => {
@@ -88,8 +86,6 @@ describe("getOfferTotals per-currency aggregation", () => {
const res = await createOffer({
status,
currency: "CZK",
- vat_rate: 21,
- apply_vat: true,
project_code: OFFER_MARKER,
items: [{ description: "X", quantity: 1, unit_price: 1000 }],
});
@@ -103,23 +99,23 @@ describe("getOfferTotals per-currency aggregation", () => {
await mk("draft");
await mk("invalidated");
- // Marker only: all three count -> 3 x 1210 = 3630.
+ // Marker only: all three count -> 3 x 1000 = 3000 (net).
const all = await getOfferTotals({ search: OFFER_MARKER });
- expect(amountFor(all.totals, "CZK")).toBe(3630);
+ expect(amountFor(all.totals, "CZK")).toBe(3000);
- // Status filter narrows to the two drafts -> 2 x 1210 = 2420.
+ // Status filter narrows to the two drafts -> 2 x 1000 = 2000.
const onlyDraft = await getOfferTotals({
search: OFFER_MARKER,
status: "draft",
});
- expect(amountFor(onlyDraft.totals, "CZK")).toBe(2420);
+ expect(amountFor(onlyDraft.totals, "CZK")).toBe(2000);
- // Invalidated alone -> 1210.
+ // Invalidated alone -> 1000.
const onlyInvalid = await getOfferTotals({
search: OFFER_MARKER,
status: "invalidated",
});
- expect(amountFor(onlyInvalid.totals, "CZK")).toBe(1210);
+ expect(amountFor(onlyInvalid.totals, "CZK")).toBe(1000);
});
});
diff --git a/src/__tests__/order-totals.test.ts b/src/__tests__/order-totals.test.ts
index 6687aed..e48118c 100644
--- a/src/__tests__/order-totals.test.ts
+++ b/src/__tests__/order-totals.test.ts
@@ -8,8 +8,9 @@ import {
// Per-currency total aggregation for both order lists. These hit the real
// `app_test` DB via the service layer (suite convention) and prove the /stats
-// endpoints sum order TOTAL (incl. VAT) per currency across the WHOLE filtered
-// set, and that the where (month/year + status) is respected.
+// endpoints sum order NET total per currency across the WHOLE filtered set
+// (orders are not tax documents — no VAT anywhere), and that the where
+// (month/year + status) is respected.
//
// A far-future month/year is used so seeded/other-test rows can't fall in the
// window and skew the deterministic sums (mirrors drafts-aggregation.test.ts).
@@ -46,19 +47,17 @@ function amountFor(
}
describe("getOrderTotals (received orders) per-currency aggregation", () => {
- it("sums TOTAL incl. VAT per currency over the full filtered set", async () => {
- // Two CZK orders + one EUR, all in the same far-future month. 21% VAT,
- // applied on the whole subtotal (enrichOrder math).
- // CZK #1: 2 x 1000 = 2000 net -> +21% = 2420
- // CZK #2: 1 x 500 = 500 net -> +21% = 605 => CZK total 3025
- // EUR : 3 x 100 = 300 net -> +21% = 363 => EUR total 363
+ it("sums NET total per currency over the full filtered set", async () => {
+ // Two CZK orders + one EUR, all in the same far-future month. NET only
+ // (enrichOrder math — orders carry no VAT).
+ // CZK #1: 2 x 1000 = 2000
+ // CZK #2: 1 x 500 = 500 => CZK total 2500
+ // EUR : 3 x 100 = 300 => EUR total 300
const mk = async (currency: string, qty: number, price: number) => {
const res = await createOrder({
status: "prijata",
currency,
language: "cs",
- vat_rate: 21,
- apply_vat: true,
create_project: false,
items: [{ description: "X", quantity: qty, unit_price: price }],
});
@@ -83,8 +82,8 @@ describe("getOrderTotals (received orders) per-currency aggregation", () => {
month: STATS_MONTH,
year: STATS_YEAR,
});
- expect(amountFor(totals, "CZK")).toBe(3025);
- expect(amountFor(totals, "EUR")).toBe(363);
+ expect(amountFor(totals, "CZK")).toBe(2500);
+ expect(amountFor(totals, "EUR")).toBe(300);
});
it("respects the where: a different month and a status filter change the result", async () => {
@@ -93,8 +92,6 @@ describe("getOrderTotals (received orders) per-currency aggregation", () => {
status,
currency: "CZK",
language: "cs",
- vat_rate: 21,
- apply_vat: true,
create_project: false,
items: [{ description: "X", quantity: 1, unit_price: 1000 }],
});
@@ -122,48 +119,43 @@ describe("getOrderTotals (received orders) per-currency aggregation", () => {
await mk("stornovana", 0);
await mk("prijata", 1);
- // Whole month: both same-month orders count -> 2 x 1210 = 2420.
+ // Whole month: both same-month orders count -> 2 x 1000 = 2000 (net).
const whole = await getOrderTotals({
month: STATS_MONTH,
year: STATS_YEAR,
});
- expect(amountFor(whole.totals, "CZK")).toBe(2420);
+ expect(amountFor(whole.totals, "CZK")).toBe(2000);
- // Status filter narrows to the single 'prijata' in the target month -> 1210.
+ // Status filter narrows to the single 'prijata' in the target month -> 1000.
const onlyPrijata = await getOrderTotals({
month: STATS_MONTH,
year: STATS_YEAR,
status: "prijata",
});
- expect(amountFor(onlyPrijata.totals, "CZK")).toBe(1210);
+ expect(amountFor(onlyPrijata.totals, "CZK")).toBe(1000);
- // Next month sees only the one 'prijata' there -> 1210.
+ // Next month sees only the one 'prijata' there -> 1000.
const nextMonth = await getOrderTotals({
month: STATS_MONTH + 1,
year: STATS_YEAR,
});
- expect(amountFor(nextMonth.totals, "CZK")).toBe(1210);
+ expect(amountFor(nextMonth.totals, "CZK")).toBe(1000);
});
});
describe("getIssuedOrderTotals (issued orders) per-currency aggregation", () => {
- it("sums TOTAL incl. VAT per currency over the full filtered set", async () => {
- // order_date is settable at create, so no post-create pin needed. VAT is
- // applied per-line (computeIssuedOrderTotals) but with a single line per
- // order here the result matches the on-the-whole subtotal math.
- // CZK #1: 2 x 1000 @21% = 2420
- // CZK #2: 1 x 500 @21% = 605 => CZK total 3025
- // EUR : 3 x 100 @21% = 363 => EUR total 363
+ it("sums NET total per currency over the full filtered set", async () => {
+ // order_date is settable at create, so no post-create pin needed. NET only
+ // (computeIssuedOrderTotals — issued orders carry no VAT).
+ // CZK #1: 2 x 1000 = 2000
+ // CZK #2: 1 x 500 = 500 => CZK total 2500
+ // EUR : 3 x 100 = 300 => EUR total 300
const dateStr = `${STATS_YEAR}-0${STATS_MONTH}-15`;
const mk = async (currency: string, qty: number, price: number) => {
const o = await createIssuedOrder({
currency,
- vat_rate: 21,
- apply_vat: true,
order_date: dateStr,
- items: [
- { description: "X", quantity: qty, unit_price: price, vat_rate: 21 },
- ],
+ items: [{ description: "X", quantity: qty, unit_price: price }],
});
if ("error" in o) throw new Error(`createIssuedOrder failed: ${o.error}`);
createdIssuedIds.push(o.id);
@@ -178,23 +170,19 @@ describe("getIssuedOrderTotals (issued orders) per-currency aggregation", () =>
month: STATS_MONTH,
year: STATS_YEAR,
});
- expect(amountFor(totals, "CZK")).toBe(3025);
- expect(amountFor(totals, "EUR")).toBe(363);
+ expect(amountFor(totals, "CZK")).toBe(2500);
+ expect(amountFor(totals, "EUR")).toBe(300);
});
it("respects the where: a different month and a status filter change the result", async () => {
const inMonth = `${STATS_YEAR}-0${STATS_MONTH}-15`;
const nextMonth = `${STATS_YEAR}-0${STATS_MONTH + 1}-15`;
- // Target month: one draft, one already-sent (both 1210). Next month: one.
+ // Target month: one draft, one already-sent (both 1000 net). Next month: one.
const draft = await createIssuedOrder({
currency: "CZK",
- vat_rate: 21,
- apply_vat: true,
order_date: inMonth,
- items: [
- { description: "X", quantity: 1, unit_price: 1000, vat_rate: 21 },
- ],
+ items: [{ description: "X", quantity: 1, unit_price: 1000 }],
});
if ("error" in draft)
throw new Error(`createIssuedOrder failed: ${draft.error}`);
@@ -203,12 +191,8 @@ describe("getIssuedOrderTotals (issued orders) per-currency aggregation", () =>
const sent = await createIssuedOrder({
status: "sent",
currency: "CZK",
- vat_rate: 21,
- apply_vat: true,
order_date: inMonth,
- items: [
- { description: "X", quantity: 1, unit_price: 1000, vat_rate: 21 },
- ],
+ items: [{ description: "X", quantity: 1, unit_price: 1000 }],
});
if ("error" in sent)
throw new Error(`createIssuedOrder failed: ${sent.error}`);
@@ -216,37 +200,33 @@ describe("getIssuedOrderTotals (issued orders) per-currency aggregation", () =>
const next = await createIssuedOrder({
currency: "CZK",
- vat_rate: 21,
- apply_vat: true,
order_date: nextMonth,
- items: [
- { description: "X", quantity: 1, unit_price: 1000, vat_rate: 21 },
- ],
+ items: [{ description: "X", quantity: 1, unit_price: 1000 }],
});
if ("error" in next)
throw new Error(`createIssuedOrder failed: ${next.error}`);
createdIssuedIds.push(next.id);
- // Whole target month: both count -> 2 x 1210 = 2420.
+ // Whole target month: both count -> 2 x 1000 = 2000 (net).
const whole = await getIssuedOrderTotals({
month: STATS_MONTH,
year: STATS_YEAR,
});
- expect(amountFor(whole.totals, "CZK")).toBe(2420);
+ expect(amountFor(whole.totals, "CZK")).toBe(2000);
- // Status filter narrows to the single 'sent' in the target month -> 1210.
+ // Status filter narrows to the single 'sent' in the target month -> 1000.
const onlySent = await getIssuedOrderTotals({
month: STATS_MONTH,
year: STATS_YEAR,
status: "sent",
});
- expect(amountFor(onlySent.totals, "CZK")).toBe(1210);
+ expect(amountFor(onlySent.totals, "CZK")).toBe(1000);
- // Next month sees only the one order there -> 1210.
+ // Next month sees only the one order there -> 1000.
const nextStats = await getIssuedOrderTotals({
month: STATS_MONTH + 1,
year: STATS_YEAR,
});
- expect(amountFor(nextStats.totals, "CZK")).toBe(1210);
+ expect(amountFor(nextStats.totals, "CZK")).toBe(1000);
});
});
diff --git a/src/__tests__/orders-list.test.ts b/src/__tests__/orders-list.test.ts
index add344e..a92a391 100644
--- a/src/__tests__/orders-list.test.ts
+++ b/src/__tests__/orders-list.test.ts
@@ -30,8 +30,6 @@ const baseOrder = {
status: "prijata" as const,
currency: "CZK",
language: "cs",
- vat_rate: 21,
- apply_vat: true,
exchange_rate: 1,
};
diff --git a/src/__tests__/pdf-vat-note.test.ts b/src/__tests__/pdf-vat-note.test.ts
new file mode 100644
index 0000000..74707a8
--- /dev/null
+++ b/src/__tests__/pdf-vat-note.test.ts
@@ -0,0 +1,147 @@
+import { describe, it, expect, beforeAll, afterAll } from "vitest";
+import Fastify from "fastify";
+import jwt from "jsonwebtoken";
+import prisma from "../config/database";
+import { config } from "../config/env";
+import { renderOrderConfirmationHtml } from "../routes/admin/orders-pdf";
+import offersPdfRoutes from "../routes/admin/offers-pdf";
+
+// Offers, received orders (their confirmation PDF) and issued orders are NOT
+// tax documents — VAT must never appear on them. These tests pin that contract
+// for the confirmation and offer PDFs: no VAT columns/rows, the grand total is
+// "Celkem bez DPH" / "Total excl. VAT", and the fixed prices-excl.-VAT notice
+// is printed. (The issued-order PDF is covered in issued-orders.test.ts.)
+
+const CS_NOTE =
+ "Ceny jsou uvedeny bez DPH. DPH bude účtováno dle platných předpisů.";
+const EN_NOTE =
+ "Prices are exclusive of VAT. VAT will be charged according to applicable regulations.";
+
+describe("renderOrderConfirmationHtml (order confirmation PDF)", () => {
+ const order = {
+ order_number: "26730042",
+ customer_order_number: "PO-XYZ",
+ created_at: new Date("2026-06-09T12:00:00"),
+ currency: "CZK",
+ notes: null,
+ customers: null,
+ };
+ const items = [
+ {
+ description: "Materiál",
+ quantity: 2,
+ unit: "ks",
+ unit_price: 100,
+ is_included_in_total: true,
+ },
+ ];
+
+ it("cs: NET total labeled 'Celkem bez DPH', no VAT columns, notice present", () => {
+ const html = renderOrderConfirmationHtml(order, items, null, "cs", "Jan");
+ expect(html).not.toContain("%DPH");
+ expect(html).not.toContain(">DPH<");
+ expect(html).not.toContain("Mezisoučet");
+ // Line total = netto (2 × 100), no VAT-on-top anywhere.
+ expect(html).toContain('
200,00
');
+ expect(html).toContain("Celkem bez DPH");
+ expect(html).toContain(CS_NOTE);
+ });
+
+ it("en: 'Total excl. VAT' + the English notice, no VAT columns", () => {
+ const html = renderOrderConfirmationHtml(order, items, null, "en", "Jan");
+ expect(html).not.toContain("VAT%");
+ expect(html).toContain("Total excl. VAT");
+ expect(html).toContain(EN_NOTE);
+ });
+
+ it("excludes not-included lines from the NET total", () => {
+ const html = renderOrderConfirmationHtml(
+ order,
+ [
+ ...items,
+ {
+ description: "Mimo cenu",
+ quantity: 1,
+ unit: "ks",
+ unit_price: 999,
+ is_included_in_total: false,
+ },
+ ],
+ null,
+ "cs",
+ "Jan",
+ );
+ // Grand total stays 200,00 — the excluded line doesn't count.
+ expect(html).toContain("200,00 CZK");
+ expect(html).not.toContain("1 199,00 CZK");
+ });
+});
+
+/* -------------------------------------------------------------------------- */
+/* Offer PDF route (returns the HTML for the print view) */
+/* -------------------------------------------------------------------------- */
+
+let app: ReturnType | null = null;
+let adminToken = "";
+const createdQuotationIds: number[] = [];
+
+beforeAll(async () => {
+ app = Fastify({ logger: false });
+ await app.register(offersPdfRoutes, { prefix: "/api/admin/offers-pdf" });
+
+ const admin = await prisma.users.findFirst({
+ where: { roles: { name: "admin" } },
+ });
+ if (!admin) throw new Error("Test setup: admin user not found");
+ adminToken = jwt.sign(
+ { sub: admin.id, username: admin.username, role: "admin" },
+ config.jwt.secret,
+ { expiresIn: "15m" },
+ );
+});
+
+afterAll(async () => {
+ for (const id of createdQuotationIds)
+ await prisma.quotations.deleteMany({ where: { id } });
+ if (app) await app.close();
+});
+
+describe("GET /api/admin/offers-pdf/:id (offer PDF html)", () => {
+ it("renders NET-only totals with 'Celkem bez DPH' and the cs notice", async () => {
+ // Direct prisma insert (explicit number → no sequence consumed).
+ const quotation = await prisma.quotations.create({
+ data: {
+ quotation_number: `Q-VATNOTE-${Date.now()}`,
+ status: "active",
+ currency: "CZK",
+ language: "cs",
+ quotation_items: {
+ create: [
+ {
+ description: "Položka",
+ quantity: 2,
+ unit: "ks",
+ unit_price: 1000,
+ is_included_in_total: true,
+ position: 0,
+ },
+ ],
+ },
+ },
+ });
+ createdQuotationIds.push(quotation.id);
+
+ const res = await app!.inject({
+ method: "GET",
+ url: `/api/admin/offers-pdf/${quotation.id}`,
+ headers: { Authorization: `Bearer ${adminToken}` },
+ });
+ expect(res.statusCode).toBe(200);
+ const html = res.body;
+ expect(html).toContain("Celkem bez DPH");
+ expect(html).toContain(CS_NOTE);
+ expect(html).not.toContain("%DPH");
+ expect(html).not.toContain("Mezisoučet");
+ expect(html).not.toContain("Celkem k úhradě");
+ });
+});
diff --git a/src/__tests__/schema-nan.test.ts b/src/__tests__/schema-nan.test.ts
index abf47f9..c505e42 100644
--- a/src/__tests__/schema-nan.test.ts
+++ b/src/__tests__/schema-nan.test.ts
@@ -58,34 +58,6 @@ describe("Zod form coercion + NaN rejection", () => {
});
describe("CreateQuotationSchema", () => {
- it("rejects NaN string in top-level vat_rate", () => {
- const result = CreateQuotationSchema.safeParse({
- customer_id: 1,
- vat_rate: "bad",
- });
- expect(result.success).toBe(false);
- });
-
- it("accepts valid vat_rate", () => {
- const result = CreateQuotationSchema.safeParse({
- customer_id: 1,
- vat_rate: 21,
- });
- expect(result.success).toBe(true);
- });
-
- it("coerces a valid numeric STRING vat_rate to a number", () => {
- const result = CreateQuotationSchema.safeParse({
- customer_id: 1,
- vat_rate: "21",
- });
- expect(result.success).toBe(true);
- if (result.success) {
- expect(result.data.vat_rate).toBe(21);
- expect(typeof result.data.vat_rate).toBe("number");
- }
- });
-
it("rejects NaN string in item quantity", () => {
const result = CreateQuotationSchema.safeParse({
customer_id: 1,
@@ -300,8 +272,8 @@ describe("schema hardening — does not reject previously-valid input", () => {
expect(b.success).toBe(true);
if (b.success) expect(b.data.trip_date).toBe("2026-06-09");
// Genuinely malformed dates are still rejected.
- expect(CreateTripSchema.safeParse({ ...base, trip_date: "09/06/2026" }).success).toBe(
- false,
- );
+ expect(
+ CreateTripSchema.safeParse({ ...base, trip_date: "09/06/2026" }).success,
+ ).toBe(false);
});
});
diff --git a/src/admin/components/OrderConfirmationModal.tsx b/src/admin/components/OrderConfirmationModal.tsx
index 1a13eac..57ef5f3 100644
--- a/src/admin/components/OrderConfirmationModal.tsx
+++ b/src/admin/components/OrderConfirmationModal.tsx
@@ -7,8 +7,8 @@ import { useTheme } from "@mui/material/styles";
import { Modal, Button, TextField, Field } from "../ui";
import { useAlert } from "../context/AlertContext";
-// Editable line-item. quantity/unit_price/vat_rate are held as the raw typed
-// string while editing (so a field can be cleared — empty renders fine in a
+// Editable line-item. quantity/unit_price are held as the raw typed string
+// while editing (so a field can be cleared — empty renders fine in a
// type="number" input) and are coerced to numbers in handleEditGenerate before
// being handed to onGenerate (the parent posts them verbatim).
interface ConfirmationItem {
@@ -17,7 +17,6 @@ interface ConfirmationItem {
unit: string;
unit_price: string | number;
is_included_in_total: boolean;
- vat_rate: string | number;
}
// Numeric shape handed to the parent (the raw editing strings are coerced in
@@ -28,21 +27,14 @@ interface GeneratedItem {
unit: string;
unit_price: number;
is_included_in_total: boolean;
- vat_rate: number;
}
interface OrderConfirmationModalProps {
isOpen: boolean;
onClose: () => void;
- onGenerate: (
- lang: string,
- applyVat: boolean,
- items?: GeneratedItem[],
- ) => Promise;
+ onGenerate: (lang: string, items?: GeneratedItem[]) => Promise;
initialItems: ConfirmationItem[];
orderNumber: string;
- defaultVatRate: number;
- applyVat: boolean;
}
export default function OrderConfirmationModal({
@@ -51,15 +43,12 @@ export default function OrderConfirmationModal({
onGenerate,
initialItems,
orderNumber,
- defaultVatRate,
- applyVat,
}: OrderConfirmationModalProps) {
const alert = useAlert();
const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down("sm"));
const [step, setStep] = useState<"choose" | "edit">("choose");
const [lang, setLang] = useState("cs");
- const [applyVatState, setApplyVatState] = useState(applyVat);
const [items, setItems] = useState(initialItems);
const [loading, setLoading] = useState(false);
@@ -72,17 +61,16 @@ export default function OrderConfirmationModal({
if (!isOpen) return;
setStep("choose");
setLang("cs");
- setApplyVatState(applyVat);
setItems(initialItems);
- // initialItems/applyVat are captured at open time; intentionally not in the
- // dep array so an unrelated parent re-render doesn't clobber the user's edits.
+ // initialItems are captured at open time; intentionally not in the dep
+ // array so an unrelated parent re-render doesn't clobber the user's edits.
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isOpen]);
const handleUseExisting = async () => {
setLoading(true);
try {
- await onGenerate(lang, applyVatState, undefined);
+ await onGenerate(lang, undefined);
// Only close on success — a generation error must keep the modal open.
onClose();
} catch (err) {
@@ -104,9 +92,8 @@ export default function OrderConfirmationModal({
unit: it.unit,
unit_price: Number(it.unit_price) || 0,
is_included_in_total: it.is_included_in_total,
- vat_rate: Number(it.vat_rate) || 0,
}));
- await onGenerate(lang, applyVatState, coercedItems);
+ await onGenerate(lang, coercedItems);
// Only close on success — on error keep the user's edited items intact.
onClose();
} catch (err) {
@@ -145,10 +132,9 @@ export default function OrderConfirmationModal({
unit: "ks",
unit_price: 0,
is_included_in_total: true,
- vat_rate: defaultVatRate,
},
]);
- }, [defaultVatRate]);
+ }, []);
return (
-
-
-
-
-
-
-
Jak chcete připravit potvrzení objednávky?
@@ -291,7 +256,7 @@ export default function OrderConfirmationModal({
@@ -318,15 +283,6 @@ export default function OrderConfirmationModal({
}
slotProps={{ htmlInput: { step: "0.01" } }}
/>
-
- updateItem(i, "vat_rate", e.target.value)
- }
- slotProps={{ htmlInput: { step: "1" } }}
- />
))}
@@ -356,7 +312,6 @@ export default function OrderConfirmationModal({