Auto-assign projects to first round, auto-filter on close, pipeline UX consolidation
- New projects (admin create, CSV import, public form) auto-assign to program's first round (by sortOrder) when no round is specified - Closing a FILTERING round auto-starts filtering job (configurable via autoFilterOnClose setting, defaults to true) - Add SUBMISSION_RECEIVED notification type for confirming submissions - Replace separate List/Pipeline toggle with integrated pipeline view below the sortable round list - Add autoFilterOnClose toggle to filtering round type settings UI Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -4,9 +4,12 @@ import { Prisma } from '@prisma/client'
|
||||
import { router, protectedProcedure, adminProcedure } from '../trpc'
|
||||
import {
|
||||
notifyRoundJury,
|
||||
notifyAdmins,
|
||||
NotificationTypes,
|
||||
} from '../services/in-app-notification'
|
||||
import { logAudit } from '@/server/utils/audit'
|
||||
import { runFilteringJob } from './filtering'
|
||||
import { prisma as globalPrisma } from '@/lib/prisma'
|
||||
|
||||
// Valid round status transitions (state machine)
|
||||
const VALID_ROUND_TRANSITIONS: Record<string, string[]> = {
|
||||
@@ -437,6 +440,60 @@ export const roundRouter = router({
|
||||
}
|
||||
}
|
||||
|
||||
// Auto-run filtering when a FILTERING round is closed (if enabled in settings)
|
||||
const roundSettings = (round.settingsJson as Record<string, unknown>) || {}
|
||||
const autoFilterEnabled = roundSettings.autoFilterOnClose !== false // Default to true
|
||||
if (input.status === 'CLOSED' && round.roundType === 'FILTERING' && autoFilterEnabled) {
|
||||
try {
|
||||
const [filteringRules, projectCount] = await Promise.all([
|
||||
ctx.prisma.filteringRule.findMany({
|
||||
where: { roundId: input.id, isActive: true },
|
||||
}),
|
||||
ctx.prisma.project.count({ where: { roundId: input.id } }),
|
||||
])
|
||||
|
||||
// Check for existing running job
|
||||
const existingJob = await ctx.prisma.filteringJob.findFirst({
|
||||
where: { roundId: input.id, status: 'RUNNING' },
|
||||
})
|
||||
|
||||
if (filteringRules.length > 0 && projectCount > 0 && !existingJob) {
|
||||
// Create filtering job
|
||||
const job = await globalPrisma.filteringJob.create({
|
||||
data: {
|
||||
roundId: input.id,
|
||||
status: 'PENDING',
|
||||
totalProjects: projectCount,
|
||||
},
|
||||
})
|
||||
|
||||
// Start background execution (non-blocking)
|
||||
setImmediate(() => {
|
||||
runFilteringJob(job.id, input.id, ctx.user.id).catch(console.error)
|
||||
})
|
||||
|
||||
// Notify admins that auto-filtering has started
|
||||
await notifyAdmins({
|
||||
type: NotificationTypes.FILTERING_COMPLETE,
|
||||
title: 'Auto-Filtering Started',
|
||||
message: `Filtering automatically started for "${round.name}" after closing. ${projectCount} projects will be processed.`,
|
||||
linkUrl: `/admin/rounds/${input.id}/filtering`,
|
||||
linkLabel: 'View Progress',
|
||||
metadata: {
|
||||
roundId: input.id,
|
||||
roundName: round.name,
|
||||
projectCount,
|
||||
ruleCount: filteringRules.length,
|
||||
autoTriggered: true,
|
||||
},
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
// Auto-filtering failure should not block round closure
|
||||
console.error('[Auto-Filtering] Failed to start:', error)
|
||||
}
|
||||
}
|
||||
|
||||
return round
|
||||
}),
|
||||
|
||||
|
||||
Reference in New Issue
Block a user