refactor(shifts): modified return and switched bank_code_id for types
This commit is contained in:
parent
bdbec4f68c
commit
6adb614931
|
|
@ -279,6 +279,20 @@
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/timesheets/timesheet-approval": {
|
||||||
|
"patch": {
|
||||||
|
"operationId": "TimesheetController_approveTimesheet",
|
||||||
|
"parameters": [],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tags": [
|
||||||
|
"Timesheet"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
"/preferences": {
|
"/preferences": {
|
||||||
"patch": {
|
"patch": {
|
||||||
"operationId": "PreferencesController_updatePreferences",
|
"operationId": "PreferencesController_updatePreferences",
|
||||||
|
|
|
||||||
|
|
@ -18,14 +18,14 @@ export abstract class BaseApprovalService<T> {
|
||||||
//returns the corresponding Prisma delegate
|
//returns the corresponding Prisma delegate
|
||||||
protected abstract get delegate(): UpdatableDelegate<T>;
|
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
|
//standard update Aproval
|
||||||
async updateApproval(id: number, isApproved: boolean): Promise<T> {
|
async updateApproval(id: number, is_approved: boolean): Promise<T> {
|
||||||
try{
|
try{
|
||||||
return await this.delegate.update({
|
return await this.delegate.update({
|
||||||
where: { id },
|
where: { id },
|
||||||
data: { is_approved: isApproved },
|
data: { is_approved: is_approved },
|
||||||
});
|
});
|
||||||
}catch (error: any) {
|
}catch (error: any) {
|
||||||
if (error instanceof PrismaClientKnownRequestError && error.code === "P2025") {
|
if (error instanceof PrismaClientKnownRequestError && error.code === "P2025") {
|
||||||
|
|
@ -36,11 +36,11 @@ export abstract class BaseApprovalService<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
//approval with transaction to avoid many requests
|
//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 {
|
try {
|
||||||
return await this.delegateFor(transaction).update({
|
return await this.delegateFor(tx).update({
|
||||||
where: { id },
|
where: { id },
|
||||||
data: { is_approved: isApproved },
|
data: { is_approved: is_approved },
|
||||||
});
|
});
|
||||||
} catch (error: any ){
|
} catch (error: any ){
|
||||||
if(error instanceof PrismaClientKnownRequestError && error.code === 'P2025') {
|
if(error instanceof PrismaClientKnownRequestError && error.code === 'P2025') {
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ export class HolidayLeaveRequestsService {
|
||||||
async create(dto: UpsertLeaveRequestDto): Promise<UpsertResult> {
|
async create(dto: UpsertLeaveRequestDto): Promise<UpsertResult> {
|
||||||
const email = dto.email.trim();
|
const email = dto.email.trim();
|
||||||
const employee_id = await this.emailResolver.findIdByEmail(email);
|
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);
|
const dates = normalizeDates(dto.dates);
|
||||||
if (!bank_code) throw new NotFoundException(`bank_code not found`);
|
if (!bank_code) throw new NotFoundException(`bank_code not found`);
|
||||||
if (!dates.length) throw new BadRequestException('Dates array must not be empty');
|
if (!dates.length) throw new BadRequestException('Dates array must not be empty');
|
||||||
|
|
|
||||||
|
|
@ -95,7 +95,7 @@ export class LeaveRequestsService {
|
||||||
async update(dto: UpsertLeaveRequestDto, type: LeaveTypes): Promise<UpsertResult> {
|
async update(dto: UpsertLeaveRequestDto, type: LeaveTypes): Promise<UpsertResult> {
|
||||||
const email = dto.email.trim();
|
const email = dto.email.trim();
|
||||||
const employee_id = await this.emailResolver.findIdByEmail(email);
|
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`);
|
if(!bank_code) throw new NotFoundException(`bank_code not found`);
|
||||||
const modifier = Number(bank_code.modifier ?? 1);
|
const modifier = Number(bank_code.modifier ?? 1);
|
||||||
const dates = normalizeDates(dto.dates);
|
const dates = normalizeDates(dto.dates);
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ export class SickLeaveRequestsService {
|
||||||
async create(dto: UpsertLeaveRequestDto): Promise<UpsertResult> {
|
async create(dto: UpsertLeaveRequestDto): Promise<UpsertResult> {
|
||||||
const email = dto.email.trim();
|
const email = dto.email.trim();
|
||||||
const employee_id = await this.emailResolver.findIdByEmail(email);
|
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`);
|
if(!bank_code) throw new NotFoundException(`bank_code not found`);
|
||||||
|
|
||||||
const modifier = bank_code.modifier ?? 1;
|
const modifier = bank_code.modifier ?? 1;
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ export class VacationLeaveRequestsService {
|
||||||
async create(dto: UpsertLeaveRequestDto): Promise<UpsertResult> {
|
async create(dto: UpsertLeaveRequestDto): Promise<UpsertResult> {
|
||||||
const email = dto.email.trim();
|
const email = dto.email.trim();
|
||||||
const employee_id = await this.emailResolver.findIdByEmail(email);
|
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`);
|
if(!bank_code) throw new NotFoundException(`bank_code not found`);
|
||||||
|
|
||||||
const modifier = bank_code.modifier ?? 1;
|
const modifier = bank_code.modifier ?? 1;
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@ import { Module } from "@nestjs/common";
|
||||||
import { PayPeriodsCommandService } from "src/time-and-attendance/pay-period/services/pay-periods-command.service";
|
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 { TimesheetsModule } from "src/time-and-attendance/time-tracker/timesheets/timesheets.module";
|
||||||
import { EmailToIdResolver } from "src/time-and-attendance/utils/resolve-email-id.utils";
|
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({
|
@Module({
|
||||||
imports:[TimesheetsModule],
|
imports:[TimesheetsModule],
|
||||||
|
|
@ -13,7 +12,6 @@ import { TimesheetApprovalService } from "src/time-and-attendance/time-tracker/t
|
||||||
PayPeriodsQueryService,
|
PayPeriodsQueryService,
|
||||||
PayPeriodsCommandService,
|
PayPeriodsCommandService,
|
||||||
EmailToIdResolver,
|
EmailToIdResolver,
|
||||||
TimesheetApprovalService,
|
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ import { TimesheetApprovalService } from "src/time-and-attendance/time-tracker/t
|
||||||
export class PayPeriodsCommandService {
|
export class PayPeriodsCommandService {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly prisma: PrismaService,
|
private readonly prisma: PrismaService,
|
||||||
private readonly timesheets_approval: TimesheetApprovalService,
|
private readonly timesheetsApproval: TimesheetApprovalService,
|
||||||
private readonly query: PayPeriodsQueryService,
|
private readonly query: PayPeriodsQueryService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
|
|
@ -49,7 +49,7 @@ export class PayPeriodsCommandService {
|
||||||
for(const item of items) {
|
for(const item of items) {
|
||||||
const { period_start, period_end } = await getPeriod(item.pay_year, item.period_no);
|
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: {
|
where: {
|
||||||
employee: { user: { email: item.employee_email } },
|
employee: { user: { email: item.employee_email } },
|
||||||
OR: [
|
OR: [
|
||||||
|
|
@ -60,8 +60,8 @@ export class PayPeriodsCommandService {
|
||||||
select: { id: true },
|
select: { id: true },
|
||||||
});
|
});
|
||||||
|
|
||||||
for(const { id } of t_sheets) {
|
for(const { id } of timesheets) {
|
||||||
await this.timesheets_approval.cascadeApprovalWithtx(transaction, id, item.approve);
|
await this.timesheetsApproval.cascadeApprovalWithtx(transaction, id, item.approve);
|
||||||
updated++;
|
updated++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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 { 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 { 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 { 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 { 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 { BankCodesResolver } from "src/time-and-attendance/utils/resolve-bank-type-id.utils";
|
||||||
import { EmailToIdResolver } from "src/time-and-attendance/utils/resolve-email-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: [
|
imports: [
|
||||||
BusinessLogicsModule,
|
BusinessLogicsModule,
|
||||||
PayperiodsModule,
|
PayperiodsModule,
|
||||||
|
TimesheetsModule,
|
||||||
],
|
],
|
||||||
controllers: [
|
controllers: [
|
||||||
TimesheetController,
|
TimesheetController,
|
||||||
|
|
@ -37,6 +40,7 @@ import { EmailToIdResolver } from "src/time-and-attendance/utils/resolve-email-i
|
||||||
SchedulePresetsApplyService,
|
SchedulePresetsApplyService,
|
||||||
EmailToIdResolver,
|
EmailToIdResolver,
|
||||||
BankCodesResolver,
|
BankCodesResolver,
|
||||||
|
TimesheetApprovalService,
|
||||||
],
|
],
|
||||||
exports: [],
|
exports: [TimesheetApprovalService ],
|
||||||
}) export class TimeAndAttendanceModule { };
|
}) export class TimeAndAttendanceModule { };
|
||||||
|
|
@ -171,7 +171,7 @@ export class SchedulePresetsUpsertService {
|
||||||
const bank_code_set = new Map<string, number>();
|
const bank_code_set = new Map<string, number>();
|
||||||
|
|
||||||
for (const type of types) {
|
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)
|
bank_code_set.set(type, id)
|
||||||
}
|
}
|
||||||
const toTime = (hhmm: string) => new Date(`1970-01-01T${hhmm}:00.000Z`);
|
const toTime = (hhmm: string) => new Date(`1970-01-01T${hhmm}:00.000Z`);
|
||||||
|
|
|
||||||
|
|
@ -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 { CreateShiftResult, UpdateShiftResult } from "src/time-and-attendance/utils/type.utils";
|
||||||
import { Roles as RoleEnum } from '.prisma/client';
|
import { Roles as RoleEnum } from '.prisma/client';
|
||||||
import { RolesAllowed } from "src/common/decorators/roles.decorators";
|
import { RolesAllowed } from "src/common/decorators/roles.decorators";
|
||||||
|
import { BankCodesResolver } from "src/time-and-attendance/utils/resolve-bank-type-id.utils";
|
||||||
|
|
||||||
@Controller('shift')
|
@Controller('shift')
|
||||||
export class ShiftController {
|
export class ShiftController {
|
||||||
constructor( private readonly upsert_service: ShiftsUpsertService ){}
|
constructor(
|
||||||
|
private readonly upsert_service: ShiftsUpsertService,
|
||||||
|
private readonly typeResolver: BankCodesResolver,
|
||||||
|
){}
|
||||||
|
|
||||||
@Post('create')
|
@Post('create')
|
||||||
@RolesAllowed(RoleEnum.EMPLOYEE, RoleEnum.ACCOUNTING, RoleEnum.HR, RoleEnum.SUPERVISOR, RoleEnum.ADMIN)
|
@RolesAllowed(RoleEnum.EMPLOYEE, RoleEnum.ACCOUNTING, RoleEnum.HR, RoleEnum.SUPERVISOR, RoleEnum.ADMIN)
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ import { IsBoolean, IsInt, IsOptional, IsString, MaxLength } from "class-validat
|
||||||
export class ShiftDto {
|
export class ShiftDto {
|
||||||
@IsInt() @IsOptional() id: number;
|
@IsInt() @IsOptional() id: number;
|
||||||
@IsInt() timesheet_id!: number;
|
@IsInt() timesheet_id!: number;
|
||||||
@IsInt() bank_code_id!: number;
|
@IsString() type!: string;
|
||||||
|
|
||||||
@IsString() date!: string;
|
@IsString() date!: string;
|
||||||
@IsString() start_time!: string;
|
@IsString() start_time!: string;
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
export class GetShiftDto {
|
export class GetShiftDto {
|
||||||
timesheet_id: number;
|
timesheet_id: number;
|
||||||
bank_code_id: number;
|
type: string;
|
||||||
date: string;
|
date: string;
|
||||||
start_time: string;
|
start_time: string;
|
||||||
end_time: string;
|
end_time: string;
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,7 @@ export class ShiftsGetService {
|
||||||
const shift = row_by_id.get(id)!;
|
const shift = row_by_id.get(id)!;
|
||||||
return {
|
return {
|
||||||
timesheet_id: shift.timesheet_id,
|
timesheet_id: shift.timesheet_id,
|
||||||
bank_code_id: shift.bank_code_id,
|
type: shift.bank_code.type,
|
||||||
date: toStringFromDate(shift.date),
|
date: toStringFromDate(shift.date),
|
||||||
start_time: toStringFromHHmm(shift.start_time),
|
start_time: toStringFromHHmm(shift.start_time),
|
||||||
end_time: toStringFromHHmm(shift.end_time),
|
end_time: toStringFromHHmm(shift.end_time),
|
||||||
|
|
|
||||||
|
|
@ -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 { 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 { 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 { 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 prisma: PrismaService,
|
||||||
private readonly overtime: OvertimeService,
|
private readonly overtime: OvertimeService,
|
||||||
private readonly emailResolver: EmailToIdResolver,
|
private readonly emailResolver: EmailToIdResolver,
|
||||||
|
private readonly typeResolver: BankCodesResolver,
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
//_________________________________________________________________
|
//_________________________________________________________________
|
||||||
|
|
@ -35,7 +37,7 @@ export class ShiftsUpsertService {
|
||||||
const normed_shifts = await Promise.all(
|
const normed_shifts = await Promise.all(
|
||||||
dtos.map(async (dto, index) => {
|
dtos.map(async (dto, index) => {
|
||||||
try {
|
try {
|
||||||
const normed = this.normalizeShiftDto(dto);
|
const normed = await this.normalizeShiftDto(dto);
|
||||||
if (normed.end_time <= normed.start_time) {
|
if (normed.end_time <= normed.start_time) {
|
||||||
return {
|
return {
|
||||||
index,
|
index,
|
||||||
|
|
@ -45,18 +47,12 @@ export class ShiftsUpsertService {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const start_date = weekStartSunday(normed.date);
|
const timesheet = await this.prisma.timesheets.findUnique({
|
||||||
|
where: { id: dto.timesheet_id, employee_id },
|
||||||
const timesheet = await this.prisma.timesheets.findFirst({
|
select: timesheet_select,
|
||||||
where: { start_date, employee_id },
|
|
||||||
select: { id: true },
|
|
||||||
});
|
});
|
||||||
if (!timesheet) {
|
if (!timesheet) {
|
||||||
return {
|
return { index, error: new NotFoundException(`Timesheet not found`)};
|
||||||
index,
|
|
||||||
error: new NotFoundException(`Timesheet not found`),
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
@ -181,7 +177,7 @@ export class ShiftsUpsertService {
|
||||||
const row = await tx.shifts.create({
|
const row = await tx.shifts.create({
|
||||||
data: {
|
data: {
|
||||||
timesheet_id: timesheet_id,
|
timesheet_id: timesheet_id,
|
||||||
bank_code_id: dto.bank_code_id,
|
bank_code_id: normed.id,
|
||||||
date: normed.date,
|
date: normed.date,
|
||||||
start_time: normed.start_time,
|
start_time: normed.start_time,
|
||||||
end_time: normed.end_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.push({ start_time: row.start_time, end_time: row.end_time });
|
||||||
existing_map.set(map_key, existing);
|
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 summary = await this.overtime.getWeekOvertimeSummary(timesheet_id, normed.date, tx);
|
||||||
|
|
||||||
const shift: GetShiftDto = {
|
const shift: GetShiftDto = {
|
||||||
timesheet_id: timesheet_id,
|
timesheet_id: timesheet_id,
|
||||||
bank_code_id: row.bank_code_id,
|
type: bank_type,
|
||||||
date: toStringFromDate(row.date),
|
date: toStringFromDate(row.date),
|
||||||
start_time: toStringFromHHmm(row.start_time),
|
start_time: toStringFromHHmm(row.start_time),
|
||||||
end_time: toStringFromHHmm(row.end_time),
|
end_time: toStringFromHHmm(row.end_time),
|
||||||
|
|
@ -227,7 +225,7 @@ export class ShiftsUpsertService {
|
||||||
async updateShifts(dtos: UpdateShiftDto[]): Promise<UpdateShiftResult[]> {
|
async updateShifts(dtos: UpdateShiftDto[]): Promise<UpdateShiftResult[]> {
|
||||||
if (!Array.isArray(dtos) || dtos.length === 0) return [];
|
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;
|
const { id, ...rest } = item;
|
||||||
if (!Number.isInteger(id)) {
|
if (!Number.isInteger(id)) {
|
||||||
throw new BadRequestException('Update shift payload is missing a valid 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.date !== undefined) changes.date = rest.date;
|
||||||
if (rest.start_time !== undefined) changes.start_time = rest.start_time;
|
if (rest.start_time !== undefined) changes.start_time = rest.start_time;
|
||||||
if (rest.end_time !== undefined) changes.end_time = rest.end_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.is_remote !== undefined) changes.is_remote = rest.is_remote;
|
||||||
if (rest.comment !== undefined) changes.comment = rest.comment;
|
if (rest.comment !== undefined) changes.comment = rest.comment;
|
||||||
|
|
||||||
return { id, dto: changes };
|
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);
|
||||||
|
|
@ -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 exist_shift = regroup_id.get(update.id)!;
|
||||||
const date_string = update.dto.date ?? toStringFromDate(exist_shift.date);
|
const date_string = update.dto.date ?? toStringFromDate(exist_shift.date);
|
||||||
const start_string = update.dto.start_time ?? toStringFromHHmm(exist_shift.start_time);
|
const start_string = update.dto.start_time ?? toStringFromHHmm(exist_shift.start_time);
|
||||||
|
|
@ -275,6 +273,7 @@ export class ShiftsUpsertService {
|
||||||
date: toDateFromString(date_string),
|
date: toDateFromString(date_string),
|
||||||
start_time: toHHmmFromString(start_string),
|
start_time: toHHmmFromString(start_string),
|
||||||
end_time: toHHmmFromString(end_string),
|
end_time: toHHmmFromString(end_string),
|
||||||
|
id: exist_shift.id,
|
||||||
};
|
};
|
||||||
return { update, exist_shift, normed };
|
return { update, exist_shift, normed };
|
||||||
});
|
});
|
||||||
|
|
@ -345,7 +344,7 @@ export class ShiftsUpsertService {
|
||||||
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.start_time !== undefined) data.start_time = planned.normed.start_time;
|
||||||
if (dto.end_time !== undefined) data.end_time = planned.normed.end_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.type !== undefined) data.type = dto.type;
|
||||||
if (dto.is_remote !== undefined) data.is_remote = dto.is_remote;
|
if (dto.is_remote !== undefined) data.is_remote = dto.is_remote;
|
||||||
if (dto.comment !== undefined) data.comment = dto.comment ?? null;
|
if (dto.comment !== undefined) data.comment = dto.comment ?? null;
|
||||||
|
|
||||||
|
|
@ -362,7 +361,7 @@ export class ShiftsUpsertService {
|
||||||
|
|
||||||
const shift: GetShiftDto = {
|
const shift: GetShiftDto = {
|
||||||
timesheet_id: row.timesheet_id,
|
timesheet_id: row.timesheet_id,
|
||||||
bank_code_id: row.bank_code_id,
|
type: data.type,
|
||||||
date: toStringFromDate(row.date),
|
date: toStringFromDate(row.date),
|
||||||
start_time: toStringFromHHmm(row.start_time),
|
start_time: toStringFromHHmm(row.start_time),
|
||||||
end_time: toStringFromHHmm(row.end_time),
|
end_time: toStringFromHHmm(row.end_time),
|
||||||
|
|
@ -406,10 +405,11 @@ export class ShiftsUpsertService {
|
||||||
// LOCAL HELPERS
|
// LOCAL HELPERS
|
||||||
//_________________________________________________________________
|
//_________________________________________________________________
|
||||||
//converts all string hours and date to Date and HHmm formats
|
//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 date = toDateFromString(dto.date);
|
||||||
const start_time = toHHmmFromString(dto.start_time);
|
const start_time = toHHmmFromString(dto.start_time);
|
||||||
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, id: bank_code_id };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 { RolesAllowed } from "src/common/decorators/roles.decorators";
|
||||||
import { GetTimesheetsOverviewService } from "src/time-and-attendance/time-tracker/timesheets/services/timesheet-get-overview.service";
|
import { GetTimesheetsOverviewService } from "src/time-and-attendance/time-tracker/timesheets/services/timesheet-get-overview.service";
|
||||||
import { Roles as RoleEnum } from '.prisma/client';
|
import { Roles as RoleEnum } from '.prisma/client';
|
||||||
|
import { TimesheetApprovalService } from "src/time-and-attendance/time-tracker/timesheets/services/timesheet-approval.service";
|
||||||
|
|
||||||
|
|
||||||
@Controller('timesheets')
|
@Controller('timesheets')
|
||||||
export class TimesheetController {
|
export class TimesheetController {
|
||||||
constructor( private readonly timesheetOverview: GetTimesheetsOverviewService ){}
|
constructor(
|
||||||
|
private readonly timesheetOverview: GetTimesheetsOverviewService,
|
||||||
|
private readonly approvalService: TimesheetApprovalService,
|
||||||
|
){}
|
||||||
|
|
||||||
@Get()
|
@Get()
|
||||||
@RolesAllowed(RoleEnum.SUPERVISOR, RoleEnum.HR, RoleEnum.ACCOUNTING, RoleEnum.ADMIN)
|
@RolesAllowed(RoleEnum.SUPERVISOR, RoleEnum.HR, RoleEnum.ACCOUNTING, RoleEnum.ADMIN)
|
||||||
async getTimesheetByIds(
|
async getTimesheetByIds(
|
||||||
@Req() req, @Query('year', ParseIntPipe) year:number, @Query('period_number', ParseIntPipe) period_number: number) {
|
@Req() req, @Query('year', ParseIntPipe) year:number, @Query('period_number', ParseIntPipe) period_number: number) {
|
||||||
const email = req.user?.email;
|
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);
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,15 @@
|
||||||
import { BaseApprovalService } from "src/common/shared/base-approval.service";
|
import { BaseApprovalService } from "src/common/shared/base-approval.service";
|
||||||
import { Prisma, Timesheets } from "@prisma/client";
|
import { Prisma, Timesheets } from "@prisma/client";
|
||||||
import { PrismaService } from "src/prisma/prisma.service";
|
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()
|
@Injectable()
|
||||||
export class TimesheetApprovalService extends BaseApprovalService<Timesheets>{
|
export class TimesheetApprovalService extends BaseApprovalService<Timesheets>{
|
||||||
constructor(prisma: PrismaService){super(prisma)}
|
constructor(
|
||||||
|
prisma: PrismaService,
|
||||||
|
){super(prisma)}
|
||||||
|
|
||||||
//_____________________________________________________________________________________________
|
//_____________________________________________________________________________________________
|
||||||
// APPROVAL AND DELEGATE METHODS
|
// APPROVAL AND DELEGATE METHODS
|
||||||
//_____________________________________________________________________________________________
|
//_____________________________________________________________________________________________
|
||||||
|
|
@ -13,26 +17,43 @@ import { Injectable } from "@nestjs/common";
|
||||||
return this.prisma.timesheets;
|
return this.prisma.timesheets;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected delegateFor(transaction: Prisma.TransactionClient) {
|
protected delegateFor(tx: Prisma.TransactionClient) {
|
||||||
return transaction.timesheets;
|
return tx.timesheets;
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateApproval(id: number, isApproved: boolean): Promise<Timesheets> {
|
async updateApproval(id: number, is_approved: boolean): Promise<Timesheets> {
|
||||||
return this.prisma.$transaction((transaction) =>
|
return this.prisma.$transaction((tx) =>
|
||||||
this.updateApprovalWithTransaction(transaction, id, isApproved),
|
this.updateApprovalWithTransaction(tx, id, is_approved),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async cascadeApprovalWithtx(transaction: Prisma.TransactionClient, timesheetId: number, isApproved: boolean): Promise<Timesheets> {
|
async cascadeApprovalWithtx(tx: Prisma.TransactionClient, timesheet_id: number, is_approved: boolean): Promise<Timesheets> {
|
||||||
const timesheet = await this.updateApprovalWithTransaction(transaction, timesheetId, isApproved);
|
const timesheet = await this.updateApprovalWithTransaction(tx, timesheet_id, is_approved);
|
||||||
await transaction.shifts.updateMany({
|
await tx.shifts.updateMany({
|
||||||
where: { timesheet_id: timesheetId },
|
where: { timesheet_id: timesheet_id },
|
||||||
data: { is_approved: isApproved },
|
data: { is_approved: is_approved },
|
||||||
});
|
});
|
||||||
await transaction.expenses.updateManyAndReturn({
|
await tx.expenses.updateManyAndReturn({
|
||||||
where: { timesheet_id: timesheetId },
|
where: { timesheet_id: timesheet_id },
|
||||||
data: { is_approved: isApproved },
|
data: { is_approved: is_approved },
|
||||||
});
|
});
|
||||||
return timesheet;
|
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,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,49 +1,49 @@
|
||||||
import { TimesheetsArchive } from "@prisma/client";
|
// import { TimesheetsArchive } from "@prisma/client";
|
||||||
import { PrismaService } from "src/prisma/prisma.service";
|
// import { PrismaService } from "src/prisma/prisma.service";
|
||||||
|
|
||||||
export class TimesheetArchiveService {
|
// export class TimesheetArchiveService {
|
||||||
constructor(private readonly prisma: PrismaService){}
|
// constructor(private readonly prisma: PrismaService){}
|
||||||
|
|
||||||
async archiveOld(): Promise<void> {
|
// async archiveOld(): Promise<void> {
|
||||||
//calcul du cutoff pour archivation
|
// //calcul du cutoff pour archivation
|
||||||
const cutoff = new Date();
|
// const cutoff = new Date();
|
||||||
cutoff.setMonth(cutoff.getMonth() - 6)
|
// cutoff.setMonth(cutoff.getMonth() - 6)
|
||||||
|
|
||||||
await this.prisma.$transaction(async transaction => {
|
// await this.prisma.$transaction(async transaction => {
|
||||||
//fetches all timesheets to cutoff
|
// //fetches all timesheets to cutoff
|
||||||
const oldSheets = await transaction.timesheets.findMany({
|
// const oldSheets = await transaction.timesheets.findMany({
|
||||||
where: { shift: { some: { date: { lt: cutoff } } },
|
// where: { shift: { some: { date: { lt: cutoff } } },
|
||||||
},
|
// },
|
||||||
select: {
|
// select: {
|
||||||
id: true,
|
// id: true,
|
||||||
employee_id: true,
|
// employee_id: true,
|
||||||
is_approved: true,
|
// is_approved: true,
|
||||||
},
|
// },
|
||||||
});
|
// });
|
||||||
if( oldSheets.length === 0) return;
|
// if( oldSheets.length === 0) return;
|
||||||
|
|
||||||
//preping data for archivation
|
// //preping data for archivation
|
||||||
const archiveDate = oldSheets.map(sheet => ({
|
// const archiveDate = oldSheets.map(sheet => ({
|
||||||
timesheet_id: sheet.id,
|
// timesheet_id: sheet.id,
|
||||||
employee_id: sheet.employee_id,
|
// employee_id: sheet.employee_id,
|
||||||
is_approved: sheet.is_approved,
|
// is_approved: sheet.is_approved,
|
||||||
}));
|
// }));
|
||||||
|
|
||||||
//copying data from timesheets table to archive table
|
// //copying data from timesheets table to archive table
|
||||||
await transaction.timesheetsArchive.createMany({ data: archiveDate });
|
// await transaction.timesheetsArchive.createMany({ data: archiveDate });
|
||||||
|
|
||||||
//removing data from timesheets table
|
// //removing data from timesheets table
|
||||||
await transaction.timesheets.deleteMany({ where: { id: { in: oldSheets.map(s => s.id) } } });
|
// await transaction.timesheets.deleteMany({ where: { id: { in: oldSheets.map(s => s.id) } } });
|
||||||
});
|
// });
|
||||||
}
|
// }
|
||||||
|
|
||||||
//fetches all archived timesheets
|
// //fetches all archived timesheets
|
||||||
async findAllArchived(): Promise<TimesheetsArchive[]> {
|
// async findAllArchived(): Promise<TimesheetsArchive[]> {
|
||||||
return this.prisma.timesheetsArchive.findMany();
|
// return this.prisma.timesheetsArchive.findMany();
|
||||||
}
|
// }
|
||||||
|
|
||||||
//fetches an archived timesheet
|
// //fetches an archived timesheet
|
||||||
async findOneArchived(id: number): Promise<TimesheetsArchive> {
|
// async findOneArchived(id: number): Promise<TimesheetsArchive> {
|
||||||
return this.prisma.timesheetsArchive.findUniqueOrThrow({ where: { id } });
|
// return this.prisma.timesheetsArchive.findUniqueOrThrow({ where: { id } });
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
@ -2,19 +2,16 @@
|
||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
import { TimesheetController } from 'src/time-and-attendance/time-tracker/timesheets/controllers/timesheet.controller';
|
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 { 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 { 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';
|
import { EmailToIdResolver } from 'src/time-and-attendance/utils/resolve-email-id.utils';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
|
|
||||||
controllers: [TimesheetController],
|
controllers: [TimesheetController],
|
||||||
providers: [
|
providers: [
|
||||||
TimesheetArchiveService,
|
|
||||||
GetTimesheetsOverviewService,
|
GetTimesheetsOverviewService,
|
||||||
TimesheetApprovalService,
|
|
||||||
EmailToIdResolver,
|
EmailToIdResolver,
|
||||||
|
TimesheetApprovalService,
|
||||||
],
|
],
|
||||||
exports: [],
|
exports: [TimesheetApprovalService],
|
||||||
})
|
})
|
||||||
export class TimesheetsModule {}
|
export class TimesheetsModule {}
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ export class BankCodesResolver {
|
||||||
constructor(private readonly prisma: PrismaService) {}
|
constructor(private readonly prisma: PrismaService) {}
|
||||||
|
|
||||||
//find id and modifier by type
|
//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 }> => {
|
): Promise<{id:number; modifier: number }> => {
|
||||||
const db = client ?? this.prisma;
|
const db = client ?? this.prisma;
|
||||||
const bank = await db.bankCodes.findFirst({
|
const bank = await db.bankCodes.findFirst({
|
||||||
|
|
@ -20,4 +20,25 @@ export class BankCodesResolver {
|
||||||
if(!bank) throw new NotFoundException(`Unknown bank code type: ${type}`);
|
if(!bank) throw new NotFoundException(`Unknown bank code type: ${type}`);
|
||||||
return { id: bank.id, modifier: bank.modifier };
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import { Prisma } from "@prisma/client";
|
import { Prisma } from "@prisma/client";
|
||||||
|
import { dmmfToRuntimeDataModel } from "@prisma/client/runtime/library";
|
||||||
|
|
||||||
export const expense_select = {
|
export const expense_select = {
|
||||||
id: true,
|
id: true,
|
||||||
|
|
@ -17,6 +18,9 @@ export const shift_select = {
|
||||||
id: true,
|
id: true,
|
||||||
timesheet_id: true,
|
timesheet_id: true,
|
||||||
bank_code_id: true,
|
bank_code_id: true,
|
||||||
|
bank_code: {
|
||||||
|
select: { type: true },
|
||||||
|
},
|
||||||
date: true,
|
date: true,
|
||||||
start_time: true,
|
start_time: true,
|
||||||
end_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 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;
|
||||||
|
|
@ -25,7 +25,7 @@ export type TotalExpenses = {
|
||||||
mileage: number;
|
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 = {
|
export type ShiftWithOvertimeDto = {
|
||||||
shift: GetShiftDto;
|
shift: GetShiftDto;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user