Skip to content

Commit 0d118ea

Browse files
committed
Timeline ID optional match to default reviewers. Helps for matching the single round / two round design challenges
1 parent 1be6e5a commit 0d118ea

File tree

3 files changed

+106
-35
lines changed

3 files changed

+106
-35
lines changed
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
-- Add optional timelineTemplateId to DefaultChallengeReviewer for template-specific defaults
2+
ALTER TABLE "DefaultChallengeReviewer"
3+
ADD COLUMN "timelineTemplateId" TEXT;
4+
5+
-- Support lookups by type/track/timelineTemplate combo
6+
CREATE INDEX IF NOT EXISTS "DefaultChallengeReviewer_typeId_trackId_timelineTemplateId_idx"
7+
ON "DefaultChallengeReviewer" ("typeId", "trackId", "timelineTemplateId");
8+
9+
-- Reference the timeline template record when provided
10+
ALTER TABLE "DefaultChallengeReviewer"
11+
ADD CONSTRAINT "DefaultChallengeReviewer_timelineTemplateId_fkey"
12+
FOREIGN KEY ("timelineTemplateId") REFERENCES "TimelineTemplate"("id")
13+
ON DELETE SET NULL ON UPDATE CASCADE;

prisma/schema.prisma

Lines changed: 30 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -584,15 +584,15 @@ model ChallengeReviewer {
584584
challengeId String
585585
586586
// Reviewer configuration
587-
scorecardId String
588-
isMemberReview Boolean
589-
memberReviewerCount Int?
590-
phaseId String
591-
basePayment Float?
592-
incrementalPayment Float?
593-
type ReviewOpportunityTypeEnum?
594-
aiWorkflowId String? @db.VarChar(14)
595-
shouldOpenOpportunity Boolean @default(true)
587+
scorecardId String
588+
isMemberReview Boolean
589+
memberReviewerCount Int?
590+
phaseId String
591+
basePayment Float?
592+
incrementalPayment Float?
593+
type ReviewOpportunityTypeEnum?
594+
aiWorkflowId String? @db.VarChar(14)
595+
shouldOpenOpportunity Boolean @default(true)
596596
597597
// Relation to the challenge
598598
challenge Challenge @relation(fields: [challengeId], references: [id], onDelete: Cascade)
@@ -614,27 +614,29 @@ model ChallengeReviewer {
614614
//////////////////////////////////////////
615615

616616
model DefaultChallengeReviewer {
617-
id String @id @default(uuid())
618-
typeId String
619-
trackId String
617+
id String @id @default(uuid())
618+
typeId String
619+
trackId String
620+
timelineTemplateId String?
620621
// Reviewer configuration (mirrors ChallengeReviewer)
621-
scorecardId String
622-
isMemberReview Boolean
623-
memberReviewerCount Int?
624-
phaseName String
622+
scorecardId String
623+
isMemberReview Boolean
624+
memberReviewerCount Int?
625+
phaseName String
625626
// Optional explicit link to Phase for better fidelity
626-
phaseId String?
627-
basePayment Float?
628-
incrementalPayment Float?
629-
opportunityType ReviewOpportunityTypeEnum?
630-
isAIReviewer Boolean
631-
shouldOpenOpportunity Boolean @default(true)
627+
phaseId String?
628+
basePayment Float?
629+
incrementalPayment Float?
630+
opportunityType ReviewOpportunityTypeEnum?
631+
isAIReviewer Boolean
632+
shouldOpenOpportunity Boolean @default(true)
632633
633634
// Relations
634-
challengeType ChallengeType @relation(fields: [typeId], references: [id])
635-
challengeTrack ChallengeTrack @relation(fields: [trackId], references: [id])
635+
challengeType ChallengeType @relation(fields: [typeId], references: [id])
636+
challengeTrack ChallengeTrack @relation(fields: [trackId], references: [id])
637+
timelineTemplate TimelineTemplate? @relation(fields: [timelineTemplateId], references: [id])
636638
// Relation to Phase (optional for backward compatibility with phaseName)
637-
phase Phase? @relation(fields: [phaseId], references: [id])
639+
phase Phase? @relation(fields: [phaseId], references: [id])
638640
639641
// Auditing fields
640642
createdAt DateTime @default(now())
@@ -643,6 +645,7 @@ model DefaultChallengeReviewer {
643645
updatedBy String
644646
645647
@@index([typeId, trackId])
648+
@@index([typeId, trackId, timelineTemplateId])
646649
@@index([phaseId])
647650
}
648651

@@ -665,7 +668,8 @@ model TimelineTemplate {
665668
challengeTimelineTemplates ChallengeTimelineTemplate[]
666669
667670
// Opposite relation field for Challenge.
668-
challenges Challenge[]
671+
challenges Challenge[]
672+
DefaultChallengeReviewer DefaultChallengeReviewer[]
669673
670674
@@unique([name])
671675
}

src/services/ChallengeService.js

Lines changed: 63 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -230,15 +230,28 @@ async function getDefaultReviewers(currentUser, criteria) {
230230
.keys({
231231
typeId: Joi.id(),
232232
trackId: Joi.id(),
233+
timelineTemplateId: Joi.optionalId(),
233234
})
234235
.required();
235236
const { error, value } = schema.validate(criteria);
236237
if (error) throw error;
237238

238-
const rows = await prisma.defaultChallengeReviewer.findMany({
239-
where: { typeId: value.typeId, trackId: value.trackId },
240-
orderBy: { createdAt: "asc" },
241-
});
239+
const baseWhere = { typeId: value.typeId, trackId: value.trackId };
240+
let rows = [];
241+
242+
if (value.timelineTemplateId) {
243+
rows = await prisma.defaultChallengeReviewer.findMany({
244+
where: { ...baseWhere, timelineTemplateId: value.timelineTemplateId },
245+
orderBy: { createdAt: "asc" },
246+
});
247+
}
248+
249+
if (!rows || rows.length === 0) {
250+
rows = await prisma.defaultChallengeReviewer.findMany({
251+
where: { ...baseWhere, timelineTemplateId: null },
252+
orderBy: { createdAt: "asc" },
253+
});
254+
}
242255

243256
return rows.map((r) => ({
244257
scorecardId: r.scorecardId,
@@ -267,6 +280,7 @@ async function setDefaultReviewers(currentUser, data) {
267280
.keys({
268281
typeId: Joi.id().required(),
269282
trackId: Joi.id().required(),
283+
timelineTemplateId: Joi.optionalId(),
270284
reviewers: Joi.array()
271285
.items(
272286
Joi.object().keys({
@@ -310,6 +324,17 @@ async function setDefaultReviewers(currentUser, data) {
310324
if (!track)
311325
throw new errors.NotFoundError(`ChallengeTrack with id: ${value.trackId} doesn't exist`);
312326

327+
if (value.timelineTemplateId) {
328+
const timelineTemplate = await prisma.timelineTemplate.findUnique({
329+
where: { id: value.timelineTemplateId },
330+
});
331+
if (!timelineTemplate) {
332+
throw new errors.NotFoundError(
333+
`TimelineTemplate with id: ${value.timelineTemplateId} doesn't exist`
334+
);
335+
}
336+
}
337+
313338
const userId = _.toString(currentUser && currentUser.userId ? currentUser.userId : "system");
314339
const auditFields = { createdBy: userId, updatedBy: userId };
315340

@@ -326,14 +351,23 @@ async function setDefaultReviewers(currentUser, data) {
326351

327352
await prisma.$transaction(async (tx) => {
328353
await tx.defaultChallengeReviewer.deleteMany({
329-
where: { typeId: value.typeId, trackId: value.trackId },
354+
where: {
355+
typeId: value.typeId,
356+
trackId: value.trackId,
357+
timelineTemplateId: _.isNil(value.timelineTemplateId)
358+
? null
359+
: value.timelineTemplateId,
360+
},
330361
});
331362
if (value.reviewers.length > 0) {
332363
await tx.defaultChallengeReviewer.createMany({
333364
data: value.reviewers.map((r) => ({
334365
...auditFields,
335366
typeId: value.typeId,
336367
trackId: value.trackId,
368+
timelineTemplateId: _.isNil(value.timelineTemplateId)
369+
? null
370+
: value.timelineTemplateId,
337371
scorecardId: String(r.scorecardId),
338372
isMemberReview: !!r.isMemberReview,
339373
isAIReviewer: !!r.isAIReviewer,
@@ -356,6 +390,7 @@ async function setDefaultReviewers(currentUser, data) {
356390
return await getDefaultReviewers(currentUser, {
357391
typeId: value.typeId,
358392
trackId: value.trackId,
393+
timelineTemplateId: value.timelineTemplateId,
359394
});
360395
}
361396
setDefaultReviewers.schema = { currentUser: Joi.any(), data: Joi.any() };
@@ -1377,10 +1412,29 @@ async function createChallenge(currentUser, challenge, userToken) {
13771412
logger.debug(
13781413
`createChallenge: loading default reviewers (trackId=${challenge.trackId}, typeId=${challenge.typeId}) ${buildLogContext()}`
13791414
);
1380-
const defaultReviewers = await prisma.defaultChallengeReviewer.findMany({
1381-
where: { typeId: challenge.typeId, trackId: challenge.trackId },
1382-
orderBy: { createdAt: "asc" },
1383-
});
1415+
const defaultReviewerWhere = {
1416+
typeId: challenge.typeId,
1417+
trackId: challenge.trackId,
1418+
};
1419+
let defaultReviewers = [];
1420+
if (challenge.timelineTemplateId) {
1421+
defaultReviewers = await prisma.defaultChallengeReviewer.findMany({
1422+
where: {
1423+
...defaultReviewerWhere,
1424+
timelineTemplateId: challenge.timelineTemplateId,
1425+
},
1426+
orderBy: { createdAt: "asc" },
1427+
});
1428+
}
1429+
if (_.isEmpty(defaultReviewers)) {
1430+
defaultReviewers = await prisma.defaultChallengeReviewer.findMany({
1431+
where: {
1432+
...defaultReviewerWhere,
1433+
timelineTemplateId: null,
1434+
},
1435+
orderBy: { createdAt: "asc" },
1436+
});
1437+
}
13841438
logger.debug(
13851439
`createChallenge: loaded ${defaultReviewers.length} default reviewers ${buildLogContext()}`
13861440
);

0 commit comments

Comments
 (0)