Skip to content

Commit

Permalink
Merge main into branch
Browse files Browse the repository at this point in the history
  • Loading branch information
duncanc19 committed Dec 11, 2024
2 parents b8554aa + efad3af commit 1818d76
Show file tree
Hide file tree
Showing 204 changed files with 13,401 additions and 6,449 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
'use strict';

/** @type {import('sequelize-cli').Migration} */
module.exports = {
async up(queryInterface, Sequelize) {
return queryInterface.createTable(
'QualificationCertificates',
{
ID: {
type: Sequelize.INTEGER,
primaryKey: true,
autoIncrement: true,
},
UID: {
type: Sequelize.DataTypes.UUID,
defaultValue: Sequelize.literal('uuid_generate_v4()'),
allowNull: false,
unique: true,
},
WorkerQualificationsFK: {
type: Sequelize.DataTypes.INTEGER,
allowNull: false,
references: {
model: {
tableName: 'WorkerQualifications',
schema: 'cqc',
},
key: 'ID',
},
},
WorkerFK: {
type: Sequelize.DataTypes.INTEGER,
allowNull: false,
references: {
model: {
tableName: 'Worker',
schema: 'cqc',
},
key: 'ID',
},
},
FileName: {
type: Sequelize.DataTypes.TEXT,
allowNull: false,
},
UploadDate: {
type: Sequelize.DataTypes.DATE,
allowNull: true,
},
Key: {
type: Sequelize.DataTypes.TEXT,
allowNull: false,
},
},
{ schema: 'cqc' },
);
},

async down(queryInterface) {
/**
* Add reverting commands here.
*
* Example:
* await queryInterface.dropTable('users');
*/
return queryInterface.dropTable({
tableName: 'QualificationCertificates',
schema: 'cqc',
});
},
};
201 changes: 184 additions & 17 deletions backend/server/models/BulkImport/csv/crossValidate.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
const { chain } = require('lodash');
const models = require('../../../models');

const MAIN_JOB_ROLE_ERROR = () => 1280;
const { addCrossValidateError, MAIN_JOB_ERRORS, TRANSFER_STAFF_RECORD_ERRORS } = require('./crossValidateErrors');

const crossValidate = async (csvWorkerSchemaErrors, myEstablishments, JSONWorker) => {
if (workerNotChanged(JSONWorker)) {
Expand All @@ -14,24 +14,23 @@ const crossValidate = async (csvWorkerSchemaErrors, myEstablishments, JSONWorker

const _crossValidateMainJobRole = (csvWorkerSchemaErrors, isCqcRegulated, JSONWorker) => {
if (!isCqcRegulated && JSONWorker.mainJobRoleId === 4) {
csvWorkerSchemaErrors.unshift({
worker: JSONWorker.uniqueWorkerId,
name: JSONWorker.localId,
lineNumber: JSONWorker.lineNumber,
errCode: MAIN_JOB_ROLE_ERROR(),
errType: 'MAIN_JOB_ROLE_ERROR',
source: JSONWorker.mainJobRoleId,
column: 'MAINJOBROLE',
error:
'Workers MAINJOBROLE is Registered Manager but you are not providing a CQC regulated service. Please change to another Job Role',
});
addCrossValidateError(
csvWorkerSchemaErrors,
MAIN_JOB_ERRORS.RegisteredManagerWithoutCqcRegulatedService,
JSONWorker,
);
}
};

const _isCQCRegulated = async (myEstablishments, JSONWorker) => {
const workerEstablishment = myEstablishments.find(
(establishment) => JSONWorker.establishmentKey === establishment.key,
);
let workerEstablishmentKey;
if (isMovingToNewWorkplace(JSONWorker)) {
workerEstablishmentKey = JSONWorker.transferStaffRecord.replace(/\s/g, '');
} else {
workerEstablishmentKey = JSONWorker.establishmentKey;
}

const workerEstablishment = myEstablishments.find((establishment) => workerEstablishmentKey === establishment.key);

if (workerEstablishment) {
switch (workerEstablishment.status) {
Expand All @@ -49,13 +48,181 @@ const _isCQCRegulated = async (myEstablishments, JSONWorker) => {

const _checkEstablishmentRegulatedInDatabase = async (establishmentId) => {
const establishment = await models.establishment.findbyId(establishmentId);
return establishment.isRegulated;
return establishment?.isRegulated;
};

const workerNotChanged = (JSONWorker) => !['NEW', 'UPDATE'].includes(JSONWorker.status);

const crossValidateTransferStaffRecord = async (
csvWorkerSchemaErrors,
myAPIEstablishments,
myEstablishments,
myJSONWorkers,
) => {
const relatedEstablishmentIds = myEstablishments.map((establishment) => establishment.id);

const allMovingWorkers = myJSONWorkers.filter(isMovingToNewWorkplace);
const allNewWorkers = myJSONWorkers.filter((worker) => worker.status === 'NEW');
const allOtherWorkers = myJSONWorkers.filter((worker) => !isMovingToNewWorkplace(worker) && worker.status !== 'NEW');

const newWorkerWithDuplicateIdErrorAdded = _crossValidateWorkersWithDuplicateRefsMovingToWorkplace(
csvWorkerSchemaErrors,
allMovingWorkers,
allNewWorkers,
TRANSFER_STAFF_RECORD_ERRORS.SameRefsMovingToWorkplace,
);

if (newWorkerWithDuplicateIdErrorAdded) return;

const existingWorkerWithDuplicateIdErrorAdded = _crossValidateWorkersWithDuplicateRefsMovingToWorkplace(
csvWorkerSchemaErrors,
allMovingWorkers,
allOtherWorkers,
TRANSFER_STAFF_RECORD_ERRORS.SameLocalIdExistInNewWorkplace,
);

if (existingWorkerWithDuplicateIdErrorAdded) return;

for (const JSONWorker of allMovingWorkers) {
const newWorkplaceId = await _validateTransferIsPossible(
csvWorkerSchemaErrors,
relatedEstablishmentIds,
JSONWorker,
);

if (newWorkplaceId) {
_addNewWorkplaceIdToWorkerEntity(myAPIEstablishments, JSONWorker, newWorkplaceId);
}
}
};

const isMovingToNewWorkplace = (JSONWorker) => {
return JSONWorker.status === 'UPDATE' && JSONWorker.transferStaffRecord;
};

const _validateTransferIsPossible = async (csvWorkerSchemaErrors, relatedEstablishmentIds, JSONWorker) => {
const newWorkplaceLocalRef = JSONWorker.transferStaffRecord;
const newWorkplaceId = await _getNewWorkplaceId(newWorkplaceLocalRef, relatedEstablishmentIds);

if (newWorkplaceId === null) {
addCrossValidateError(csvWorkerSchemaErrors, TRANSFER_STAFF_RECORD_ERRORS.NewWorkplaceNotFound, JSONWorker);
return;
}

const workerReferenceToLookup = JSONWorker.changeUniqueWorker
? JSONWorker.changeUniqueWorker
: JSONWorker.uniqueWorkerId;

// if worker with duplicated reference found in database but not in csv file,
// changes to unique worker ID are applied before deleting workers not in file,
// which would cause bulk upload to break
// the code below prevents this issue

const uniqueWorkerIdFoundInWorkplaceInDatabase = await models.worker.findOneWithConflictingLocalRef(
newWorkplaceId,
workerReferenceToLookup,
);

if (uniqueWorkerIdFoundInWorkplaceInDatabase) {
addCrossValidateError(
csvWorkerSchemaErrors,
TRANSFER_STAFF_RECORD_ERRORS.SameLocalIdExistInNewWorkplace,
JSONWorker,
);
return;
}

if (_workerPassedAllValidations(csvWorkerSchemaErrors, JSONWorker)) {
return newWorkplaceId;
}
};

const _getNewWorkplaceId = async (newWorkplaceLocalRef, relatedEstablishmentIds) => {
const newWorkplaceFound = await models.establishment.findOne({
where: {
LocalIdentifierValue: newWorkplaceLocalRef,
id: relatedEstablishmentIds,
},
});
if (newWorkplaceFound) {
return newWorkplaceFound.id;
}

return null;
};

const _workerPassedAllValidations = (csvWorkerSchemaErrors, JSONWorker) => {
const errorForThisWorker = csvWorkerSchemaErrors.find(
(error) => error?.lineNumber === JSONWorker.lineNumber && error?.errType === 'TRANSFERSTAFFRECORD_ERROR',
);

return !errorForThisWorker;
};

const _addNewWorkplaceIdToWorkerEntity = (myAPIEstablishments, JSONWorker, newWorkplaceId) => {
const oldWorkplaceKey = JSONWorker.localId.replace(/\s/g, '');
const workerEntityKey = JSONWorker.uniqueWorkerId.replace(/\s/g, '');

const workerEntity = myAPIEstablishments[oldWorkplaceKey].theWorker(workerEntityKey);

if (workerEntity) {
workerEntity._newWorkplaceId = newWorkplaceId;
}
};

const _crossValidateWorkersWithDuplicateRefsMovingToWorkplace = (
csvWorkerSchemaErrors,
allMovingWorkers,
otherWorkers,
errorType,
) => {
const workplacesDict = _buildWorkplaceDictWithOtherWorkers(otherWorkers);

let errorAdded = false;

for (const JSONWorker of allMovingWorkers) {
const newWorkplaceRef = JSONWorker.transferStaffRecord;

const workerRef = JSONWorker.changeUniqueWorker
? JSONWorker.changeUniqueWorker.replace(/\s/g, '')
: JSONWorker.uniqueWorkerId.replace(/\s/g, '');

if (!workplacesDict[newWorkplaceRef]) {
workplacesDict[newWorkplaceRef] = new Set([workerRef]);
continue;
}

if (!workplacesDict[newWorkplaceRef].has(workerRef)) {
workplacesDict[newWorkplaceRef].add(workerRef);
continue;
}
// worker's ID exists in workplace in file
addCrossValidateError(csvWorkerSchemaErrors, errorType, JSONWorker);

errorAdded = true;
}

return errorAdded;
};

const _buildWorkplaceDictWithOtherWorkers = (otherWorkers) => {
return chain(otherWorkers)
.groupBy('localId') // workplace ref
.mapValues((JSONWorkers) =>
JSONWorkers.map((JSONWorker) => {
if (JSONWorker.changeUniqueWorker) {
return [JSONWorker.changeUniqueWorker.replace(/\s/g, ''), JSONWorker.uniqueWorkerId.replace(/\s/g, '')];
}
return [JSONWorker.uniqueWorkerId.replace(/\s/g, '')];
}),
)
.mapValues((workerRefs) => new Set(workerRefs.flat()))
.value();
};

module.exports = {
crossValidate,
_crossValidateMainJobRole,
crossValidateTransferStaffRecord,
_isCQCRegulated,
};
57 changes: 57 additions & 0 deletions backend/server/models/BulkImport/csv/crossValidateErrors.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
const MAIN_JOB_ROLE_ERROR_CODE = 1280;
const TRANSFER_STAFF_RECORD_BASE_ERROR_CODE = 1400;

const MAIN_JOB_ERRORS = {
RegisteredManagerWithoutCqcRegulatedService: Object.freeze({
errCode: MAIN_JOB_ROLE_ERROR_CODE,
errType: 'MAIN_JOB_ROLE_ERROR',
column: 'MAINJOBROLE',
_sourceFieldName: 'mainJobRoleId',
error:
'Workers MAINJOBROLE is Registered Manager but you are not providing a CQC regulated service. Please change to another Job Role',
}),
};

const TRANSFER_STAFF_RECORD_ERRORS = {
NewWorkplaceNotFound: Object.freeze({
errCode: TRANSFER_STAFF_RECORD_BASE_ERROR_CODE + 1,
errType: 'TRANSFERSTAFFRECORD_ERROR',
column: 'TRANSFERSTAFFRECORD',
_sourceFieldName: 'transferStaffRecord',
error: 'The LOCALESTID in TRANSFERSTAFFRECORD does not exist',
}),
SameLocalIdExistInNewWorkplace: Object.freeze({
errCode: TRANSFER_STAFF_RECORD_BASE_ERROR_CODE + 2,
errType: 'TRANSFERSTAFFRECORD_ERROR',
column: 'UNIQUEWORKERID',
_sourceFieldName: 'uniqueWorkerId',
error:
"The UNIQUEWORKERID already exists in the LOCALESTID given in TRANSFERSTAFFRECORD. Use CHGUNIQUEWRKID to change this worker's UNIQUEWORKERID",
}),
SameRefsMovingToWorkplace: Object.freeze({
errCode: TRANSFER_STAFF_RECORD_BASE_ERROR_CODE + 3,
errType: 'TRANSFERSTAFFRECORD_ERROR',
column: 'UNIQUEWORKERID',
_sourceFieldName: 'uniqueWorkerId',
error: 'Duplicate UNIQUEWORKERID’s are being moved to the same LOCALESTID in TRANSFERSTAFFRECORD',
}),
};

const addCrossValidateError = (errorsArray, errorType, JSONWorker) => {
const newErrorObject = {
...errorType,
worker: JSONWorker.uniqueWorkerId,
name: JSONWorker.localId,
lineNumber: JSONWorker.lineNumber,
source: JSONWorker[errorType._sourceFieldName],
};
delete newErrorObject._sourceFieldName;

errorsArray.unshift(newErrorObject);
};

module.exports = {
addCrossValidateError,
MAIN_JOB_ERRORS,
TRANSFER_STAFF_RECORD_ERRORS,
};
Original file line number Diff line number Diff line change
Expand Up @@ -2780,7 +2780,7 @@ class WorkplaceCSVValidator {

let registeredManagers = 0;

const dataInCSV = ['NEW', 'UPDATE', 'CHGSUB']; //For theses statuses trust the data in the CSV
const dataInCSV = ['NEW', 'UPDATE']; //For theses statuses trust the data in the CSV

myJSONWorkers.forEach((worker) => {
if (this.key === worker.establishmentKey && dataInCSV.includes(worker.status)) {
Expand Down
Loading

0 comments on commit 1818d76

Please sign in to comment.