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:
@@ -5,8 +5,10 @@ import { Prisma, CompetitionCategory, OceanIssue, TeamMemberRole } from '@prisma
|
||||
import {
|
||||
createNotification,
|
||||
notifyAdmins,
|
||||
notifyProjectTeam,
|
||||
NotificationTypes,
|
||||
} from '../services/in-app-notification'
|
||||
import { getFirstRoundForProgram } from '@/server/utils/round-helpers'
|
||||
import { checkRateLimit } from '@/lib/rate-limit'
|
||||
import { logAudit } from '@/server/utils/audit'
|
||||
import { parseWizardConfig } from '@/lib/wizard-config'
|
||||
@@ -458,6 +460,18 @@ export const applicationRouter = router({
|
||||
},
|
||||
})
|
||||
|
||||
// Auto-assign to first round if project has no roundId (edition-wide mode)
|
||||
let assignedRound: { id: string; name: string; entryNotificationType: string | null } | null = null
|
||||
if (!project.roundId) {
|
||||
assignedRound = await getFirstRoundForProgram(ctx.prisma, program.id)
|
||||
if (assignedRound) {
|
||||
await ctx.prisma.project.update({
|
||||
where: { id: project.id },
|
||||
data: { roundId: assignedRound.id },
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Create team lead membership
|
||||
await ctx.prisma.teamMember.create({
|
||||
data: {
|
||||
@@ -510,6 +524,7 @@ export const applicationRouter = router({
|
||||
source: 'public_application_form',
|
||||
title: data.projectName,
|
||||
category: data.competitionCategory,
|
||||
autoAssignedRound: assignedRound?.name || null,
|
||||
},
|
||||
ipAddress: ctx.ip,
|
||||
userAgent: ctx.userAgent,
|
||||
@@ -544,6 +559,26 @@ export const applicationRouter = router({
|
||||
},
|
||||
})
|
||||
|
||||
// Send SUBMISSION_RECEIVED notification if the round is configured for it
|
||||
if (assignedRound?.entryNotificationType === 'SUBMISSION_RECEIVED') {
|
||||
try {
|
||||
await notifyProjectTeam(project.id, {
|
||||
type: NotificationTypes.SUBMISSION_RECEIVED,
|
||||
title: 'Submission Received',
|
||||
message: `Your submission "${data.projectName}" has been received and is now under review.`,
|
||||
linkUrl: `/team/projects/${project.id}`,
|
||||
linkLabel: 'View Submission',
|
||||
metadata: {
|
||||
projectName: data.projectName,
|
||||
roundName: assignedRound.name,
|
||||
programName: program.name,
|
||||
},
|
||||
})
|
||||
} catch {
|
||||
// Never fail on notification
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
projectId: project.id,
|
||||
@@ -816,6 +851,18 @@ export const applicationRouter = router({
|
||||
},
|
||||
})
|
||||
|
||||
// Auto-assign to first round if project has no roundId
|
||||
let draftAssignedRound: { id: string; name: string; entryNotificationType: string | null } | null = null
|
||||
if (!updated.roundId) {
|
||||
draftAssignedRound = await getFirstRoundForProgram(ctx.prisma, updated.programId)
|
||||
if (draftAssignedRound) {
|
||||
await ctx.prisma.project.update({
|
||||
where: { id: updated.id },
|
||||
data: { roundId: draftAssignedRound.id },
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Audit log
|
||||
try {
|
||||
await logAudit({
|
||||
@@ -828,6 +875,7 @@ export const applicationRouter = router({
|
||||
source: 'draft_submission',
|
||||
title: data.projectName,
|
||||
category: data.competitionCategory,
|
||||
autoAssignedRound: draftAssignedRound?.name || null,
|
||||
},
|
||||
ipAddress: ctx.ip,
|
||||
userAgent: ctx.userAgent,
|
||||
@@ -836,6 +884,25 @@ export const applicationRouter = router({
|
||||
// Never throw on audit failure
|
||||
}
|
||||
|
||||
// Send SUBMISSION_RECEIVED notification if the round is configured for it
|
||||
if (draftAssignedRound?.entryNotificationType === 'SUBMISSION_RECEIVED') {
|
||||
try {
|
||||
await notifyProjectTeam(updated.id, {
|
||||
type: NotificationTypes.SUBMISSION_RECEIVED,
|
||||
title: 'Submission Received',
|
||||
message: `Your submission "${data.projectName}" has been received and is now under review.`,
|
||||
linkUrl: `/team/projects/${updated.id}`,
|
||||
linkLabel: 'View Submission',
|
||||
metadata: {
|
||||
projectName: data.projectName,
|
||||
roundName: draftAssignedRound.name,
|
||||
},
|
||||
})
|
||||
} catch {
|
||||
// Never fail on notification
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
projectId: updated.id,
|
||||
|
||||
Reference in New Issue
Block a user