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:
@@ -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,
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user