refactor(shifts): modified return and switched bank_code_id for types

This commit is contained in:
Matthieu Haineault 2025-11-04 08:31:38 -05:00
parent bdbec4f68c
commit 6adb614931
22 changed files with 203 additions and 118 deletions

View File

@ -279,6 +279,20 @@
]
}
},
"/timesheets/timesheet-approval": {
"patch": {
"operationId": "TimesheetController_approveTimesheet",
"parameters": [],
"responses": {
"200": {
"description": ""
}
},
"tags": [
"Timesheet"
]
}
},
"/preferences": {
"patch": {
"operationId": "PreferencesController_updatePreferences",

View File

@ -18,14 +18,14 @@ export abstract class BaseApprovalService<T> {
//returns the corresponding Prisma delegate
protected abstract get delegate(): UpdatableDelegate<T>;
protected abstract delegateFor(transaction: Prisma.TransactionClient): UpdatableDelegate<T>;
protected abstract delegateFor(tx: Prisma.TransactionClient): UpdatableDelegate<T>;
//standard update Aproval
async updateApproval(id: number, isApproved: boolean): Promise<T> {
async updateApproval(id: number, is_approved: boolean): Promise<T> {
try{
return await this.delegate.update({
where: { id },
data: { is_approved: isApproved },
data: { is_approved: is_approved },
});
}catch (error: any) {
if (error instanceof PrismaClientKnownRequestError && error.code === "P2025") {
@ -36,11 +36,11 @@ export abstract class BaseApprovalService<T> {
}
//approval with transaction to avoid many requests
async updateApprovalWithTransaction(transaction: Prisma.TransactionClient, id: number, isApproved: boolean): Promise<T> {
async updateApprovalWithTransaction(tx: Prisma.TransactionClient, id: number, is_approved: boolean): Promise<T> {
try {
return await this.delegateFor(transaction).update({
return await this.delegateFor(tx).update({
where: { id },
data: { is_approved: isApproved },
data: { is_approved: is_approved },
});
} catch (error: any ){
if(error instanceof PrismaClientKnownRequestError && error.code === 'P2025') {

View File

@ -26,7 +26,7 @@ export class HolidayLeaveRequestsService {
async create(dto: UpsertLeaveRequestDto): Promise<UpsertResult> {
const email = dto.email.trim();
const employee_id = await this.emailResolver.findIdByEmail(email);
const bank_code = await this.typeResolver.findByType(LeaveTypes.HOLIDAY);
const bank_code = await this.typeResolver.findIdAndModifierByType(LeaveTypes.HOLIDAY);
const dates = normalizeDates(dto.dates);
if (!bank_code) throw new NotFoundException(`bank_code not found`);
if (!dates.length) throw new BadRequestException('Dates array must not be empty');

View File

@ -95,7 +95,7 @@ export class LeaveRequestsService {
async update(dto: UpsertLeaveRequestDto, type: LeaveTypes): Promise<UpsertResult> {
const email = dto.email.trim();
const employee_id = await this.emailResolver.findIdByEmail(email);
const bank_code = await this.typeResolver.findByType(type);
const bank_code = await this.typeResolver.findIdAndModifierByType(type);
if(!bank_code) throw new NotFoundException(`bank_code not found`);
const modifier = Number(bank_code.modifier ?? 1);
const dates = normalizeDates(dto.dates);

View File

@ -25,7 +25,7 @@ export class SickLeaveRequestsService {
async create(dto: UpsertLeaveRequestDto): Promise<UpsertResult> {
const email = dto.email.trim();
const employee_id = await this.emailResolver.findIdByEmail(email);
const bank_code = await this.typeResolver.findByType(LeaveTypes.SICK);
const bank_code = await this.typeResolver.findIdAndModifierByType(LeaveTypes.SICK);
if(!bank_code) throw new NotFoundException(`bank_code not found`);
const modifier = bank_code.modifier ?? 1;

View File

@ -24,7 +24,7 @@ export class VacationLeaveRequestsService {
async create(dto: UpsertLeaveRequestDto): Promise<UpsertResult> {
const email = dto.email.trim();
const employee_id = await this.emailResolver.findIdByEmail(email);
const bank_code = await this.typeResolver.findByType(LeaveTypes.VACATION);
const bank_code = await this.typeResolver.findIdAndModifierByType(LeaveTypes.VACATION);
if(!bank_code) throw new NotFoundException(`bank_code not found`);
const modifier = bank_code.modifier ?? 1;

View File

@ -4,7 +4,6 @@ import { Module } from "@nestjs/common";
import { PayPeriodsCommandService } from "src/time-and-attendance/pay-period/services/pay-periods-command.service";
import { TimesheetsModule } from "src/time-and-attendance/time-tracker/timesheets/timesheets.module";
import { EmailToIdResolver } from "src/time-and-attendance/utils/resolve-email-id.utils";
import { TimesheetApprovalService } from "src/time-and-attendance/time-tracker/timesheets/services/timesheet-approval.service";
@Module({
imports:[TimesheetsModule],
@ -13,8 +12,7 @@ import { TimesheetApprovalService } from "src/time-and-attendance/time-tracker/t
PayPeriodsQueryService,
PayPeriodsCommandService,
EmailToIdResolver,
TimesheetApprovalService,
],
})
export class PayperiodsModule {}
export class PayperiodsModule {}

View File

@ -8,7 +8,7 @@ import { TimesheetApprovalService } from "src/time-and-attendance/time-tracker/t
export class PayPeriodsCommandService {
constructor(
private readonly prisma: PrismaService,
private readonly timesheets_approval: TimesheetApprovalService,
private readonly timesheetsApproval: TimesheetApprovalService,
private readonly query: PayPeriodsQueryService,
) {}
@ -49,7 +49,7 @@ export class PayPeriodsCommandService {
for(const item of items) {
const { period_start, period_end } = await getPeriod(item.pay_year, item.period_no);
const t_sheets = await transaction.timesheets.findMany({
const timesheets = await transaction.timesheets.findMany({
where: {
employee: { user: { email: item.employee_email } },
OR: [
@ -60,8 +60,8 @@ export class PayPeriodsCommandService {
select: { id: true },
});
for(const { id } of t_sheets) {
await this.timesheets_approval.cascadeApprovalWithtx(transaction, id, item.approve);
for(const { id } of timesheets) {
await this.timesheetsApproval.cascadeApprovalWithtx(transaction, id, item.approve);
updated++;
}

View File

@ -12,7 +12,9 @@ import { ShiftController } from "src/time-and-attendance/time-tracker/shifts/con
import { ShiftsGetService } from "src/time-and-attendance/time-tracker/shifts/services/shifts-get.service";
import { ShiftsUpsertService } from "src/time-and-attendance/time-tracker/shifts/services/shifts-upsert.service";
import { TimesheetController } from "src/time-and-attendance/time-tracker/timesheets/controllers/timesheet.controller";
import { TimesheetApprovalService } from "src/time-and-attendance/time-tracker/timesheets/services/timesheet-approval.service";
import { GetTimesheetsOverviewService } from "src/time-and-attendance/time-tracker/timesheets/services/timesheet-get-overview.service";
import { TimesheetsModule } from "src/time-and-attendance/time-tracker/timesheets/timesheets.module";
import { BankCodesResolver } from "src/time-and-attendance/utils/resolve-bank-type-id.utils";
import { EmailToIdResolver } from "src/time-and-attendance/utils/resolve-email-id.utils";
@ -20,6 +22,7 @@ import { EmailToIdResolver } from "src/time-and-attendance/utils/resolve-email-i
imports: [
BusinessLogicsModule,
PayperiodsModule,
TimesheetsModule,
],
controllers: [
TimesheetController,
@ -37,6 +40,7 @@ import { EmailToIdResolver } from "src/time-and-attendance/utils/resolve-email-i
SchedulePresetsApplyService,
EmailToIdResolver,
BankCodesResolver,
TimesheetApprovalService,
],
exports: [],
exports: [TimesheetApprovalService ],
}) export class TimeAndAttendanceModule { };

View File

@ -171,7 +171,7 @@ export class SchedulePresetsUpsertService {
const bank_code_set = new Map<string, number>();
for (const type of types) {
const { id } = await this.typeResolver.findByType(type);
const { id } = await this.typeResolver.findIdAndModifierByType(type);
bank_code_set.set(type, id)
}
const toTime = (hhmm: string) => new Date(`1970-01-01T${hhmm}:00.000Z`);

View File

@ -5,10 +5,14 @@ import { ShiftsUpsertService } from "src/time-and-attendance/time-tracker/shifts
import { CreateShiftResult, UpdateShiftResult } from "src/time-and-attendance/utils/type.utils";
import { Roles as RoleEnum } from '.prisma/client';
import { RolesAllowed } from "src/common/decorators/roles.decorators";
import { BankCodesResolver } from "src/time-and-attendance/utils/resolve-bank-type-id.utils";
@Controller('shift')
export class ShiftController {
constructor( private readonly upsert_service: ShiftsUpsertService ){}
constructor(
private readonly upsert_service: ShiftsUpsertService,
private readonly typeResolver: BankCodesResolver,
){}
@Post('create')
@RolesAllowed(RoleEnum.EMPLOYEE, RoleEnum.ACCOUNTING, RoleEnum.HR, RoleEnum.SUPERVISOR, RoleEnum.ADMIN)

View File

@ -3,7 +3,7 @@ import { IsBoolean, IsInt, IsOptional, IsString, MaxLength } from "class-validat
export class ShiftDto {
@IsInt() @IsOptional() id: number;
@IsInt() timesheet_id!: number;
@IsInt() bank_code_id!: number;
@IsString() type!: string;
@IsString() date!: string;
@IsString() start_time!: string;

View File

@ -1,10 +1,10 @@
export class GetShiftDto {
timesheet_id: number;
bank_code_id: number;
date: string;
type: string;
date: string;
start_time: string;
end_time: string;
is_remote: boolean;
end_time: string;
is_remote: boolean;
is_approved: boolean;
comment?: string;
}

View File

@ -44,7 +44,7 @@ export class ShiftsGetService {
const shift = row_by_id.get(id)!;
return {
timesheet_id: shift.timesheet_id,
bank_code_id: shift.bank_code_id,
type: shift.bank_code.type,
date: toStringFromDate(shift.date),
start_time: toStringFromHHmm(shift.start_time),
end_time: toStringFromHHmm(shift.end_time),

View File

@ -7,7 +7,8 @@ import { EmailToIdResolver } from "src/time-and-attendance/utils/resolve-email-i
import { ShiftDto } from "src/time-and-attendance/time-tracker/shifts/dtos/shift-create.dto";
import { GetShiftDto } from "src/time-and-attendance/time-tracker/shifts/dtos/shift-get.dto";
import { UpdateShiftDto } from "src/time-and-attendance/time-tracker/shifts/dtos/shift-update.dto";
import { shift_select } from "src/time-and-attendance/utils/selects.utils";
import { shift_select, timesheet_select } from "src/time-and-attendance/utils/selects.utils";
import { BankCodesResolver } from "src/time-and-attendance/utils/resolve-bank-type-id.utils";
@ -17,6 +18,7 @@ export class ShiftsUpsertService {
private readonly prisma: PrismaService,
private readonly overtime: OvertimeService,
private readonly emailResolver: EmailToIdResolver,
private readonly typeResolver: BankCodesResolver,
) { }
//_________________________________________________________________
@ -35,7 +37,7 @@ export class ShiftsUpsertService {
const normed_shifts = await Promise.all(
dtos.map(async (dto, index) => {
try {
const normed = this.normalizeShiftDto(dto);
const normed = await this.normalizeShiftDto(dto);
if (normed.end_time <= normed.start_time) {
return {
index,
@ -45,18 +47,12 @@ export class ShiftsUpsertService {
};
}
const start_date = weekStartSunday(normed.date);
const timesheet = await this.prisma.timesheets.findFirst({
where: { start_date, employee_id },
select: { id: true },
const timesheet = await this.prisma.timesheets.findUnique({
where: { id: dto.timesheet_id, employee_id },
select: timesheet_select,
});
if (!timesheet) {
return {
index,
error: new NotFoundException(`Timesheet not found`),
};
return { index, error: new NotFoundException(`Timesheet not found`)};
}
return {
@ -181,7 +177,7 @@ export class ShiftsUpsertService {
const row = await tx.shifts.create({
data: {
timesheet_id: timesheet_id,
bank_code_id: dto.bank_code_id,
bank_code_id: normed.id,
date: normed.date,
start_time: normed.start_time,
end_time: normed.end_time,
@ -194,10 +190,12 @@ export class ShiftsUpsertService {
existing.push({ start_time: row.start_time, end_time: row.end_time });
existing_map.set(map_key, existing);
const {type: bank_type} = await this.typeResolver.findTypeByBankCodeId(row.bank_code_id);
const summary = await this.overtime.getWeekOvertimeSummary(timesheet_id, normed.date, tx);
const shift: GetShiftDto = {
timesheet_id: timesheet_id,
bank_code_id: row.bank_code_id,
type: bank_type,
date: toStringFromDate(row.date),
start_time: toStringFromHHmm(row.start_time),
end_time: toStringFromHHmm(row.end_time),
@ -227,7 +225,7 @@ export class ShiftsUpsertService {
async updateShifts(dtos: UpdateShiftDto[]): Promise<UpdateShiftResult[]> {
if (!Array.isArray(dtos) || dtos.length === 0) return [];
const updates: UpdateShiftPayload[] = dtos.map((item) => {
const updates: UpdateShiftPayload[] = await Promise.all(dtos.map((item) => {
const { id, ...rest } = item;
if (!Number.isInteger(id)) {
throw new BadRequestException('Update shift payload is missing a valid id');
@ -237,12 +235,12 @@ export class ShiftsUpsertService {
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.type !== undefined) changes.type = rest.type;
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);
@ -266,7 +264,7 @@ export class ShiftsUpsertService {
}
}
const planned_updates = updates.map(update => {
const planned_updates = updates.map( update => {
const exist_shift = regroup_id.get(update.id)!;
const date_string = update.dto.date ?? toStringFromDate(exist_shift.date);
const start_string = update.dto.start_time ?? toStringFromHHmm(exist_shift.start_time);
@ -275,6 +273,7 @@ export class ShiftsUpsertService {
date: toDateFromString(date_string),
start_time: toHHmmFromString(start_string),
end_time: toHHmmFromString(end_string),
id: exist_shift.id,
};
return { update, exist_shift, normed };
});
@ -342,12 +341,12 @@ export class ShiftsUpsertService {
for (const planned of planned_updates) {
const data: any = {};
const { dto } = planned.update;
if (dto.date !== undefined) data.date = planned.normed.date;
if (dto.date !== undefined) data.date = planned.normed.date;
if (dto.start_time !== undefined) data.start_time = planned.normed.start_time;
if (dto.end_time !== undefined) data.end_time = planned.normed.end_time;
if (dto.bank_code_id !== undefined) data.bank_code_id = dto.bank_code_id;
if (dto.is_remote !== undefined) data.is_remote = dto.is_remote;
if (dto.comment !== undefined) data.comment = dto.comment ?? null;
if (dto.end_time !== undefined) data.end_time = planned.normed.end_time;
if (dto.type !== undefined) data.type = dto.type;
if (dto.is_remote !== undefined) data.is_remote = dto.is_remote;
if (dto.comment !== undefined) data.comment = dto.comment ?? null;
const row = await tx.shifts.update({
where: { id: planned.exist_shift.id },
@ -362,7 +361,7 @@ export class ShiftsUpsertService {
const shift: GetShiftDto = {
timesheet_id: row.timesheet_id,
bank_code_id: row.bank_code_id,
type: data.type,
date: toStringFromDate(row.date),
start_time: toStringFromHHmm(row.start_time),
end_time: toStringFromHHmm(row.end_time),
@ -406,10 +405,11 @@ export class ShiftsUpsertService {
// LOCAL HELPERS
//_________________________________________________________________
//converts all string hours and date to Date and HHmm formats
private normalizeShiftDto = (dto: ShiftDto): Normalized => {
private normalizeShiftDto = async (dto: ShiftDto): Promise<Normalized> => {
const { id: bank_code_id} = await this.typeResolver.findBankCodeIDByType(dto.type);
const date = toDateFromString(dto.date);
const start_time = toHHmmFromString(dto.start_time);
const end_time = toHHmmFromString(dto.end_time);
return { date, start_time, end_time };
return { date, start_time, end_time, id: bank_code_id };
}
}

View File

@ -1,19 +1,33 @@
import { Controller, Get, ParseIntPipe, Query, Req, UnauthorizedException} from "@nestjs/common";
import { Body, Controller, Get, ParseBoolPipe, ParseIntPipe, Patch, Query, Req, UnauthorizedException} from "@nestjs/common";
import { RolesAllowed } from "src/common/decorators/roles.decorators";
import { GetTimesheetsOverviewService } from "src/time-and-attendance/time-tracker/timesheets/services/timesheet-get-overview.service";
import { Roles as RoleEnum } from '.prisma/client';
import { TimesheetApprovalService } from "src/time-and-attendance/time-tracker/timesheets/services/timesheet-approval.service";
@Controller('timesheets')
export class TimesheetController {
constructor( private readonly timesheetOverview: GetTimesheetsOverviewService ){}
constructor(
private readonly timesheetOverview: GetTimesheetsOverviewService,
private readonly approvalService: TimesheetApprovalService,
){}
@Get()
@RolesAllowed(RoleEnum.SUPERVISOR, RoleEnum.HR, RoleEnum.ACCOUNTING, RoleEnum.ADMIN)
async getTimesheetByIds(
@Req() req, @Query('year', ParseIntPipe) year:number, @Query('period_number', ParseIntPipe) period_number: number) {
const email = req.user?.email;
if(!email) throw new UnauthorizedException('Unauthorized User'); 
if(!email) throw new UnauthorizedException('Unauthorized User');
return this.timesheetOverview.getTimesheetsForEmployeeByPeriod(email, year, period_number);
}
@Patch('timesheet-approval')
@RolesAllowed(RoleEnum.SUPERVISOR, RoleEnum.HR, RoleEnum.ACCOUNTING, RoleEnum.ADMIN)
async approveTimesheet(
@Body('timesheet_id', ParseIntPipe) timesheet_id: number,
@Body('is_approved' , ParseBoolPipe) is_approved: boolean,
) {
return this.approvalService.approveTimesheetById(timesheet_id, is_approved);
}
}

View File

@ -1,11 +1,15 @@
import { BaseApprovalService } from "src/common/shared/base-approval.service";
import { Prisma, Timesheets } from "@prisma/client";
import { PrismaService } from "src/prisma/prisma.service";
import { Injectable } from "@nestjs/common";
import { Injectable, NotFoundException } from "@nestjs/common";
import { timesheet_select } from "src/time-and-attendance/utils/selects.utils";
@Injectable()
export class TimesheetApprovalService extends BaseApprovalService<Timesheets>{
constructor(prisma: PrismaService){super(prisma)}
constructor(
prisma: PrismaService,
){super(prisma)}
//_____________________________________________________________________________________________
// APPROVAL AND DELEGATE METHODS
//_____________________________________________________________________________________________
@ -13,26 +17,43 @@ import { Injectable } from "@nestjs/common";
return this.prisma.timesheets;
}
protected delegateFor(transaction: Prisma.TransactionClient) {
return transaction.timesheets;
protected delegateFor(tx: Prisma.TransactionClient) {
return tx.timesheets;
}
async updateApproval(id: number, isApproved: boolean): Promise<Timesheets> {
return this.prisma.$transaction((transaction) =>
this.updateApprovalWithTransaction(transaction, id, isApproved),
async updateApproval(id: number, is_approved: boolean): Promise<Timesheets> {
return this.prisma.$transaction((tx) =>
this.updateApprovalWithTransaction(tx, id, is_approved),
);
}
async cascadeApprovalWithtx(transaction: Prisma.TransactionClient, timesheetId: number, isApproved: boolean): Promise<Timesheets> {
const timesheet = await this.updateApprovalWithTransaction(transaction, timesheetId, isApproved);
await transaction.shifts.updateMany({
where: { timesheet_id: timesheetId },
data: { is_approved: isApproved },
async cascadeApprovalWithtx(tx: Prisma.TransactionClient, timesheet_id: number, is_approved: boolean): Promise<Timesheets> {
const timesheet = await this.updateApprovalWithTransaction(tx, timesheet_id, is_approved);
await tx.shifts.updateMany({
where: { timesheet_id: timesheet_id },
data: { is_approved: is_approved },
});
await transaction.expenses.updateManyAndReturn({
where: { timesheet_id: timesheetId },
data: { is_approved: isApproved },
await tx.expenses.updateManyAndReturn({
where: { timesheet_id: timesheet_id },
data: { is_approved: is_approved },
});
return timesheet;
}
async approveTimesheetById( timesheet_id: number, is_approved: boolean){
return this.prisma.$transaction(async (tx) => {
const timesheet = await tx.timesheets.findUnique({
where: { id: timesheet_id },
select: { id: true },
});
if(!timesheet) throw new NotFoundException(`Timesheet with id: ${timesheet_id} not found`);
await this.cascadeApprovalWithtx(tx, timesheet_id, is_approved);
return tx.timesheets.findUnique({
where: { id: timesheet_id },
select: timesheet_select,
});
});
}
}

View File

@ -1,49 +1,49 @@
import { TimesheetsArchive } from "@prisma/client";
import { PrismaService } from "src/prisma/prisma.service";
// import { TimesheetsArchive } from "@prisma/client";
// import { PrismaService } from "src/prisma/prisma.service";
export class TimesheetArchiveService {
constructor(private readonly prisma: PrismaService){}
// export class TimesheetArchiveService {
// constructor(private readonly prisma: PrismaService){}
async archiveOld(): Promise<void> {
//calcul du cutoff pour archivation
const cutoff = new Date();
cutoff.setMonth(cutoff.getMonth() - 6)
// async archiveOld(): Promise<void> {
// //calcul du cutoff pour archivation
// const cutoff = new Date();
// cutoff.setMonth(cutoff.getMonth() - 6)
await this.prisma.$transaction(async transaction => {
//fetches all timesheets to cutoff
const oldSheets = await transaction.timesheets.findMany({
where: { shift: { some: { date: { lt: cutoff } } },
},
select: {
id: true,
employee_id: true,
is_approved: true,
},
});
if( oldSheets.length === 0) return;
// await this.prisma.$transaction(async transaction => {
// //fetches all timesheets to cutoff
// const oldSheets = await transaction.timesheets.findMany({
// where: { shift: { some: { date: { lt: cutoff } } },
// },
// select: {
// id: true,
// employee_id: true,
// is_approved: true,
// },
// });
// if( oldSheets.length === 0) return;
//preping data for archivation
const archiveDate = oldSheets.map(sheet => ({
timesheet_id: sheet.id,
employee_id: sheet.employee_id,
is_approved: sheet.is_approved,
}));
// //preping data for archivation
// const archiveDate = oldSheets.map(sheet => ({
// timesheet_id: sheet.id,
// employee_id: sheet.employee_id,
// is_approved: sheet.is_approved,
// }));
//copying data from timesheets table to archive table
await transaction.timesheetsArchive.createMany({ data: archiveDate });
// //copying data from timesheets table to archive table
// await transaction.timesheetsArchive.createMany({ data: archiveDate });
//removing data from timesheets table
await transaction.timesheets.deleteMany({ where: { id: { in: oldSheets.map(s => s.id) } } });
});
}
// //removing data from timesheets table
// await transaction.timesheets.deleteMany({ where: { id: { in: oldSheets.map(s => s.id) } } });
// });
// }
//fetches all archived timesheets
async findAllArchived(): Promise<TimesheetsArchive[]> {
return this.prisma.timesheetsArchive.findMany();
}
// //fetches all archived timesheets
// async findAllArchived(): Promise<TimesheetsArchive[]> {
// return this.prisma.timesheetsArchive.findMany();
// }
//fetches an archived timesheet
async findOneArchived(id: number): Promise<TimesheetsArchive> {
return this.prisma.timesheetsArchive.findUniqueOrThrow({ where: { id } });
}
}
// //fetches an archived timesheet
// async findOneArchived(id: number): Promise<TimesheetsArchive> {
// return this.prisma.timesheetsArchive.findUniqueOrThrow({ where: { id } });
// }
// }

View File

@ -2,19 +2,16 @@
import { Module } from '@nestjs/common';
import { TimesheetController } from 'src/time-and-attendance/time-tracker/timesheets/controllers/timesheet.controller';
import { TimesheetApprovalService } from 'src/time-and-attendance/time-tracker/timesheets/services/timesheet-approval.service';
import { TimesheetArchiveService } from 'src/time-and-attendance/time-tracker/timesheets/services/timesheet-archive.service';
import { GetTimesheetsOverviewService } from 'src/time-and-attendance/time-tracker/timesheets/services/timesheet-get-overview.service';
import { EmailToIdResolver } from 'src/time-and-attendance/utils/resolve-email-id.utils';
@Module({
controllers: [TimesheetController],
providers: [
TimesheetArchiveService,
GetTimesheetsOverviewService,
TimesheetApprovalService,
EmailToIdResolver,
TimesheetApprovalService,
],
exports: [],
exports: [TimesheetApprovalService],
})
export class TimesheetsModule {}

View File

@ -9,7 +9,7 @@ export class BankCodesResolver {
constructor(private readonly prisma: PrismaService) {}
//find id and modifier by type
readonly findByType = async ( type: string, client?: Tx
readonly findIdAndModifierByType = async ( type: string, client?: Tx
): Promise<{id:number; modifier: number }> => {
const db = client ?? this.prisma;
const bank = await db.bankCodes.findFirst({
@ -20,4 +20,25 @@ export class BankCodesResolver {
if(!bank) throw new NotFoundException(`Unknown bank code type: ${type}`);
return { id: bank.id, modifier: bank.modifier };
};
//finds only id by type
readonly findBankCodeIDByType = async (type: string, client?: Tx) => {
const db = client ?? this.prisma;
const bank_code_id = await db.bankCodes.findFirst({
where: { type },
select: {id: true},
});
if(!bank_code_id) throw new NotFoundException(`Unkown bank type: ${type}`);
return bank_code_id;
}
readonly findTypeByBankCodeId = async (bank_code_id: number, client?: Tx) => {
const db = client ?? this.prisma;
const type = await db.bankCodes.findFirst({
where: { id: bank_code_id },
select: { type: true },
});
if(!type) throw new NotFoundException(`Type with id : ${bank_code_id} not found`);
return type;
}
}

View File

@ -1,4 +1,5 @@
import { Prisma } from "@prisma/client";
import { dmmfToRuntimeDataModel } from "@prisma/client/runtime/library";
export const expense_select = {
id: true,
@ -17,6 +18,9 @@ export const shift_select = {
id: true,
timesheet_id: true,
bank_code_id: true,
bank_code: {
select: { type: true },
},
date: true,
start_time: true,
end_time: true,
@ -78,3 +82,11 @@ export const SHIFT_SELECT = {
export const SHIFT_ASC_ORDER = [{date: 'asc' as const}, {start_time: 'asc' as const}];
export const timesheet_select = {
id: true,
employee_id: true,
shift: true,
expense: true,
start_date: true,
is_approved: true,
} satisfies Prisma.TimesheetsSelect;

View File

@ -25,7 +25,7 @@ export type TotalExpenses = {
mileage: number;
};
export type Normalized = { date: Date; start_time: Date; end_time: Date; };
export type Normalized = { date: Date; start_time: Date; end_time: Date; id: number};
export type ShiftWithOvertimeDto = {
shift: GetShiftDto;