feat: CNB exchange rates, multi-currency KPI stats, invoice PDF VAT in CZK

- ČNB exchange rate service with date-specific rates and caching
- Invoice/received invoice stats convert foreign currencies to CZK
- Dashboard revenue converts all currencies to CZK
- Invoice PDF: VAT recap table always in CZK with CNB rate footer
- Inline styles replaced with utility classes (step 4 cleanup)
- Spinner animation exempt from prefers-reduced-motion

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
BOHA
2026-03-27 13:44:53 +01:00
parent a3ef37d0d2
commit 8cdf057ab3
13 changed files with 173 additions and 117 deletions

View File

@@ -12,6 +12,7 @@ import {
UpdateReceivedInvoiceSchema,
} from "../../schemas/received-invoices.schema";
import { nasFinancialsManager } from "../../services/nas-financials-manager";
import { toCzk } from "../../services/exchange-rates";
const VALID_STATUSES = ["unpaid", "paid"] as const;
const ALLOWED_SORT_FIELDS = [
@@ -108,12 +109,15 @@ export default async function receivedInvoicesRoutes(
}));
};
const sumCzk = (
const sumCzk = async (
invs: typeof monthInvoices,
field: "amount" | "vat_amount",
) => {
let total = 0;
for (const inv of invs) total += Number(inv[field]) || 0;
for (const inv of invs) {
const amount = Number(inv[field]) || 0;
total += await toCzk(amount, inv.currency);
}
return Math.round(total * 100) / 100;
};
@@ -124,11 +128,11 @@ export default async function receivedInvoicesRoutes(
return success(reply, {
total_month: aggregateByCurrency(monthInvoices, "amount"),
total_month_czk: sumCzk(monthInvoices, "amount"),
total_month_czk: await sumCzk(monthInvoices, "amount"),
vat_month: aggregateByCurrency(monthInvoices, "vat_amount"),
vat_month_czk: sumCzk(monthInvoices, "vat_amount"),
vat_month_czk: await sumCzk(monthInvoices, "vat_amount"),
unpaid: aggregateByCurrency(allUnpaid, "amount"),
unpaid_czk: sumCzk(allUnpaid, "amount"),
unpaid_czk: await sumCzk(allUnpaid, "amount"),
unpaid_count: allUnpaid.length,
month_count: monthInvoices.length,
});