diff --git a/docs/swagger/swagger-spec.json b/docs/swagger/swagger-spec.json index 1ca3c19..4167a3f 100644 --- a/docs/swagger/swagger-spec.json +++ b/docs/swagger/swagger-spec.json @@ -377,19 +377,10 @@ ] } }, - "/shift/{timesheet_id}": { + "/shift/create": { "post": { "operationId": "ShiftController_createBatch", - "parameters": [ - { - "name": "timesheet_id", - "required": true, - "in": "path", - "schema": { - "type": "number" - } - } - ], + "parameters": [], "requestBody": { "required": true, "content": { @@ -413,10 +404,23 @@ ] } }, - "/shift": { + "/shift/update": { "patch": { "operationId": "ShiftController_updateBatch", "parameters": [], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + }, "responses": { "200": { "description": "" diff --git a/src/time-and-attendance/modules/expenses/services/expense-upsert.service.ts b/src/time-and-attendance/modules/expenses/services/expense-upsert.service.ts index bc35988..b2e8766 100644 --- a/src/time-and-attendance/modules/expenses/services/expense-upsert.service.ts +++ b/src/time-and-attendance/modules/expenses/services/expense-upsert.service.ts @@ -1,5 +1,5 @@ -import { toDateFromString, toStringFromDate } from "src/time-and-attendance/utils/date-time-helpers"; +import { toDateFromString, toStringFromDate } from "src/time-and-attendance/utils/date-time.utils"; import { Injectable, NotFoundException } from "@nestjs/common"; import { updateExpenseDto } from "../dtos/expense-update.dto"; import { GetExpenseDto } from "../dtos/expense-get.dto"; diff --git a/src/time-and-attendance/modules/leave-requests/mappers/leave-requests.mapper.ts b/src/time-and-attendance/modules/leave-requests/mappers/leave-requests.mapper.ts index 48d1214..9f823fc 100644 --- a/src/time-and-attendance/modules/leave-requests/mappers/leave-requests.mapper.ts +++ b/src/time-and-attendance/modules/leave-requests/mappers/leave-requests.mapper.ts @@ -1,6 +1,6 @@ -import { Prisma } from "@prisma/client"; import { LeaveRequestViewDto } from "../dtos/leave-request-view.dto"; -import { LeaveRequestRow } from "src/time-and-attendance/utils/selects.utils"; +import { LeaveRequestRow } from "src/time-and-attendance/utils/type.utils"; +import { Prisma } from "@prisma/client"; const toNum = (value?: Prisma.Decimal | null) => value !== null && value !== undefined ? Number(value) : undefined; diff --git a/src/time-and-attendance/modules/leave-requests/utils/leave-request.transform.ts b/src/time-and-attendance/modules/leave-requests/utils/leave-request.transform.ts index f5501c0..042c796 100644 --- a/src/time-and-attendance/modules/leave-requests/utils/leave-request.transform.ts +++ b/src/time-and-attendance/modules/leave-requests/utils/leave-request.transform.ts @@ -1,8 +1,8 @@ -import { LeaveRequestRow } from 'src/time-and-attendance/utils/selects.utils'; +import { LeaveRequestArchiveRow } from './leave-requests-archive.select'; import { LeaveRequestViewDto } from '../dtos/leave-request-view.dto'; import { mapArchiveRowToView } from '../mappers/leave-requests-archive.mapper'; +import { LeaveRequestRow } from 'src/time-and-attendance/utils/type.utils'; import { mapRowToView } from '../mappers/leave-requests.mapper'; -import { LeaveRequestArchiveRow } from './leave-requests-archive.select'; /** Active (table leave_requests) : proxy to base mapper */ export function mapRowToViewWithDays(row: LeaveRequestRow): LeaveRequestViewDto { diff --git a/src/time-and-attendance/modules/leave-requests/utils/leave-request.util.ts b/src/time-and-attendance/modules/leave-requests/utils/leave-request.util.ts index 776c56e..7c66afb 100644 --- a/src/time-and-attendance/modules/leave-requests/utils/leave-request.util.ts +++ b/src/time-and-attendance/modules/leave-requests/utils/leave-request.util.ts @@ -4,7 +4,7 @@ import { PrismaService } from "src/prisma/prisma.service"; import { LeaveTypes } from "@prisma/client"; // import { toDateOnly, toStringFromDate } from "src/time-and-attendance/modules/shared/helpers/date-time.helpers"; import { UpsertAction } from "src/time-and-attendance/modules/shared/types/upsert-actions.types"; -import { toDateFromString, toStringFromDate } from "src/time-and-attendance/utils/date-time-helpers"; +import { toDateFromString, toStringFromDate } from "src/time-and-attendance/utils/date-time.utils"; // import { ShiftsUpsertService } from "src/time-and-attendance/modules/time-tracker/shifts/services/shifts-upsert.service"; @Injectable() diff --git a/src/time-and-attendance/modules/pay-period/services/pay-periods-query.service.ts b/src/time-and-attendance/modules/pay-period/services/pay-periods-query.service.ts index 0e6aac0..86ce75e 100644 --- a/src/time-and-attendance/modules/pay-period/services/pay-periods-query.service.ts +++ b/src/time-and-attendance/modules/pay-period/services/pay-periods-query.service.ts @@ -3,9 +3,9 @@ import { PrismaService } from "src/prisma/prisma.service"; import { computeHours } from "src/common/utils/date-utils"; import { PayPeriodOverviewDto } from "../dtos/overview-pay-period.dto"; import { EmployeePeriodOverviewDto } from "../dtos/overview-employee-period.dto"; -import { computePeriod, listPayYear, payYearOfDate } from "../utils/pay-year.util"; import { PayPeriodDto } from "../dtos/pay-period.dto"; import { mapPayPeriodToDto } from "../mappers/pay-periods.mapper"; +import { computePeriod, listPayYear, payYearOfDate } from "src/time-and-attendance/utils/date-time.utils"; @Injectable() export class PayPeriodsQueryService { diff --git a/src/time-and-attendance/modules/pay-period/utils/pay-year.util.ts b/src/time-and-attendance/modules/pay-period/utils/pay-year.util.ts deleted file mode 100644 index 1dbf0d0..0000000 --- a/src/time-and-attendance/modules/pay-period/utils/pay-year.util.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { toStringFromDate, toUTCDate } from "src/time-and-attendance/utils/date-time-helpers"; - -export const ANCHOR_ISO = '2023-12-17'; // ancre date -const PERIOD_DAYS = 14; -const PERIODS_PER_YEAR = 26; -const MS_PER_DAY = 86_400_000; - -export function payYearOfDate(date: string | Date, anchorISO = ANCHOR_ISO): number { - const ANCHOR = toUTCDate(anchorISO); - const d = toUTCDate(date); - const days = Math.floor((+d - +ANCHOR) / MS_PER_DAY); - const cycles = Math.floor(days / (PERIODS_PER_YEAR * PERIOD_DAYS)); - return ANCHOR.getUTCFullYear() + 1 + cycles; -} -//compute labels for periods -export function computePeriod(pay_year: number, period_no: number, anchorISO = ANCHOR_ISO) { - const ANCHOR = toUTCDate(anchorISO); - const cycles = pay_year - (ANCHOR.getUTCFullYear() + 1); - const offsetPeriods = cycles * PERIODS_PER_YEAR + (period_no - 1); - const start = new Date(+ANCHOR + offsetPeriods * PERIOD_DAYS * MS_PER_DAY); - const end = new Date(+start + (PERIOD_DAYS - 1) * MS_PER_DAY); - const pay = new Date(end.getTime() + 6 * MS_PER_DAY); - return { - period_no: period_no, - pay_year: pay_year, - payday: toStringFromDate(pay), - period_start: toStringFromDate(start), - period_end: toStringFromDate(end), - label: `${toStringFromDate(start)}.${toStringFromDate(end)}`, - start, end, - }; -} - -//list of all 26 periods for a full year -export function listPayYear(pay_year: number, anchorISO = ANCHOR_ISO) { - return Array.from({ length: PERIODS_PER_YEAR }, (_, i) => computePeriod(pay_year, i + 1, anchorISO)); -} diff --git a/src/time-and-attendance/modules/shared/utils/resolve-timesheet.utils.ts b/src/time-and-attendance/modules/shared/utils/resolve-timesheet.utils.ts index a992af8..f556a8e 100644 --- a/src/time-and-attendance/modules/shared/utils/resolve-timesheet.utils.ts +++ b/src/time-and-attendance/modules/shared/utils/resolve-timesheet.utils.ts @@ -1,6 +1,6 @@ import { Injectable, NotFoundException } from "@nestjs/common"; import { Prisma, PrismaClient } from "@prisma/client"; -import { weekStartSunday } from "src/time-and-attendance/utils/date-time-helpers"; +import { weekStartSunday } from "src/time-and-attendance/utils/date-time.utils"; import { PrismaService } from "src/prisma/prisma.service"; import { EmailToIdResolver } from "./resolve-email-id.utils"; diff --git a/src/time-and-attendance/modules/time-tracker/schedule-presets/controller/schedule-presets.controller.ts b/src/time-and-attendance/modules/time-tracker/schedule-presets/controller/schedule-presets.controller.ts index 79a8976..f02b958 100644 --- a/src/time-and-attendance/modules/time-tracker/schedule-presets/controller/schedule-presets.controller.ts +++ b/src/time-and-attendance/modules/time-tracker/schedule-presets/controller/schedule-presets.controller.ts @@ -1,8 +1,8 @@ import { BadRequestException, Body, Controller, Get, NotFoundException, Param, Post, Put, Query } from "@nestjs/common"; -import { SchedulePresetsDto } from "../dtos/create-schedule-presets.dto"; import { SchedulePresetsCommandService } from "../services/schedule-presets-command.service"; -import { UpsertAction } from "src/time-and-attendance/modules/shared/types/upsert-actions.types"; import { SchedulePresetsQueryService } from "../services/schedule-presets-query.service"; +import { SchedulePresetsDto } from "../dtos/create-schedule-presets.dto"; +import { UpsertAction } from "src/time-and-attendance/modules/shared/types/upsert-actions.types"; @Controller('schedule-presets') export class SchedulePresetsController { diff --git a/src/time-and-attendance/modules/time-tracker/schedule-presets/dtos/create-schedule-presets.dto.ts b/src/time-and-attendance/modules/time-tracker/schedule-presets/dtos/create-schedule-presets.dto.ts index 7bd822f..cb1c512 100644 --- a/src/time-and-attendance/modules/time-tracker/schedule-presets/dtos/create-schedule-presets.dto.ts +++ b/src/time-and-attendance/modules/time-tracker/schedule-presets/dtos/create-schedule-presets.dto.ts @@ -1,4 +1,4 @@ -import { ArrayMinSize, IsArray, IsBoolean, IsEmail, IsOptional, IsString } from "class-validator"; +import { ArrayMinSize, IsArray, IsBoolean, IsOptional, IsString } from "class-validator"; import { SchedulePresetShiftsDto } from "./create-schedule-preset-shifts.dto"; export class SchedulePresetsDto { diff --git a/src/time-and-attendance/modules/time-tracker/schedule-presets/services/schedule-presets-apply.service.ts b/src/time-and-attendance/modules/time-tracker/schedule-presets/services/schedule-presets-apply.service.ts index 98bde74..7bad7f6 100644 --- a/src/time-and-attendance/modules/time-tracker/schedule-presets/services/schedule-presets-apply.service.ts +++ b/src/time-and-attendance/modules/time-tracker/schedule-presets/services/schedule-presets-apply.service.ts @@ -1,9 +1,9 @@ import { BadRequestException, ConflictException, Injectable, NotFoundException } from "@nestjs/common"; import { EmailToIdResolver } from "src/time-and-attendance/modules/shared/utils/resolve-email-id.utils"; import { PrismaService } from "src/prisma/prisma.service"; -import { ApplyResult } from "../types/schedule-presets.types"; import { Prisma, Weekday } from "@prisma/client"; -import { WEEKDAY } from "../mappers/schedule-presets.mappers"; +import { WEEKDAY } from "../../../../utils/mappers.utils"; +import { ApplyResult } from "src/time-and-attendance/utils/type.utils"; @Injectable() export class SchedulePresetsApplyService { diff --git a/src/time-and-attendance/modules/time-tracker/schedule-presets/services/schedule-presets-query.service.ts b/src/time-and-attendance/modules/time-tracker/schedule-presets/services/schedule-presets-query.service.ts index e406151..74d464b 100644 --- a/src/time-and-attendance/modules/time-tracker/schedule-presets/services/schedule-presets-query.service.ts +++ b/src/time-and-attendance/modules/time-tracker/schedule-presets/services/schedule-presets-query.service.ts @@ -1,8 +1,8 @@ import { Injectable, NotFoundException } from "@nestjs/common"; import { EmailToIdResolver } from "src/time-and-attendance/modules/shared/utils/resolve-email-id.utils"; import { PrismaService } from "src/prisma/prisma.service"; -import { PresetResponse, ShiftResponse } from "../types/schedule-presets.types"; import { Prisma } from "@prisma/client"; +import { PresetResponse, ShiftResponse } from "src/time-and-attendance/utils/type.utils"; @Injectable() export class SchedulePresetsQueryService { diff --git a/src/time-and-attendance/modules/time-tracker/schedule-presets/types/schedule-presets.types.ts b/src/time-and-attendance/modules/time-tracker/schedule-presets/types/schedule-presets.types.ts deleted file mode 100644 index ea2a3cd..0000000 --- a/src/time-and-attendance/modules/time-tracker/schedule-presets/types/schedule-presets.types.ts +++ /dev/null @@ -1,21 +0,0 @@ -export type ShiftResponse = { - week_day: string; - sort_order: number; - start_time: string; - end_time: string; - is_remote: boolean; - type: string; -}; - -export type PresetResponse = { - id: number; - name: string; - is_default: boolean; - shifts: ShiftResponse[]; -} - -export type ApplyResult = { - timesheet_id: number; - created: number; - skipped: number; -} \ No newline at end of file diff --git a/src/time-and-attendance/modules/time-tracker/shifts/controllers/shift.controller.ts b/src/time-and-attendance/modules/time-tracker/shifts/controllers/shift.controller.ts index 6f0358f..1658e3a 100644 --- a/src/time-and-attendance/modules/time-tracker/shifts/controllers/shift.controller.ts +++ b/src/time-and-attendance/modules/time-tracker/shifts/controllers/shift.controller.ts @@ -1,32 +1,30 @@ -import { BadRequestException, Body, Controller, Delete, Param, ParseIntPipe, Patch, Post } from "@nestjs/common"; -import { CreateResult, ShiftsUpsertService, UpdateResult } from "../services/shifts-upsert.service"; -import { updateShiftDto } from "../dtos/shift-update.dto"; +import { BadRequestException, Body, Controller, Delete, Param, Patch, Post } from "@nestjs/common"; +import { CreateResult, UpdateResult } from "src/time-and-attendance/utils/type.utils"; +import { ShiftsUpsertService } from "src/time-and-attendance/modules/time-tracker/shifts/services/shifts-upsert.service"; import { ShiftDto } from "../dtos/shift-create.dto"; +import { UpdateShiftDto } from "src/time-and-attendance/modules/time-tracker/shifts/dtos/shift-update.dto"; @Controller('shift') export class ShiftController { constructor( private readonly upsert_service: ShiftsUpsertService ){} - @Post(':timesheet_id') + @Post('create') createBatch( - @Param('timesheet_id', ParseIntPipe) timesheet_id: number, @Body()dtos: ShiftDto[]): Promise { const list = Array.isArray(dtos) ? dtos : []; - if(list.length === 0) throw new BadRequestException('Body is missing or invalid (create shifts)') - return this.upsert_service.createShifts(timesheet_id, dtos) + if(list.length === 0) throw new BadRequestException('Body is missing or invalid (create shifts)'); + return this.upsert_service.createShifts(dtos) } - @Patch() + + //change Body to receive dtos + @Patch('update') updateBatch( - @Body() body: { updates: { id: number; dto: updateShiftDto }[] }): Promise{ - const updates = Array.isArray(body?.updates) - ? body.updates.filter(update => Number.isFinite(update?.id) && typeof update.dto === "object") - : []; - if(updates.length === 0) { - throw new BadRequestException(`Body is missing or invalid (update shifts)`); - } - return this.upsert_service.updateShifts(updates); + @Body() dtos: UpdateShiftDto[]): Promise{ + const list = Array.isArray(dtos) ? dtos: []; + if(list.length === 0) throw new BadRequestException('Body is missing or invalid (update shifts)'); + return this.upsert_service.updateShifts(dtos); } @Delete(':shift_id') @@ -34,4 +32,4 @@ export class ShiftController { return this.upsert_service.deleteShift(shift_id); } -} \ No newline at end of file +} diff --git a/src/time-and-attendance/modules/time-tracker/shifts/dtos/shift-create.dto.ts b/src/time-and-attendance/modules/time-tracker/shifts/dtos/shift-create.dto.ts index 65413da..9393b04 100644 --- a/src/time-and-attendance/modules/time-tracker/shifts/dtos/shift-create.dto.ts +++ b/src/time-and-attendance/modules/time-tracker/shifts/dtos/shift-create.dto.ts @@ -1,6 +1,7 @@ import { IsBoolean, IsInt, IsOptional, IsString, MaxLength } from "class-validator"; export class ShiftDto { + @IsInt() @IsOptional() id: number; @IsInt() timesheet_id!: number; @IsInt() bank_code_id!: number; diff --git a/src/time-and-attendance/modules/time-tracker/shifts/dtos/shift-update.dto.ts b/src/time-and-attendance/modules/time-tracker/shifts/dtos/shift-update.dto.ts index 95ef660..a9f2901 100644 --- a/src/time-and-attendance/modules/time-tracker/shifts/dtos/shift-update.dto.ts +++ b/src/time-and-attendance/modules/time-tracker/shifts/dtos/shift-update.dto.ts @@ -1,7 +1,11 @@ import { PartialType, OmitType } from "@nestjs/swagger"; +import { IsInt } from "class-validator"; import { ShiftDto } from "./shift-create.dto"; -export class updateShiftDto extends PartialType ( - //allows update using ShiftDto and preventing OmitType variables to be modified - OmitType(ShiftDto, [ 'is_approved', 'timesheet_id'] as const) -){} \ No newline at end of file +export class UpdateShiftDto extends PartialType( + // allows update using ShiftDto and preventing OmitType variables to be modified + OmitType(ShiftDto, ['is_approved', 'timesheet_id'] as const), +) { + @IsInt() + id!: number; +} diff --git a/src/time-and-attendance/modules/time-tracker/shifts/services/shifts-archival.service.ts b/src/time-and-attendance/modules/time-tracker/shifts/services/shifts-archival.service.ts index 9a6671a..6f60390 100644 --- a/src/time-and-attendance/modules/time-tracker/shifts/services/shifts-archival.service.ts +++ b/src/time-and-attendance/modules/time-tracker/shifts/services/shifts-archival.service.ts @@ -2,7 +2,6 @@ import { Injectable } from "@nestjs/common"; import { ShiftsArchive } from "@prisma/client"; import { PrismaService } from "src/prisma/prisma.service"; - /** * _____________________________________________________________________________________ * diff --git a/src/time-and-attendance/modules/time-tracker/shifts/services/shifts-get.service.ts b/src/time-and-attendance/modules/time-tracker/shifts/services/shifts-get.service.ts index af1e947..67f887a 100644 --- a/src/time-and-attendance/modules/time-tracker/shifts/services/shifts-get.service.ts +++ b/src/time-and-attendance/modules/time-tracker/shifts/services/shifts-get.service.ts @@ -1,7 +1,7 @@ import { Injectable, NotFoundException } from "@nestjs/common"; import { PrismaService } from "src/prisma/prisma.service"; import { GetShiftDto } from "../dtos/shift-get.dto"; -import { toStringFromDate, toStringFromHHmm } from "../../../../utils/date-time-helpers"; +import { toStringFromDate, toStringFromHHmm } from "../../../../utils/date-time.utils"; import { shift_select } from "src/time-and-attendance/utils/selects.utils"; /** diff --git a/src/time-and-attendance/modules/time-tracker/shifts/services/shifts-upsert.service.ts b/src/time-and-attendance/modules/time-tracker/shifts/services/shifts-upsert.service.ts index 34d605c..e9c18d8 100644 --- a/src/time-and-attendance/modules/time-tracker/shifts/services/shifts-upsert.service.ts +++ b/src/time-and-attendance/modules/time-tracker/shifts/services/shifts-upsert.service.ts @@ -1,26 +1,12 @@ -import { toDateFromString, toHHmmFromString, toStringFromDate, toStringFromHHmm } from "../../../../utils/date-time-helpers"; +import { CreateResult, NormedOk, NormedErr, UpdatePayload, UpdateResult, Normalized, UpdateChanges } from "src/time-and-attendance/utils/type.utils"; +import { toDateFromString, toHHmmFromString, toStringFromDate, toStringFromHHmm } from "../../../../utils/date-time.utils"; import { BadRequestException, ConflictException, Injectable, NotFoundException } from "@nestjs/common"; -import { OvertimeService, WeekOvertimeSummary } from "src/time-and-attendance/domains/services/overtime.service"; -import { updateShiftDto } from "../dtos/shift-update.dto"; +import { OvertimeService } from "src/time-and-attendance/domains/services/overtime.service"; import { PrismaService } from "src/prisma/prisma.service"; +import { shift_select } from "src/time-and-attendance/utils/selects.utils"; import { GetShiftDto } from "../dtos/shift-get.dto"; import { ShiftDto } from "../dtos/shift-create.dto"; -import { shift_select } from "src/time-and-attendance/utils/selects.utils"; - -type Normalized = { date: Date; start_time: Date; end_time: Date; }; - -export type ShiftWithOvertimeDto = { - shift: GetShiftDto; - overtime: WeekOvertimeSummary; -}; - -export type CreateResult = { ok: true; data: ShiftWithOvertimeDto } | { ok: false; error: any }; -export type UpdatePayload = { id: number; dto: updateShiftDto }; -export type UpdateResult = { ok: true; id: number; data: ShiftWithOvertimeDto } | { ok: false; id: number; error: any }; -export type DeleteResult = { ok: true; id: number; overtime: WeekOvertimeSummary } | { ok: false; id: number; error: any }; - -type NormedOk = { index: number; dto: ShiftDto; normed: Normalized }; -type NormedErr = { index: number; error: any }; +import { UpdateShiftDto } from "src/time-and-attendance/modules/time-tracker/shifts/dtos/shift-update.dto"; const overlaps = (a: { start: Date; end: Date }, b: { start: Date; end: Date }) => !(a.end <= b.start || a.start >= b.end); @@ -40,7 +26,7 @@ export class ShiftsUpsertService { //checks for overlaping shifts //create new shifts //calculate overtime - async createShifts(timesheet_id: number, dtos: ShiftDto[]): Promise { + async createShifts(dtos: ShiftDto[]): Promise { if (!Array.isArray(dtos) || dtos.length === 0) return []; const normed_shift: Array = dtos.map((dto, index) => { @@ -99,7 +85,7 @@ export class ShiftsUpsertService { const existing_date = new Map(); for (const d of unique_dates) { const rows = await tx.shifts.findMany({ - where: { timesheet_id, date: d }, + where: { date: d }, select: { start_time: true, end_time: true }, }); existing_date.set(d.getTime(), rows.map(r => ({ start_time: r.start_time, end_time: r.end_time }))); @@ -129,7 +115,7 @@ export class ShiftsUpsertService { const row = await tx.shifts.create({ data: { - timesheet_id, + timesheet_id: dto.timesheet_id, bank_code_id: dto.bank_code_id, date: normed.date, start_time: normed.start_time, @@ -142,7 +128,7 @@ export class ShiftsUpsertService { existing.push({ start_time: row.start_time, end_time: row.end_time }); - const summary = await this.overtime.getWeekOvertimeSummary(timesheet_id, normed.date, tx); + const summary = await this.overtime.getWeekOvertimeSummary(dto.timesheet_id, normed.date, tx); const shift: GetShiftDto = { timesheet_id: row.timesheet_id, bank_code_id: row.bank_code_id, @@ -172,8 +158,25 @@ export class ShiftsUpsertService { // update shifts in DB // recalculate overtime after update // return an updated version to display - async updateShifts(updates: UpdatePayload[]): Promise { - if (!Array.isArray(updates) || updates.length === 0) return []; + async updateShifts(dtos: UpdateShiftDto[]): Promise { + if (!Array.isArray(dtos) || dtos.length === 0) return []; + + const updates: UpdatePayload[] = dtos.map((item) => { + const { id, ...rest } = item; + if (!Number.isInteger(id)) { + throw new BadRequestException('Update shift payload is missing a valid id'); + } + + const changes: UpdateChanges = {}; + if (rest.date !== undefined) changes.date = rest.date; + if (rest.start_time !== undefined) changes.start_time = rest.start_time; + if (rest.end_time !== undefined) changes.end_time = rest.end_time; + if (rest.bank_code_id !== undefined) changes.bank_code_id = rest.bank_code_id; + if (rest.is_remote !== undefined) changes.is_remote = rest.is_remote; + if (rest.comment !== undefined) changes.comment = rest.comment; + + return { id, dto: changes }; + }); return this.prisma.$transaction(async (tx) => { const shift_ids = updates.map(update_shift => update_shift.id); @@ -343,4 +346,4 @@ export class ShiftsUpsertService { const end_time = toHHmmFromString(dto.end_time); return { date, start_time, end_time }; } -} \ No newline at end of file +} diff --git a/src/time-and-attendance/modules/time-tracker/timesheets/helpers/timesheets-date-time-helpers.ts b/src/time-and-attendance/modules/time-tracker/timesheets/helpers/timesheets-date-time-helpers.ts deleted file mode 100644 index 3b06344..0000000 --- a/src/time-and-attendance/modules/time-tracker/timesheets/helpers/timesheets-date-time-helpers.ts +++ /dev/null @@ -1,36 +0,0 @@ -export function weekStartSunday(date_local: Date): Date { - const start = new Date(Date.UTC(date_local.getFullYear(), date_local.getMonth(), date_local.getDate())); - const dow = start.getDay(); - start.setDate(start.getDate() - dow); - start.setHours(0, 0, 0, 0); - return start; -} - -export const toDateFromString = ( date: Date | string):Date => { - const d = new Date(date); - return new Date(Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate())); -} - -export const sevenDaysFrom = (date: Date | string): Date[] => { - return Array.from({length: 7 }, (_,i) => { - const d = new Date(date); - d.setUTCDate(d.getUTCDate() + i ); - return d; - }); -} - -export const toStringFromDate = (date: Date | string): string => { - const d = toDateFromString(date); - const year = d.getUTCFullYear(); - const month = String(d.getUTCMonth() + 1).padStart(2, '0'); - const day = String(d.getUTCDate()).padStart(2, '0'); - return `${year}-${month}-${day}`; -} - -export const toHHmmFromDate = (input: Date | string): string => { - const date = new Date(input); - const hh = String(date.getUTCHours()).padStart(2, '0'); - const mm = String(date.getUTCMinutes()).padStart(2, '0'); - return `${hh}:${mm}`; -} - diff --git a/src/time-and-attendance/modules/time-tracker/timesheets/services/timesheet-get-overview.service.ts b/src/time-and-attendance/modules/time-tracker/timesheets/services/timesheet-get-overview.service.ts index 6576477..86b9134 100644 --- a/src/time-and-attendance/modules/time-tracker/timesheets/services/timesheet-get-overview.service.ts +++ b/src/time-and-attendance/modules/time-tracker/timesheets/services/timesheet-get-overview.service.ts @@ -1,25 +1,7 @@ -import { sevenDaysFrom, toDateFromString, toHHmmFromDate, toStringFromDate } from "../helpers/timesheets-date-time-helpers"; import { Injectable, NotFoundException } from "@nestjs/common"; import { PrismaService } from "src/prisma/prisma.service"; - -type TotalHours = { - regular: number; - evening: number; - emergency: number; - overtime: number; - vacation: number; - holiday: number; - sick: number; -}; - -type TotalExpenses = { - expenses: number; - per_diem: number; - on_call: number; - mileage: number; -}; - -const NUMBER_OF_TIMESHEETS_TO_RETURN = 2; +import { sevenDaysFrom, toStringFromDate, toHHmmFromDate, toDateFromString } from "src/time-and-attendance/utils/date-time.utils"; +import { TotalExpenses, TotalHours } from "src/time-and-attendance/utils/type.utils"; @Injectable() export class GetTimesheetsOverviewService { diff --git a/src/time-and-attendance/utils/constants.utils.ts b/src/time-and-attendance/utils/constants.utils.ts new file mode 100644 index 0000000..10318ce --- /dev/null +++ b/src/time-and-attendance/utils/constants.utils.ts @@ -0,0 +1,12 @@ +const NUMBER_OF_TIMESHEETS_TO_RETURN = 2; +const DAILY_LIMIT_HOURS = 8; +const WEEKLY_LIMIT_HOURS = 40; +const PAY_PERIOD_ANCHOR = 2023-12-17; +const ANCHOR_ISO = '2023-12-17'; // ancre date +const PERIOD_DAYS = 14; +const PERIODS_PER_YEAR = 26; +const MS_PER_DAY = 86_400_000; + +//REGEX CONSTANTS +const DATE_ISO_FORMAT = /^\d{4}-\d{2}-\d{2}$/; +const HH_MM_REGEX = /^([01]\d|2[0-3]):[0-5]\d$/; diff --git a/src/time-and-attendance/utils/date-time-helpers.ts b/src/time-and-attendance/utils/date-time-helpers.ts deleted file mode 100644 index 504a41b..0000000 --- a/src/time-and-attendance/utils/date-time-helpers.ts +++ /dev/null @@ -1,37 +0,0 @@ -//ensures the week starts from sunday -export function weekStartSunday(date_local: Date): Date { - const start = new Date(Date.UTC(date_local.getFullYear(), date_local.getMonth(), date_local.getDate())); - const dow = start.getDay(); // 0 = dimanche - start.setDate(start.getDate() - dow); - start.setHours(0, 0, 0, 0); - return start; -} - -//converts string to HHmm format -export const toStringFromHHmm = (date: Date): string => { - const hh = date.getUTCHours().toString().padStart(2, '0'); - const mm = date.getUTCMinutes().toString().padStart(2, '0'); - return `${hh}:${mm}`; -} - -//converts string to Date format -export const toStringFromDate = (date: Date) => - date.toISOString().slice(0,10); - -//converts HHmm format to string -export const toHHmmFromString = (hhmm: string): Date => { - const [hh, mm] = hhmm.split(':').map(Number); - const date = new Date('1970-01-01T00:00:00.000Z'); - date.setUTCHours(hh, mm, 0, 0); - return new Date(date); -} - -//converts Date format to string -export const toDateFromString = (ymd: string): Date => { - return new Date(`${ymd}T00:00:00:000Z`); -} - -export const toUTCDate = (iso: string | Date) => { - const d = typeof iso === 'string' ? new Date(iso + 'T00:00:00.000Z') : iso; - return new Date(Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate())); -}; \ No newline at end of file diff --git a/src/time-and-attendance/utils/date-time.utils.ts b/src/time-and-attendance/utils/date-time.utils.ts new file mode 100644 index 0000000..4373fe7 --- /dev/null +++ b/src/time-and-attendance/utils/date-time.utils.ts @@ -0,0 +1,86 @@ +//ensures the week starts from sunday +export function weekStartSunday(date_local: Date): Date { + const start = new Date(Date.UTC(date_local.getFullYear(), date_local.getMonth(), date_local.getDate())); + const dow = start.getDay(); // 0 = dimanche + start.setDate(start.getDate() - dow); + start.setHours(0, 0, 0, 0); + return start; +} + +//converts string to HHmm format +export const toStringFromHHmm = (date: Date): string => { + const hh = date.getUTCHours().toString().padStart(2, '0'); + const mm = date.getUTCMinutes().toString().padStart(2, '0'); + return `${hh}:${mm}`; +} + +//converts string to Date format +export const toStringFromDate = (date: Date) => + date.toISOString().slice(0,10); + + + +//converts HHmm format to string +export const toHHmmFromString = (hhmm: string): Date => { + const [hh, mm] = hhmm.split(':').map(Number); + const date = new Date('1970-01-01T00:00:00.000Z'); + date.setUTCHours(hh, mm, 0, 0); + return new Date(date); +} + +//converts string to HHmm format +export const toHHmmFromDate = (input: Date | string): string => { + const date = new Date(input); + const hh = String(date.getUTCHours()).padStart(2, '0'); + const mm = String(date.getUTCMinutes()).padStart(2, '0'); + return `${hh}:${mm}`; +} + +//converts Date format to string +export const toDateFromString = (ymd: string | Date): Date => { + return new Date(`${ymd}T00:00:00:000Z`); +} + +export const toUTCDateFromString = (iso: string | Date) => { + const d = typeof iso === 'string' ? new Date(iso + 'T00:00:00.000Z') : iso; + return new Date(Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate())); +}; + +export const sevenDaysFrom = (date: Date | string): Date[] => { + return Array.from({length: 7 }, (_,i) => { + const d = new Date(date); + d.setUTCDate(d.getUTCDate() + i ); + return d; + }); +} + +export function payYearOfDate(date: string | Date, anchorISO = ANCHOR_ISO): number { + const ANCHOR = toUTCDateFromString(anchorISO); + const d = toUTCDateFromString(date); + const days = Math.floor((+d - +ANCHOR) / MS_PER_DAY); + const cycles = Math.floor(days / (PERIODS_PER_YEAR * PERIOD_DAYS)); + return ANCHOR.getUTCFullYear() + 1 + cycles; +} +//compute labels for periods +export function computePeriod(pay_year: number, period_no: number, anchorISO = ANCHOR_ISO) { + const ANCHOR = toUTCDateFromString(anchorISO); + const cycles = pay_year - (ANCHOR.getUTCFullYear() + 1); + const offsetPeriods = cycles * PERIODS_PER_YEAR + (period_no - 1); + const start = new Date(+ANCHOR + offsetPeriods * PERIOD_DAYS * MS_PER_DAY); + const end = new Date(+start + (PERIOD_DAYS - 1) * MS_PER_DAY); + const pay = new Date(end.getTime() + 6 * MS_PER_DAY); + return { + period_no: period_no, + pay_year: pay_year, + payday: toStringFromDate(pay), + period_start: toStringFromDate(start), + period_end: toStringFromDate(end), + label: `${toStringFromDate(start)}.${toStringFromDate(end)}`, + start, end, + }; +} + +//list of all 26 periods for a full year +export function listPayYear(pay_year: number, anchorISO = ANCHOR_ISO) { + return Array.from({ length: PERIODS_PER_YEAR }, (_, i) => computePeriod(pay_year, i + 1, anchorISO)); +} \ No newline at end of file diff --git a/src/time-and-attendance/modules/time-tracker/schedule-presets/mappers/schedule-presets.mappers.ts b/src/time-and-attendance/utils/mappers.utils.ts similarity index 100% rename from src/time-and-attendance/modules/time-tracker/schedule-presets/mappers/schedule-presets.mappers.ts rename to src/time-and-attendance/utils/mappers.utils.ts diff --git a/src/time-and-attendance/utils/regex.constants.ts b/src/time-and-attendance/utils/regex.constants.ts deleted file mode 100644 index d192d70..0000000 --- a/src/time-and-attendance/utils/regex.constants.ts +++ /dev/null @@ -1,2 +0,0 @@ -const DATE_ISO_FORMAT = /^\d{4}-\d{2}-\d{2}$/; -const HH_MM_REGEX = /^([01]\d|2[0-3]):[0-5]\d$/; \ No newline at end of file diff --git a/src/time-and-attendance/utils/selects.utils.ts b/src/time-and-attendance/utils/selects.utils.ts index 0140936..3dbf127 100644 --- a/src/time-and-attendance/utils/selects.utils.ts +++ b/src/time-and-attendance/utils/selects.utils.ts @@ -1,48 +1,49 @@ import { Prisma } from "@prisma/client"; - export const expense_select = { - id: true, - timesheet_id: true, - bank_code_id: true, - attachment: true, - date: true, - amount: true, - mileage: true, - comment: true, - supervisor_comment: true, - is_approved: true, + id: true, + timesheet_id: true, + bank_code_id: true, + attachment: true, + date: true, + amount: true, + mileage: true, + comment: true, + supervisor_comment: true, + is_approved: true, } satisfies Prisma.ExpensesSelect; export const shift_select = { - id: true, - timesheet_id: true, - bank_code_id: true, - date: true, - start_time: true, - end_time: true, - is_remote: true, - is_approved: true, - comment: true, + id: true, + timesheet_id: true, + bank_code_id: true, + date: true, + start_time: true, + end_time: true, + is_remote: true, + is_approved: true, + comment: true, } satisfies Prisma.ShiftsSelect; export const leaveRequestsSelect = { - id: true, - bank_code_id: true, - leave_type: true, - date: true, - payable_hours: true, - requested_hours: true, - comment: true, - approval_status: true, - employee: { select: { - id: true, - user: { select: { - email: true, - first_name: true, - last_name: true, - }}, - }}, + id: true, + bank_code_id: true, + leave_type: true, + date: true, + payable_hours: true, + requested_hours: true, + comment: true, + approval_status: true, + employee: { + select: { + id: true, + user: { + select: { + email: true, + first_name: true, + last_name: true, + }, + }, + } + }, } satisfies Prisma.LeaveRequestsSelect; - -export type LeaveRequestRow = Prisma.LeaveRequestsGetPayload<{ select: typeof leaveRequestsSelect}>; \ No newline at end of file diff --git a/src/time-and-attendance/utils/type.utils.ts b/src/time-and-attendance/utils/type.utils.ts new file mode 100644 index 0000000..e25b41c --- /dev/null +++ b/src/time-and-attendance/utils/type.utils.ts @@ -0,0 +1,64 @@ +import { WeekOvertimeSummary } from "src/time-and-attendance/domains/services/overtime.service"; +import { leaveRequestsSelect } from "src/time-and-attendance/utils/selects.utils"; +import { UpdateShiftDto } from "src/time-and-attendance/modules/time-tracker/shifts/dtos/shift-update.dto"; +import { GetShiftDto } from "src/time-and-attendance/modules/time-tracker/shifts/dtos/shift-get.dto"; +import { ShiftDto } from "src/time-and-attendance/modules/time-tracker/shifts/dtos/shift-create.dto"; +import { Prisma } from "@prisma/client"; + +export type TotalHours = { + regular: number; + evening: number; + emergency: number; + overtime: number; + vacation: number; + holiday: number; + sick: number; +}; + +export type TotalExpenses = { + expenses: number; + per_diem: number; + on_call: number; + mileage: number; +}; + +export type Normalized = { date: Date; start_time: Date; end_time: Date; }; + +export type ShiftWithOvertimeDto = { + shift: GetShiftDto; + overtime: WeekOvertimeSummary; +}; + +export type CreateResult = { ok: true; data: ShiftWithOvertimeDto } | { ok: false; error: any }; +export type UpdateChanges = Omit; +export type UpdatePayload = { id: number; dto: UpdateChanges }; +export type UpdateResult = { ok: true; id: number; data: ShiftWithOvertimeDto } | { ok: false; id: number; error: any }; +export type DeleteResult = { ok: true; id: number; overtime: WeekOvertimeSummary } | { ok: false; id: number; error: any }; + +export type NormedOk = { index: number; dto: ShiftDto; normed: Normalized }; +export type NormedErr = { index: number; error: any }; + + +export type ShiftResponse = { + week_day: string; + sort_order: number; + start_time: string; + end_time: string; + is_remote: boolean; + type: string; +}; + +export type PresetResponse = { + id: number; + name: string; + is_default: boolean; + shifts: ShiftResponse[]; +} + +export type ApplyResult = { + timesheet_id: number; + created: number; + skipped: number; +} + +export type LeaveRequestRow = Prisma.LeaveRequestsGetPayload<{ select: typeof leaveRequestsSelect}>;