fix: audit fix pass #1 — all 19 verified HIGH findings + critical dep cleanup
Fixes every CRITICAL/HIGH finding from the 2026-06-09 full-codebase audit
(REVIEW_FINDINGS.md); each fix went through independent spec + code-quality
review. Plan and per-task log: docs/superpowers/plans/2026-06-10-audit-high-fix-pass.md
- attendance: schemas accept the combined local datetimes the forms/service
use (new dateTimeString helpers in schemas/common.ts), breaks persist on
create, AttendanceCreate submit rebuilt — every submit 400'd since 519edce
- 2fa: backup codes wired to /totp/backup-verify (+ remember-me parity),
enrollment QR generated locally via qrcode (CSP-blocked external service
also leaked the secret), dashboard shows per-user enrollment, not policy
- invoices/orders: per-line VAT survives re-saves (numberOr 0-respecting
coercion in formatters.ts), billing_text persists on update, issued-order
status transitions update UI gates
- trips: real pagination on all 3 pages, GET /trips/stats server aggregate
(shared buildTripsWhere + legacy distance coalesce), vehicle_id applies on
PUT with both-vehicle odometer recompute, print rebuilt (sync window.open,
escaped template, server totals)
- orders api: attachment_data PDF blob excluded from all non-binary reads
- warehouse: unit field is a Select over UnitEnum, receipt attachments
downloadable via new authenticated GET route
- downloads: shared RFC 5987 contentDisposition helper — Czech filenames no
longer 500 (warehouse, received-invoices, orders endpoints)
- misc: block-env hook actually blocks (exit 2 + stderr), project create
works with empty dates, NaN filter guards on trips endpoints
- deps: remove unused concurrently (clears both critical advisories), pin
@hono/node-server >=1.19.13 via overrides (clears the 3 moderates without
the Prisma 6 downgrade), drop deprecated @types stubs
Gates: tsc -b clean - vitest 30 files / 342 tests (31 new) - eslint 0 errors
- build OK
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -44,7 +44,7 @@ import { jsonQuery } from "../lib/apiAdapter";
|
||||
import { offerCustomersOptions, type Customer } from "../lib/queries/offers";
|
||||
import { issuedOrderDetailOptions } from "../lib/queries/issued-orders";
|
||||
import { companySettingsOptions } from "../lib/queries/settings";
|
||||
import { formatCurrency, todayLocalStr } from "../utils/formatters";
|
||||
import { formatCurrency, numberOr, todayLocalStr } from "../utils/formatters";
|
||||
import { normalizeDateStr } from "../utils/attendanceHelpers";
|
||||
import {
|
||||
Button,
|
||||
@@ -585,7 +585,7 @@ export default function IssuedOrderDetail() {
|
||||
customer_name: d.customer_name ?? "",
|
||||
currency: d.currency || "CZK",
|
||||
apply_vat: d.apply_vat !== false,
|
||||
vat_rate: Number(d.vat_rate) || 21,
|
||||
vat_rate: numberOr(d.vat_rate, 21),
|
||||
order_date: normalizeDateStr(d.order_date),
|
||||
delivery_date: normalizeDateStr(d.delivery_date),
|
||||
language: d.language || "cs",
|
||||
@@ -605,10 +605,10 @@ export default function IssuedOrderDetail() {
|
||||
id: it.id,
|
||||
description: it.description || "",
|
||||
item_description: it.item_description || "",
|
||||
quantity: Number(it.quantity) || 1,
|
||||
quantity: numberOr(it.quantity, 1),
|
||||
unit: it.unit || "",
|
||||
unit_price: Number(it.unit_price) || 0,
|
||||
vat_rate: Number(it.vat_rate) || Number(d.vat_rate) || 21,
|
||||
vat_rate: numberOr(it.vat_rate, numberOr(d.vat_rate, 21)),
|
||||
}))
|
||||
: [];
|
||||
if (mapped.length > 0) setItems(mapped);
|
||||
@@ -831,6 +831,12 @@ export default function IssuedOrderDetail() {
|
||||
try {
|
||||
await statusMutation.mutateAsync({ status: newStatus });
|
||||
alert.success("Stav byl změněn");
|
||||
// Mirror the new status into the form (same as handleSubmit's success
|
||||
// path): the one-shot hydration won't re-run (dataReady stays true), so
|
||||
// without this the StatusChip, the `editable` flag and the delete-button
|
||||
// gate would keep reading the stale status until a full remount. The PO
|
||||
// number is repopulated by the detail-query sync effect.
|
||||
setForm((prev) => ({ ...prev, status: newStatus }));
|
||||
} catch (err) {
|
||||
alert.error(err instanceof Error ? err.message : "Chyba připojení");
|
||||
} finally {
|
||||
|
||||
Reference in New Issue
Block a user