v1.7.0: fix offer created_at override, invoice PDF translations, remove has_order filter
- Add created_at to offer create/update schemas so user-selected date is stored instead of always defaulting to now() - Translate payment method in invoice PDF based on language (Příkazem → Bank transfer, etc.) - Add order_date and cnb_rate translation keys, replace hardcoded inline strings - Remove duplicate has_order filter from offers backend route Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "app-ts",
|
||||
"version": "1.6.9",
|
||||
"version": "1.7.0",
|
||||
"description": "",
|
||||
"main": "dist/server.js",
|
||||
"scripts": {
|
||||
|
||||
@@ -73,7 +73,6 @@ export const offerListOptions = (filters: {
|
||||
perPage?: number;
|
||||
status?: string;
|
||||
customer_id?: number;
|
||||
has_order?: string;
|
||||
}) =>
|
||||
queryOptions({
|
||||
queryKey: ["offers", "list", filters],
|
||||
@@ -87,7 +86,6 @@ export const offerListOptions = (filters: {
|
||||
if (filters.status) params.set("status", filters.status);
|
||||
if (filters.customer_id)
|
||||
params.set("customer_id", String(filters.customer_id));
|
||||
if (filters.has_order) params.set("has_order", filters.has_order);
|
||||
const qs = params.toString();
|
||||
return paginatedJsonQuery(`/api/admin/offers${qs ? `?${qs}` : ""}`);
|
||||
},
|
||||
|
||||
@@ -168,6 +168,24 @@ function buildAddressLines(
|
||||
|
||||
/* ── Translations ────────────────────────────────────────────────── */
|
||||
|
||||
const paymentMethodMap: Record<string, Record<string, string>> = {
|
||||
cs: {
|
||||
"Bank transfer": "Příkazem",
|
||||
Cash: "Hotově",
|
||||
"Cash on delivery": "Dobírka",
|
||||
},
|
||||
en: {
|
||||
Příkazem: "Bank transfer",
|
||||
Hotově: "Cash",
|
||||
Dobírka: "Cash on delivery",
|
||||
},
|
||||
};
|
||||
|
||||
function translatePaymentMethod(value: string | null, lang: string): string {
|
||||
if (!value) return "";
|
||||
return paymentMethodMap[lang]?.[value] || value;
|
||||
}
|
||||
|
||||
const translations: Record<string, Record<string, string>> = {
|
||||
cs: {
|
||||
title: "Faktura",
|
||||
@@ -181,6 +199,7 @@ const translations: Record<string, Record<string, string>> = {
|
||||
var_symbol: "Variabilní s.:",
|
||||
const_symbol: "Konstantní s.:",
|
||||
order_no: "Objednávka č.:",
|
||||
order_date: "Objednávka ze dne:",
|
||||
issue_date: "Datum vystavení:",
|
||||
due_date: "Datum splatnosti:",
|
||||
tax_date: "Datum uskutečnění plnění:",
|
||||
@@ -213,6 +232,7 @@ const translations: Record<string, Record<string, string>> = {
|
||||
stamp: "Razítko:",
|
||||
ico: "IČ: ",
|
||||
dic: "DIČ: ",
|
||||
cnb_rate: "Přepočet kurzem ČNB ke dni",
|
||||
},
|
||||
en: {
|
||||
title: "Invoice",
|
||||
@@ -226,6 +246,7 @@ const translations: Record<string, Record<string, string>> = {
|
||||
var_symbol: "Variable symbol:",
|
||||
const_symbol: "Constant symbol:",
|
||||
order_no: "Order No.:",
|
||||
order_date: "Order date:",
|
||||
issue_date: "Issue date:",
|
||||
due_date: "Due date:",
|
||||
tax_date: "Tax point date:",
|
||||
@@ -257,6 +278,7 @@ const translations: Record<string, Record<string, string>> = {
|
||||
stamp: "Stamp:",
|
||||
ico: "Reg. No.: ",
|
||||
dic: "Tax ID: ",
|
||||
cnb_rate: "CNB exchange rate as of",
|
||||
},
|
||||
};
|
||||
|
||||
@@ -923,9 +945,9 @@ ${indentCSS}
|
||||
<div class="info-row"><span class="lbl">${escapeHtml(t.issue_date)}</span> <span class="val">${escapeHtml(formatDate(invoice.issue_date))}</span></div>
|
||||
<div class="info-row"><span class="lbl">${escapeHtml(t.due_date)}</span> <span class="val">${escapeHtml(formatDate(invoice.due_date))}</span></div>
|
||||
<div class="info-row"><span class="lbl">${escapeHtml(t.tax_date)}</span> <span class="val">${escapeHtml(formatDate(invoice.tax_date))}</span></div>
|
||||
<div class="info-row"><span class="lbl">${escapeHtml(t.payment_method)}</span> <span class="val">${escapeHtml(invoice.payment_method)}</span></div>
|
||||
${orderNumber ? `<div class="info-row"><span class="lbl">${lang === "cs" ? "Objednávka č.:" : "Order no.:"}</span> <span class="val">${orderNumber}</span></div>` : ""}
|
||||
${orderDate ? `<div class="info-row"><span class="lbl">${lang === "cs" ? "Objednávka ze dne:" : "Order date:"}</span> <span class="val">${escapeHtml(orderDate)}</span></div>` : ""}
|
||||
<div class="info-row"><span class="lbl">${escapeHtml(t.payment_method)}</span> <span class="val">${escapeHtml(translatePaymentMethod(invoice.payment_method, lang))}</span></div>
|
||||
${orderNumber ? `<div class="info-row"><span class="lbl">${escapeHtml(t.order_no)}</span> <span class="val">${orderNumber}</span></div>` : ""}
|
||||
${orderDate ? `<div class="info-row"><span class="lbl">${escapeHtml(t.order_date)}</span> <span class="val">${escapeHtml(orderDate)}</span></div>` : ""}
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
@@ -1008,7 +1030,7 @@ ${indentCSS}
|
||||
? `<tfoot>
|
||||
<tr>
|
||||
<td colspan="4" style="font-size:0.7em; color:#666; padding-top:6px; text-align:left;">
|
||||
Přepočet kurzem ČNB ke dni ${formatDate(invoice.issue_date)}: 1 ${escapeHtml(currency)} = ${cnbRate.toFixed(3).replace(".", ",")} CZK
|
||||
${escapeHtml(t.cnb_rate)} ${formatDate(invoice.issue_date)}: 1 ${escapeHtml(currency)} = ${cnbRate.toFixed(3).replace(".", ",")} CZK
|
||||
</td>
|
||||
</tr>
|
||||
</tfoot>`
|
||||
|
||||
@@ -42,7 +42,6 @@ export default async function quotationsRoutes(
|
||||
search,
|
||||
status: query.status ? String(query.status) : undefined,
|
||||
customer_id: query.customer_id ? Number(query.customer_id) : undefined,
|
||||
has_order: query.has_order ? String(query.has_order) : undefined,
|
||||
});
|
||||
|
||||
return paginated(
|
||||
|
||||
@@ -46,6 +46,7 @@ export const CreateQuotationSchema = z.object({
|
||||
.transform((v) => Number(v))
|
||||
.refine((v) => !Number.isNaN(v), { message: "Musí být platné číslo" })
|
||||
.nullish(),
|
||||
created_at: z.string().nullish(),
|
||||
valid_until: z.string().nullish(),
|
||||
currency: z.string().optional().default("CZK"),
|
||||
language: z.string().optional().default("cs"),
|
||||
@@ -76,6 +77,7 @@ export const UpdateQuotationSchema = z.object({
|
||||
.transform((v) => Number(v))
|
||||
.refine((v) => !Number.isNaN(v), { message: "Must be a valid number" })
|
||||
.optional(),
|
||||
created_at: z.union([z.string(), z.null()]).optional(),
|
||||
valid_until: z.union([z.string(), z.null()]).optional(),
|
||||
currency: z.string().optional(),
|
||||
language: z.string().optional(),
|
||||
|
||||
@@ -44,7 +44,6 @@ interface ListOffersParams {
|
||||
search: string;
|
||||
status?: string;
|
||||
customer_id?: number;
|
||||
has_order?: string;
|
||||
}
|
||||
|
||||
function enrichQuotation(q: any) {
|
||||
@@ -73,24 +72,13 @@ function enrichQuotation(q: any) {
|
||||
}
|
||||
|
||||
export async function listOffers(params: ListOffersParams) {
|
||||
const {
|
||||
page,
|
||||
limit,
|
||||
skip,
|
||||
sort,
|
||||
order,
|
||||
search,
|
||||
status,
|
||||
customer_id,
|
||||
has_order,
|
||||
} = params;
|
||||
const { page, limit, skip, sort, order, search, status, customer_id } =
|
||||
params;
|
||||
const sortField = ALLOWED_SORT_FIELDS.includes(sort) ? sort : "id";
|
||||
|
||||
const where: Record<string, unknown> = {};
|
||||
if (status) where.status = status;
|
||||
if (customer_id) where.customer_id = customer_id;
|
||||
if (has_order === "yes") where.order_id = { not: null };
|
||||
if (has_order === "no") where.order_id = null;
|
||||
if (search) {
|
||||
where.OR = [
|
||||
{ quotation_number: { contains: search } },
|
||||
@@ -189,6 +177,9 @@ export async function createOffer(body: Record<string, any>) {
|
||||
quotation_number: quotationNumber,
|
||||
project_code: body.project_code ? String(body.project_code) : null,
|
||||
customer_id: body.customer_id ? Number(body.customer_id) : null,
|
||||
created_at: body.created_at
|
||||
? new Date(String(body.created_at))
|
||||
: undefined,
|
||||
valid_until: body.valid_until
|
||||
? new Date(String(body.valid_until))
|
||||
: null,
|
||||
@@ -251,6 +242,12 @@ export async function updateOffer(id: number, body: Record<string, any>) {
|
||||
const data = {
|
||||
customer_id:
|
||||
body.customer_id !== undefined ? Number(body.customer_id) : undefined,
|
||||
created_at:
|
||||
body.created_at !== undefined
|
||||
? body.created_at
|
||||
? new Date(String(body.created_at))
|
||||
: null
|
||||
: undefined,
|
||||
valid_until:
|
||||
body.valid_until !== undefined
|
||||
? body.valid_until
|
||||
|
||||
Reference in New Issue
Block a user