v1.6.8: remove offer exchange rate, add invoice item description, offer filters, table animations

- Remove exchange_rate and exchange_rate_date from quotations (schema, service, PDF, form)
- Add item_description field to invoice_items (schema, migration, service, form, PDF)
- Add offer status/customer/order filters with tab-based UI
- Clean up offer statuses to active/ordered/invalidated
- Allow invalidate action for non-ordered offers (not just expired)
- Add fade animations on offers and invoices table data changes

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
BOHA
2026-05-28 12:58:51 +02:00
parent 66b98e2765
commit 0330453ad6
20 changed files with 3126 additions and 3029 deletions

View File

@@ -450,10 +450,11 @@ export default async function invoicesPdfRoutes(
const lineVat = applyVat ? (lineSubtotal * vatRate) / 100 : 0;
const lineTotal = lineSubtotal + lineVat;
const qtyDecimals = Math.floor(qty) === qty ? 0 : 2;
const subDesc = item.item_description || "";
return `<tr>
<td class="row-num">${i + 1}</td>
<td class="desc">${escapeHtml(item.description)}</td>
<td class="desc">${escapeHtml(item.description)}${subDesc ? `<div class="item-subdesc">${escapeHtml(subDesc)}</div>` : ""}</td>
<td class="center">${formatNum(qty, qtyDecimals)}${item.unit ? ` / ${escapeHtml(item.unit)}` : ""}</td>
<td class="right">${formatNum(unitPrice)}</td>
<td class="right">${formatNum(lineSubtotal)}</td>
@@ -693,6 +694,11 @@ export default async function invoicesPdfRoutes(
font-weight: 600;
color: #1a1a1a;
}
.item-subdesc {
font-size: 9pt;
color: #646464;
margin-top: 2px;
}
table.items tbody td.total-cell { font-weight: 700; }
/* Soucet + total - styl z nabidek */

View File

@@ -199,7 +199,6 @@ const TRANSLATIONS: Record<string, Record<string, string>> = {
subtotal: { EN: "Subtotal", CZ: "Mezisou\u010Det" },
vat: { EN: "VAT", CZ: "DPH" },
total_to_pay: { EN: "Total to pay", CZ: "Celkem k \u00FAhrad\u011B" },
exchange_rate: { EN: "Exchange rate", CZ: "Sm\u011Bnn\u00FD kurz" },
ico: { EN: "ID", CZ: "I\u010CO" },
dic: { EN: "VAT ID", CZ: "DI\u010C" },
page: { EN: "Page", CZ: "Strana" },
@@ -262,8 +261,6 @@ export default async function offersPdfRoutes(
const vatRate = Number(quotation.vat_rate) || 21;
const vatAmount = applyVat ? subtotal * (vatRate / 100) : 0;
const totalToPay = subtotal + vatAmount;
const exchangeRate = Number(quotation.exchange_rate) || 0;
let hasScopeContent = false;
for (const s of quotation.scope_sections) {
if ((s.content || "").trim() || (s.title || "").trim()) {
@@ -331,10 +328,6 @@ export default async function offersPdfRoutes(
<span class="label">${escapeHtml(t("total_to_pay"))}</span>
<span class="value">${formatCurrency(totalToPay, currency)}</span>
</div>`;
if (exchangeRate > 0) {
totalsHtml += `<div class="exchange-rate">${escapeHtml(t("exchange_rate"))}: ${formatNum(exchangeRate, 4)}</div>`;
}
const quotationNumber = escapeHtml(quotation.quotation_number);
let scopeHtml = "";

View File

@@ -42,6 +42,7 @@ 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(