feat(time_and_attendance): clean files of deprecated imports and moved utils to shared utils folder. Modified @Body for create and update shifts to use only an array of shifts

This commit is contained in:
Matthieu Haineault 2025-10-27 15:23:28 -04:00
parent 488f0341cc
commit 4cb01de970
26 changed files with 186 additions and 166 deletions

View File

@ -377,19 +377,10 @@
] ]
} }
}, },
"/shift/{timesheet_id}": { "/shift/create": {
"post": { "post": {
"operationId": "ShiftController_createBatch", "operationId": "ShiftController_createBatch",
"parameters": [ "parameters": [],
{
"name": "timesheet_id",
"required": true,
"in": "path",
"schema": {
"type": "number"
}
}
],
"requestBody": { "requestBody": {
"required": true, "required": true,
"content": { "content": {
@ -413,10 +404,23 @@
] ]
} }
}, },
"/shift": { "/shift/update": {
"patch": { "patch": {
"operationId": "ShiftController_updateBatch", "operationId": "ShiftController_updateBatch",
"parameters": [], "parameters": [],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"type": "string"
}
}
}
}
},
"responses": { "responses": {
"200": { "200": {
"description": "" "description": ""

View File

@ -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 { Injectable, NotFoundException } from "@nestjs/common";
import { updateExpenseDto } from "../dtos/expense-update.dto"; import { updateExpenseDto } from "../dtos/expense-update.dto";
import { GetExpenseDto } from "../dtos/expense-get.dto"; import { GetExpenseDto } from "../dtos/expense-get.dto";

View File

@ -1,6 +1,6 @@
import { Prisma } from "@prisma/client";
import { LeaveRequestViewDto } from "../dtos/leave-request-view.dto"; 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) => const toNum = (value?: Prisma.Decimal | null) =>
value !== null && value !== undefined ? Number(value) : undefined; value !== null && value !== undefined ? Number(value) : undefined;

View File

@ -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 { LeaveRequestViewDto } from '../dtos/leave-request-view.dto';
import { mapArchiveRowToView } from '../mappers/leave-requests-archive.mapper'; 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 { mapRowToView } from '../mappers/leave-requests.mapper';
import { LeaveRequestArchiveRow } from './leave-requests-archive.select';
/** Active (table leave_requests) : proxy to base mapper */ /** Active (table leave_requests) : proxy to base mapper */
export function mapRowToViewWithDays(row: LeaveRequestRow): LeaveRequestViewDto { export function mapRowToViewWithDays(row: LeaveRequestRow): LeaveRequestViewDto {

View File

@ -4,7 +4,7 @@ import { PrismaService } from "src/prisma/prisma.service";
import { LeaveTypes } from "@prisma/client"; import { LeaveTypes } from "@prisma/client";
// import { toDateOnly, toStringFromDate } from "src/time-and-attendance/modules/shared/helpers/date-time.helpers"; // 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 { 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"; // import { ShiftsUpsertService } from "src/time-and-attendance/modules/time-tracker/shifts/services/shifts-upsert.service";
@Injectable() @Injectable()

View File

@ -1,20 +1,18 @@
import { toStringFromDate, toUTCDate } from "src/time-and-attendance/utils/date-time-helpers"; import { toStringFromDate, toUTCDateFromString } from "src/time-and-attendance/utils/date-time.utils";
export const ANCHOR_ISO = '2023-12-17'; // ancre date 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 { export function payYearOfDate(date: string | Date, anchorISO = ANCHOR_ISO): number {
const ANCHOR = toUTCDate(anchorISO); const ANCHOR = toUTCDateFromString(anchorISO);
const d = toUTCDate(date); const d = toUTCDateFromString(date);
const days = Math.floor((+d - +ANCHOR) / MS_PER_DAY); const days = Math.floor((+d - +ANCHOR) / MS_PER_DAY);
const cycles = Math.floor(days / (PERIODS_PER_YEAR * PERIOD_DAYS)); const cycles = Math.floor(days / (PERIODS_PER_YEAR * PERIOD_DAYS));
return ANCHOR.getUTCFullYear() + 1 + cycles; return ANCHOR.getUTCFullYear() + 1 + cycles;
} }
//compute labels for periods //compute labels for periods
export function computePeriod(pay_year: number, period_no: number, anchorISO = ANCHOR_ISO) { export function computePeriod(pay_year: number, period_no: number, anchorISO = ANCHOR_ISO) {
const ANCHOR = toUTCDate(anchorISO); const ANCHOR = toUTCDateFromString(anchorISO);
const cycles = pay_year - (ANCHOR.getUTCFullYear() + 1); const cycles = pay_year - (ANCHOR.getUTCFullYear() + 1);
const offsetPeriods = cycles * PERIODS_PER_YEAR + (period_no - 1); const offsetPeriods = cycles * PERIODS_PER_YEAR + (period_no - 1);
const start = new Date(+ANCHOR + offsetPeriods * PERIOD_DAYS * MS_PER_DAY); const start = new Date(+ANCHOR + offsetPeriods * PERIOD_DAYS * MS_PER_DAY);

View File

@ -1,6 +1,6 @@
import { Injectable, NotFoundException } from "@nestjs/common"; import { Injectable, NotFoundException } from "@nestjs/common";
import { Prisma, PrismaClient } from "@prisma/client"; 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 { PrismaService } from "src/prisma/prisma.service";
import { EmailToIdResolver } from "./resolve-email-id.utils"; import { EmailToIdResolver } from "./resolve-email-id.utils";

View File

@ -1,8 +1,8 @@
import { BadRequestException, Body, Controller, Get, NotFoundException, Param, Post, Put, Query } from "@nestjs/common"; 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 { 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 { 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') @Controller('schedule-presets')
export class SchedulePresetsController { export class SchedulePresetsController {

View File

@ -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"; import { SchedulePresetShiftsDto } from "./create-schedule-preset-shifts.dto";
export class SchedulePresetsDto { export class SchedulePresetsDto {

View File

@ -1,9 +1,9 @@
import { BadRequestException, ConflictException, Injectable, NotFoundException } from "@nestjs/common"; import { BadRequestException, ConflictException, Injectable, NotFoundException } from "@nestjs/common";
import { EmailToIdResolver } from "src/time-and-attendance/modules/shared/utils/resolve-email-id.utils"; import { EmailToIdResolver } from "src/time-and-attendance/modules/shared/utils/resolve-email-id.utils";
import { PrismaService } from "src/prisma/prisma.service"; import { PrismaService } from "src/prisma/prisma.service";
import { ApplyResult } from "../types/schedule-presets.types";
import { Prisma, Weekday } from "@prisma/client"; 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() @Injectable()
export class SchedulePresetsApplyService { export class SchedulePresetsApplyService {

View File

@ -1,8 +1,8 @@
import { Injectable, NotFoundException } from "@nestjs/common"; import { Injectable, NotFoundException } from "@nestjs/common";
import { EmailToIdResolver } from "src/time-and-attendance/modules/shared/utils/resolve-email-id.utils"; import { EmailToIdResolver } from "src/time-and-attendance/modules/shared/utils/resolve-email-id.utils";
import { PrismaService } from "src/prisma/prisma.service"; import { PrismaService } from "src/prisma/prisma.service";
import { PresetResponse, ShiftResponse } from "../types/schedule-presets.types";
import { Prisma } from "@prisma/client"; import { Prisma } from "@prisma/client";
import { PresetResponse, ShiftResponse } from "src/time-and-attendance/utils/type.utils";
@Injectable() @Injectable()
export class SchedulePresetsQueryService { export class SchedulePresetsQueryService {

View File

@ -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;
}

View File

@ -1,32 +1,30 @@
import { BadRequestException, Body, Controller, Delete, Param, ParseIntPipe, Patch, Post } from "@nestjs/common"; import { BadRequestException, Body, Controller, Delete, Param, Patch, Post } from "@nestjs/common";
import { CreateResult, ShiftsUpsertService, UpdateResult } from "../services/shifts-upsert.service"; import { CreateResult, UpdateResult } from "src/time-and-attendance/utils/type.utils";
import { updateShiftDto } from "../dtos/shift-update.dto"; import { ShiftsUpsertService } from "src/time-and-attendance/modules/time-tracker/shifts/services/shifts-upsert.service";
import { ShiftDto } from "../dtos/shift-create.dto"; import { ShiftDto } from "../dtos/shift-create.dto";
import { UpdateShiftDto } from "src/time-and-attendance/modules/time-tracker/shifts/dtos/shift-update.dto";
@Controller('shift') @Controller('shift')
export class ShiftController { export class ShiftController {
constructor( private readonly upsert_service: ShiftsUpsertService ){} constructor( private readonly upsert_service: ShiftsUpsertService ){}
@Post(':timesheet_id') @Post('create')
createBatch( createBatch(
@Param('timesheet_id', ParseIntPipe) timesheet_id: number,
@Body()dtos: ShiftDto[]): Promise<CreateResult[]> { @Body()dtos: ShiftDto[]): Promise<CreateResult[]> {
const list = Array.isArray(dtos) ? dtos : []; const list = Array.isArray(dtos) ? dtos : [];
if(list.length === 0) throw new BadRequestException('Body is missing or invalid (create shifts)') if(list.length === 0) throw new BadRequestException('Body is missing or invalid (create shifts)');
return this.upsert_service.createShifts(timesheet_id, dtos) return this.upsert_service.createShifts(dtos)
} }
@Patch()
//change Body to receive dtos
@Patch('update')
updateBatch( updateBatch(
@Body() body: { updates: { id: number; dto: updateShiftDto }[] }): Promise<UpdateResult[]>{ @Body() dtos: UpdateShiftDto[]): Promise<UpdateResult[]>{
const updates = Array.isArray(body?.updates) const list = Array.isArray(dtos) ? dtos: [];
? body.updates.filter(update => Number.isFinite(update?.id) && typeof update.dto === "object") if(list.length === 0) throw new BadRequestException('Body is missing or invalid (update shifts)');
: []; return this.upsert_service.updateShifts(dtos);
if(updates.length === 0) {
throw new BadRequestException(`Body is missing or invalid (update shifts)`);
}
return this.upsert_service.updateShifts(updates);
} }
@Delete(':shift_id') @Delete(':shift_id')
@ -34,4 +32,4 @@ export class ShiftController {
return this.upsert_service.deleteShift(shift_id); return this.upsert_service.deleteShift(shift_id);
} }
} }

View File

@ -1,6 +1,7 @@
import { IsBoolean, IsInt, IsOptional, IsString, MaxLength } from "class-validator"; import { IsBoolean, IsInt, IsOptional, IsString, MaxLength } from "class-validator";
export class ShiftDto { export class ShiftDto {
@IsInt() @IsOptional() id: number;
@IsInt() timesheet_id!: number; @IsInt() timesheet_id!: number;
@IsInt() bank_code_id!: number; @IsInt() bank_code_id!: number;

View File

@ -1,7 +1,11 @@
import { PartialType, OmitType } from "@nestjs/swagger"; import { PartialType, OmitType } from "@nestjs/swagger";
import { IsInt } from "class-validator";
import { ShiftDto } from "./shift-create.dto"; import { ShiftDto } from "./shift-create.dto";
export class updateShiftDto extends PartialType ( export class UpdateShiftDto extends PartialType(
//allows update using ShiftDto and preventing OmitType variables to be modified // allows update using ShiftDto and preventing OmitType variables to be modified
OmitType(ShiftDto, [ 'is_approved', 'timesheet_id'] as const) OmitType(ShiftDto, ['is_approved', 'timesheet_id'] as const),
){} ) {
@IsInt()
id!: number;
}

View File

@ -2,7 +2,6 @@ import { Injectable } from "@nestjs/common";
import { ShiftsArchive } from "@prisma/client"; import { ShiftsArchive } from "@prisma/client";
import { PrismaService } from "src/prisma/prisma.service"; import { PrismaService } from "src/prisma/prisma.service";
/** /**
* _____________________________________________________________________________________ * _____________________________________________________________________________________
* *

View File

@ -1,7 +1,7 @@
import { Injectable, NotFoundException } from "@nestjs/common"; import { Injectable, NotFoundException } from "@nestjs/common";
import { PrismaService } from "src/prisma/prisma.service"; import { PrismaService } from "src/prisma/prisma.service";
import { GetShiftDto } from "../dtos/shift-get.dto"; 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"; import { shift_select } from "src/time-and-attendance/utils/selects.utils";
/** /**

View File

@ -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 { BadRequestException, ConflictException, Injectable, NotFoundException } from "@nestjs/common";
import { OvertimeService, WeekOvertimeSummary } from "src/time-and-attendance/domains/services/overtime.service"; import { OvertimeService } from "src/time-and-attendance/domains/services/overtime.service";
import { updateShiftDto } from "../dtos/shift-update.dto";
import { PrismaService } from "src/prisma/prisma.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 { GetShiftDto } from "../dtos/shift-get.dto";
import { ShiftDto } from "../dtos/shift-create.dto"; import { ShiftDto } from "../dtos/shift-create.dto";
import { shift_select } from "src/time-and-attendance/utils/selects.utils"; import { UpdateShiftDto } from "src/time-and-attendance/modules/time-tracker/shifts/dtos/shift-update.dto";
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 };
const overlaps = (a: { start: Date; end: Date }, b: { start: Date; end: Date }) => const overlaps = (a: { start: Date; end: Date }, b: { start: Date; end: Date }) =>
!(a.end <= b.start || a.start >= b.end); !(a.end <= b.start || a.start >= b.end);
@ -40,7 +26,7 @@ export class ShiftsUpsertService {
//checks for overlaping shifts //checks for overlaping shifts
//create new shifts //create new shifts
//calculate overtime //calculate overtime
async createShifts(timesheet_id: number, dtos: ShiftDto[]): Promise<CreateResult[]> { async createShifts(dtos: ShiftDto[]): Promise<CreateResult[]> {
if (!Array.isArray(dtos) || dtos.length === 0) return []; if (!Array.isArray(dtos) || dtos.length === 0) return [];
const normed_shift: Array<NormedOk | NormedErr> = dtos.map((dto, index) => { const normed_shift: Array<NormedOk | NormedErr> = dtos.map((dto, index) => {
@ -99,7 +85,7 @@ export class ShiftsUpsertService {
const existing_date = new Map<number, { start_time: Date; end_time: Date }[]>(); const existing_date = new Map<number, { start_time: Date; end_time: Date }[]>();
for (const d of unique_dates) { for (const d of unique_dates) {
const rows = await tx.shifts.findMany({ const rows = await tx.shifts.findMany({
where: { timesheet_id, date: d }, where: { date: d },
select: { start_time: true, end_time: true }, 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 }))); 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({ const row = await tx.shifts.create({
data: { data: {
timesheet_id, timesheet_id: dto.timesheet_id,
bank_code_id: dto.bank_code_id, bank_code_id: dto.bank_code_id,
date: normed.date, date: normed.date,
start_time: normed.start_time, start_time: normed.start_time,
@ -142,7 +128,7 @@ export class ShiftsUpsertService {
existing.push({ start_time: row.start_time, end_time: row.end_time }); 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 = { const shift: GetShiftDto = {
timesheet_id: row.timesheet_id, timesheet_id: row.timesheet_id,
bank_code_id: row.bank_code_id, bank_code_id: row.bank_code_id,
@ -172,8 +158,25 @@ export class ShiftsUpsertService {
// update shifts in DB // update shifts in DB
// recalculate overtime after update // recalculate overtime after update
// return an updated version to display // return an updated version to display
async updateShifts(updates: UpdatePayload[]): Promise<UpdateResult[]> { async updateShifts(dtos: UpdateShiftDto[]): Promise<UpdateResult[]> {
if (!Array.isArray(updates) || updates.length === 0) return []; 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) => { return this.prisma.$transaction(async (tx) => {
const shift_ids = updates.map(update_shift => update_shift.id); const shift_ids = updates.map(update_shift => update_shift.id);
@ -343,4 +346,4 @@ export class ShiftsUpsertService {
const end_time = toHHmmFromString(dto.end_time); const end_time = toHHmmFromString(dto.end_time);
return { date, start_time, end_time }; return { date, start_time, end_time };
} }
} }

View File

@ -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}`;
}

View File

@ -1,25 +1,7 @@
import { sevenDaysFrom, toDateFromString, toHHmmFromDate, toStringFromDate } from "../helpers/timesheets-date-time-helpers";
import { Injectable, NotFoundException } from "@nestjs/common"; import { Injectable, NotFoundException } from "@nestjs/common";
import { PrismaService } from "src/prisma/prisma.service"; import { PrismaService } from "src/prisma/prisma.service";
import { sevenDaysFrom, toStringFromDate, toHHmmFromDate, toDateFromString } from "src/time-and-attendance/utils/date-time.utils";
type TotalHours = { import { TotalExpenses, TotalHours } from "src/time-and-attendance/utils/type.utils";
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;
@Injectable() @Injectable()
export class GetTimesheetsOverviewService { export class GetTimesheetsOverviewService {

View File

@ -0,0 +1,11 @@
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 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$/;

View File

@ -18,6 +18,8 @@ export const toStringFromHHmm = (date: Date): string => {
export const toStringFromDate = (date: Date) => export const toStringFromDate = (date: Date) =>
date.toISOString().slice(0,10); date.toISOString().slice(0,10);
//converts HHmm format to string //converts HHmm format to string
export const toHHmmFromString = (hhmm: string): Date => { export const toHHmmFromString = (hhmm: string): Date => {
const [hh, mm] = hhmm.split(':').map(Number); const [hh, mm] = hhmm.split(':').map(Number);
@ -26,12 +28,28 @@ export const toHHmmFromString = (hhmm: string): Date => {
return new Date(date); 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 //converts Date format to string
export const toDateFromString = (ymd: string): Date => { export const toDateFromString = (ymd: string | Date): Date => {
return new Date(`${ymd}T00:00:00:000Z`); return new Date(`${ymd}T00:00:00:000Z`);
} }
export const toUTCDate = (iso: string | Date) => { export const toUTCDateFromString = (iso: string | Date) => {
const d = typeof iso === 'string' ? new Date(iso + 'T00:00:00.000Z') : iso; const d = typeof iso === 'string' ? new Date(iso + 'T00:00:00.000Z') : iso;
return new Date(Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate())); 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;
});
}

View File

@ -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$/;

View File

@ -1,6 +1,5 @@
import { Prisma } from "@prisma/client"; import { Prisma } from "@prisma/client";
export const expense_select = { export const expense_select = {
id: true, id: true,
timesheet_id: true, timesheet_id: true,
@ -44,5 +43,3 @@ export const leaveRequestsSelect = {
}}, }},
}}, }},
} satisfies Prisma.LeaveRequestsSelect; } satisfies Prisma.LeaveRequestsSelect;
export type LeaveRequestRow = Prisma.LeaveRequestsGetPayload<{ select: typeof leaveRequestsSelect}>;

View File

@ -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<UpdateShiftDto, 'id'>;
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}>;