Skip to content

Commit 13aa9ad

Browse files
committed
feat: new phase logic
1 parent 54f18e1 commit 13aa9ad

File tree

2 files changed

+145
-36
lines changed

2 files changed

+145
-36
lines changed

src/common/phase-helper.js

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,125 @@ class ChallengePhaseHelper {
160160
}
161161
}
162162

163+
async populatePhasesForChallengeCreation(phases, startDate, timelineTemplateId) {
164+
if (_.isUndefined(timelineTemplateId)) {
165+
throw new errors.BadRequestError(`Invalid timeline template ID: ${timelineTemplateId}`);
166+
}
167+
const { timelineTempate } = await this.getTemplateAndTemplateMap(timelineTemplateId);
168+
const { phaseDefinitionMap } = await this.getPhaseDefinitionsAndMap();
169+
const finalPhases = _.map(timelineTempate, (phaseFromTemplate) => {
170+
const phaseDefinition = phaseDefinitionMap.get(phaseFromTemplate.phaseId);
171+
const phaseFromInput = _.find(phases, (p) => p.phaseId === phaseFromTemplate.phaseId);
172+
const phase = {
173+
id: uuid(),
174+
phaseId: phaseFromTemplate.phaseId,
175+
name: phaseDefinition.name,
176+
description: phaseDefinition.description,
177+
duration: _.defaultTo(_.get(phaseFromInput, "duration"), phaseFromTemplate.defaultDuration),
178+
isOpen: false,
179+
predecessor: phaseFromTemplate.predecessor,
180+
constraints: _.defaultTo(_.get(phaseFromInput, "constraints"), []),
181+
scheduledStartDate: undefined,
182+
scheduledEndDate: undefined,
183+
actualStartDate: undefined,
184+
actualEndDate: undefined,
185+
};
186+
if (_.isUndefined(phase.predecessor)) {
187+
if (_.isUndefined(_.get(phaseFromInput, "scheduledStartDate"))) {
188+
phase.scheduledStartDate = moment(startDate).toDate();
189+
} else {
190+
phase.scheduledStartDate = moment(_.get(phaseFromInput, "scheduledStartDate")).toDate();
191+
}
192+
phase.scheduledEndDate = moment(phase.scheduledStartDate)
193+
.add(phase.duration, "seconds")
194+
.toDate();
195+
}
196+
return phase;
197+
});
198+
for (let phase of finalPhases) {
199+
if (_.isUndefined(phase.predecessor)) {
200+
continue;
201+
}
202+
const precedecessorPhase = _.find(finalPhases, {
203+
phaseId: phase.predecessor,
204+
});
205+
if (phase.name === "Iterative Review Phase") {
206+
phase.scheduledStartDate = precedecessorPhase.scheduledStartDate;
207+
} else {
208+
phase.scheduledStartDate = precedecessorPhase.scheduledEndDate;
209+
}
210+
phase.scheduledEndDate = moment(phase.scheduledStartDate)
211+
.add(phase.duration, "seconds")
212+
.toDate();
213+
}
214+
return finalPhases;
215+
}
216+
217+
async populatePhasesForChallengeUpdate(challengePhases, newPhases, isBeingActivated) {
218+
const { timelineTempate, timelineTemplateMap } = await this.getTemplateAndTemplateMap(
219+
timelineTemplateId
220+
);
221+
const { phaseDefinitionMap } = await this.getPhaseDefinitionsAndMap();
222+
223+
const updatedPhases = _.map(challengePhases, (phase) => {
224+
const phaseFromTemplate = timelineTemplateMap.get(phase.phaseId);
225+
const phaseDefinition = phaseDefinitionMap.get(phase.phaseId);
226+
const updatedPhase = {
227+
...phase,
228+
predecessor: phaseFromTemplate.predecessor,
229+
description: phaseDefinition.description,
230+
};
231+
if (!_.isUndefined(phase.actualEndDate)) {
232+
return updatedPhase;
233+
}
234+
if (phase.name === "Iterative Review Phase") {
235+
return updatedPhase;
236+
}
237+
const newPhase = _.find(newPhases, (p) => p.phaseId === phase.phaseId);
238+
if (_.isUndefined(newPhase) && !isBeingActivated) {
239+
return updatedPhase;
240+
}
241+
phase.duration = _.defaultTo(_.get(newPhase, "duration"), phase.duration);
242+
if (_.isUndefined(phase.predecessor)) {
243+
if (
244+
isBeingActivated &&
245+
moment(
246+
_.defaultTo(_.get(newPhase, "scheduledStartDate"), phase.scheduledStartDate)
247+
).isSameOrBefore(moment())
248+
) {
249+
phase.isOpen = true;
250+
phase.scheduledStartDate = moment().toDate();
251+
phase.actualStartDate = phase.scheduledStartDate;
252+
} else if (phase.isOpen === false && !_.isUndefined(newPhase.scheduledStartDate)) {
253+
phase.scheduledStartDate = moment(newPhase.scheduledStartDate).toDate();
254+
}
255+
phase.scheduledEndDate = moment(phase.scheduledStartDate)
256+
.add(phase.duration, "seconds")
257+
.toDate();
258+
}
259+
if (!_.isUndefined(newPhase) && !_.isUndefined(newPhase.constraints)) {
260+
phase.constraints = newPhase.constraints;
261+
}
262+
return updatedPhase;
263+
});
264+
for (let phase of updatedPhases) {
265+
if (_.isUndefined(phase.predecessor)) {
266+
continue;
267+
}
268+
if (phase.name === "Iterative Review Phase") {
269+
continue;
270+
}
271+
const precedecessorPhase = _.find(updatedPhases, {
272+
phaseId: phase.predecessor,
273+
});
274+
phase.scheduledStartDate = precedecessorPhase.scheduledEndDate;
275+
phase.scheduledEndDate = moment(phase.scheduledStartDate)
276+
.add(phase.duration, "seconds")
277+
.toDate();
278+
}
279+
return updatedPhases;
280+
}
281+
163282
async validatePhases(phases) {
164283
if (!phases || phases.length === 0) {
165284
return;

src/services/ChallengeService.js

Lines changed: 26 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1146,17 +1146,12 @@ async function createChallenge(currentUser, challenge, userToken) {
11461146
} else {
11471147
throw new errors.BadRequestError(`trackId and typeId are required to create a challenge`);
11481148
}
1149-
} else {
1150-
if (challenge.phases == null) {
1151-
challenge.phases = [];
1152-
}
1153-
1154-
await phaseHelper.populatePhases(
1155-
challenge.phases,
1156-
challenge.startDate,
1157-
challenge.timelineTemplateId
1158-
);
11591149
}
1150+
challenge.phases = await phaseHelper.populatePhasesForChallengeCreation(
1151+
challenge.phases,
1152+
challenge.startDate,
1153+
challenge.timelineTemplateId
1154+
);
11601155

11611156
// populate challenge terms
11621157
// const projectTerms = await helper.getProjectDefaultTerms(challenge.projectId)
@@ -1708,6 +1703,7 @@ async function update(currentUser, challengeId, data, isFull) {
17081703
});
17091704
}
17101705
}
1706+
let isChallengeBeingActivated = false;
17111707
if (data.status) {
17121708
if (data.status === constants.challengeStatuses.Active) {
17131709
if (
@@ -1728,6 +1724,9 @@ async function update(currentUser, challengeId, data, isFull) {
17281724
"Cannot Activate this project, it has no active billing account."
17291725
);
17301726
}
1727+
if (challenge.status === constants.challengeStatuses.Draft) {
1728+
isChallengeBeingActivated = true;
1729+
}
17311730
}
17321731
if (
17331732
data.status === constants.challengeStatuses.CancelledRequirementsInfeasible ||
@@ -1910,6 +1909,7 @@ async function update(currentUser, challengeId, data, isFull) {
19101909
// TODO: Fix this Tech Debt once legacy is turned off
19111910
const finalStatus = data.status || challenge.status;
19121911
const finalTimelineTemplateId = data.timelineTemplateId || challenge.timelineTemplateId;
1912+
const timelineTemplateChanged = false;
19131913
if (!_.get(data, "legacy.pureV5") && !_.get(challenge, "legacy.pureV5")) {
19141914
if (
19151915
finalStatus !== constants.challengeStatuses.New &&
@@ -1922,6 +1922,7 @@ async function update(currentUser, challengeId, data, isFull) {
19221922
} else if (finalTimelineTemplateId !== challenge.timelineTemplateId) {
19231923
// make sure there are no previous phases if the timeline template has changed
19241924
challenge.phases = [];
1925+
timelineTemplateChanged = true;
19251926
}
19261927

19271928
if (data.prizeSets && data.prizeSets.length > 0) {
@@ -1949,7 +1950,7 @@ async function update(currentUser, challengeId, data, isFull) {
19491950
}
19501951
}
19511952

1952-
if (data.phases || data.startDate) {
1953+
if (data.phases || data.startDate || timelineTemplateChanged) {
19531954
if (
19541955
challenge.status === constants.challengeStatuses.Completed ||
19551956
challenge.status.indexOf(constants.challengeStatuses.Cancelled) > -1
@@ -1958,33 +1959,22 @@ async function update(currentUser, challengeId, data, isFull) {
19581959
`Challenge phase/start date can not be modified for Completed or Cancelled challenges.`
19591960
);
19601961
}
1961-
1962-
if (data.phases && data.phases.length > 0) {
1963-
for (let i = 0; i < challenge.phases.length; i += 1) {
1964-
const updatedPhaseInfo = _.find(
1965-
data.phases,
1966-
(p) => p.phaseId === challenge.phases[i].phaseId
1967-
);
1968-
if (updatedPhaseInfo) {
1969-
_.extend(challenge.phases[i], updatedPhaseInfo);
1970-
}
1971-
}
1972-
if (challenge.phases.length === 0 && data.phases && data.phases.length > 0) {
1973-
challenge.phases = data.phases;
1974-
}
1975-
}
1976-
1977-
const newPhases = _.cloneDeep(challenge.phases) || [];
19781962
const newStartDate = data.startDate || challenge.startDate;
1963+
let newPhases;
1964+
if (timelineTemplateChanged) {
1965+
newPhases = await phaseHelper.populatePhasesForChallengeCreation(
1966+
data.phases,
1967+
newStartDate,
1968+
finalTimelineTemplateId
1969+
);
1970+
} else if (data.startDate || (data.phases && data.phases.length > 0)) {
1971+
newPhases = await phaseHelper.populatePhasesForChallengeUpdate(
1972+
challenge.phases,
1973+
data.phases,
1974+
isChallengeBeingActivated
1975+
);
1976+
}
19791977

1980-
await PhaseService.validatePhases(newPhases);
1981-
1982-
// populate phases
1983-
await phaseHelper.populatePhases(
1984-
newPhases,
1985-
newStartDate,
1986-
data.timelineTemplateId || challenge.timelineTemplateId
1987-
);
19881978
data.phases = newPhases;
19891979
challenge.phases = newPhases;
19901980
data.startDate = newStartDate;

0 commit comments

Comments
 (0)