Skip to content

Commit

Permalink
hcm config updates (#32)
Browse files Browse the repository at this point in the history
* Dedupe employees

* hcmValidateReportingStructure

* Job name length validation

* Validations

* Clean up validations
  • Loading branch information
gaelyn authored Jun 3, 2024
1 parent a38d132 commit 999425f
Show file tree
Hide file tree
Showing 9 changed files with 364 additions and 21 deletions.
4 changes: 4 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ import { HcmShowApiService } from '@/shared/hcm-show-api-service';
import { hcmEmbeddedSpaceConfigure } from '@/workflows/hcm/actions/hcmEmbeddedSpaceConfigure';
import { hcmFileFeedSpaceConfigure } from '@/workflows/hcm/actions/hcmFileFeedSpaceConfigure';
import { BENEFITS_SHEET_NAME } from '@/workflows/hcm/blueprints/benefits';
import { hcmDedupeEmployees } from '@/shared/eventHandlers/hcmDedupeEmployees';
import { hcmValidateReportingStructure } from '@/shared/eventHandlers/hcmValidateReportingStructure';

function configureSharedUses({
listener,
Expand Down Expand Up @@ -141,6 +143,8 @@ export default function (listener: FlatfileListener) {
listener.namespace('space:hcmproject', (listener) => {
listener.use(hcmProjectSpaceConfigure);
listener.use(hcmPrefillData());
listener.use(hcmDedupeEmployees());
listener.use(hcmValidateReportingStructure());
configureSharedUses({ listener, apiService: HcmShowApiService });
});

Expand Down
63 changes: 63 additions & 0 deletions src/shared/eventHandlers/hcmDedupeEmployees.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { FlatfileEvent, FlatfileListener } from '@flatfile/listener';
import api from '@flatfile/api';
import { dedupeEmployees } from '@/workflows/hcm/actions/dedupeEmployees';

export const hcmDedupeEmployees =
() =>
(listener: FlatfileListener): void => {
// Listen for the 'dedupe employees' action
listener.filter({ job: 'sheet:dedupeEmployees' }, (configure) => {
configure.on(
'job:ready',
async ({ context: { jobId, sheetId } }: FlatfileEvent) => {
console.log(JSON.stringify(sheetId, null, 2));
try {
await api.jobs.ack(jobId, {
info: 'Checking for duplicates.',
progress: 10,
});

let count = 0;
try {
console.log('Sheet ID: ' + sheetId);

// Call the 'get' method of api.records with the sheetId
const response = await api.records.get(sheetId);

// Check if the response is valid and contains records
if (response?.data?.records) {
const records = response.data.records;

// Call the dedupeEmployees function with the records
const removeThese = dedupeEmployees(records);
console.log('Records to Remove: ' + removeThese);
count = removeThese.length;

// Check if there are any records to remove
if (removeThese.length > 0) {
// Delete the records identified for removal from the API
await api.records.delete(sheetId, { ids: removeThese });
} else {
console.log('No records found for removal.');
}
} else {
console.log('No records found in the response.');
}
} catch (error) {
console.log('Error occurred:', error);
}

await api.jobs.complete(jobId, {
info: `${count} employees deduplicated.`,
});
} catch (error) {
console.error('Error:', error.stack);

await api.jobs.fail(jobId, {
info: 'Unable to deduplicate employees.',
});
}
}
);
});
};
64 changes: 64 additions & 0 deletions src/shared/eventHandlers/hcmValidateReportingStructure.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { FlatfileEvent, FlatfileListener } from '@flatfile/listener';
import api from '@flatfile/api';
import { validateReportingStructure } from '@/workflows/hcm/actions/validateReportingStructure';

export const hcmValidateReportingStructure =
() =>
(listener: FlatfileListener): void => {
listener.filter(
{ job: 'sheet:validateReportingStructure' },
(configure) => {
configure.on(
'job:ready',
async ({ context: { jobId, sheetId } }: FlatfileEvent) => {
try {
await api.jobs.ack(jobId, {
info: 'Validating reporting structure.',
progress: 10,
});

let count = 0;
try {
console.log('Sheet ID: ' + sheetId);

// Call the 'get' method of api.records with the sheetId
const response = await api.records.get(sheetId);

// Check if the response is valid and contains records
if (response?.data?.records) {
const records = response.data.records;

// Call the validateReportingStructure function with the records
const reportingErrors = validateReportingStructure(records);
count = reportingErrors.length;

// Update the records if there are any reporting errors
if (reportingErrors.length > 0) {
await api.records.update(sheetId, reportingErrors);
console.log('Records updated successfully.');
// For example, you can send them as a notification or store them in a database
} else {
console.log('No records found for updating.');
}
} else {
console.log('No records found in the response.');
}
} catch (error) {
console.log('Error occurred:' + error);
}

await api.jobs.complete(jobId, {
info: `${count} records found and flagged.`,
});
} catch (error) {
console.error('Error:', error.stack);

await api.jobs.fail(jobId, {
info: 'Unable to validate reporting structure.',
});
}
}
);
}
);
};
32 changes: 32 additions & 0 deletions src/workflows/hcm/actions/dedupeEmployees.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
export const dedupeEmployees = (records) => {
const uniques = {}; // Object to store unique records based on employeeId
const removeThese = []; // Array to store record IDs to be removed

for (const record of records) {
const employeeId = record.values.employeeId.value; // Get the employeeId value from the record

if (typeof employeeId === 'string') {
// Check if the employeeId is of type string
if (!(employeeId in uniques)) {
// If the employeeId is not found in the uniques object, add the record as a unique record
uniques[employeeId] = record;
} else {
// If the employeeId is already in the uniques object, compare hire dates to determine which record to keep
const latestRecord = uniques[employeeId];

if (record.values.hireDate.value > latestRecord.values.hireDate.value) {
// If the current record has a later hire date, remove the latestRecord and add the current record
removeThese.push(latestRecord.id);
uniques[employeeId] = record;
} else {
// If the current record has an earlier or equal hire date, add the current record to the removal list
removeThese.push(record.id);
}
}
} else {
console.log('Invalid employeeId:', employeeId); // Log an error message for invalid employeeId values
}
}

return removeThese; // Return the list of record IDs to be removed
};
148 changes: 148 additions & 0 deletions src/workflows/hcm/actions/validateReportingStructure.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
export const validateReportingStructure = (records) => {
const reportingErrors = [];
const employees = {};
const visited = new Set();
const managerIds = new Set();
const employeesWithManagerId = new Set();
const updatedRecords = []; // Array to store only the modified parts of records

for (const record of records) {
if (
record.values.employeeId.messages &&
record.values.employeeId.messages.length > 0
) {
updatedRecords.push({
id: record.id,
values: {
employeeId: {
...record.values.employeeId,
messages: [],
},
},
});
}
}

const detectCircularDependency = (employeeId: string, path: string[]) => {
if (visited.has(employeeId)) {
const record = employees[employeeId];
const error = {
message: `Circular dependency detected: ${path.join(
' -> '
)} -> ${employeeId}`,
source: 'custom-logic',
type: 'error',
};

const updatedRecord = updatedRecords.find((r) => r.id === record.id);
if (updatedRecord) {
updatedRecord.values.employeeId.messages.push(error);
} else {
updatedRecords.push({
id: record.id,
values: {
employeeId: {
...record.values.employeeId,
messages: [error],
},
},
});
}
reportingErrors.push(record);
} else {
visited.add(employeeId);
const managerId = employees[employeeId]?.values.managerId.value;

if (managerId && managerId !== employeeId) {
detectCircularDependency(managerId, [...path, employeeId]);
}

visited.delete(employeeId);
}
};

for (const record of records) {
const employeeId = record.values.employeeId.value;
const managerId = record.values.managerId.value;

employees[employeeId] = record;

if (managerId && managerId !== '') {
managerIds.add(managerId);

if (employeeId === managerId) {
employeesWithManagerId.add(employeeId);
}
}
}

if (employeesWithManagerId.size > 1) {
for (const employeeId of employeesWithManagerId) {
const record = employees[employeeId as string];
const error = {
message: `Multiple records have the scenario where employeeId = managerId. Please ensure that only one record has the employeeId = managerId scenario.`,
source: 'custom-logic',
type: 'error',
};

const updatedRecord = updatedRecords.find((r) => r.id === record.id);
if (updatedRecord) {
updatedRecord.values.employeeId.messages.push(error);
} else {
updatedRecords.push({
id: record.id,
values: {
employeeId: {
...record.values.employeeId,
messages: [error],
},
},
});
}
reportingErrors.push(record);
}
}

for (const record of records) {
const managerId = record.values.managerId.value;
if (managerId && managerId !== '' && !employees[managerId]) {
const error = {
message: `Manager with ID: ${managerId} does not exist as an employee`,
source: 'custom-logic',
type: 'error',
};

const updatedRecord = updatedRecords.find((r) => r.id === record.id);
if (updatedRecord) {
if (!updatedRecord.values.managerId) {
updatedRecord.values.managerId = {
...record.values.managerId,
messages: [],
};
}
updatedRecord.values.managerId.messages.push(error);
} else {
updatedRecords.push({
id: record.id,
values: {
managerId: {
...record.values.managerId,
messages: [error],
},
},
});
}
reportingErrors.push(record);
}
}

for (const employeeId in employees) {
if (!visited.has(employeeId)) {
detectCircularDependency(employeeId, []);
}
}

console.log('Reporting Errors: ' + JSON.stringify(reportingErrors));

return updatedRecords; // Return only the modified records
};
5 changes: 2 additions & 3 deletions src/workflows/hcm/blueprints/employees.ts
Original file line number Diff line number Diff line change
Expand Up @@ -152,9 +152,8 @@ export const employees: SheetConfig = {
label: 'Job Name',
description: 'The Job Profile for the Employee.',
constraints: [
{
type: 'required',
},
{ type: 'required' },
{ type: 'external', validator: 'length', config: { min: 1, max: 50 } },
],
readonly: false,
config: {
Expand Down
17 changes: 10 additions & 7 deletions src/workflows/hcm/validations/employeeValidations.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { HcmShowApiService } from '@/shared/hcm-show-api-service';
import { isNil, isNotNil } from '@/shared/validations/validations';
import { jobValidations } from '@/workflows/hcm/validations/jobValidations';
import { addJobCode } from '@/workflows/hcm/validations/jobValidations';
import { FlatfileEvent } from '@flatfile/listener';
import { FlatfileRecord } from '@flatfile/plugin-record-hook';

Expand All @@ -21,16 +21,14 @@ export async function employeeValidations(
return;
}

records.forEach((record: FlatfileRecord) => {
for (const record of records) {
checkApiForDuplicateEmployeeId(record, employees);
concatinateNames(record);
splitFullName(record);
employeeHours(record);
validateJobDates(record);
jobValidations(record);

return record;
});
addJobCode(record);
}
}

function checkApiForDuplicateEmployeeId(record: FlatfileRecord, employees) {
Expand Down Expand Up @@ -174,7 +172,12 @@ function employeeHours(record: FlatfileRecord) {
}

// Add a warning if schedHours exceeds defHours but is within the allowed range
if (schedHours > defHours && schedHours <= 168) {
if (
typeof schedHours === 'number' &&
typeof defHours === 'number' &&
schedHours > defHours &&
schedHours <= 168
) {
record.addWarning(
'scheduledWeeklyHours',
'Scheduled Hours exceeds Default Hours'
Expand Down
4 changes: 1 addition & 3 deletions src/workflows/hcm/validations/hcmValidations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,7 @@ export async function hcmValidations(listener: FlatfileListener) {

listener.use(
bulkRecordHook('jobs-sheet', async (records) => {
records.forEach((record) => {
jobValidations(record);
});
await jobValidations(records);
})
);
}
Loading

0 comments on commit 999425f

Please sign in to comment.