feat(plan): validate dynamic category keys, resolve audit label from DB

- Replace hardcoded planCategoryEnum z.enum with z.string().trim().min(1)
- Add assertActiveCategory() helper that rejects inactive/unknown keys
- Add resolveCategoryLabel() helper that resolves the Czech label from plan_categories DB table, falling back to built-in map
- Change PlanAuditDescriptionInput.category -> categoryLabel (pre-resolved string)
- Update buildPlanAuditDescription to use opts.categoryLabel directly
- Update all 6 buildPlanAuditDescription call sites in plan.service.ts to await resolveCategoryLabel(row.category)
- Add category validation in createEntry, updateEntry, createOverride, updateOverride
- Update planAuditDescription tests to pass categoryLabel instead of category

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
BOHA
2026-06-06 00:41:50 +02:00
parent c983747418
commit a55356d417
4 changed files with 61 additions and 30 deletions

View File

@@ -47,8 +47,8 @@ export function formatPlanDate(iso: string): string {
export interface PlanAuditDescriptionInput {
/** Employee the plan row belongs to (display name). */
userName: string;
/** `plan_category` enum value. */
category: string;
/** Already-resolved Czech category label (resolved from plan_categories). */
categoryLabel: string;
/** Resolved project name, or null when the row has no project. */
projectName?: string | null;
/** Range start (YYYY-MM-DD). For overrides, pass the shift date here. */
@@ -74,11 +74,7 @@ export function buildPlanAuditDescription(
? formatPlanDate(opts.dateFrom)
: `${formatPlanDate(opts.dateFrom)} ${formatPlanDate(opts.dateTo)}`;
const parts: string[] = [
opts.userName,
dateLabel,
planCategoryLabel(opts.category),
];
const parts: string[] = [opts.userName, dateLabel, opts.categoryLabel];
if (opts.projectName && opts.projectName.trim()) {
parts.push(opts.projectName.trim());