|
4 | 4 | const _ = require("lodash"); |
5 | 5 | const Joi = require("joi"); |
6 | 6 | const moment = require("moment"); |
| 7 | +const { Prisma } = require("@prisma/client"); |
| 8 | +const config = require("config"); |
7 | 9 | const helper = require("../common/helper"); |
8 | 10 | const logger = require("../common/logger"); |
9 | 11 | const errors = require("../common/errors"); |
10 | 12 | const constants = require("../../app-constants"); |
| 13 | +const { getReviewClient } = require("../common/review-prisma"); |
11 | 14 |
|
12 | 15 | const { getClient } = require("../common/prisma"); |
13 | 16 | const prisma = getClient(); |
| 17 | +const PENDING_REVIEW_STATUSES = Object.freeze(["PENDING", "IN_PROGRESS", "DRAFT", "SUBMITTED"]); |
| 18 | + |
| 19 | +async function hasPendingScorecardsForPhase(challengePhaseId) { |
| 20 | + if (!config.REVIEW_DB_URL) { |
| 21 | + logger.debug( |
| 22 | + `Skipping pending scorecard check for phase ${challengePhaseId} because REVIEW_DB_URL is not configured` |
| 23 | + ); |
| 24 | + return false; |
| 25 | + } |
| 26 | + |
| 27 | + const reviewPrisma = getReviewClient(); |
| 28 | + const reviewSchema = config.REVIEW_DB_SCHEMA; |
| 29 | + const reviewTable = Prisma.raw(`"${reviewSchema}"."review"`); |
| 30 | + const statusText = Prisma.raw(`"status"::text`); |
| 31 | + |
| 32 | + const pendingStatusClause = |
| 33 | + PENDING_REVIEW_STATUSES.length > 0 |
| 34 | + ? Prisma.sql`${statusText} IN (${Prisma.join( |
| 35 | + PENDING_REVIEW_STATUSES.map((status) => Prisma.sql`${status}`) |
| 36 | + )})` |
| 37 | + : Prisma.sql`FALSE`; |
| 38 | + |
| 39 | + let rows; |
| 40 | + try { |
| 41 | + rows = await reviewPrisma.$queryRaw( |
| 42 | + Prisma.sql` |
| 43 | + SELECT COUNT(*)::int AS count |
| 44 | + FROM ${reviewTable} |
| 45 | + WHERE "phaseId" = ${challengePhaseId} |
| 46 | + AND ( |
| 47 | + "status" IS NULL |
| 48 | + OR ${pendingStatusClause} |
| 49 | + ) |
| 50 | + ` |
| 51 | + ); |
| 52 | + } catch (err) { |
| 53 | + logger.error( |
| 54 | + `Failed to check pending scorecards for phase ${challengePhaseId}: ${err.message}`, |
| 55 | + err |
| 56 | + ); |
| 57 | + throw err; |
| 58 | + } |
| 59 | + |
| 60 | + const [{ count = 0 } = {}] = rows || []; |
| 61 | + return Number(count) > 0; |
| 62 | +} |
14 | 63 |
|
15 | 64 | async function checkChallengeExists(challengeId) { |
16 | 65 | const challenge = await prisma.challenge.findUnique({ where: { id: challengeId } }); |
@@ -147,8 +196,41 @@ async function partiallyUpdateChallengePhase(currentUser, challengeId, id, data) |
147 | 196 | } |
148 | 197 | } |
149 | 198 |
|
| 199 | + const isClosingPhase = |
| 200 | + "isOpen" in data && data["isOpen"] === false && Boolean(challengePhase.isOpen); |
| 201 | + const isReopeningPhase = |
| 202 | + "isOpen" in data && data["isOpen"] === true && !challengePhase.isOpen; |
| 203 | + if (isClosingPhase) { |
| 204 | + const pendingScorecards = await hasPendingScorecardsForPhase(challengePhase.id); |
| 205 | + if (pendingScorecards) { |
| 206 | + const phaseName = challengePhase.name || "phase"; |
| 207 | + throw new errors.ForbiddenError( |
| 208 | + `Cannot close ${phaseName} because there are still pending scorecards` |
| 209 | + ); |
| 210 | + } |
| 211 | + } |
| 212 | + if (isReopeningPhase) { |
| 213 | + data.actualEndDate = null; |
| 214 | + } |
| 215 | + |
150 | 216 | // Update ChallengePhase |
151 | 217 | data.updatedBy = String(currentUser.userId); |
| 218 | + if (!_.isNil(data.duration)) { |
| 219 | + const startInput = !_.isNil(data.scheduledStartDate) |
| 220 | + ? data.scheduledStartDate |
| 221 | + : !_.isNil(challengePhase.scheduledStartDate) |
| 222 | + ? challengePhase.scheduledStartDate |
| 223 | + : null; |
| 224 | + if (startInput) { |
| 225 | + const startDate = new Date(startInput); |
| 226 | + if (!Number.isNaN(startDate.getTime())) { |
| 227 | + const recalculatedScheduledEndDate = new Date( |
| 228 | + startDate.getTime() + Number(data.duration) * 1000 |
| 229 | + ); |
| 230 | + data.scheduledEndDate = recalculatedScheduledEndDate; |
| 231 | + } |
| 232 | + } |
| 233 | + } |
152 | 234 | const dataToUpdate = _.omit(data, "constraints"); |
153 | 235 | const result = await prisma.$transaction(async (tx) => { |
154 | 236 | const result = await tx.challengePhase.update({ |
|
0 commit comments