Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/admin 2231 Eligibility Report #1024

Merged
merged 8 commits into from
Mar 13, 2024
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
22 changes: 22 additions & 0 deletions database/firestore.indexes.json
Original file line number Diff line number Diff line change
Expand Up @@ -1104,6 +1104,28 @@
}
]
},
{
"collectionGroup": "applicationRecords",
"queryScope": "COLLECTION",
"fields": [
{
"fieldPath": "exercise.id",
"order": "ASCENDING"
},
{
"fieldPath": "flags.eligibilityIssues",
"order": "ASCENDING"
},
{
"fieldPath": "flags.eligibilityIssuesMet",
"order": "ASCENDING"
},
{
"fieldPath": "candidate.fullName",
"order": "ASCENDING"
}
]
},
{
"collectionGroup": "applicationRecords",
"queryScope": "COLLECTION",
Expand Down
140 changes: 119 additions & 21 deletions functions/actions/applications/flagApplicationIssues.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
const { getDocument, getDocuments, isEmpty, applyUpdates, getDate, formatDate } = require('../../shared/helpers');
const { getDocument, getDocuments, isEmpty, applyUpdates, getDate } = require('../../shared/helpers');
const lookup = require('../../shared/converters/lookup');

module.exports = (config, db) => {
module.exports = (firebase, config, db) => {
return {
flagApplicationIssues,
flagApplicationIssuesForExercise,
Expand Down Expand Up @@ -95,9 +96,12 @@ module.exports = (config, db) => {
const data = {};
if (eligibilityIssues && eligibilityIssues.length > 0) {
data['flags.eligibilityIssues'] = true;
// check if all eligibility issues are met
data['flags.eligibilityIssuesMet'] = eligibilityIssues.every(issue => ['rls', 'pq', 'pqe', 'pje'].includes(issue.type) ? issue.summary.indexOf('Met') === 0 : true);
data['issues.eligibilityIssues'] = eligibilityIssues;
} else {
data['flags.eligibilityIssues'] = false;
data['flags.eligibilityIssuesMet'] = false;
data['issues.eligibilityIssues'] = [];
}
if (characterIssues && characterIssues.length > 0) {
Expand Down Expand Up @@ -134,6 +138,23 @@ module.exports = (config, db) => {
},
},
});
// add timestamp for character and eligibility issues reports
commands.push(
{
command: 'set',
ref: db.collection('exercises').doc(exerciseId).collection('reports').doc('characterIssues'),
data: {
createdAt: firebase.firestore.Timestamp.fromDate(new Date()),
},
},
{
command: 'set',
ref: db.collection('exercises').doc(exerciseId).collection('reports').doc('eligibilityIssues'),
data: {
createdAt: firebase.firestore.Timestamp.fromDate(new Date()),
},
}
);

// write to db
const result = await applyUpdates(db, commands);
Expand All @@ -146,15 +167,6 @@ module.exports = (config, db) => {

const issues = [];

// citizenship
if (application.personalDetails && application.personalDetails.citizenship) {
if (['uk', 'republic-of-ireland', 'another-commonwealth-country'].indexOf(application.personalDetails.citizenship) < 0) {
issues.push(newIssue('citizenship', 'Not a UK, RoI or Commonwealth citizen'));
}
} else {
issues.push(newIssue('citizenship', 'No citizenship information'));
}

// reasonable length of service - calculated from dob, characterAndSCCDate, reasonable length of service and retirement age
if (application.personalDetails && application.personalDetails.dateOfBirth) {
const reasonableLengthOfService = parseInt(exercise.reasonableLengthService === 'other' ? exercise.otherLOS : exercise.reasonableLengthService);
Expand All @@ -165,20 +177,26 @@ module.exports = (config, db) => {
const dateOfRetirement = new Date(dateOfBirth.getFullYear() + retirementAge, dateOfBirth.getMonth(), dateOfBirth.getDate());
const age = new Duration(dateOfBirth, expectedEndDate).toString();
if (application.canGiveReasonableLOS === false) {
issues.push(newIssue('rls', `Self-declared. Candidate will be ${age} old at end of service. DOB: ${formatDate(dateOfBirth)}. Candidate comments: ${application.cantGiveReasonableLOSDetails}`));
issues.push(newIssue('rls', `Not Met (${application.cantGiveReasonableLOSDetails})`));
} else {
if (expectedEndDate > dateOfRetirement) {
issues.push(newIssue('rls', `Candidate will be ${age} old at end of service. DOB: ${formatDate(dateOfBirth)}`));
issues.push(newIssue('rls', 'Not Met'));
} else {
issues.push(newIssue('rls', 'Met'));
}
}
} else {
issues.push(newIssue('rls', 'No date of birth provided'));
issues.push(newIssue('rls', 'Not Met (No date of birth provided)'));
}

// post qualification experience
if (['legal', 'leadership'].indexOf(exercise.typeOfExercise) >= 0) {
// professional qualification
const qualificationIssue = getQualificationIssue(exercise, application);
if (qualificationIssue) issues.push(qualificationIssue);

const minimumYearsExperience = exercise.postQualificationExperience === 'other' ? exercise.otherYears : exercise.postQualificationExperience;

// post qualification experience
if (application.qualifications && application.qualifications.length) {
application.qualifications = application.qualifications.filter((el) => el.date);
if (application.qualifications.length) {
Expand Down Expand Up @@ -209,6 +227,8 @@ module.exports = (config, db) => {
// @TODO look for any un-explained gaps > 1 year
const el = experienceSinceFirstQualification[i];
const startDate = getDate(el.startDate) < latestValidEndDate ? latestValidEndDate : getDate(el.startDate);
// subtract 1 month from the total calculated
startDate.setMonth(startDate.getMonth() + 1);
const endDate = el.endDate ? getDate(el.endDate) : getDate(exercise.characterAndSCCDate);
if (el.tasks && el.tasks.length > 0) {
if (el.tasks.indexOf('other') >= 0) {
Expand All @@ -224,28 +244,106 @@ module.exports = (config, db) => {
if (relevantExperience.years < minimumYearsExperience) {
if (relevantExperience.hasValue()) {
if (otherExperience.hasValue()) {
issues.push(newIssue('pqe', `Candidate has ${relevantExperience.toString()} of relevant experience and ${otherExperience.toString()} to be checked`));
issues.push(newIssue('pqe', `Not Met (Candidate has ${relevantExperience.toString()} of relevant experience and ${otherExperience.toString()} to be checked)`));
} else {
issues.push(newIssue('pqe', `Candidate has ${relevantExperience.toString()} of relevant experience`));
issues.push(newIssue('pqe', `Not Met (${relevantExperience.toString()})`));
}
} else {
issues.push(newIssue('pqe', 'Candidate has no relevant experience'));
issues.push(newIssue('pqe', 'Not Met (Candidate has no relevant experience)'));
}
} else {
issues.push(newIssue('pqe', `Met (${relevantExperience.toString()})`));
}
} else {
issues.push(newIssue('pqe', 'No experience provided'));
issues.push(newIssue('pqe', 'Not Met (No experience provided)'));
}
} else {
issues.push(newIssue('pqe', 'No qualifications provided'));
issues.push(newIssue('pqe', 'Not Met (No qualifications provided)'));
}
} else {
issues.push(newIssue('pqe', 'No qualifications provided'));
issues.push(newIssue('pqe', 'Not Met (No qualifications provided)'));
}

// previous judicial experience
const previousJudicialExperienceIssue = getPreviousJudicialExperienceIssue(exercise, application);
if (previousJudicialExperienceIssue) issues.push(previousJudicialExperienceIssue);

} else if (['non-legal', 'leadership-non-legal'].includes(exercise.typeOfExercise)) {
// non-legal exercise

// professional registration
const professionalRegistrationIssue = getProfessionalRegistrationIssue(exercise, application);
if (professionalRegistrationIssue) issues.push(professionalRegistrationIssue);
}

return issues;
}

function getQualificationIssue(exercise, application) {
if (!exercise.qualifications || !exercise.qualifications.length) return null;
if (!application.qualifications || !application.qualifications.length) return newIssue('pq', 'Not Met');

let isMet = false;
for (let i = 0; i < exercise.qualifications.length; i++) {
const qualification = exercise.qualifications[i];
if (application.qualifications.find(item => item.type === qualification)) {
isMet = true;
break;
}
}

return newIssue('pq', isMet ? 'Met' : 'Not Met');
}

function getPreviousJudicialExperienceIssue(exercise, application) {
if (!exercise.pjeDays) return null;

// met: the number of sitting days acquired by the candidate (PQE is `judicial` with`the carrying-out of judicial functions of any court or tribunal`) is greater than or equal to what is requested
let isMet = false;

if (exercise._applicationVersion > 2) {
if (Array.isArray(application.experience)) {
const totalJudicialDays = application.experience.reduce((acc, cur) => {
if (Array.isArray(cur.tasks) && cur.tasks.includes('judicial-functions') && cur.judicialFunctions && cur.judicialFunctions.type === 'judicial-post' && cur.judicialFunctions.duration) {
acc += cur.judicialFunctions.duration;
}
return acc;
}, 0);
if (totalJudicialDays >= exercise.pjeDays) {
isMet = true;
}
}
} else {
if (application.feePaidOrSalariedSatForThirtyDays) {
isMet = true;
}
}

return newIssue('pje', isMet ? 'Met' : 'Not Met');
}

function getProfessionalRegistrationIssue(exercise, application) {
if (!exercise.memberships || !exercise.memberships.length || exercise.memberships.indexOf('none') > -1) return null;

const membershipData = [];
const membershipList = [
{ field: 'charteredAssociationBuildingEngineersNumber', value: 'chartered-association-of-building-engineers' },
{ field: 'charteredInstituteBuildingNumber', value: 'chartered-institute-of-building' },
{ field: 'charteredInstituteEnvironmentalHealthNumber', value: 'chartered-institute-of-environmental-health' },
{ field: 'generalMedicalCouncilNumber', value: 'general-medical-council' },
{ field: 'royalCollegeOfPsychiatristsNumber', value: 'royal-college-of-psychiatrists' },
{ field: 'royalInstitutionCharteredSurveyorsNumber', value: 'royal-institution-of-chartered-surveyors' },
{ field: 'royalInstituteBritishArchitectsNumber', value: 'royal-institute-of-british-architects' },
];
membershipList.forEach(item => {
if (application[item.field]) {
membershipData.push(lookup(item.value));
}
});

return newIssue('pr', membershipData.join(', '));
}

function getCharacterIssues(exercise, application) {

let questions;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
const functions = require('firebase-functions');
const config = require('../shared/config.js');
const { db } = require('../shared/admin.js');
const flagApplicationIssues = require('../actions/applications/flagApplicationIssues')(config, db);
const { firebase, db } = require('../shared/admin.js');
const flagApplicationIssues = require('../actions/applications/flagApplicationIssues')(firebase, config, db);
const { checkFunctionEnabled } = require('../shared/serviceSettings.js')(db);
const { PERMISSIONS, hasPermissions } = require('../shared/permissions');

Expand Down
4 changes: 2 additions & 2 deletions nodeScripts/flagApplicationIssues.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
'use strict';

const config = require('./shared/config');
const { app, db } = require('./shared/admin.js');
const { flagApplicationIssuesForExercise } = require('../functions/actions/applications/flagApplicationIssues')(config, db);
const { firebase, app, db } = require('./shared/admin.js');
const { flagApplicationIssuesForExercise } = require('../functions/actions/applications/flagApplicationIssues')(firebase, config, db);

const main = async () => {
return flagApplicationIssuesForExercise('wdpALbyICL7ZxxN5AQt8');
Expand Down
4 changes: 2 additions & 2 deletions test/actions/applications/flagApplicationIssues.spec.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@

const firebase = require('firebase-admin');
const mockDb = jest.fn();
const mockSlack = jest.fn();

Expand Down Expand Up @@ -45,7 +45,7 @@ const mockApplication = (id) => {
};
};

const flagApplicationIssues = require('../../../functions/actions/applications/flagApplicationIssues.js')(mockDb, mockSlack);
const flagApplicationIssues = require('../../../functions/actions/applications/flagApplicationIssues.js')(firebase, mockDb, mockSlack);

xdescribe('getEligibilityIssues()', () => {

Expand Down
Loading