diff --git a/src/admin/pages/OfferDetail.tsx b/src/admin/pages/OfferDetail.tsx index 967c6b9..d53ee24 100644 --- a/src/admin/pages/OfferDetail.tsx +++ b/src/admin/pages/OfferDetail.tsx @@ -678,7 +678,7 @@ export default function OfferDetail() { } } if (!isEdit && result.data?.id) { - queryClient.invalidateQueries({ queryKey: ["offers", "list"] }); + queryClient.invalidateQueries({ queryKey: ["offers"] }); queryClient.invalidateQueries({ queryKey: ["orders"] }); queryClient.invalidateQueries({ queryKey: ["projects"] }); queryClient.invalidateQueries({ queryKey: ["invoices"] }); @@ -690,7 +690,7 @@ export default function OfferDetail() { items, sections, }); - queryClient.invalidateQueries({ queryKey: ["offers", "list"] }); + queryClient.invalidateQueries({ queryKey: ["offers"] }); queryClient.invalidateQueries({ queryKey: ["orders"] }); queryClient.invalidateQueries({ queryKey: ["projects"] }); queryClient.invalidateQueries({ queryKey: ["invoices"] }); @@ -736,7 +736,7 @@ export default function OfferDetail() { if (result.success) { setShowOrderModal(false); alert.success(result.message || "Objednávka byla vytvořena"); - await queryClient.invalidateQueries({ queryKey: ["offers", "list"] }); + await queryClient.invalidateQueries({ queryKey: ["offers"] }); await queryClient.invalidateQueries({ queryKey: ["orders"] }); await queryClient.invalidateQueries({ queryKey: ["projects"] }); await queryClient.invalidateQueries({ queryKey: ["invoices"] }); @@ -762,7 +762,7 @@ export default function OfferDetail() { setInvalidateConfirm(false); setOfferStatus("invalidated"); alert.success(result.message || "Nabídka byla zneplatněna"); - queryClient.invalidateQueries({ queryKey: ["offers", "list"] }); + queryClient.invalidateQueries({ queryKey: ["offers"] }); queryClient.invalidateQueries({ queryKey: ["orders"] }); queryClient.invalidateQueries({ queryKey: ["projects"] }); queryClient.invalidateQueries({ queryKey: ["invoices"] }); @@ -789,7 +789,7 @@ export default function OfferDetail() { deletingRef.current = true; queryClient.removeQueries({ queryKey: ["offers", id] }); alert.success(result.message || "Nabídka byla smazána"); - await queryClient.invalidateQueries({ queryKey: ["offers", "list"] }); + await queryClient.invalidateQueries({ queryKey: ["offers"] }); await queryClient.invalidateQueries({ queryKey: ["orders"] }); await queryClient.invalidateQueries({ queryKey: ["projects"] }); await queryClient.invalidateQueries({ queryKey: ["invoices"] }); diff --git a/src/routes/admin/attendance.ts b/src/routes/admin/attendance.ts index 5a0d7b1..9eb6de7 100644 --- a/src/routes/admin/attendance.ts +++ b/src/routes/admin/attendance.ts @@ -185,7 +185,7 @@ export default async function attendanceRoutes( return error(reply, "Nedostatečná oprávnění", 403); } const attendanceId = Number(query.attendance_id); - if (!attendanceId) return error(reply, "Missing attendance_id", 400); + if (!attendanceId) return error(reply, "Chybí attendance_id", 400); const data = await attendanceService.getProjectLogs(attendanceId); return reply.send({ success: true, data }); } @@ -193,7 +193,7 @@ export default async function attendanceRoutes( // --- action=location: single record with GPS data --- if (action === "location") { const id = Number(query.id); - if (!id) return error(reply, "Missing id", 400); + if (!id) return error(reply, "Chybí id záznamu", 400); const record = await attendanceService.getLocationRecord(id); if (!record) return error(reply, "Záznam nenalezen", 404); const isAdmin = authData.permissions.includes("attendance.manage"); diff --git a/src/routes/admin/audit-log.ts b/src/routes/admin/audit-log.ts index fbd9e0c..1550ef9 100644 --- a/src/routes/admin/audit-log.ts +++ b/src/routes/admin/audit-log.ts @@ -12,12 +12,10 @@ import { parseBody } from "../../schemas/common"; // forensic history. const DELETE_ALL_CONFIRM = "DELETE_ALL_AUDIT" as const; -const AuditCleanupSchema = z - .object({ - days: z.number().int().nonnegative().optional(), - confirm: z.string().optional(), - }) - .strict(); +const AuditCleanupSchema = z.strictObject({ + days: z.number().int().nonnegative().optional(), + confirm: z.string().optional(), +}); export default async function auditLogRoutes( fastify: FastifyInstance, diff --git a/src/routes/admin/project-files.ts b/src/routes/admin/project-files.ts index dd6f0cb..18609fc 100644 --- a/src/routes/admin/project-files.ts +++ b/src/routes/admin/project-files.ts @@ -58,7 +58,7 @@ export default async function projectFilesRoutes( const { project_id: projectIdStr, path: subPath = "" } = parsedQuery.data; const projectId = Number(projectIdStr); const project = await getProjectForFiles(projectId); - if (!project) return error(reply, "Projekt nebyl nalezen", 404); + if (!project) return error(reply, "Projekt nenalezen", 404); if (!fm.isConfigured()) { return error(reply, "Souborový systém není nakonfigurován", 500); @@ -70,7 +70,7 @@ export default async function projectFilesRoutes( return error(reply, "Projekt nemá číslo projektu"); const result = fm.downloadFile(project.project_number, subPath); - if (!result) return error(reply, "Soubor nebyl nalezen", 404); + if (!result) return error(reply, "Soubor nenalezen", 404); const stream = fs.createReadStream(result.filePath); const encodedName = encodeURIComponent(result.fileName); @@ -89,7 +89,7 @@ export default async function projectFilesRoutes( const result = fm.listFiles(project.project_number, subPath); if (result === null) { - return error(reply, "Složka nebyla nalezena", 404); + return error(reply, "Složka nenalezena", 404); } return success(reply, { @@ -109,7 +109,7 @@ export default async function projectFilesRoutes( if ("error" in parsedQuery) return error(reply, parsedQuery.error, 400); const projectId = Number(parsedQuery.data.project_id); const project = await getProjectForFiles(projectId); - if (!project) return error(reply, "Projekt nebyl nalezen", 404); + if (!project) return error(reply, "Projekt nenalezen", 404); if (!project.project_number) return error(reply, "Projekt nemá číslo projektu"); @@ -162,7 +162,7 @@ export default async function projectFilesRoutes( if ("error" in parsedQuery) return error(reply, parsedQuery.error, 400); const projectId = Number(parsedQuery.data.project_id); const project = await getProjectForFiles(projectId); - if (!project) return error(reply, "Projekt nebyl nalezen", 404); + if (!project) return error(reply, "Projekt nenalezen", 404); if (!project.project_number) return error(reply, "Projekt nemá číslo projektu"); @@ -225,7 +225,7 @@ export default async function projectFilesRoutes( if ("error" in parsedQuery) return error(reply, parsedQuery.error, 400); const projectId = Number(parsedQuery.data.project_id); const project = await getProjectForFiles(projectId); - if (!project) return error(reply, "Projekt nebyl nalezen", 404); + if (!project) return error(reply, "Projekt nenalezen", 404); if (!project.project_number) return error(reply, "Projekt nemá číslo projektu"); @@ -267,7 +267,7 @@ export default async function projectFilesRoutes( if ("error" in parsedQuery) return error(reply, parsedQuery.error, 400); const projectId = Number(parsedQuery.data.project_id); const project = await getProjectForFiles(projectId); - if (!project) return error(reply, "Projekt nebyl nalezen", 404); + if (!project) return error(reply, "Projekt nenalezen", 404); if (!project.project_number) return error(reply, "Projekt nemá číslo projektu"); diff --git a/src/routes/admin/sessions.ts b/src/routes/admin/sessions.ts index 4fa63b8..8adec12 100644 --- a/src/routes/admin/sessions.ts +++ b/src/routes/admin/sessions.ts @@ -1,7 +1,7 @@ import { FastifyInstance } from "fastify"; import prisma from "../../config/database"; import { requireAuth } from "../../middleware/auth"; -import { success, error } from "../../utils/response"; +import { success, error, parseId } from "../../utils/response"; import { hashToken } from "../../services/auth"; /** Parse user-agent string into browser, OS, and device icon */ @@ -81,8 +81,8 @@ export default async function sessionsRoutes( "/:id", { preHandler: requireAuth }, async (request, reply) => { - const id = parseInt(request.params.id, 10); - if (Number.isNaN(id)) return error(reply, "Neplatné ID relace", 400); + const id = parseId((request.params as any).id, reply); + if (id === null) return; const authData = request.authData!; const session = await prisma.refresh_tokens.findFirst({ diff --git a/src/routes/admin/trips.ts b/src/routes/admin/trips.ts index a92cab5..74eb4d5 100644 --- a/src/routes/admin/trips.ts +++ b/src/routes/admin/trips.ts @@ -2,7 +2,7 @@ import { FastifyInstance } from "fastify"; import prisma from "../../config/database"; import { requireAuth, requirePermission } from "../../middleware/auth"; import { logAudit } from "../../services/audit"; -import { success, error } from "../../utils/response"; +import { success, error, parseId } from "../../utils/response"; import { parsePagination, buildPaginationMeta } from "../../utils/pagination"; import { parseBody } from "../../schemas/common"; import { CreateTripSchema, UpdateTripSchema } from "../../schemas/trips.schema"; @@ -189,8 +189,8 @@ export default async function tripsRoutes( "/last-km/:vehicleId", { preHandler: requirePermission("trips.manage") }, async (request, reply) => { - const vehicleId = parseInt(request.params.vehicleId, 10); - if (isNaN(vehicleId)) return error(reply, "Neplatné ID vozidla", 400); + const vehicleId = parseId((request.params as any).vehicleId, reply); + if (vehicleId === null) return; const [lastTrip, vehicle] = await Promise.all([ prisma.trips.findFirst({ @@ -261,8 +261,8 @@ export default async function tripsRoutes( "/:id", { preHandler: requireAuth }, async (request, reply) => { - const id = parseInt(request.params.id, 10); - if (isNaN(id)) return error(reply, "Neplatné ID", 400); + const id = parseId((request.params as any).id, reply); + if (id === null) return; const parsed = parseBody(UpdateTripSchema, request.body); if ("error" in parsed) return error(reply, parsed.error, 400); const body = parsed.data; @@ -335,8 +335,8 @@ export default async function tripsRoutes( "/:id", { preHandler: requireAuth }, async (request, reply) => { - const id = parseInt(request.params.id, 10); - if (isNaN(id)) return error(reply, "Neplatné ID", 400); + const id = parseId((request.params as any).id, reply); + if (id === null) return; const authData = request.authData!; const existing = await prisma.trips.findUnique({ where: { id } }); if (!existing) return error(reply, "Jízda nenalezena", 404); diff --git a/src/schemas/offers.schema.ts b/src/schemas/offers.schema.ts index bd9f724..ad08637 100644 --- a/src/schemas/offers.schema.ts +++ b/src/schemas/offers.schema.ts @@ -6,14 +6,14 @@ const QuotationItemSchema = z.object({ quantity: z .union([z.number(), z.string()]) .transform((v) => Number(v)) - .refine((v) => !Number.isNaN(v), { message: "Must be a valid number" }) + .refine((v) => !Number.isNaN(v), { message: "Musí být platné číslo" }) .optional() .default(1), unit: z.string().nullish(), unit_price: z .union([z.number(), z.string()]) .transform((v) => Number(v)) - .refine((v) => !Number.isNaN(v), { message: "Must be a valid number" }) + .refine((v) => !Number.isNaN(v), { message: "Musí být platné číslo" }) .optional() .default(0), is_included_in_total: z @@ -23,7 +23,7 @@ const QuotationItemSchema = z.object({ position: z .union([z.number(), z.string()]) .transform((v) => Number(v)) - .refine((v) => !Number.isNaN(v), { message: "Must be a valid number" }) + .refine((v) => !Number.isNaN(v), { message: "Musí být platné číslo" }) .optional(), }); @@ -34,7 +34,7 @@ const ScopeSectionSchema = z.object({ position: z .union([z.number(), z.string()]) .transform((v) => Number(v)) - .refine((v) => !Number.isNaN(v), { message: "Must be a valid number" }) + .refine((v) => !Number.isNaN(v), { message: "Musí být platné číslo" }) .optional(), }); @@ -53,7 +53,7 @@ export const CreateQuotationSchema = z.object({ vat_rate: z .union([z.number(), z.string()]) .transform((v) => Number(v)) - .refine((v) => !Number.isNaN(v), { message: "Must be a valid number" }) + .refine((v) => !Number.isNaN(v), { message: "Musí být platné číslo" }) .optional() .default(21.0), apply_vat: z @@ -75,7 +75,7 @@ export const UpdateQuotationSchema = z.object({ customer_id: z .union([z.number(), z.string()]) .transform((v) => Number(v)) - .refine((v) => !Number.isNaN(v), { message: "Must be a valid number" }) + .refine((v) => !Number.isNaN(v), { message: "Musí být platné číslo" }) .optional(), created_at: z.union([z.string(), z.null()]).optional(), valid_until: z.union([z.string(), z.null()]).optional(), @@ -84,7 +84,7 @@ export const UpdateQuotationSchema = z.object({ vat_rate: z .union([z.number(), z.string()]) .transform((v) => Number(v)) - .refine((v) => !Number.isNaN(v), { message: "Must be a valid number" }) + .refine((v) => !Number.isNaN(v), { message: "Musí být platné číslo" }) .optional(), apply_vat: z .preprocess((v) => v === true || v === 1 || v === "1", z.boolean()) diff --git a/src/schemas/orders.schema.ts b/src/schemas/orders.schema.ts index 2da2eff..0f676f2 100644 --- a/src/schemas/orders.schema.ts +++ b/src/schemas/orders.schema.ts @@ -6,14 +6,14 @@ const OrderItemSchema = z.object({ quantity: z .union([z.number(), z.string()]) .transform((v) => Number(v)) - .refine((v) => !Number.isNaN(v), { message: "Must be a valid number" }) + .refine((v) => !Number.isNaN(v), { message: "Musí být platné číslo" }) .optional() .default(1), unit: z.string().nullish(), unit_price: z .union([z.number(), z.string()]) .transform((v) => Number(v)) - .refine((v) => !Number.isNaN(v), { message: "Must be a valid number" }) + .refine((v) => !Number.isNaN(v), { message: "Musí být platné číslo" }) .optional() .default(0), is_included_in_total: z @@ -23,7 +23,7 @@ const OrderItemSchema = z.object({ position: z .union([z.number(), z.string()]) .transform((v) => Number(v)) - .refine((v) => !Number.isNaN(v), { message: "Must be a valid number" }) + .refine((v) => !Number.isNaN(v), { message: "Musí být platné číslo" }) .optional(), }); @@ -34,7 +34,7 @@ const OrderSectionSchema = z.object({ position: z .union([z.number(), z.string()]) .transform((v) => Number(v)) - .refine((v) => !Number.isNaN(v), { message: "Must be a valid number" }) + .refine((v) => !Number.isNaN(v), { message: "Musí být platné číslo" }) .optional(), }); @@ -42,7 +42,7 @@ export const CreateOrderFromQuotationSchema = z.object({ quotationId: z .union([z.number(), z.string()]) .transform((v) => Number(v)) - .refine((v) => !Number.isNaN(v), { message: "Must be a valid number" }), + .refine((v) => !Number.isNaN(v), { message: "Musí být platné číslo" }), customerOrderNumber: z.string().optional().default(""), }); @@ -68,7 +68,7 @@ export const CreateOrderSchema = z.object({ vat_rate: z .union([z.number(), z.string()]) .transform((v) => Number(v)) - .refine((v) => !Number.isNaN(v), { message: "Must be a valid number" }) + .refine((v) => !Number.isNaN(v), { message: "Musí být platné číslo" }) .optional() .default(21.0), apply_vat: z @@ -78,7 +78,7 @@ export const CreateOrderSchema = z.object({ exchange_rate: z .union([z.number(), z.string()]) .transform((v) => Number(v)) - .refine((v) => !Number.isNaN(v), { message: "Must be a valid number" }) + .refine((v) => !Number.isNaN(v), { message: "Musí být platné číslo" }) .optional() .default(1.0), scope_title: z.string().nullish(), @@ -103,7 +103,7 @@ export const UpdateOrderSchema = z.object({ vat_rate: z .union([z.number(), z.string()]) .transform((v) => Number(v)) - .refine((v) => !Number.isNaN(v), { message: "Must be a valid number" }) + .refine((v) => !Number.isNaN(v), { message: "Musí být platné číslo" }) .optional(), apply_vat: z .preprocess((v) => v === true || v === 1 || v === "1", z.boolean())