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:
@@ -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 */
|
||||
|
||||
@@ -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 = "";
|
||||
|
||||
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user