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>
125 lines
3.3 KiB
TypeScript
125 lines
3.3 KiB
TypeScript
import { queryOptions } from "@tanstack/react-query";
|
|
import { jsonQuery, paginatedJsonQuery } from "../apiAdapter";
|
|
|
|
export interface OrderItem {
|
|
id?: number;
|
|
description: string;
|
|
item_description?: string;
|
|
quantity: number;
|
|
unit: string;
|
|
unit_price: number;
|
|
is_included_in_total: number | boolean;
|
|
}
|
|
|
|
export interface OrderSection {
|
|
id?: number;
|
|
title: string;
|
|
title_cz?: string;
|
|
content: string;
|
|
}
|
|
|
|
export interface OrderInvoice {
|
|
id: number;
|
|
invoice_number: string;
|
|
}
|
|
|
|
export interface OrderProject {
|
|
id: number;
|
|
project_number: string;
|
|
name: string;
|
|
has_nas_folder?: boolean;
|
|
}
|
|
|
|
export interface OrderData {
|
|
id: number;
|
|
order_number: string;
|
|
quotation_id: number;
|
|
quotation_number: string;
|
|
project_code?: string;
|
|
customer_name: string;
|
|
customer_order_number: string;
|
|
currency: string;
|
|
created_at: string;
|
|
status: string;
|
|
notes: string;
|
|
attachment_name?: string;
|
|
language?: string;
|
|
items: OrderItem[];
|
|
sections: OrderSection[];
|
|
scope_title?: string;
|
|
scope_description?: string;
|
|
valid_transitions?: string[];
|
|
invoice?: OrderInvoice;
|
|
project?: OrderProject;
|
|
}
|
|
|
|
export const orderListOptions = (filters: {
|
|
search?: string;
|
|
sort?: string;
|
|
order?: string;
|
|
page?: number;
|
|
perPage?: number;
|
|
status?: string;
|
|
month?: number;
|
|
year?: number;
|
|
}) =>
|
|
queryOptions({
|
|
queryKey: ["orders", "list", filters],
|
|
queryFn: () => {
|
|
const params = new URLSearchParams();
|
|
if (filters.search) params.set("search", filters.search);
|
|
if (filters.sort) params.set("sort", filters.sort);
|
|
if (filters.order) params.set("order", filters.order);
|
|
if (filters.page) params.set("page", String(filters.page));
|
|
if (filters.perPage) params.set("per_page", String(filters.perPage));
|
|
if (filters.status) params.set("status", filters.status);
|
|
if (filters.month) params.set("month", String(filters.month));
|
|
if (filters.year) params.set("year", String(filters.year));
|
|
const qs = params.toString();
|
|
return paginatedJsonQuery(`/api/admin/orders${qs ? `?${qs}` : ""}`);
|
|
},
|
|
});
|
|
|
|
export interface CurrencyAmount {
|
|
amount: number;
|
|
currency: string;
|
|
}
|
|
|
|
export const orderStatsOptions = (filters: {
|
|
search?: string;
|
|
status?: string;
|
|
month?: number;
|
|
year?: number;
|
|
}) =>
|
|
queryOptions({
|
|
queryKey: ["orders", "stats", filters],
|
|
queryFn: async () => {
|
|
const params = new URLSearchParams();
|
|
if (filters.search) params.set("search", filters.search);
|
|
if (filters.status) params.set("status", filters.status);
|
|
if (filters.month) params.set("month", String(filters.month));
|
|
if (filters.year) params.set("year", String(filters.year));
|
|
const qs = params.toString();
|
|
const data = await jsonQuery<{ totals?: CurrencyAmount[] }>(
|
|
`/api/admin/orders/stats${qs ? `?${qs}` : ""}`,
|
|
);
|
|
return data.totals ?? [];
|
|
},
|
|
});
|
|
|
|
export const orderDetailOptions = (id: string | undefined) =>
|
|
queryOptions({
|
|
queryKey: ["orders", id],
|
|
queryFn: () => jsonQuery<OrderData>(`/api/admin/orders/${id}`),
|
|
enabled: !!id,
|
|
});
|
|
|
|
export const orderNextNumberOptions = () =>
|
|
queryOptions({
|
|
queryKey: ["orders", "next-number"],
|
|
queryFn: () =>
|
|
jsonQuery<{ next_number?: string; number?: string }>(
|
|
"/api/admin/orders/next-number",
|
|
),
|
|
});
|