Skip to content

Commit

Permalink
TASK-433 correct worker hours info calculation and TASK-439 wrong hea…
Browse files Browse the repository at this point in the history
…ders in export (#345)

Co-authored-by: Tomasz Pęcak <[email protected]>
  • Loading branch information
2 people authored and prenc committed Apr 11, 2021
1 parent 4d1a5d3 commit 14d0923
Show file tree
Hide file tree
Showing 22 changed files with 1,549 additions and 471 deletions.

Large diffs are not rendered by default.

Binary file not shown.
Binary file not shown.
53 changes: 53 additions & 0 deletions cypress/fixtures/worker-data/worker-data-preprocessor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
import { ShiftCode } from "../../../src/common-models/shift-info.model";
import { ContractType } from "../../../src/common-models/worker-info.model";
import { MonthDataArray } from "../../../src/helpers/shifts.helper";
import { monthWorkerData } from "./march-2021-raw-worker-data";

const MARCH_DAY_COUNT = 31;
const MONTH = 2; // march
const YEAR = 2021;

const createWorkerInfoObject = (workerName: string): WorkerTestDataInstance => {
const cropToMonth = <T>(data: T[]): MonthDataArray<T> =>
data.slice(0, MARCH_DAY_COUNT) as MonthDataArray<T>;
const actualWorkerShifts = cropToMonth<ShiftCode>(
monthWorkerData.actualScheduleShifts[workerName] as ShiftCode[]
);
const primaryWorkerShifts = cropToMonth<ShiftCode>(
monthWorkerData.primaryScheduleShifts[workerName] as ShiftCode[]
);

return {
workerName,
workerNorm: monthWorkerData.time[workerName],
workerContract: ContractType.EMPLOYMENT_CONTRACT,
workerReqiuredHours: monthWorkerData.requiredWorkHours[workerName],
workerActualHours: monthWorkerData.actualWorkHours[workerName],
workerOvertime: monthWorkerData.overtime[workerName],
actualWorkerShifts,
primaryWorkerShifts,
month: MONTH,
year: YEAR,
dates: cropToMonth(monthWorkerData.dates),
};
};

export interface WorkerTestDataInstance {
workerName: string;
workerNorm: number;
workerContract: ContractType;
workerReqiuredHours: number;
workerActualHours: number;
workerOvertime: number;
actualWorkerShifts: ShiftCode[];
primaryWorkerShifts: MonthDataArray<ShiftCode>;
month: number;
year: number;
dates: number[];
}
export const workerTestData: WorkerTestDataInstance[] = Object.keys(monthWorkerData.time).map(
createWorkerInfoObject
);
8 changes: 4 additions & 4 deletions cypress/integration/e2e/table/update-workhours-info.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,19 +18,19 @@ const nurseInitialWorkHours: HoursInfo[] = [
];
const babysitterInitialWorkHours: HoursInfo[] = [
{
[HoursInfoCells.required]: 88,
[HoursInfoCells.required]: 80,
[HoursInfoCells.actual]: 116,
[HoursInfoCells.overtime]: 28,
[HoursInfoCells.overtime]: 36,
},
{
[HoursInfoCells.required]: 136,
[HoursInfoCells.actual]: 184,
[HoursInfoCells.overtime]: 48,
},
{
[HoursInfoCells.required]: 72,
[HoursInfoCells.required]: 160,
[HoursInfoCells.actual]: 96,
[HoursInfoCells.overtime]: 24,
[HoursInfoCells.overtime]: -64,
},
];

Expand Down
2 changes: 1 addition & 1 deletion cypress/integration/unit/helpers/shift.helper.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ describe("ShiftHelper", () => {
Object.keys(expectedHours).forEach((shiftCode) => {
it(`Should calculate correct duration ${shiftCode}`, () => {
const shiftModel = SHIFTS[shiftCode];
const hours = ShiftHelper.shiftCodeToWorkTime(shiftModel);
const hours = ShiftHelper.shiftToWorkTime(shiftModel);
expect(hours).to.equal(expectedHours[shiftCode]);
});
});
Expand Down
162 changes: 108 additions & 54 deletions cypress/integration/unit/helpers/worker-hours-info.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,60 +3,109 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
import * as _ from "lodash";
import {
FREE_SHIFTS,
NotWorkingShift,
NotWorkingShiftType,
Shift,
ShiftCode,
SHIFTS,
WORKING_SHIFTS,
} from "../../../../src/common-models/shift-info.model";
import { MonthDataArray, ShiftHelper } from "../../../../src/helpers/shifts.helper";
import { WorkerHourInfo } from "../../../../src/helpers/worker-hours-info.model";
import { workerTestData, WorkerTestDataInstance } from "../../../fixtures/march-2021-worker-data";
import { ContractType } from "../../../../src/common-models/worker-info.model";
import { MonthDataArray, ShiftHelper } from "../../../../src/helpers/shifts.helper";
import {
DEFAULT_NORM_SUBTRACTION,
WorkerHourInfo,
} from "../../../../src/helpers/worker-hours-info.model";
import {
workerTestData,
WorkerTestDataInstance,
} from "../../../fixtures/worker-data/worker-data-preprocessor";

describe("Worker hours info", () => {
workerTestData.forEach((workerInstance) => {
it(`Should calculate same worker norm as norm in example schedule when no base schedule exists for worker ${workerInstance.workerName}`, () => {
const workerHours = calculateWorkerHoursFromWorkerInstance(workerInstance);
expect(workerHours.workerHourNorm).to.equal(workerInstance.workerReqiuredHours);
expect(workerHours.workerTime).to.equal(workerInstance.workerActualHours);
});
});
context(
`Same data in calculation as in original schedule for ${workerInstance.workerName}`,
() => {
const calculation = calculateWorkerHoursFromWorkerInstance(workerInstance);
it("worker norm", () => {
expect(calculation.workerHourNorm).to.equal(workerInstance.workerReqiuredHours);
});

const exampleWorker = workerTestData[0];
it("worker actual hours", () => {
expect(calculation.workerTime).to.equal(workerInstance.workerActualHours);
});

const exampleWorkringShift = SHIFTS[WORKING_SHIFTS[0]];
const exampleFreeShift = SHIFTS[FREE_SHIFTS[0]];
it(`Should subtract from worker required hours duration of ${exampleWorkringShift.code} when it
is replaced with ${exampleFreeShift.code}`, () => {
const testedShiftDuration = ShiftHelper.shiftCodeToWorkTime(exampleWorkringShift);
const {
primaryScheduleWorkerHoursInfoRequiredTime,
actualScheduleWorkerHoursInfoRequiredTime,
} = calculateRequiredTimeForPrimaryScheduleWithShiftAndForActualScheduleAfterShiftReplacement(
exampleWorker,
exampleWorkringShift,
exampleFreeShift
it("overtime", () => {
expect(calculation.overTime).to.equal(workerInstance.workerOvertime);
});
}
);
const expectedWorkHourNorm =
primaryScheduleWorkerHoursInfoRequiredTime.workerHourNorm - testedShiftDuration;
expect(actualScheduleWorkerHoursInfoRequiredTime.workerHourNorm).to.equal(expectedWorkHourNorm);
});

it("Worker norm should not be recalculated if working shift was replaced with W", () => {
const exampleShift = SHIFTS[ShiftCode.D];
const freeShift = SHIFTS[ShiftCode.W];
const {
primaryScheduleWorkerHoursInfoRequiredTime,
actualScheduleWorkerHoursInfoRequiredTime,
} = calculateRequiredTimeForPrimaryScheduleWithShiftAndForActualScheduleAfterShiftReplacement(
exampleWorker,
exampleShift,
freeShift
);
const expectedWorkHourNorm = primaryScheduleWorkerHoursInfoRequiredTime.workerHourNorm;
expect(actualScheduleWorkerHoursInfoRequiredTime.workerHourNorm).to.equal(expectedWorkHourNorm);
expect(actualScheduleWorkerHoursInfoRequiredTime.workerHourNorm).to.equal(expectedWorkHourNorm);
const exampleWorker = workerTestData[0];
const exampleWorkringShift = SHIFTS[ShiftCode.D];

context(`After replacing working shift ${exampleWorkringShift.code} with`, () => {
const annualLeaveFreeShift = findFreeShift(NotWorkingShiftType.AnnualLeave);
it(`annual leave free shift ${annualLeaveFreeShift.code} should subtract from required hours max(duration of working shift, shift subtraction)`, () => {
const subtraction = Math.max(
ShiftHelper.shiftToWorkTime(exampleWorkringShift),
annualLeaveFreeShift.normSubtraction ?? DEFAULT_NORM_SUBTRACTION
);
const {
primaryScheduleWorkerHoursInfo,
actualScheduleWorkerHoursInfo,
} = calculateRequiredTimeBeforeAndAfterShiftReplacement(
exampleWorker,
exampleWorkringShift,
annualLeaveFreeShift
);
const expectedWorkHourNorm = primaryScheduleWorkerHoursInfo.workerHourNorm - subtraction;
expect(actualScheduleWorkerHoursInfo.workerHourNorm).to.equal(expectedWorkHourNorm);
});

const medicalLeaveFreeShift = findFreeShift(NotWorkingShiftType.MedicalLeave);
it(`medical leave free shift: ${medicalLeaveFreeShift.code} should not subtract from required hours if there was no working shift in primary schedule`, () => {
const {
primaryScheduleWorkerHoursInfo,
actualScheduleWorkerHoursInfo,
} = calculateRequiredTimeBeforeAndAfterShiftReplacement(
exampleWorker,
SHIFTS[ShiftCode.W],
medicalLeaveFreeShift
);
const expectedWorkHourNorm = primaryScheduleWorkerHoursInfo.workerHourNorm;
expect(actualScheduleWorkerHoursInfo.workerHourNorm).to.equal(expectedWorkHourNorm);
});

it(`medical leave free shift ${medicalLeaveFreeShift.code} should subtract from required hours max(duration of working shift, shift subtraction, 8) if there was working shift in primary schedule`, () => {
const subtraction = Math.max(
ShiftHelper.shiftToWorkTime(exampleWorkringShift),
medicalLeaveFreeShift.normSubtraction ?? DEFAULT_NORM_SUBTRACTION
);
const {
primaryScheduleWorkerHoursInfo,
actualScheduleWorkerHoursInfo,
} = calculateRequiredTimeBeforeAndAfterShiftReplacement(
exampleWorker,
exampleWorkringShift,
medicalLeaveFreeShift
);
const expectedWorkHourNorm = primaryScheduleWorkerHoursInfo.workerHourNorm - subtraction;
expect(actualScheduleWorkerHoursInfo.workerHourNorm).to.equal(expectedWorkHourNorm);
});

it("wolne, should not subtract from required hours", () => {
const {
primaryScheduleWorkerHoursInfo,
actualScheduleWorkerHoursInfo,
} = calculateRequiredTimeBeforeAndAfterShiftReplacement(
exampleWorker,
exampleWorkringShift,
SHIFTS[ShiftCode.W]
);
const expectedWorkHourNorm = primaryScheduleWorkerHoursInfo.workerHourNorm;
expect(actualScheduleWorkerHoursInfo.workerHourNorm).to.equal(expectedWorkHourNorm);
});
});

it("Should not throw when test data contains more days than one month could normally contain", () => {
Expand Down Expand Up @@ -86,18 +135,18 @@ describe("Worker hours info", () => {

//#region helper function
interface RequiredTimeForPrimaryAndActualSchedule {
primaryScheduleWorkerHoursInfoRequiredTime: WorkerHourInfo;
actualScheduleWorkerHoursInfoRequiredTime: WorkerHourInfo;
primaryScheduleWorkerHoursInfo: WorkerHourInfo;
actualScheduleWorkerHoursInfo: WorkerHourInfo;
}

function calculateWorkerHoursFromWorkerInstance(
workerInstance: WorkerTestDataInstance,
baseWorkerShifts?: ShiftCode[],
primaryWorkerShifts?: ShiftCode[],
actualWorkerShifts?: ShiftCode[]
): WorkerHourInfo {
return WorkerHourInfo.fromWorkerInfo(
actualWorkerShifts ?? workerInstance.actualWorkerShifts,
baseWorkerShifts as MonthDataArray<ShiftCode>,
(primaryWorkerShifts as MonthDataArray<ShiftCode>) ?? workerInstance.primaryWorkerShifts,
workerInstance.workerNorm,
workerInstance.workerContract,
workerInstance.month,
Expand All @@ -106,31 +155,36 @@ function calculateWorkerHoursFromWorkerInstance(
SHIFTS
);
}
function calculateRequiredTimeForPrimaryScheduleWithShiftAndForActualScheduleAfterShiftReplacement(
function calculateRequiredTimeBeforeAndAfterShiftReplacement(
workerInstance: WorkerTestDataInstance,
primaryShift: Shift,
primaryShiftReplacement: Shift
): RequiredTimeForPrimaryAndActualSchedule {
const testedShiftIndex = 0;
const baseWorkerShifts = [...workerInstance.baseWorkerShifts];
baseWorkerShifts[testedShiftIndex] = primaryShift.code as ShiftCode;
const actualWorkerShifts = [...baseWorkerShifts];
const primaryWorkerShifts = [...workerInstance.primaryWorkerShifts];
primaryWorkerShifts[testedShiftIndex] = primaryShift.code as ShiftCode;
const actualWorkerShifts = [...primaryWorkerShifts];
const primaryScheduleWorkerHoursInfoRequiredTime = calculateWorkerHoursFromWorkerInstance(
workerInstance,
baseWorkerShifts,
primaryWorkerShifts,
actualWorkerShifts
);
// Act
actualWorkerShifts[testedShiftIndex] = primaryShiftReplacement.code as ShiftCode;
const actualScheduleWorkerHoursInfoRequiredTime = calculateWorkerHoursFromWorkerInstance(
workerInstance,
baseWorkerShifts,
primaryWorkerShifts,
actualWorkerShifts
);
return {
primaryScheduleWorkerHoursInfoRequiredTime,
actualScheduleWorkerHoursInfoRequiredTime,
primaryScheduleWorkerHoursInfo: primaryScheduleWorkerHoursInfoRequiredTime,
actualScheduleWorkerHoursInfo: actualScheduleWorkerHoursInfoRequiredTime,
};
}

function findFreeShift(shiftType: NotWorkingShiftType) {
return Object.values(SHIFTS).find(
(shift) => shift.isWorkingShift === false && shift.type === shiftType
) as NotWorkingShift;
}

//#endregion
Binary file removed docs/brak_dni.xlsx
Binary file not shown.
Binary file removed docs/brak_info.xlsx
Binary file not shown.
Binary file removed docs/brak_podziału.xlsx
Binary file not shown.
Binary file removed docs/brak_pracowników.xlsx
Binary file not shown.
Binary file removed docs/legit.xlsx
Binary file not shown.
Binary file added docs/marzec_2021_wersja_bazowa.xlsx
Binary file not shown.
Binary file not shown.
Binary file removed docs/niedozwolone_zmiany.xlsx
Binary file not shown.
Binary file removed docs/powinno_działać.xlsx
Binary file not shown.
6 changes: 3 additions & 3 deletions src/api/server.middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,8 +108,8 @@ export class ServerMiddleware {
primaryMonthData: PrimaryMonthRevisionDataModel,
scheduleErrors: ScheduleError[]
): ScheduleError[] {
const calculateNormHoursDiff = (workerName: string): number =>
WorkerHourInfo.fromSchedules(workerName, actualSchedule, primaryMonthData).workHoursDiff;
const calculateOvertime = (workerName: string): number =>
WorkerHourInfo.fromSchedules(workerName, actualSchedule, primaryMonthData).overTime;

const validBackendScheduleErros = scheduleErrors.filter(
(err) =>
Expand All @@ -118,7 +118,7 @@ export class ServerMiddleware {
);
const overtimeAndUndetimeErrors: (WorkerOvertime | WorkerUnderTime)[] = [];
Object.keys(actualSchedule.shifts).forEach((workerName) => {
const workHoursDiff = calculateNormHoursDiff(workerName);
const workHoursDiff = calculateOvertime(workerName);
if (workHoursDiff < 0) {
overtimeAndUndetimeErrors.push({
kind: AlgorithmErrorCode.WorkerUnderTime,
Expand Down
Loading

0 comments on commit 14d0923

Please sign in to comment.