diff --git a/cypress/fixtures/march-2021-worker-data.ts b/cypress/fixtures/worker-data/march-2021-raw-worker-data.ts similarity index 50% rename from cypress/fixtures/march-2021-worker-data.ts rename to cypress/fixtures/worker-data/march-2021-raw-worker-data.ts index 56bdc2bcd..db6e53677 100644 --- a/cypress/fixtures/march-2021-worker-data.ts +++ b/cypress/fixtures/worker-data/march-2021-raw-worker-data.ts @@ -1,27 +1,43 @@ /* 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 { MonthDataArray } from "../../src/helpers/shifts.helper"; -import { ContractType } from "../../src/common-models/worker-info.model"; -const monthWorkerData = { - expectedRequiredWorkHours: { +/** + Data completed based on schedules + marzec_2021_wersja_bazowa + and marzec_koniec_miesiąca_2021 + Data is anonymized +*/ + +type WorkerHoursDict = { [workerName: string]: number }; +type WorkerShiftsDicts = { [workerName: string]: string[] }; +interface MonthWorkerData { + requiredWorkHours: WorkerHoursDict; + actualWorkHours: WorkerHoursDict; + overtime: WorkerHoursDict; + time: WorkerHoursDict; + actualScheduleShifts: WorkerShiftsDicts; + primaryScheduleShifts: WorkerShiftsDicts; + dates: number[]; +} + +export const monthWorkerData: MonthWorkerData = { + requiredWorkHours: { "Pracownik 1": 104, - "Pracownik 2": 184, + "Pracownik 2": 176, "Pracownik 3": 92, "Pracownik 4": 184, - "Pracownik 5": 184, - "Pracownik 6": 184, + "Pracownik 5": 136, // 118 in actual schedule, but it looks like a mistake + "Pracownik 6": 24, "Pracownik 7": 138, "Pracownik 8": 184, "Pracownik 9": 112, "Pracownik 10": 184, "Pracownik 11": 184, "Pracownik 12": 184, - "Pracownik 13": 184, + "Pracownik 13": 48, "Pracownik 14": 92, - "Pracownik 15": 92, + "Pracownik 15": 56, "Pracownik 16": 184, "Pracownik 17": 184, "Pracownik 18": 184, @@ -30,33 +46,59 @@ const monthWorkerData = { "Pracownik 21": 112, "Pracownik 22": 184, "Pracownik 23": 104, - "Pracownik 24": 184, + "Pracownik 24": 168, }, - expectedActualHours: { - "Pracownik 1": 120, + actualWorkHours: { + "Pracownik 1": 152, "Pracownik 2": 192, - "Pracownik 3": 12, - "Pracownik 4": 198, - "Pracownik 5": 144, - "Pracownik 6": 192, - "Pracownik 7": 156, - "Pracownik 8": 204, - "Pracownik 9": 132, - "Pracownik 10": 190, - "Pracownik 11": 198, - "Pracownik 12": 208, - "Pracownik 13": 184, + "Pracownik 3": 108, + "Pracownik 4": 216, + "Pracownik 5": 118, + "Pracownik 6": 24, + "Pracownik 7": 176, + "Pracownik 8": 228, + "Pracownik 9": 120, + "Pracownik 10": 198, + "Pracownik 11": 218, + "Pracownik 12": 216, + "Pracownik 13": 48, "Pracownik 14": 96, - "Pracownik 15": 96, - "Pracownik 16": 184, - "Pracownik 17": 184, - "Pracownik 18": 184, - "Pracownik 19": 184, - "Pracownik 20": 184, - "Pracownik 21": 116, - "Pracownik 22": 188, - "Pracownik 23": 84, - "Pracownik 24": 176, + "Pracownik 15": 60, + "Pracownik 16": 204, + "Pracownik 17": 232, + "Pracownik 18": 236, + "Pracownik 19": 260, + "Pracownik 20": 240, + "Pracownik 21": 120, + "Pracownik 22": 256, + "Pracownik 23": 108, + "Pracownik 24": 160, + }, + overtime: { + "Pracownik 1": 48, + "Pracownik 2": 16, + "Pracownik 3": 16, + "Pracownik 4": 32, + "Pracownik 5": -18, // 0 in actual schedule, but it looks like a mistake + "Pracownik 6": 0, + "Pracownik 7": 38, + "Pracownik 8": 44, + "Pracownik 9": 8, + "Pracownik 10": 14, + "Pracownik 11": 34, + "Pracownik 12": 32, + "Pracownik 13": 0, + "Pracownik 14": 4, + "Pracownik 15": 4, + "Pracownik 16": 20, + "Pracownik 17": 48, + "Pracownik 18": 52, + "Pracownik 19": 76, + "Pracownik 20": 56, + "Pracownik 21": 8, + "Pracownik 22": 72, + "Pracownik 23": 4, + "Pracownik 24": -8, }, time: { "Pracownik 1": 1, @@ -84,90 +126,89 @@ const monthWorkerData = { "Pracownik 23": 1, "Pracownik 24": 1, }, - shifts: { - "Pracownik 13": [ - "W", - "W", - "D", - "N", - "W", - "W", + actualScheduleShifts: { + "Pracownik 1": [ "D", - "N", - "W", "W", - "D", + "U", + "U", + "U", + "U", + "U", + "U", + "U", + "U", + "U", + "U", + "U", + "U", + "U", + "U", + "R", + "DN", "N", "W", "W", - "D", + "DN", "N", "W", - "W", "D", - "N", - "W", - "W", "D", "N", "W", "W", - "D", - "PN", - "W", + "DN", "W", - "D", "W", "W", "W", "W", ], - "Pracownik 14": [ - "W", - "W", - "W", - "D", + "Pracownik 2": [ + "L4", + "DN", "W", "W", - "D", + "RP", + "N", "W", "W", "W", + "DN", "W", "W", "W", + "DN", "W", "W", + "D", "N", "N", "W", - "D", - "W", - "W", - "W", "W", + "U", "N", "W", "W", - "D", + "DN", "W", "W", "W", "D", + "N", "W", "W", "W", "W", ], - "Pracownik 15": [ + "Pracownik 3": [ "W", "W", - "D", "N", "W", "W", - "W", - "DN", + "D", + "N", "W", "W", "D", @@ -181,14 +222,15 @@ const monthWorkerData = { "D", "W", "W", + "D", "W", "W", "W", - "N", - "W", + "RP", "W", "W", "W", + "D", "W", "W", "W", @@ -196,55 +238,25 @@ const monthWorkerData = { "W", "W", ], - "Pracownik 16": [ - "W", - "W", - "D", - "N", - "W", + "Pracownik 4": [ "W", "D", "N", "W", "W", - "W", - "D", - "N", - "W", "D", "N", "W", "W", "D", "N", - "N", - "W", - "W", - "PN", "W", "W", "D", "N", "W", + "RP", "W", - "D", - "W", - "W", - "W", - "W", - ], - "Pracownik 17": [ - "W", - "W", - "W", - "DN", - "W", - "W", - "D", - "PN", - "W", - "W", - "D", "N", "W", "W", @@ -252,104 +264,132 @@ const monthWorkerData = { "N", "W", "W", - "W", "DN", - "W", - "W", - "D", "N", "W", "W", - "W", "DN", - "W", - "W", - "D", + "N", "W", "W", "W", "W", ], - "Pracownik 18": [ + "Pracownik 5": [ + "R", + "D1", "W", "W", - "D", + "R", "D", "W", "W", "D", + "D", "R", "W", - "W", "D", - "N", + "D", "W", + "L4", + "L4", + "L4", + "L4", "W", - "R", "D", + "D", + "L4", + "IZ", + "IZ", + "IZ", + "IZ", + "IZ", + "IZ", + "IZ", + "IZ", "W", "W", - "RP", - "N", - "W", "W", - "RP", - "D", "W", + ], + "Pracownik 6": [ "W", - "D", "N", "N", "W", - "RP", + "U", + "U", + "U", + "U", + "W", + "L4", + "L4", + "L4", + "L4", + "L4", + "L4", + "L4", + "L4", + "L4", + "L4", + "L4", + "L4", + "L4", + "L4", + "L4", + "L4", + "L4", + "L4", + "L4", + "L4", + "L4", + "L4", "W", "W", "W", "W", ], - "Pracownik 19": [ + "Pracownik 7": [ + "D", "N", "W", "W", - "D", - "N", "W", "W", - "R", "N", "W", "W", - "DN", "W", + "N", "W", "W", - "D", + "RP", "N", "W", "W", "D", "N", "W", + "W", "D", - "R", "N", "W", "W", "D", "N", "W", - "W", + "D", + "R", + "N", "W", "W", "W", "W", ], - "Pracownik 20": [ - "N", + "Pracownik 8": [ "W", "D", "W", - "N", "W", "W", "D", @@ -359,45 +399,44 @@ const monthWorkerData = { "D", "N", "W", - "W", "D", - "W", - "W", + "N", + "N", "W", "D", + "DN", "N", "W", - "R", "D", - "N", + "DN", "W", "W", - "R", - "N", "W", + "D", "W", "W", + "D", + "D", "W", "W", "W", - ], - "Pracownik 21": [ - "N", "W", "W", + ], + "Pracownik 9": [ "D", - "N", "W", + "OP", "W", "D", - "N", + "DN", + "W", "W", - "R", "D", "N", "W", + "OP", "W", - "D", "N", "W", "U", @@ -408,38 +447,42 @@ const monthWorkerData = { "U", "U", "U", - "U", - "U", - "U", - "U", - "U", "W", + "D", + "N", "W", "W", "W", - ], - "Pracownik 22": [ "N", "W", + "W", + "W", + "W", + "W", + ], + "Pracownik 10": [ "D", - "R", "N", "W", "W", "D", - "N", + "W", "W", "W", "D", + "P1", "N", "W", + "D", + "W", + "W", "W", "D", - "N", + "D", "W", "W", "D", - "N", + "RPN", "W", "W", "D", @@ -448,84 +491,628 @@ const monthWorkerData = { "W", "D", "N", + "N", "W", "W", "W", "W", + ], + "Pracownik 11": [ + "D", + "P1", "W", "W", - ], - "Pracownik 23": [ - "U", - "U", - "U", - "U", - "U", - "U", - "U", - "U", - "U", - "U", - "U", - "U", - "U", - "U", "D", + "PN", "W", "W", + "D", + "N", "W", "W", "D", + "N", "W", "W", "D", + "PN", + "W", + "W", "D", + "N", "W", "W", "D", "D", - "W", + "N", "W", "D", + "D", + "N", "W", "W", "W", "W", ], - "Pracownik 24": [ - "R", + "Pracownik 12": [ + "W", + "D", + "N", + "W", + "D", + "N", + "W", + "W", + "D", + "N", + "W", + "W", + "D", + "W", + "N", + "W", + "P", + "PN", + "W", + "W", + "D", + "N", + "N", + "W", + "D", + "N", + "N", + "W", + "D", + "PN", + "W", + "W", + "W", + "W", + "W", + ], + "Pracownik 13": [ + "W", + "W", + "D", + "N", + "W", + "W", + "D", + "N", + "W", + "L4", + "L4", + "L4", + "L4", + "L4", + "L4", + "L4", + "L4", + "IZ", + "IZ", + "IZ", + "IZ", + "IZ", + "IZ", + "IZ", + "IZ", + "IZ", + "IZ", + "L4", + "L4", + "L4", + "L4", + "W", + "W", + "W", + "W", + ], + "Pracownik 14": [ + "W", + "W", + "W", + "D", + "W", + "W", + "RP", + "W", + "W", + "W", + "W", + "W", + "W", + "W", + "W", + "N", + "N", + "W", + "D", + "W", + "W", + "W", + "W", + "N", + "W", + "W", + "D", + "W", + "W", + "W", + "D", + "W", + "W", + "W", + "W", + ], + "Pracownik 15": [ + "W", + "W", + "RP", + "N", + "W", + "W", + "W", + "DN", + "W", + "W", + "D", + "W", + "W", + "W", + "IZ", + "IZ", + "IZ", + "IZ", + "IZ", + "IZ", + "IZ", + "IZ", + "IZ", + "IZ", + "IZ", + "IZ", + "W", + "W", + "W", + "W", + "W", + "W", + "W", + "W", + "W", + ], + "Pracownik 16": [ + "W", + "W", + "D", + "N", + "W", + "W", + "D", + "N", + "W", + "W", + "W", + "D", + "N", + "W", + "D", + "N", + "N", + "W", + "D", + "N", + "N", + "W", + "W", + "RPN", + "W", + "W", + "D", + "N", + "W", + "W", + "D", + "W", + "W", + "W", + "W", + ], + "Pracownik 17": [ + "W", + "W", + "W", + "DN", + "W", + "W", + "D", + "PN", + "W", + "W", + "D", + "N", + "W", + "W", + "D", + "DN", + "W", + "W", + "W", + "DN", + "W", + "W", + "D", + "N", + "N", + "W", + "D", + "DN", + "N", + "W", + "D", + "W", + "W", + "W", + "W", + ], + "Pracownik 18": [ + "W", + "W", + "D", + "D", + "W", + "W", + "D", + "R", + "W", + "W", + "D", + "N", + "W", + "W", + "RP", + "D", + "W", + "W", + "D", + "DN", + "W", + "W", + "D", + "DN", + "N", + "W", + "D", + "DN", + "N", + "W", + "D", + "W", + "W", + "W", + "W", + ], + "Pracownik 19": [ + "N", + "W", + "W", + "D", + "N", + "W", + "W", "R", + "N", + "W", + "W", + "DN", + "W", + "W", + "D", + "D", + "N", + "W", + "D", + "DN", + "N", + "W", + "D", + "RPN", + "N", + "W", + "W", + "DN", + "N", + "W", + "D", + "W", + "W", + "W", + "W", + ], + "Pracownik 20": [ + "N", + "W", + "D", + "W", + "N", + "W", + "W", + "D", + "N", + "W", + "D", + "D", + "N", + "W", + "D", + "DN", + "W", + "W", + "D", + "D", + "N", + "W", + "RP", + "D", + "N", + "W", + "D", + "RP", + "N", + "W", + "W", + "W", + "W", + "W", + "W", + ], + "Pracownik 21": [ + "N", + "W", + "W", + "D", + "N", + "W", + "W", + "RP", + "N", + "W", + "RP", + "D", + "N", + "W", + "W", + "D", + "N", + "W", + "U", + "U", + "U", + "U", + "U", + "U", + "U", + "U", + "U", + "U", + "U", + "U", + "U", + "W", + "W", + "W", + "W", + ], + "Pracownik 22": [ + "N", + "W", + "RP", "R", + "N", + "W", + "W", + "D", + "N", + "W", "R", + "DN", + "N", + "W", + "W", + "D", + "N", + "W", + "D", + "DN", + "N", + "W", + "D", + "D", + "N", + "W", + "W", + "DN", + "N", + "W", + "W", + "W", + "W", + "W", + "W", + ], + "Pracownik 23": [ + "U", + "U", + "U", + "U", + "U", + "U", + "U", + "U", + "U", + "U", + "U", + "U", + "U", + "U", + "D", + "N", + "W", + "W", + "W", + "D", + "N", + "W", + "D", + "D", + "W", + "W", + "D", + "RP", + "W", + "W", + "D", + "W", + "W", + "W", + "W", + ], + "Pracownik 24": [ + "R", + "R", + "R", + "R", + "W", + "W", + "W", + "R", + "U", + "U", + "R", + "R", + "W", + "W", + "R", + "R", + "R", + "R", + "R", + "W", + "W", + "R", + "R", + "R", + "R", + "R", + "W", + "W", + "R", + "R", + "R", + "W", + "W", + "W", + "W", + ], + }, + primaryScheduleShifts: { + "Pracownik 14": [ + "W", + "W", + "W", + "D", + "W", + "W", + "D", + "W", + "W", + "W", + "W", "W", "W", "W", - "R", - "R", - "R", - "R", - "R", "W", + "N", + "N", "W", - "R", - "R", - "R", - "R", - "R", + "D", "W", "W", - "R", - "R", - "R", - "R", - "R", "W", "W", + "N", + "W", + "W", + "D", + "W", + "W", + "W", + "D", + ], + "Pracownik 9": [ + "D", + "W", + "W", + "W", + "D", + "DN", + "W", + "W", + "D", + "N", + "W", + "W", + "W", + "N", + "W", + "U", + "U", + "U", + "U", + "U", + "U", + "U", + "U", + "W", + "D", + "N", + "W", + "W", + "W", + "N", + "W", + ], + "Pracownik 20": [ + "N", + "W", + "D", + "W", + "N", + "W", + "W", + "D", + "N", + "W", + "D", + "D", + "N", + "W", + "W", + "D", + "W", + "W", + "W", + "D", + "N", + "W", "R", - "R", - "R", + "D", + "N", "W", "W", + "R", + "N", "W", "W", ], @@ -554,15 +1141,44 @@ const monthWorkerData = { "D", "N", "W", + "RP", "D", + "N", + "W", + "W", + "DN", + "W", + ], + "Pracownik 19": [ + "N", + "W", + "W", "D", "N", "W", "W", + "R", + "N", + "W", + "W", "DN", "W", "W", "W", + "D", + "N", + "W", + "W", + "D", + "N", + "W", + "D", + "R", + "N", + "W", + "W", + "D", + "N", "W", "W", ], @@ -588,7 +1204,7 @@ const monthWorkerData = { "N", "W", "W", - "D", + "W", "N", "W", "W", @@ -598,49 +1214,91 @@ const monthWorkerData = { "W", "D", "N", + ], + "Pracownik 8": [ "W", + "D", "W", "W", "W", - ], - "Pracownik 3": [ + "D", + "N", + "W", + "D", + "D", + "N", "W", + "D", + "N", + "N", "W", + "D", + "D", + "N", "W", + "D", + "DN", "W", "W", "W", + "D", "W", "W", + "D", "W", "W", + ], + "Pracownik 10": [ + "D", + "N", "W", "W", "D", "W", "W", "W", + "D", + "P1", + "N", "W", + "D", "W", "W", "W", + "D", + "D", "W", "W", + "D", + "PN", "W", "W", + "D", + "N", "W", "W", + "D", + "N", + "N", + ], + "Pracownik 13": [ "W", "W", + "D", + "N", "W", "W", + "D", + "N", "W", "W", + "D", + "N", "W", "W", + "D", + "N", "W", - ], - "Pracownik 4": [ "W", "D", "N", @@ -651,17 +1309,82 @@ const monthWorkerData = { "W", "W", "D", + "PN", + "W", + "W", + "D", + ], + "Pracownik 3": [ + "W", + "W", "N", "W", "W", "D", "N", "W", - "P1", + "W", + "D", + "W", + "W", + "W", + "D", + "W", + "W", + "W", + "D", + "W", + "W", + "W", + "W", + "W", + "W", + "D", + "W", + "W", + "W", + "D", + "W", + "W", + ], + "Pracownik 15": [ + "W", + "W", + "D", + "N", + "W", + "W", + "W", + "DN", + "W", + "W", + "D", + "W", + "W", + "W", + "D", + "W", + "W", + "W", "D", + "W", + "W", + "W", + "W", + "W", "N", "W", "W", + "W", + "W", + "W", + "W", + ], + "Pracownik 11": [ + "D", + "P1", + "W", + "W", "D", "N", "W", @@ -674,45 +1397,54 @@ const monthWorkerData = { "N", "W", "W", + "D", + "N", "W", "W", - ], - "Pracownik 5": [ - "R", - "R", + "D", + "N", "W", "W", - "R", "D", + "N", "W", "W", - "R", - "R", + "D", + "D", + "N", + ], + "Pracownik 18": [ "W", "W", "D", "D", "W", "W", - "R", + "D", "R", "W", "W", "D", - "R", + "N", "W", "W", "R", - "R", + "D", "W", "W", - "R", - "R", + "D", + "N", "W", "W", + "D", + "D", "W", "W", + "D", + "N", + "N", "W", + "D", ], "Pracownik 6": [ "W", @@ -746,23 +1478,32 @@ const monthWorkerData = { "W", "N", "W", + ], + "Pracownik 16": [ "W", "W", + "D", + "N", "W", "W", - ], - "Pracownik 7": [ "D", "N", "W", "W", "W", + "D", + "N", "W", + "D", "N", "W", "W", "D", "N", + "N", + "W", + "W", + "PN", "W", "W", "D", @@ -770,6 +1511,46 @@ const monthWorkerData = { "W", "W", "D", + ], + "Pracownik 23": [ + "U", + "U", + "U", + "U", + "U", + "U", + "U", + "U", + "U", + "U", + "U", + "U", + "U", + "U", + "D", + "W", + "W", + "W", + "W", + "D", + "W", + "W", + "D", + "D", + "W", + "W", + "D", + "D", + "W", + "W", + "D", + ], + "Pracownik 12": [ + "W", + "D", + "N", + "W", + "D", "N", "W", "W", @@ -778,17 +1559,26 @@ const monthWorkerData = { "W", "W", "D", + "W", "N", "W", "W", + "N", "W", "W", + "D", + "N", "W", "W", + "D", + "N", + "N", "W", + "D", + "PN", "W", ], - "Pracownik 8": [ + "Pracownik 4": [ "W", "D", "N", @@ -797,58 +1587,82 @@ const monthWorkerData = { "D", "N", "W", - "D", + "W", "D", "N", "W", + "W", "D", "N", + "W", + "P1", + "W", "N", "W", - "D", + "W", "D", "N", "W", + "W", "D", "N", "W", "W", - "W", "D", + "N", + ], + "Pracownik 24": [ + "R", + "R", + "R", + "R", + "R", "W", "W", - "D", + "R", + "R", + "R", + "R", + "R", "W", "W", + "R", + "R", + "R", + "R", + "R", "W", "W", + "R", + "R", + "R", + "R", + "R", "W", "W", + "R", + "R", + "R", ], - "Pracownik 9": [ + "Pracownik 22": [ + "N", + "W", "D", + "R", + "N", "W", - "OP", "W", "D", - "DN", + "N", "W", "W", "D", "N", "W", - "OP", "W", + "D", "N", "W", - "U", - "U", - "U", - "U", - "U", - "U", - "U", - "U", "W", "D", "N", @@ -858,29 +1672,49 @@ const monthWorkerData = { "N", "W", "W", - "W", + "D", + "N", "W", "W", ], - "Pracownik 10": [ - "D", + "Pracownik 21": [ "N", "W", "W", "D", + "N", "W", "W", + "D", + "N", "W", + "R", "D", - "P1", "N", "W", + "W", "D", + "N", + "W", + "U", + "U", + "U", + "U", + "U", + "U", + "U", + "U", + "U", + "U", + "U", + "U", + "U", + ], + "Pracownik 17": [ "W", "W", "W", - "D", - "D", + "DN", "W", "W", "D", @@ -893,67 +1727,70 @@ const monthWorkerData = { "W", "D", "N", - "N", - "W", "W", "W", "W", - ], - "Pracownik 11": [ - "D", - "P1", + "DN", "W", "W", "D", "N", "W", "W", - "D", - "N", + "W", + "DN", "W", "W", "D", - "N", + ], + "Pracownik 5": [ + "R", + "R", "W", "W", + "R", "D", - "N", "W", "W", - "D", - "N", + "R", + "R", "W", "W", "D", - "N", + "D", + "W", + "W", + "R", + "R", "W", "W", "D", - "D", - "N", + "R", "W", "W", + "R", + "R", "W", "W", - ], - "Pracownik 12": [ + "R", + "R", "W", + ], + "Pracownik 7": [ "D", "N", "W", - "D", - "N", "W", "W", - "D", + "W", "N", "W", "W", - "D", - "D", + "W", "N", "W", "W", + "RP", "N", "W", "W", @@ -963,16 +1800,48 @@ const monthWorkerData = { "W", "D", "N", - "N", "W", - "D", - "PN", "W", + "D", + "N", "W", "W", "W", "W", ], + "Wódkowska Agnieszka": [ + "U", + "U", + "U", + "U", + "U", + "U", + "U", + "U", + "U", + "U", + "U", + "U", + "U", + "U", + "U", + "U", + "U", + "U", + "U", + "U", + "U", + "U", + "U", + "U", + "U", + "U", + "U", + "U", + "U", + "U", + "U", + ], }, dates: [ 1, @@ -1012,41 +1881,3 @@ const monthWorkerData = { 4, ], }; - -const MARCH_DAY_COUNT = 31; -const MONTH = 2; // march -const YEAR = 2021; - -const createWorkerInfoObject = (workerName: string): WorkerTestDataInstance => { - const cropToMonth = (data: T[]): MonthDataArray => - data.slice(0, MARCH_DAY_COUNT) as MonthDataArray; - const currentMonthShifts = cropToMonth(monthWorkerData.shifts[workerName]); - return { - workerName, - workerNorm: monthWorkerData.time[workerName], - workerContract: ContractType.EMPLOYMENT_CONTRACT, - workerReqiuredHours: monthWorkerData.expectedRequiredWorkHours[workerName], - workerActualHours: monthWorkerData.expectedActualHours[workerName], - actualWorkerShifts: currentMonthShifts, - baseWorkerShifts: currentMonthShifts, - month: MONTH, - year: YEAR, - dates: cropToMonth(monthWorkerData.dates), - }; -}; - -export interface WorkerTestDataInstance { - workerName: string; - workerNorm: number; - workerContract: ContractType; - workerReqiuredHours: number; - workerActualHours: number; - actualWorkerShifts: ShiftCode[]; - baseWorkerShifts: MonthDataArray; - month: number; - year: number; - dates: number[]; -} -export const workerTestData: WorkerTestDataInstance[] = Object.keys(monthWorkerData.time).map( - createWorkerInfoObject -); diff --git a/cypress/fixtures/worker-data/marzec_2021_wersja_bazowa.xlsx b/cypress/fixtures/worker-data/marzec_2021_wersja_bazowa.xlsx new file mode 100644 index 000000000..f9d60cf4a Binary files /dev/null and b/cypress/fixtures/worker-data/marzec_2021_wersja_bazowa.xlsx differ diff --git "a/cypress/fixtures/worker-data/marzec_koniec_miesi\304\205ca_2021_preprocessed.xlsx" "b/cypress/fixtures/worker-data/marzec_koniec_miesi\304\205ca_2021_preprocessed.xlsx" new file mode 100644 index 000000000..cb3df52b1 Binary files /dev/null and "b/cypress/fixtures/worker-data/marzec_koniec_miesi\304\205ca_2021_preprocessed.xlsx" differ diff --git a/cypress/fixtures/worker-data/worker-data-preprocessor.ts b/cypress/fixtures/worker-data/worker-data-preprocessor.ts new file mode 100644 index 000000000..70a0fe028 --- /dev/null +++ b/cypress/fixtures/worker-data/worker-data-preprocessor.ts @@ -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 = (data: T[]): MonthDataArray => + data.slice(0, MARCH_DAY_COUNT) as MonthDataArray; + const actualWorkerShifts = cropToMonth( + monthWorkerData.actualScheduleShifts[workerName] as ShiftCode[] + ); + const primaryWorkerShifts = cropToMonth( + 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; + month: number; + year: number; + dates: number[]; +} +export const workerTestData: WorkerTestDataInstance[] = Object.keys(monthWorkerData.time).map( + createWorkerInfoObject +); diff --git a/cypress/integration/e2e/table/update-workhours-info.spec.ts b/cypress/integration/e2e/table/update-workhours-info.spec.ts index dcb40f143..5cdd55f57 100644 --- a/cypress/integration/e2e/table/update-workhours-info.spec.ts +++ b/cypress/integration/e2e/table/update-workhours-info.spec.ts @@ -18,9 +18,9 @@ const nurseInitialWorkHours: HoursInfo[] = [ ]; const babysitterInitialWorkHours: HoursInfo[] = [ { - [HoursInfoCells.required]: 88, + [HoursInfoCells.required]: 80, [HoursInfoCells.actual]: 116, - [HoursInfoCells.overtime]: 28, + [HoursInfoCells.overtime]: 36, }, { [HoursInfoCells.required]: 136, @@ -28,9 +28,9 @@ const babysitterInitialWorkHours: HoursInfo[] = [ [HoursInfoCells.overtime]: 48, }, { - [HoursInfoCells.required]: 72, + [HoursInfoCells.required]: 160, [HoursInfoCells.actual]: 96, - [HoursInfoCells.overtime]: 24, + [HoursInfoCells.overtime]: -64, }, ]; diff --git a/cypress/integration/unit/helpers/shift.helper.spec.ts b/cypress/integration/unit/helpers/shift.helper.spec.ts index fa2c80f36..38d0ca2cb 100644 --- a/cypress/integration/unit/helpers/shift.helper.spec.ts +++ b/cypress/integration/unit/helpers/shift.helper.spec.ts @@ -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]); }); }); diff --git a/cypress/integration/unit/helpers/worker-hours-info.spec.ts b/cypress/integration/unit/helpers/worker-hours-info.spec.ts index 8f827e62b..832f9f95e 100644 --- a/cypress/integration/unit/helpers/worker-hours-info.spec.ts +++ b/cypress/integration/unit/helpers/worker-hours-info.spec.ts @@ -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", () => { @@ -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, + (primaryWorkerShifts as MonthDataArray) ?? workerInstance.primaryWorkerShifts, workerInstance.workerNorm, workerInstance.workerContract, workerInstance.month, @@ -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 diff --git a/docs/brak_dni.xlsx b/docs/brak_dni.xlsx deleted file mode 100644 index 7809f0f80..000000000 Binary files a/docs/brak_dni.xlsx and /dev/null differ diff --git a/docs/brak_info.xlsx b/docs/brak_info.xlsx deleted file mode 100644 index 0e4219dda..000000000 Binary files a/docs/brak_info.xlsx and /dev/null differ diff --git "a/docs/brak_podzia\305\202u.xlsx" "b/docs/brak_podzia\305\202u.xlsx" deleted file mode 100644 index 7ce008002..000000000 Binary files "a/docs/brak_podzia\305\202u.xlsx" and /dev/null differ diff --git "a/docs/brak_pracownik\303\263w.xlsx" "b/docs/brak_pracownik\303\263w.xlsx" deleted file mode 100644 index cf3399d50..000000000 Binary files "a/docs/brak_pracownik\303\263w.xlsx" and /dev/null differ diff --git a/docs/legit.xlsx b/docs/legit.xlsx deleted file mode 100644 index 86df30b5e..000000000 Binary files a/docs/legit.xlsx and /dev/null differ diff --git a/docs/marzec_2021_wersja_bazowa.xlsx b/docs/marzec_2021_wersja_bazowa.xlsx new file mode 100644 index 000000000..89359da4b Binary files /dev/null and b/docs/marzec_2021_wersja_bazowa.xlsx differ diff --git "a/docs/marzec_koniec_miesi\304\205ca_2021_preprocessed.xlsx" "b/docs/marzec_koniec_miesi\304\205ca_2021_preprocessed.xlsx" new file mode 100644 index 000000000..cb3df52b1 Binary files /dev/null and "b/docs/marzec_koniec_miesi\304\205ca_2021_preprocessed.xlsx" differ diff --git a/docs/niedozwolone_zmiany.xlsx b/docs/niedozwolone_zmiany.xlsx deleted file mode 100644 index f87b0dc6f..000000000 Binary files a/docs/niedozwolone_zmiany.xlsx and /dev/null differ diff --git "a/docs/powinno_dzia\305\202a\304\207.xlsx" "b/docs/powinno_dzia\305\202a\304\207.xlsx" deleted file mode 100644 index 61518eabb..000000000 Binary files "a/docs/powinno_dzia\305\202a\304\207.xlsx" and /dev/null differ diff --git a/src/api/server.middleware.ts b/src/api/server.middleware.ts index 8e621db9d..62d23ebb6 100644 --- a/src/api/server.middleware.ts +++ b/src/api/server.middleware.ts @@ -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) => @@ -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, diff --git a/src/common-models/shift-info.model.ts b/src/common-models/shift-info.model.ts index a7e6e1638..c080aae76 100644 --- a/src/common-models/shift-info.model.ts +++ b/src/common-models/shift-info.model.ts @@ -5,16 +5,32 @@ import { SCHEDULE_CONTAINERS_LENGTH, ScheduleContainerType } from "./schedule-data.model"; import * as _ from "lodash"; -export interface Shift { +interface BaseShift { code: string; name: string; from: number; to: number; color: string; - isWorkingShift?: boolean; +} + +export interface WorkingShift extends BaseShift { + isWorkingShift?: true; +} + +export enum NotWorkingShiftType { + MedicalLeave = "zwolnienie lekarskie", + AnnualLeave = "urlop", + Util = "util", +} +export interface NotWorkingShift extends BaseShift { + isWorkingShift?: false; normSubtraction?: number; + // TODO: remove optionality after migration + type?: NotWorkingShiftType; } +export type Shift = WorkingShift | NotWorkingShift; + export enum ShiftCode { RP = "RP", RPN = "RPN", @@ -133,7 +149,15 @@ export const SHIFTS: { [code in ShiftCode]: Shift } = { color: "FFD100", isWorkingShift: true, }, - W: { code: ShiftCode.W, name: "wolne", from: 0, to: 24, color: "FF8A00", isWorkingShift: false }, + W: { + code: ShiftCode.W, + name: "wolne", + from: 0, + to: 24, + color: "FF8A00", + isWorkingShift: false, + type: NotWorkingShiftType.Util, + }, U: { code: ShiftCode.U, name: "urlop wypoczynkowy", @@ -141,6 +165,7 @@ export const SHIFTS: { [code in ShiftCode]: Shift } = { to: 24, color: "92D050", isWorkingShift: false, + type: NotWorkingShiftType.AnnualLeave, }, L4: { code: ShiftCode.L4, @@ -149,6 +174,7 @@ export const SHIFTS: { [code in ShiftCode]: Shift } = { to: 24, color: "C60000", isWorkingShift: false, + type: NotWorkingShiftType.MedicalLeave, }, K: { code: ShiftCode.K, @@ -157,6 +183,7 @@ export const SHIFTS: { [code in ShiftCode]: Shift } = { to: 24, color: "000000", isWorkingShift: false, + type: NotWorkingShiftType.MedicalLeave, }, IZ: { code: ShiftCode.IZ, @@ -165,6 +192,7 @@ export const SHIFTS: { [code in ShiftCode]: Shift } = { to: 24, color: "fc03e7", isWorkingShift: false, + type: NotWorkingShiftType.MedicalLeave, }, OK: { @@ -175,6 +203,7 @@ export const SHIFTS: { [code in ShiftCode]: Shift } = { color: "127622", isWorkingShift: false, normSubtraction: 8, + type: NotWorkingShiftType.AnnualLeave, }, OP: { code: ShiftCode.OP, @@ -184,6 +213,7 @@ export const SHIFTS: { [code in ShiftCode]: Shift } = { color: "C3A000", isWorkingShift: false, normSubtraction: 12, + type: NotWorkingShiftType.AnnualLeave, }, NZ: { code: ShiftCode.NZ, @@ -192,14 +222,15 @@ export const SHIFTS: { [code in ShiftCode]: Shift } = { to: 24, color: "000000", isWorkingShift: false, + type: NotWorkingShiftType.Util, }, }; -export const FREE_SHIFTS = Object.values(SHIFTS) +export const FREE_SHIFTS_CODES = Object.values(SHIFTS) .filter((shift) => !shift.isWorkingShift && shift.code !== "W") .map((shift) => shift.code); -export const WORKING_SHIFTS = Object.values(SHIFTS) +export const WORKING_SHIFTS_CODES = Object.values(SHIFTS) .filter((shift) => shift.isWorkingShift) .map((shift) => shift.code); diff --git a/src/components/schedule-page/table/schedule/schedule.component.tsx b/src/components/schedule-page/table/schedule/schedule.component.tsx index c36c27b43..ebd5f7e0f 100644 --- a/src/components/schedule-page/table/schedule/schedule.component.tsx +++ b/src/components/schedule-page/table/schedule/schedule.component.tsx @@ -3,6 +3,7 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ import React from "react"; import { useSelector } from "react-redux"; +import { WorkerHourInfo } from "../../../../helpers/worker-hours-info.model"; import { ApplicationStateModel } from "../../../../state/models/application-state.model"; import { OvertimeHeaderComponent } from "../../../overtime-header-table/overtime-header.component"; import { TimeTableComponent } from "../../../timetable/timetable.component"; @@ -36,7 +37,7 @@ export function ScheduleComponent(): JSX.Element {
- +
diff --git a/src/helpers/shifts.helper.ts b/src/helpers/shifts.helper.ts index f3b7811ba..3a8def150 100644 --- a/src/helpers/shifts.helper.ts +++ b/src/helpers/shifts.helper.ts @@ -4,10 +4,12 @@ import { VerboseDate } from "../common-models/month-info.model"; import { Shift, - FREE_SHIFTS, + FREE_SHIFTS_CODES, ShiftCode, ShiftInfoModel, ShiftsTypesDict, + NotWorkingShiftType, + NotWorkingShift, } from "../common-models/shift-info.model"; import { Opaque } from "../common-models/type-utils"; import { WorkerType } from "../common-models/worker-info.model"; @@ -34,19 +36,18 @@ export class ShiftHelper { workersPerDays.push( shiftsArray.reduce((a, b) => { const shift = shiftTypes[b[i]]; - return a + (this.shiftCodeToWorkTime(shift) ? 1 : 0); + return a + (this.shiftToWorkTime(shift) ? 1 : 0); }, 0) ); } return workersPerDays; } - public static isNotWorkingShift(shiftCode: ShiftCode, shiftTypes: ShiftsTypesDict): boolean { - const shift = shiftTypes[shiftCode] as Shift; - return (!shift?.isWorkingShift ?? true) && shift?.code !== ShiftCode.W; + public static isNotWorkingShift(shift?: Shift): shift is NotWorkingShift { + return shift?.isWorkingShift === false && shift?.type !== NotWorkingShiftType.Util; } - public static shiftCodeToWorkTime(shift: Shift): number { + public static shiftToWorkTime(shift: Shift): number { if (!shift?.isWorkingShift ?? true) { return 0; } @@ -59,8 +60,8 @@ export class ShiftHelper { } public static requiredFreeTimeAfterShift(shift: Shift): number { - if (this.shiftCodeToWorkTime(shift) < 9) return 11; - if (this.shiftCodeToWorkTime(shift) > 12) return 24; + if (this.shiftToWorkTime(shift) < 9) return 11; + if (this.shiftToWorkTime(shift) > 12) return 24; return 16; } @@ -162,7 +163,7 @@ export class ShiftHelper { static replaceFreeShiftsWithFreeDay(shifts: ShiftCode[], startIndex = 0): ShiftCode[] { return shifts.map((shift, idx) => { const isIndexValid = idx >= startIndex; - const shouldReplace = FREE_SHIFTS.includes(shift); + const shouldReplace = FREE_SHIFTS_CODES.includes(shift); return isIndexValid && shouldReplace ? ShiftCode.W : shift; }); } diff --git a/src/helpers/verbose-date.helper.ts b/src/helpers/verbose-date.helper.ts index 38dec09f5..16ed41a93 100644 --- a/src/helpers/verbose-date.helper.ts +++ b/src/helpers/verbose-date.helper.ts @@ -8,7 +8,9 @@ import { ColorHelper } from "./colors/color.helper"; import { Colors } from "./colors/color.model"; import * as _ from "lodash"; import { MonthDataArray } from "./shifts.helper"; +import { Opaque } from "../common-models/type-utils"; +export type WorkingDay = Opaque<"WorkingDay", VerboseDate>; export class VerboseDateHelper { public static generateVerboseDatesForMonth( month: number, @@ -36,13 +38,22 @@ export class VerboseDateHelper { return dates as MonthDataArray; } - static isWorkingDay(date?: Pick): boolean { + static isNotWeekend(date?: Pick): date is WorkingDay { if (!date) { return false; } - return ( - !date.isPublicHoliday && !(date.dayOfWeek === WeekDay.SA || date.dayOfWeek === WeekDay.SU) - ); + return !(date.dayOfWeek === WeekDay.SA || date.dayOfWeek === WeekDay.SU); + } + + /** + According to the law, for each holiday, in other day than Sunday + an employer should provide an employee with one day off. + */ + static countDayOffsFromHolidays( + dates: Pick[] + ): number { + return (dates ?? []).filter((date) => date.isPublicHoliday && date.dayOfWeek !== WeekDay.SU) + .length; } static isHolidaySaturday(date?: Pick): boolean { diff --git a/src/helpers/worker-hours-info.model.ts b/src/helpers/worker-hours-info.model.ts index a69639cd8..58f156033 100644 --- a/src/helpers/worker-hours-info.model.ts +++ b/src/helpers/worker-hours-info.model.ts @@ -5,7 +5,13 @@ import * as _ from "lodash"; import { VerboseDate } from "../common-models/month-info.model"; import { MonthDataModel, ScheduleDataModel } from "../common-models/schedule-data.model"; -import { ShiftCode, ShiftsTypesDict } from "../common-models/shift-info.model"; +import { + NotWorkingShift, + NotWorkingShiftType, + Shift, + ShiftCode, + ShiftsTypesDict, +} from "../common-models/shift-info.model"; import { isAllValuesDefined, Opaque } from "../common-models/type-utils"; import { nameOf } from "../common-models/utils"; import { MonthInfoLogic } from "../logic/schedule-logic/month-info.logic"; @@ -15,7 +21,28 @@ import { TranslationHelper } from "./translations.helper"; import { VerboseDateHelper } from "./verbose-date.helper"; import { ContractType } from "../common-models/worker-info.model"; -interface WorkerInfoForCalculateWorkerHoursInfo { +export const DEFAULT_NORM_SUBTRACTION = WORK_HOURS_PER_DAY; + +//#region models +export enum OvertimeType { + MaxiumNotOvertimeHoursExceed = "Maxium not overtime hours exceed", + RevisionDifference = "revision difference", +} + +interface WorkHoursInfoCalulationOptions { + considerOvetimeTypes: OvertimeType[]; +} + +const DEFAULT_WORK_HOURS_INFO_CALCULATION_OPTIONS = { + considerOvetimeTypes: [], +}; +interface DataForOvertimeCalculation { + actualWorkerShifts: ShiftCode[]; + primaryScheduleWorkerShifts: ShiftCode[]; + shiftTypes: ShiftsTypesDict; +} + +interface WorkerInfoForCalculateWorkerHoursInfo extends DataForOvertimeCalculation { actualWorkerShifts: ShiftCode[]; primaryScheduleWorkerShifts: MonthDataArray; workerNorm: number; @@ -36,10 +63,10 @@ export interface WorkerHourInfoSummary { overTime: number; workerHourNorm: number; workerTime: number; - workHoursDiff: number; } export type WorkerHourInfoSummaryTranslation = { [key in keyof WorkerHourInfoSummary]: string }; +//#endregion export class WorkerHourInfo { public readonly overTime: number; @@ -49,62 +76,57 @@ export class WorkerHourInfo { constructor(workerHourNorm: number, workerTime: number, overTime: number) { this.workerHourNorm = Math.round(workerHourNorm); this.workerTime = Math.round(workerTime); - this.overTime = overTime; - } - - public get workHoursDiff(): number { - return this.workerTime - this.workerHourNorm; + this.overTime = Math.round(overTime); } public get summary(): WorkerHourInfoSummary { return { workerHourNorm: this.workerHourNorm, workerTime: this.workerTime, - workHoursDiff: this.workHoursDiff, overTime: this.overTime, }; } public static get summaryTranslations(): WorkerHourInfoSummaryTranslation { return { - overTime: "nagodziny", workerHourNorm: "norma", workerTime: "aktualne", - workHoursDiff: "różnica", + overTime: "nagodziny", }; } + //#region preprocessing public static fromSchedules( workerName: string, - scheduleModel: ScheduleDataModel | MonthDataModel, - primarySchedule?: PrimaryMonthRevisionDataModel + actualScheduleModel: ScheduleDataModel | MonthDataModel, + primaryScheduleModel?: PrimaryMonthRevisionDataModel ): WorkerHourInfo { - const { time, contractType } = scheduleModel.employee_info; + const { time, contractType } = actualScheduleModel.employee_info; const workerContractType = contractType && contractType[workerName] ? contractType[workerName] : ContractType.CIVIL_CONTRACT; - const { shifts } = scheduleModel; + const { shifts } = actualScheduleModel; let month: number, year: number; - if (!_.isNil((scheduleModel as ScheduleDataModel).schedule_info)) { - const info = (scheduleModel as ScheduleDataModel).schedule_info; + if (!_.isNil((actualScheduleModel as ScheduleDataModel).schedule_info)) { + const info = (actualScheduleModel as ScheduleDataModel).schedule_info; month = info.month_number; year = info.year; } else { - const info = (scheduleModel as MonthDataModel).scheduleKey; + const info = (actualScheduleModel as MonthDataModel).scheduleKey; month = info.month; year = info.year; } - const { dates } = scheduleModel.month_info; + const { dates } = actualScheduleModel.month_info; return this.fromWorkerInfo( shifts[workerName], - primarySchedule?.shifts[workerName] as MonthDataArray, // TODO: modify MonthDataModel to contain only MonthDataArray + primaryScheduleModel?.shifts[workerName] as MonthDataArray, // TODO: modify MonthDataModel to contain only MonthDataArray time[workerName], workerContractType, month, year, dates, - scheduleModel.shift_types + actualScheduleModel.shift_types ); } @@ -130,25 +152,33 @@ export class WorkerHourInfo { shiftTypes, }); } + //#endregion - private static calculateWorkHoursInfo({ - actualWorkerShifts, - workerNorm, - dates, - month, - primaryScheduleWorkerShifts, - workerContractType, - shiftTypes, - }: WorkerInfoForCalculateWorkerHoursInfo): WorkerHourInfo { + //#region logic + private static calculateWorkHoursInfo( + { + actualWorkerShifts, + workerNorm, + dates, + month, + primaryScheduleWorkerShifts, + workerContractType, + shiftTypes, + }: WorkerInfoForCalculateWorkerHoursInfo, + options: WorkHoursInfoCalulationOptions = DEFAULT_WORK_HOURS_INFO_CALCULATION_OPTIONS + ): WorkerHourInfo { this.validateActualWorkersShifts(actualWorkerShifts, dates); const cropToMonth = this.createCropToMonthFunc(dates, month); const currentMonthDates = cropToMonth(dates); const actualShiftsFromCurrentMonth = cropToMonth(actualWorkerShifts); + primaryScheduleWorkerShifts = primaryScheduleWorkerShifts ?? actualShiftsFromCurrentMonth; + if (!isAllValuesDefined([primaryScheduleWorkerShifts, actualShiftsFromCurrentMonth])) { return new WorkerHourInfo(0, 0, 0); } + if (actualShiftsFromCurrentMonth.length !== primaryScheduleWorkerShifts.length) { primaryScheduleWorkerShifts = cropToMonth(primaryScheduleWorkerShifts); } @@ -167,13 +197,15 @@ export class WorkerHourInfo { actualShiftsFromCurrentMonth, shiftTypes ); + const workerOvertime = this.calculateWorkerOvertime( actualShiftsFromCurrentMonth, primaryScheduleWorkerShifts, currentMonthDates, workerNorm, workerContractType, - shiftTypes + shiftTypes, + options.considerOvetimeTypes ); return new WorkerHourInfo(workerHourNorm, workerActualWorkTime, workerOvertime); @@ -226,30 +258,45 @@ export class WorkerHourInfo { shiftTypes: ShiftsTypesDict ): number { const requiredHours = this.calculateRequiredHoursFromVerboseDates(currentMonthDates); - const freeHours = - workerContractType === ContractType.CIVIL_CONTRACT - ? 0 - : this.calculateFreeHours( - actualShiftsFromCurrentMonth, - primaryScheduleWorkerShifts, - currentMonthDates, - shiftTypes - ); - return Math.max((requiredHours - freeHours) * workerNorm, 0); + const freeHours = this.calculateFreeHoursForContractType( + workerContractType, + actualShiftsFromCurrentMonth, + primaryScheduleWorkerShifts, + currentMonthDates, + shiftTypes + ); + return Math.max(requiredHours * workerNorm - freeHours, 0); } public static calculateRequiredHoursFromVerboseDates( verboseDates: DateInformationForWorkInfoCalculation[] ): number { - const workingDaysCount = verboseDates.filter((d) => VerboseDateHelper.isWorkingDay(d)).length; - // if holiday on saturday, monday is free day - const holidaySaturdaysCount = verboseDates.filter((d) => VerboseDateHelper.isHolidaySaturday(d)) - .length; + const weekDaysCount = verboseDates.filter((d) => VerboseDateHelper.isNotWeekend(d)).length; + const holidayDayOffs = VerboseDateHelper.countDayOffsFromHolidays(verboseDates); + return WORK_HOURS_PER_DAY * (weekDaysCount - holidayDayOffs); + } - return WORK_HOURS_PER_DAY * (workingDaysCount - holidaySaturdaysCount); + private static calculateFreeHoursForContractType( + workerContractType: ContractType, + actualShiftsFromCurrentMonth: ShiftCode[], + primaryScheduleWorkerShifts: ShiftCode[], + currentMonthDates: DateInformationForWorkInfoCalculation[], + shiftTypes: ShiftsTypesDict + ): number { + switch (workerContractType) { + case ContractType.EMPLOYMENT_CONTRACT: + return this.calculateFreeHoursForEmplContract( + actualShiftsFromCurrentMonth, + primaryScheduleWorkerShifts, + currentMonthDates, + shiftTypes + ); + default: + return 0; + } } - private static calculateFreeHours( + private static calculateFreeHoursForEmplContract( actualShiftsFromCurrentMonth: ShiftCode[], primaryScheduleWorkerShifts: ShiftCode[], currentMonthDates: DateInformationForWorkInfoCalculation[], @@ -260,42 +307,89 @@ export class WorkerHourInfo { primaryScheduleWorkerShifts, currentMonthDates ) as [ShiftCode, ShiftCode, DateInformationForWorkInfoCalculation][]; + return monthShiftsWithHistoryShiftsAndDates.reduce((calculateFreeHours, shiftPair) => { - const [actualShift, historyShift, day] = shiftPair; - if (!ShiftHelper.isNotWorkingShift(actualShift, shiftTypes)) { - return calculateFreeHours; - } - // ignore any free shifts in weekends - if (!VerboseDateHelper.isWorkingDay(day)) { + const [actualShiftCode, historyShiftCode, date] = shiftPair; + const actualShift = shiftTypes[actualShiftCode]; + const historyShift = shiftTypes[historyShiftCode]; + if (!ShiftHelper.isNotWorkingShift(actualShift)) { return calculateFreeHours; } - const shiftSubtraction = shiftTypes[actualShift]?.normSubtraction ?? WORK_HOURS_PER_DAY; - - const subtractFromNorm = - actualShift === historyShift || !shiftTypes[historyShift]?.isWorkingShift - ? shiftSubtraction - : ShiftHelper.shiftCodeToWorkTime(shiftTypes[historyShift]); - return calculateFreeHours + subtractFromNorm; + return calculateFreeHours + this.calculateNormSubtraction(date, actualShift, historyShift); }, 0); } + private static calculateNormSubtraction( + date: DateInformationForWorkInfoCalculation, + actualShift: NotWorkingShift, + primaryShift: Shift + ): number { + // ignore any free shifts in weekends + switch (actualShift.type) { + case NotWorkingShiftType.MedicalLeave: + return this.calculateMedicalLeaveSubtraction(primaryShift); + case NotWorkingShiftType.AnnualLeave: + return this.calculateAnnualLeaveSubtraction(date, actualShift, primaryShift); + default: + return 0; + } + } + + /** + * Medical leaves subtract from norm only if there was a working shift in primary schedule + */ + private static calculateMedicalLeaveSubtraction(primaryShift: Shift): number { + if (ShiftHelper.isNotWorkingShift(primaryShift)) { + return 0; + } + return ShiftHelper.shiftToWorkTime(primaryShift); + } + /** + * Annual leaves subtract from norm only in working days. + */ + private static calculateAnnualLeaveSubtraction( + date: DateInformationForWorkInfoCalculation, + actualShift: NotWorkingShift, + primaryShift: Shift + ): number { + if (!VerboseDateHelper.isNotWeekend(date)) { + return 0; + } + const actualShiftSubtraction = actualShift.normSubtraction ?? DEFAULT_NORM_SUBTRACTION; + if (ShiftHelper.isNotWorkingShift(primaryShift)) { + // If primary shift is not working shift, than shift from actual schedule + // always takes precedence and we subtracts its normSubtraction + return actualShiftSubtraction; + } else { + return Math.max(actualShiftSubtraction, ShiftHelper.shiftToWorkTime(primaryShift)); + } + } + private static calculateWorkerActualWorkTime( actualShiftsFromCurrentMonth: ShiftCode[], shiftTypes: ShiftsTypesDict ): number { return actualShiftsFromCurrentMonth.reduce( - (acc, shift) => acc + ShiftHelper.shiftCodeToWorkTime(shiftTypes[shift!]), + (acc, shift) => acc + ShiftHelper.shiftToWorkTime(shiftTypes[shift!]), 0 ); } + static overtimeHandlers: { + [ovetimeType in OvertimeType]: (calculationData: DataForOvertimeCalculation) => number; + } = { + [OvertimeType.MaxiumNotOvertimeHoursExceed]: WorkerHourInfo.calculateOvertimeForExceeding, + [OvertimeType.RevisionDifference]: WorkerHourInfo.calculateOvertimeForRevisionDifference, + }; + private static calculateWorkerOvertime( actualShiftsFromCurrentMonth: ShiftCode[], primaryScheduleWorkerShifts: ShiftCode[], currentMonthDates: DateInformationForWorkInfoCalculation[], workerNorm: number, workerContractType: ContractType, - shiftTypes: ShiftsTypesDict + shiftTypes: ShiftsTypesDict, + considerOvertimeTypes: OvertimeType[] = Object.values(OvertimeType) ): number { const workerHourNorm = this.calculateWorkerHourNorm( actualShiftsFromCurrentMonth, @@ -305,33 +399,33 @@ export class WorkerHourInfo { workerContractType, shiftTypes ); + const workerActualWorkTime = this.calculateWorkerActualWorkTime( actualShiftsFromCurrentMonth, shiftTypes ); - const diffBetweenRevisionsOvertime = this.calculateOvertimeForRevisionDifference( - actualShiftsFromCurrentMonth, - primaryScheduleWorkerShifts, - shiftTypes - ); - const exceedMaximumDayWorkTimeOvertime = this.calculateOvertimeForExceeding( - actualShiftsFromCurrentMonth, - primaryScheduleWorkerShifts, - shiftTypes - ); + const algorithmOvertime = considerOvertimeTypes.reduce((acc, ovetimeType) => { + const calculationResut = this.overtimeHandlers[ovetimeType]({ + actualWorkerShifts: actualShiftsFromCurrentMonth, + primaryScheduleWorkerShifts, + shiftTypes, + }); + return acc + calculationResut; + }, 0); const normAndActualDiff = Math.round(workerActualWorkTime) - Math.round(workerHourNorm); - const algorithmOvertime = diffBetweenRevisionsOvertime + exceedMaximumDayWorkTimeOvertime; - - return Math.max(normAndActualDiff, algorithmOvertime); + return normAndActualDiff < 0 + ? algorithmOvertime + normAndActualDiff // In case if normAndActualDiff is smaller then 0, we are trying to compensate difference + : // with overtime calculated based on selected overtime types + Math.max(normAndActualDiff, algorithmOvertime); } - private static calculateOvertimeForRevisionDifference( - actualShiftsFromCurrentMonth: ShiftCode[], - primaryScheduleWorkerShifts: ShiftCode[], - shiftTypes: ShiftsTypesDict - ): number { + private static calculateOvertimeForRevisionDifference({ + actualWorkerShifts: actualShiftsFromCurrentMonth, + primaryScheduleWorkerShifts, + shiftTypes, + }: DataForOvertimeCalculation): number { const monthShiftsWithHistoryShifts = _.zip( actualShiftsFromCurrentMonth, primaryScheduleWorkerShifts @@ -341,10 +435,11 @@ export class WorkerHourInfo { monthShiftsWithHistoryShifts.forEach(([actualShift, historyShift]) => { if (actualShift !== historyShift) { const workHoursDiffBetweenRevisions = - ShiftHelper.shiftCodeToWorkTime(shiftTypes[actualShift]) - - ShiftHelper.shiftCodeToWorkTime(shiftTypes[historyShift]); + ShiftHelper.shiftToWorkTime(shiftTypes[actualShift]) - + ShiftHelper.shiftToWorkTime(shiftTypes[historyShift]); + const exceedMaxNormalTime = - ShiftHelper.shiftCodeToWorkTime(shiftTypes[actualShift]) - MAXIMUM_NOT_OVERTIME_HOURS; + ShiftHelper.shiftToWorkTime(shiftTypes[actualShift]) - MAXIMUM_NOT_OVERTIME_HOURS; diffBetweenRevisionsOvertime += Math.max( workHoursDiffBetweenRevisions, exceedMaxNormalTime, @@ -355,11 +450,11 @@ export class WorkerHourInfo { return diffBetweenRevisionsOvertime; } - private static calculateOvertimeForExceeding( - actualShiftsFromCurrentMonth: ShiftCode[], - primaryScheduleWorkerShifts: ShiftCode[], - shiftTypes: ShiftsTypesDict - ): number { + private static calculateOvertimeForExceeding({ + actualWorkerShifts: actualShiftsFromCurrentMonth, + primaryScheduleWorkerShifts, + shiftTypes, + }: DataForOvertimeCalculation): number { const monthShiftsWithHistoryShifts = _.zip( actualShiftsFromCurrentMonth, primaryScheduleWorkerShifts @@ -370,7 +465,7 @@ export class WorkerHourInfo { .filter(([actualShift, historyShift]) => actualShift === historyShift) .map(([actualShift]) => actualShift) .forEach((shift) => { - const shiftWorkTime = ShiftHelper.shiftCodeToWorkTime(shiftTypes[shift]); + const shiftWorkTime = ShiftHelper.shiftToWorkTime(shiftTypes[shift]); if (shiftWorkTime > MAXIMUM_NOT_OVERTIME_HOURS) { exceedMaximumDayWorkTimeOvertime += Math.max( shiftWorkTime - MAXIMUM_NOT_OVERTIME_HOURS, @@ -385,4 +480,5 @@ export class WorkerHourInfo { const dates = VerboseDateHelper.generateVerboseDatesForMonth(month, year); return Math.round(this.calculateRequiredHoursFromVerboseDates(dates)); } + //#endregion }