security: fix all Medium findings from FLAWS_REPORT audit
- Auth: TOTP replay protection with counter tracking, constant-time backup code comparison, atomic lockout increment, per-token logout - Invoices/PDFs: net-based VAT calculation, dangerous URL scheme stripping in cleanQuillHtml, orders-pdf error handling - Orders: reject item changes on status transition, cascading delete cleanup, take:1 with orderBy - Projects: atomic rename collision handling, MIME/extension validation, empty customer name rejection - Attendance: Czech public holiday awareness in frontend fund calculation, leave_hours 0 handling, invalid date NaN guard, bounded per-month queries in workfund - Users/Admin: profile audit logging + password validation, session revocation guard, session ID validation, dashboard DB aggregation, soft-deleted record protection in scope templates - Frontend: FormField label linkage, Pagination ARIA, error handling in OrderConfirmationModal, 401 propagation, GPS emoji hidden from screen readers, table sort state fix, geolocation race/abort cleanup, Leaflet popup DOM safety, Vehicles toggleActive minimal body, CompanySettings ref mutation fix, OfferDetail unlock abort, AttendanceBalances combined fetches - Utils: env validation, Puppeteer concurrency mutex, invoice alert cron cleanup on shutdown, body limit alignment, TOTP error logging, trustProxy from env, symlink rejection, rate cache Map usage Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -179,24 +179,25 @@ export default async function dashboardRoutes(
|
||||
|
||||
// Invoices — only for invoices.view
|
||||
if (has("invoices.view")) {
|
||||
const [unpaidCount, issuedThisMonth] = await Promise.all([
|
||||
const [unpaidCount, revenueAgg] = await Promise.all([
|
||||
prisma.invoices.count({ where: { status: "issued" } }),
|
||||
prisma.invoices.findMany({
|
||||
where: { issue_date: { gte: monthStart, lt: monthEnd } },
|
||||
include: { invoice_items: true },
|
||||
}),
|
||||
prisma.$queryRaw<
|
||||
Array<{ currency: string | null; total: string | number | null }>
|
||||
>`
|
||||
SELECT i.currency, SUM(ii.quantity * ii.unit_price) as total
|
||||
FROM invoices i
|
||||
JOIN invoice_items ii ON i.id = ii.invoice_id
|
||||
WHERE i.issue_date >= ${monthStart} AND i.issue_date < ${monthEnd}
|
||||
GROUP BY i.currency
|
||||
`,
|
||||
]);
|
||||
|
||||
const revenueByCurrency: Record<string, number> = {};
|
||||
for (const inv of issuedThisMonth) {
|
||||
const currency = inv.currency ?? "CZK";
|
||||
let total = 0;
|
||||
for (const item of inv.invoice_items) {
|
||||
total +=
|
||||
(Number(item.quantity) || 0) * (Number(item.unit_price) || 0);
|
||||
}
|
||||
for (const row of revenueAgg) {
|
||||
const currency = row.currency || "CZK";
|
||||
const amount = Number(row.total) || 0;
|
||||
revenueByCurrency[currency] =
|
||||
(revenueByCurrency[currency] ?? 0) + total;
|
||||
(revenueByCurrency[currency] || 0) + amount;
|
||||
}
|
||||
|
||||
result.invoices = {
|
||||
|
||||
Reference in New Issue
Block a user