From cf3c7631cb418022ee78f3205f79a16af3089398 Mon Sep 17 00:00:00 2001 From: Matt Date: Tue, 17 Feb 2026 13:55:44 +0100 Subject: [PATCH] Auto-close preceding active rounds when closing a later round When closing round N, any active rounds with lower sortOrder in the same competition are automatically closed. Each cascade closure is recorded in DecisionAuditLog with closedBy: 'cascade' reference. Co-Authored-By: Claude Opus 4.6 --- src/server/services/round-engine.ts | 37 +++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/src/server/services/round-engine.ts b/src/server/services/round-engine.ts index 9e063f6..348085d 100644 --- a/src/server/services/round-engine.ts +++ b/src/server/services/round-engine.ts @@ -235,6 +235,42 @@ export async function closeRound( // Expire pending intents await expireIntentsForRound(roundId, actorId) + // Auto-close any preceding active rounds (lower sortOrder, same competition) + const precedingActiveRounds = await tx.round.findMany({ + where: { + competitionId: round.competitionId, + sortOrder: { lt: round.sortOrder }, + status: 'ROUND_ACTIVE', + }, + orderBy: { sortOrder: 'asc' }, + }) + + for (const prev of precedingActiveRounds) { + await tx.round.update({ + where: { id: prev.id }, + data: { status: 'ROUND_CLOSED' }, + }) + await tx.decisionAuditLog.create({ + data: { + eventType: 'round.closed', + entityType: 'Round', + entityId: prev.id, + actorId, + detailsJson: { + roundName: prev.name, + roundType: prev.roundType, + previousStatus: 'ROUND_ACTIVE', + closedBy: 'cascade', + triggeringRoundId: roundId, + }, + snapshotJson: { + timestamp: new Date().toISOString(), + emittedBy: 'round-engine', + }, + }, + }) + } + await tx.decisionAuditLog.create({ data: { eventType: 'round.closed', @@ -245,6 +281,7 @@ export async function closeRound( roundName: round.name, roundType: round.roundType, previousStatus: 'ROUND_ACTIVE', + cascadeClosed: precedingActiveRounds.map((r: any) => r.name), }, snapshotJson: { timestamp: new Date().toISOString(),