Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 24 additions & 13 deletions src/common/phase-helper.js
Original file line number Diff line number Diff line change
Expand Up @@ -76,21 +76,26 @@ function resolveRequestedScheduledEndDate(phase, newPhase) {
}

/**
* Validate an active phase scheduled end date change against PM-5378 rules.
* Validate a phase scheduled end date change against PM-5378 rules.
*
* @param {Object} phase existing challenge phase
* @param {Date|String|null|undefined} requestedScheduledEndDate requested scheduled end date
* @param {Object} options validation options
* @param {Boolean} options.allowActivePhaseShortening whether active phase shortening is allowed
* @param {Boolean} options.allowActivePhaseShortening whether Design track phase shortening is allowed
* @param {Boolean} options.preventPhaseShortening whether shortening is guarded for all incomplete phases
* @returns {undefined} validates only
* @throws {BadRequestError} when active phase shortening is disallowed or would end in the past
* @throws {BadRequestError} when phase shortening is disallowed or would end in the past
*/
function validateActivePhaseScheduledEndDateChange(
phase,
requestedScheduledEndDate,
options = {}
) {
if (_.isNil(phase) || phase.isOpen !== true || _.isNil(requestedScheduledEndDate)) {
if (!_.isNil(phase?.actualEndDate)) {
return;
}

if (_.isNil(phase) || _.isNil(requestedScheduledEndDate)) {
return;
}

Expand All @@ -107,19 +112,25 @@ function validateActivePhaseScheduledEndDateChange(
return;
}

if (requestedEnd.isBefore(moment())) {
const shouldValidatePhaseEnd =
phase.isOpen === true ||
options.allowActivePhaseShortening === true ||
options.preventPhaseShortening === true;
const isShortened = hasCurrentEnd && requestedEnd.isBefore(currentEnd);

if (shouldValidatePhaseEnd && requestedEnd.isBefore(moment())) {
throw new errors.BadRequestError(
"Active phase scheduledEndDate cannot be set before the current date/time."
"Phase scheduledEndDate cannot be set before the current date/time."
);
}

if (
hasCurrentEnd &&
requestedEnd.isBefore(currentEnd) &&
options.allowActivePhaseShortening !== true
isShortened &&
options.allowActivePhaseShortening !== true &&
(phase.isOpen === true || options.preventPhaseShortening === true)
) {
throw new errors.BadRequestError(
"Active phases can only be shortened for Design track challenges."
"Challenge phase schedules can only be shortened for Design track challenges."
);
}
}
Expand Down Expand Up @@ -320,7 +331,7 @@ class ChallengePhaseHelper {
const updatedPhases = _.map(challengePhasesOrdered, (phase) => {
const phaseFromTemplate = timelineTemplateMap.get(phase.phaseId);
const phaseDefinition = phaseDefinitionMap.get(phase.phaseId);
const newPhase = _.find(newPhases, (p) => p.phaseId === phase.phaseId);
const newPhase = findPhaseUpdate(newPhases, phase);
validateActivePhaseScheduledEndDateChange(
phase,
resolveRequestedScheduledEndDate(phase, newPhase),
Expand Down Expand Up @@ -495,13 +506,13 @@ class ChallengePhaseHelper {
}

/**
* Validate an active phase scheduled end date change against PM-5378 rules.
* Validate a phase scheduled end date change against PM-5378 rules.
*
* @param {Object} phase existing challenge phase
* @param {Date|String|null|undefined} requestedScheduledEndDate requested scheduled end date
* @param {Object} options validation options
* @returns {undefined} validates only
* @throws {BadRequestError} when active phase shortening is disallowed or would end in the past
* @throws {BadRequestError} when phase shortening is disallowed or would end in the past
*/
validateActivePhaseScheduledEndDateChange(phase, requestedScheduledEndDate, options = {}) {
validateActivePhaseScheduledEndDateChange(phase, requestedScheduledEndDate, options);
Expand Down
15 changes: 9 additions & 6 deletions src/services/ChallengePhaseService.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ const {
ensureAIPhaseCanBeClosed,
} = require("./ChallengeService");

const { getClient } = require("../common/prisma");
const { getClient, ChallengeStatusEnum } = require("../common/prisma");
const prisma = getClient();
const PENDING_REVIEW_STATUSES = Object.freeze(["PENDING", "IN_PROGRESS", "DRAFT", "SUBMITTED"]);
const REVIEW_PHASE_NAMES = Object.freeze([
Expand Down Expand Up @@ -648,6 +648,7 @@ getChallengePhase.schema = {
* @param {Object} data the partial phase update
* @returns {Object} the updated challengePhase
* @throws {ForbiddenError} when the current user cannot modify the challenge
* @throws {BadRequestError} when phase schedule shortening violates track or timing rules
*/
async function partiallyUpdateChallengePhase(currentUser, challengeId, id, data) {
const challenge = await getChallengeForPhaseAccess(challengeId);
Expand Down Expand Up @@ -917,11 +918,13 @@ async function partiallyUpdateChallengePhase(currentUser, challengeId, id, data)
}
}
}
phaseHelper.validateActivePhaseScheduledEndDateChange(
challengePhase,
data.scheduledEndDate,
{ allowActivePhaseShortening: phaseHelper.isDesignTrack(challenge.track) }
);
const allowActivePhaseShortening = phaseHelper.isDesignTrack(challenge.track);
const preventPhaseShortening =
challenge.status === ChallengeStatusEnum.ACTIVE && !allowActivePhaseShortening;
phaseHelper.validateActivePhaseScheduledEndDateChange(challengePhase, data.scheduledEndDate, {
allowActivePhaseShortening,
preventPhaseShortening,
});
const dataToUpdate = _.omit(data, "constraints");
const shouldRefreshPhaseNames =
Object.prototype.hasOwnProperty.call(data, "isOpen") ||
Expand Down
4 changes: 3 additions & 1 deletion src/services/ChallengeService.js
Original file line number Diff line number Diff line change
Expand Up @@ -3849,6 +3849,8 @@ async function updateChallenge(currentUser, challengeId, data, options = {}) {
let isChallengeBeingActivated = isStatusChangingToActive;
let isChallengeBeingCancelled = false;
const allowActivePhaseShortening = phaseHelper.isDesignTrack(challenge.track);
const preventPhaseShortening =
challenge.status === ChallengeStatusEnum.ACTIVE && !allowActivePhaseShortening;
const isStatusChangingToCancelled =
isCancelledChallengeStatus(data.status) && !isCancelledChallengeStatus(challenge.status);
if (data.status) {
Expand Down Expand Up @@ -4046,7 +4048,7 @@ async function updateChallenge(currentUser, challengeId, data, options = {}) {
data.phases,
challenge.timelineTemplateId,
isChallengeBeingActivated,
{ allowActivePhaseShortening },
{ allowActivePhaseShortening, preventPhaseShortening },
);
}
phasesUpdated = true;
Expand Down
Loading
Loading