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:
parent
488f0341cc
commit
4cb01de970
|
|
@ -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": ""
|
||||||
|
|
|
||||||
|
|
@ -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";
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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";
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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;
|
|
||||||
}
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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";
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* _____________________________________________________________________________________
|
* _____________________________________________________________________________________
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -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";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -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 };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
11
src/time-and-attendance/utils/constants.utils.ts
Normal file
11
src/time-and-attendance/utils/constants.utils.ts
Normal 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$/;
|
||||||
|
|
@ -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;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
@ -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$/;
|
|
||||||
|
|
@ -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}>;
|
|
||||||
64
src/time-and-attendance/utils/type.utils.ts
Normal file
64
src/time-and-attendance/utils/type.utils.ts
Normal 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}>;
|
||||||
Loading…
Reference in New Issue
Block a user