feat(plan): add plan category CRUD routes with audit

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
BOHA
2026-06-06 00:38:25 +02:00
parent 3db257faa9
commit c983747418
2 changed files with 94 additions and 1 deletions

View File

@@ -27,6 +27,16 @@ import {
listEntries,
listOverrides,
} from "../../services/plan.service";
import {
CreatePlanCategorySchema,
UpdatePlanCategorySchema,
} from "../../schemas/planCategory.schema";
import {
listPlanCategories,
createPlanCategory,
updatePlanCategory,
deactivatePlanCategory,
} from "../../services/planCategory.service";
const MAX_RANGE_DAYS = 92;
@@ -135,6 +145,88 @@ export default async function planRoutes(app: FastifyInstance) {
},
);
// --- GET /plan/categories (any planner can read; needed for display) ---
app.get(
"/categories",
{
preHandler: requireAnyPermission(
"attendance.manage",
"attendance.record",
),
},
async (_request: FastifyRequest, reply: FastifyReply) => {
const cats = await listPlanCategories(true);
return success(reply, cats);
},
);
// --- POST /plan/categories ---
app.post(
"/categories",
{ preHandler: requirePermission("attendance.manage") },
async (request: FastifyRequest, reply: FastifyReply) => {
const body = parseBody(CreatePlanCategorySchema, request.body);
if ("error" in body) return error(reply, body.error, 400);
const result = await createPlanCategory(body.data!);
if ("error" in result) return error(reply, result.error, result.status);
await logAudit({
request,
authData: request.authData,
action: "create",
entityType: "plan_category",
entityId: result.data.id,
newValues: result.data,
description: `Kategorie: ${result.data.label}`,
});
return success(reply, result.data, 201, "Kategorie vytvořena");
},
);
// --- PATCH /plan/categories/:id ---
app.patch(
"/categories/:id",
{ preHandler: requirePermission("attendance.manage") },
async (request: FastifyRequest, reply: FastifyReply) => {
const id = parseId((request.params as any).id, reply);
if (id === null) return;
const body = parseBody(UpdatePlanCategorySchema, request.body);
if ("error" in body) return error(reply, body.error, 400);
const result = await updatePlanCategory(id, body.data!);
if ("error" in result) return error(reply, result.error, result.status);
await logAudit({
request,
authData: request.authData,
action: "update",
entityType: "plan_category",
entityId: id,
newValues: result.data,
description: `Kategorie: ${result.data.label}`,
});
return success(reply, result.data, 200, "Kategorie aktualizována");
},
);
// --- DELETE /plan/categories/:id (deactivate) ---
app.delete(
"/categories/:id",
{ preHandler: requirePermission("attendance.manage") },
async (request: FastifyRequest, reply: FastifyReply) => {
const id = parseId((request.params as any).id, reply);
if (id === null) return;
const result = await deactivatePlanCategory(id);
if ("error" in result) return error(reply, result.error, result.status);
await logAudit({
request,
authData: request.authData,
action: "delete",
entityType: "plan_category",
entityId: id,
description: "Kategorie deaktivována",
});
return success(reply, { ok: true }, 200, "Kategorie deaktivována");
},
);
// --- POST /plan/entries ---
app.post(
"/entries",

View File

@@ -145,4 +145,5 @@ export type EntityType =
| "warehouse_location"
| "warehouse_receipt_attachment"
| "work_plan_entry"
| "work_plan_override";
| "work_plan_override"
| "plan_category";