refactor: route/schema consistency (parseId, broad invalidation, Czech messages, Zod 4 strictObject)
- OfferDetail: invalidate broad ["offers"] not ["offers","list"] (CLAUDE.md convention) - trips.ts/sessions.ts: use parseId helper (validated 400) instead of raw parseInt for route ids - audit-log.ts: z.object().strict() -> z.strictObject() (Zod 4 idiom) - attendance.ts, orders/offers schemas: English user-facing strings -> Czech - project-files.ts: unify 'not found' phrasing to the codebase-dominant 'nenalezen' form Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -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"] });
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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");
|
||||
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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())
|
||||
|
||||
Reference in New Issue
Block a user