From bf0268473608b396786ba4d80577a1b5c808de88 Mon Sep 17 00:00:00 2001 From: Matt Date: Fri, 20 Feb 2026 12:53:43 +0100 Subject: [PATCH] Fix COI audit log always saying conflict + fix boolean criteria submission 1. COI audit log: The declareCOI mutation always logged action 'COI_DECLARED' regardless of whether the user clicked "No Conflict" or "Yes, I Have a Conflict". Now uses 'COI_NO_CONFLICT' when hasConflict is false, showing "confirmed no conflict of interest" in the audit trail. 2. Evaluation submission: The requireAllCriteriaScored validation only accepted numeric values (typeof === 'number'), but boolean criteria (yes/no questions) store true/false. This caused jurors to get "Missing scores for criteria: criterion-xxx" errors even after completing all fields. Now correctly validates boolean criteria with typeof === 'boolean'. Also improved the error message to show criterion labels instead of cryptic IDs. Co-Authored-By: Claude Opus 4.6 --- src/components/dashboard/utils.ts | 1 + src/server/routers/evaluation.ts | 16 ++++++++++------ 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/components/dashboard/utils.ts b/src/components/dashboard/utils.ts index e095d64..09f00b5 100644 --- a/src/components/dashboard/utils.ts +++ b/src/components/dashboard/utils.ts @@ -39,6 +39,7 @@ export function formatAction(action: string, entityType: string | null): string DELETE_OWN_ACCOUNT: 'deleted their account', EVALUATION_SUBMITTED: 'submitted an evaluation', COI_DECLARED: 'declared a conflict of interest', + COI_NO_CONFLICT: 'confirmed no conflict of interest', COI_REVIEWED: 'reviewed a COI declaration', REMINDERS_TRIGGERED: 'triggered evaluation reminders', DISCUSSION_COMMENT_ADDED: 'added a discussion comment', diff --git a/src/server/routers/evaluation.ts b/src/server/routers/evaluation.ts index 31fe761..fc241b9 100644 --- a/src/server/routers/evaluation.ts +++ b/src/server/routers/evaluation.ts @@ -237,18 +237,22 @@ export const evaluationRouter = router({ select: { criteriaJson: true }, }) if (evalForm?.criteriaJson) { - const criteria = evalForm.criteriaJson as Array<{ id: string; type?: string; required?: boolean }> + const criteria = evalForm.criteriaJson as Array<{ id: string; label?: string; type?: string; required?: boolean }> const scorableCriteria = criteria.filter( (c) => c.type !== 'section_header' && c.type !== 'text' && c.required !== false ) const scores = data.criterionScoresJson as Record | undefined - const missingCriteria = scorableCriteria.filter( - (c) => !scores || typeof scores[c.id] !== 'number' - ) + const missingCriteria = scorableCriteria.filter((c) => { + if (!scores) return true + const val = scores[c.id] + // Boolean criteria store true/false, numeric criteria store numbers + if (c.type === 'boolean') return typeof val !== 'boolean' + return typeof val !== 'number' + }) if (missingCriteria.length > 0) { throw new TRPCError({ code: 'BAD_REQUEST', - message: `Missing scores for criteria: ${missingCriteria.map((c) => c.id).join(', ')}`, + message: `Missing scores for criteria: ${missingCriteria.map((c) => c.label || c.id).join(', ')}`, }) } } @@ -523,7 +527,7 @@ export const evaluationRouter = router({ await logAudit({ prisma: ctx.prisma, userId: ctx.user.id, - action: 'COI_DECLARED', + action: input.hasConflict ? 'COI_DECLARED' : 'COI_NO_CONFLICT', entityType: 'ConflictOfInterest', entityId: coi.id, detailsJson: {