Skip to content

Commit

Permalink
admin#1870 Shortlisting outcome MVP, admin#2191 Pre Selection Day tas…
Browse files Browse the repository at this point in the history
…ks (#971)

* [ admin#1870 ] Shortlisting outcome MVP

* [ jac-uk/admin/issues/2190 ] Pre Selection Day task

* WIP

* Create candidateFormResponses

* Rules for candidateForm responses

* Firestore rules for candidateForm responses
Add flag to applicationRecord for form status
Add applicationId to form response

* Update rules

---------

Co-authored-by: drieJac <jac.drie@digitalteam.uk>
  • Loading branch information
warrensearle and drieJAC authored Nov 21, 2023
1 parent 0aa2f11 commit 45c515f
Show file tree
Hide file tree
Showing 10 changed files with 511 additions and 141 deletions.
15 changes: 15 additions & 0 deletions database/firestore.rules
Original file line number Diff line number Diff line change
Expand Up @@ -296,5 +296,20 @@ service cloud.firestore {
allow read: if userIsAuthenticated() && userIsJAC();
allow write: if userIsAuthenticated() && userIsJAC() && hasPermission('u3');
}

match /candidateForms/{formId} {
allow read: if userIsAuthenticated() && userIsJAC();
allow read: if userIsAuthenticated() && currentUser() in resource.data.candidateIds;
allow write: if userIsAuthenticated() && userIsJAC() && hasPermission('cf3');

match /responses/{responseId} {
// jac admins can read and update
allow read, update: if userIsAuthenticated() && userIsJAC();

allow read: if userIsAuthenticated() && currentUser() == responseId;
allow update: if userIsAuthenticated() && currentUser() == responseId && request.resource.data.applicationId == resource.data.applicationId;
}
}

}
}
69 changes: 69 additions & 0 deletions functions/actions/candidateForms.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
const { applyUpdates } = require('../shared/helpers');

module.exports = (firebase, db) => {
return {
onCandidateFormCreate,
};

/**
* CandidateForm created event handler
* - Creates CandidateFormResponses
* - One per candidate
*/
async function createCandidateResponses(data) {
console.log('candidateForm created');

// {
// exerciseId: { id: String },
// task: { type: String },
// createdAt: Timestamp,
// lastUpdated: Timestamp,
// openDate: Timestamp,
// closeDate: Timestamp,
// candidateIds: String[],
// parts: String[],
// panellists: [{ id: String, fullName: String }],
// }

if (data.candidateIds.length) {
let commands = [];

for (let i = 0; i < data.candidateIds.length; i++) {
const candidateFormId = data.id;
const candidateId = candidateIds[i];

// {
// formId: String, // so we know ID of parent document
// status: String, // e.g. created | requested | completed
// statusLog: {}, // e.g. { created: Timestamp, requested: Timestamp, completed: Timestamp }
// progress: {} // e.g. { part1: Boolean, part2: Boolean },
// candidateAvailability: {} // TBC
// panelConflicts: {} // TBC
// }

const document = {
formId: candidateFormId,
status: 'CREATED', // @TODO: Use constant
statusLog: {
created: firebase.firestore.FieldValue.serverTimestamp(),
requested: null,
completed: null,
},
progress: {
part1: false, // @TODO: Check with Warren if this should be true here!!!
part2: false,
},
candidateAvailability: {}, // TBC
panelConflicts: {}, // TBC
};

commands.push({
command: 'set',
ref: db.collection('candidateFormRequests').doc(),
data: document,
});
}
}
return await applyUpdates(db, commands);
}
};
7 changes: 4 additions & 3 deletions functions/actions/tasks/createTask.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ const { getDocument, getDocuments, applyUpdates } = require('../../shared/helper

module.exports = (config, firebase, db) => {
const { getTimelineTasks, taskNextStatus, taskApplicationsEntryStatus } = require('./taskHelpers')(config);
const { initialisePanelTask, initialiseTestTask, initialiseStatusChangesTask, initialiseDataTask, initialiseStageOutcomeTask } = require('./updateTask')(config, firebase, db);
const { initialisePanelTask, initialiseTestTask, initialiseStatusChangesTask, initialiseCandidateFormTask, initialiseDataTask, initialiseStageOutcomeTask } = require('./updateTask')(config, firebase, db);

return createTask;

Expand All @@ -15,8 +15,6 @@ module.exports = (config, firebase, db) => {
*/
async function createTask(params) {

console.log('createTask', params);

let result = {
success: false,
data: {},
Expand Down Expand Up @@ -66,6 +64,9 @@ module.exports = (config, firebase, db) => {
case config.TASK_STATUS.STATUS_CHANGES:
result = await initialiseStatusChangesTask(exercise, params.type, applicationRecords);
break;
case config.TASK_STATUS.CANDIDATE_FORM_CONFIGURE:
result = await initialiseCandidateFormTask(exercise, params.type);
break;
case config.TASK_STATUS.DATA_INITIALISED:
result = await initialiseDataTask(exercise, params.type);
break;
Expand Down
118 changes: 74 additions & 44 deletions functions/actions/tasks/taskHelpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ const { convertToDate, calculateMean, calculateStandardDeviation } = require('..
module.exports = (config) => {
const exerciseTimeline = require('../../shared/Timeline/exerciseTimeline.TMP')(config);
const TASK_TYPE = config.TASK_TYPE;
const SHORTLISTING_TASK_TYPES = config.SHORTLISTING_TASK_TYPES;
const TASK_STATUS = config.TASK_STATUS;
const APPLICATION_STATUS = config.APPLICATION.STATUS;
return {
scoreSheet,
Expand All @@ -30,58 +32,65 @@ module.exports = (config) => {
function taskStatuses(taskType) {
let availableStatuses = [];
switch (taskType) {
case config.TASK_TYPE.CRITICAL_ANALYSIS:
case config.TASK_TYPE.SITUATIONAL_JUDGEMENT:
case TASK_TYPE.CRITICAL_ANALYSIS:
case TASK_TYPE.SITUATIONAL_JUDGEMENT:
availableStatuses = [
config.TASK_STATUS.TEST_INITIALISED,
config.TASK_STATUS.TEST_ACTIVATED,
config.TASK_STATUS.FINALISED,
config.TASK_STATUS.COMPLETED,
TASK_STATUS.TEST_INITIALISED,
TASK_STATUS.TEST_ACTIVATED,
TASK_STATUS.FINALISED,
TASK_STATUS.COMPLETED,
];
break;
case config.TASK_TYPE.QUALIFYING_TEST:
case TASK_TYPE.QUALIFYING_TEST:
availableStatuses = [
config.TASK_STATUS.FINALISED,
config.TASK_STATUS.COMPLETED,
TASK_STATUS.FINALISED,
TASK_STATUS.COMPLETED,
];
break;
case config.TASK_TYPE.SCENARIO:
case config.TASK_TYPE.EMP_TIEBREAKER:
case TASK_TYPE.SCENARIO:
case TASK_TYPE.EMP_TIEBREAKER:
availableStatuses = [
config.TASK_STATUS.TEST_INITIALISED,
config.TASK_STATUS.TEST_ACTIVATED,
// config.TASK_STATUS.PANELS_INITIALISED,
// config.TASK_STATUS.PANELS_ACTIVATED,
config.TASK_STATUS.DATA_ACTIVATED,
config.TASK_STATUS.FINALISED,
config.TASK_STATUS.COMPLETED,
TASK_STATUS.TEST_INITIALISED,
TASK_STATUS.TEST_ACTIVATED,
// TASK_STATUS.PANELS_INITIALISED,
// TASK_STATUS.PANELS_ACTIVATED,
TASK_STATUS.DATA_ACTIVATED,
TASK_STATUS.FINALISED,
TASK_STATUS.COMPLETED,
];
break;
case config.TASK_TYPE.TELEPHONE_ASSESSMENT:
case config.TASK_TYPE.ELIGIBILITY_SCC:
case config.TASK_TYPE.STATUTORY_CONSULTATION:
case config.TASK_TYPE.CHARACTER_AND_SELECTION_SCC:
case TASK_TYPE.TELEPHONE_ASSESSMENT:
case TASK_TYPE.ELIGIBILITY_SCC:
case TASK_TYPE.STATUTORY_CONSULTATION:
case TASK_TYPE.CHARACTER_AND_SELECTION_SCC:
availableStatuses = [
config.TASK_STATUS.STATUS_CHANGES,
config.TASK_STATUS.COMPLETED,
TASK_STATUS.STATUS_CHANGES,
TASK_STATUS.COMPLETED,
];
break;
case config.TASK_TYPE.SIFT:
case config.TASK_TYPE.SELECTION_DAY:
case TASK_TYPE.PRE_SELECTION_DAY_QUESTIONNAIRE:
availableStatuses = [
TASK_STATUS.CANDIDATE_FORM_CONFIGURE,
TASK_STATUS.CANDIDATE_FORM_MONITOR,
TASK_STATUS.COMPLETED,
];
break;
case TASK_TYPE.SIFT:
case TASK_TYPE.SELECTION_DAY:
availableStatuses = [
config.TASK_STATUS.DATA_INITIALISED,
config.TASK_STATUS.DATA_ACTIVATED,
// config.TASK_STATUS.PANELS_INITIALISED,
// config.TASK_STATUS.PANELS_ACTIVATED,
config.TASK_STATUS.FINALISED,
config.TASK_STATUS.COMPLETED,
TASK_STATUS.DATA_INITIALISED,
TASK_STATUS.DATA_ACTIVATED,
// TASK_STATUS.PANELS_INITIALISED,
// TASK_STATUS.PANELS_ACTIVATED,
TASK_STATUS.FINALISED,
TASK_STATUS.COMPLETED,
];
break;
case config.TASK_TYPE.SHORTLISTING_OUTCOME:
case config.TASK_TYPE.SELECTION_OUTCOME:
case TASK_TYPE.SHORTLISTING_OUTCOME:
case TASK_TYPE.SELECTION_OUTCOME:
availableStatuses = [
config.TASK_STATUS.STAGE_OUTCOME,
config.TASK_STATUS.COMPLETED,
TASK_STATUS.STAGE_OUTCOME,
TASK_STATUS.COMPLETED,
];
break;
}
Expand Down Expand Up @@ -136,6 +145,8 @@ module.exports = (config) => {
return 'noTestSubmitted';
case TASK_TYPE.SCENARIO:
return 'noScenarioTestSubmitted';
// case TASK_TYPE.PRE_SELECTION_DAY_QUESTIONNAIRE:
// return 'noSelectionDayQuestionnaireSubmitted';
default:
return null;
}
Expand Down Expand Up @@ -292,32 +303,51 @@ module.exports = (config) => {

function getTimelineTasks(exercise, taskType) {
const timeline = createTimeline(exerciseTimeline(exercise));
const timelineTasks = timeline.filter(item => item.taskType && (!taskType || item.taskType === taskType));
let timelineTasks = timeline.filter(item => item.taskType && (!taskType || item.taskType === taskType));
let supportedTaskTypes = [];
if (exercise._processingVersion >= 2) {
const supportedTaskTypes = [
supportedTaskTypes = [
TASK_TYPE.TELEPHONE_ASSESSMENT,
TASK_TYPE.SIFT,
TASK_TYPE.CRITICAL_ANALYSIS,
TASK_TYPE.SITUATIONAL_JUDGEMENT,
TASK_TYPE.QUALIFYING_TEST,
TASK_TYPE.SCENARIO,
TASK_TYPE.TELEPHONE_ASSESSMENT,
TASK_TYPE.SHORTLISTING_OUTCOME,
TASK_TYPE.ELIGIBILITY_SCC,
TASK_TYPE.STATUTORY_CONSULTATION,
TASK_TYPE.CHARACTER_AND_SELECTION_SCC,
TASK_TYPE.CHARACTER_AND_SELECTION_SCC,
TASK_TYPE.EMP_TIEBREAKER,
TASK_TYPE.PRE_SELECTION_DAY_QUESTIONNAIRE,
TASK_TYPE.SELECTION_DAY,
];
return timelineTasks.filter(task => supportedTaskTypes.indexOf(task.taskType) >= 0);
} else {
const supportedTaskTypes = [
supportedTaskTypes = [
TASK_TYPE.CRITICAL_ANALYSIS,
TASK_TYPE.SITUATIONAL_JUDGEMENT,
TASK_TYPE.QUALIFYING_TEST,
TASK_TYPE.SCENARIO,
TASK_TYPE.EMP_TIEBREAKER,
];
return timelineTasks.filter(task => supportedTaskTypes.indexOf(task.taskType) >= 0);
}
}
timelineTasks = timelineTasks.filter(task => supportedTaskTypes.indexOf(task.taskType) >= 0);
if (timelineTasks.find((item) => item.taskType === TASK_TYPE.SHORTLISTING_OUTCOME)) { // ensure shortlisting outcome comes after shortlisting methods!
let shortlistingOutcomeIndex = -1;
let lastShortlistingMethodIndex = -1;
timelineTasks.forEach((item, index) => {
if (item.taskType === TASK_TYPE.SHORTLISTING_OUTCOME) shortlistingOutcomeIndex = index;
if (
(SHORTLISTING_TASK_TYPES.indexOf(item.taskType) >= 0)
&& index > lastShortlistingMethodIndex
) {
lastShortlistingMethodIndex = index;
}
});
if (lastShortlistingMethodIndex > shortlistingOutcomeIndex) {
timelineTasks.splice(lastShortlistingMethodIndex, 0, timelineTasks.splice(shortlistingOutcomeIndex, 1)[0]);
}
}
return timelineTasks;
}

function getTaskTypes(exercise, stage) {
Expand Down
Loading

0 comments on commit 45c515f

Please sign in to comment.