193 lines
8.0 KiB
TypeScript
193 lines
8.0 KiB
TypeScript
import { BadRequestException, Injectable, Logger, NotFoundException } from "@nestjs/common";
|
|
import { DayShiftResponse } from "../types-and-interfaces/shifts-upsert.types";
|
|
import { EmailToIdResolver } from "src/modules/shared/utils/resolve-email-id.utils";
|
|
import { Prisma, Shifts } from "@prisma/client";
|
|
import { UpsertShiftDto } from "../dtos/upsert-shift.dto";
|
|
import { BaseApprovalService } from "src/common/shared/base-approval.service";
|
|
import { PrismaService } from "src/prisma/prisma.service";
|
|
import { toDateOnly } from "../helpers/shifts-date-time-helpers";
|
|
import { UpsertAction } from "src/modules/shared/types/upsert-actions.types";
|
|
import { ShiftsHelpersService } from "../helpers/shifts.helpers";
|
|
import { BankCodesResolver } from "src/modules/shared/utils/resolve-bank-type-id.utils";
|
|
|
|
@Injectable()
|
|
export class ShiftsCommandService extends BaseApprovalService<Shifts> {
|
|
private readonly logger = new Logger(ShiftsCommandService.name);
|
|
|
|
constructor(
|
|
prisma: PrismaService,
|
|
private readonly emailResolver: EmailToIdResolver,
|
|
private readonly typeResolver: BankCodesResolver,
|
|
private readonly helpersService: ShiftsHelpersService,
|
|
) { super(prisma); }
|
|
|
|
//_____________________________________________________________________________________________
|
|
// APPROVAL AND DELEGATE METHODS
|
|
//_____________________________________________________________________________________________
|
|
protected get delegate() {
|
|
return this.prisma.shifts;
|
|
}
|
|
|
|
protected delegateFor(transaction: Prisma.TransactionClient) {
|
|
return transaction.shifts;
|
|
}
|
|
|
|
async updateApproval(id: number, is_approved: boolean): Promise<Shifts> {
|
|
return this.prisma.$transaction((transaction) =>
|
|
this.updateApprovalWithTransaction(transaction, id, is_approved),
|
|
);
|
|
}
|
|
|
|
//_____________________________________________________________________________________________
|
|
// MASTER CRUD METHOD
|
|
//_____________________________________________________________________________________________
|
|
async upsertShifts(
|
|
email: string,
|
|
action: UpsertAction,
|
|
dto: UpsertShiftDto,
|
|
): Promise<{
|
|
action: UpsertAction;
|
|
day: DayShiftResponse[];
|
|
}> {
|
|
if (!dto.old_shift && !dto.new_shift) throw new BadRequestException('At least one of old or new shift must be provided');
|
|
|
|
const date = dto.new_shift?.date ?? dto.old_shift?.date;
|
|
if (!date) throw new BadRequestException("A date (YYYY-MM-DD) must be provided in old_shift or new_shift");
|
|
if (dto.old_shift?.date && dto.new_shift?.date && dto.old_shift.date !== dto.new_shift.date) {
|
|
throw new BadRequestException('old_shift.date and new_shift.date must be identical');
|
|
}
|
|
|
|
const employee_id = await this.emailResolver.findIdByEmail(email);//resolve employee id using email
|
|
|
|
if(action === 'create') {
|
|
if(!dto.new_shift || dto.old_shift) {
|
|
throw new BadRequestException(`Only new_shift must be provided for create`);
|
|
}
|
|
return this.createShift(employee_id, date, dto);
|
|
}
|
|
if(action === 'update'){
|
|
if(!dto.old_shift || !dto.new_shift) {
|
|
throw new BadRequestException(`Both new_shift and old_shift must be provided for update`);
|
|
}
|
|
return this.updateShift(employee_id, date, dto);
|
|
}
|
|
throw new BadRequestException(`Unknown action: ${action}`);
|
|
}
|
|
|
|
//_________________________________________________________________
|
|
// CREATE
|
|
//_________________________________________________________________
|
|
private async createShift(
|
|
employee_id: number,
|
|
date_iso: string,
|
|
dto: UpsertShiftDto,
|
|
): Promise<{action: UpsertAction; day: DayShiftResponse[]}> {
|
|
return this.prisma.$transaction(async (tx) => {
|
|
const date_only = toDateOnly(date_iso);
|
|
const { id: timesheet_id } = await this.helpersService.ensureTimesheet(tx, employee_id, date_only);
|
|
|
|
const new_norm_shift = await this.helpersService.normalizeRequired(dto.new_shift);
|
|
const new_bank_code_id = await this.helpersService.resolveBankIdRequired(tx, new_norm_shift.type, 'new_shift');
|
|
|
|
const day_shifts = await this.helpersService.getDayShifts(tx, timesheet_id, date_only);
|
|
|
|
await this.helpersService.assertNoOverlap(day_shifts, new_norm_shift);
|
|
|
|
await tx.shifts.create({
|
|
data: {
|
|
timesheet_id,
|
|
date: date_only,
|
|
start_time: new_norm_shift.start_time,
|
|
end_time: new_norm_shift.end_time,
|
|
is_remote: new_norm_shift.is_remote,
|
|
is_approved: new_norm_shift.is_approved,
|
|
comment: new_norm_shift.comment ?? null,
|
|
bank_code_id: new_bank_code_id,
|
|
},
|
|
});
|
|
await this.helpersService.afterWriteOvertimeAndLog(tx, employee_id, date_only,'create');
|
|
const fresh_shift = await this.helpersService.getDayShifts(tx, timesheet_id, date_only);
|
|
return { action: 'create', day: await this.helpersService.mapDay(fresh_shift)};
|
|
});
|
|
}
|
|
|
|
//_________________________________________________________________
|
|
// UPDATE
|
|
//_________________________________________________________________
|
|
private async updateShift(
|
|
employee_id: number,
|
|
date_iso: string,
|
|
dto: UpsertShiftDto,
|
|
): Promise<{ action: UpsertAction; day: DayShiftResponse[];}>{
|
|
return this.prisma.$transaction(async (tx) => {
|
|
const date_only = toDateOnly(date_iso);
|
|
const { id: timesheet_id } = await this.helpersService.ensureTimesheet(tx, employee_id, date_only);
|
|
|
|
const old_norm_shift = await this.helpersService.normalizeRequired(dto.old_shift, 'old_shift');
|
|
const new_norm_shift = await this.helpersService.normalizeRequired(dto.new_shift, 'new_shift');
|
|
|
|
const old_bank_code_id = await this.helpersService.resolveBankIdRequired(tx, old_norm_shift.type, 'old_shift');
|
|
const new_bank_code_id = await this.helpersService.resolveBankIdRequired(tx, new_norm_shift.type, 'new_shift');
|
|
|
|
const day_shifts = await this.helpersService.getDayShifts(tx, timesheet_id, date_only);
|
|
const existing = await this.helpersService.findExactOldShift(tx, {
|
|
timesheet_id,
|
|
date_only,
|
|
norm: old_norm_shift,
|
|
bank_code_id: old_bank_code_id,
|
|
});
|
|
if(!existing) throw new NotFoundException('[SHIFT_STALE]- The shift was modified or deleted by someone else');
|
|
|
|
await this.helpersService.assertNoOverlap(day_shifts, new_norm_shift, existing.id);
|
|
|
|
await tx.shifts.update({
|
|
where: { id: existing.id },
|
|
data: {
|
|
start_time: new_norm_shift.start_time,
|
|
end_time: new_norm_shift.end_time,
|
|
is_remote: new_norm_shift.is_remote,
|
|
comment: new_norm_shift.comment ?? null,
|
|
bank_code_id: new_bank_code_id,
|
|
},
|
|
});
|
|
await this.helpersService.afterWriteOvertimeAndLog(tx, employee_id, date_only, 'update');
|
|
const fresh_shift = await this.helpersService.getDayShifts(tx, timesheet_id, date_only);
|
|
return { action: 'update', day: await this.helpersService.mapDay(fresh_shift)};
|
|
});
|
|
|
|
}
|
|
|
|
//_________________________________________________________________
|
|
// DELETE
|
|
//_________________________________________________________________
|
|
async deleteShift(
|
|
email: string,
|
|
date_iso: string,
|
|
dto: UpsertShiftDto,
|
|
): Promise<{ day: DayShiftResponse[]; }>{
|
|
return this.prisma.$transaction(async (tx) => {
|
|
const date_only = toDateOnly(date_iso);
|
|
const employee_id = await this.emailResolver.findIdByEmail(email);
|
|
|
|
const { id: timesheet_id } = await this.helpersService.ensureTimesheet(tx, employee_id, date_only);
|
|
const norm_shift = await this.helpersService.normalizeRequired(dto.old_shift, 'old_shift');
|
|
const bank_code_id = await this.typeResolver.findByType(norm_shift.type);
|
|
|
|
const existing = await this.helpersService.findExactOldShift(tx, {
|
|
timesheet_id,
|
|
date_only,
|
|
norm: norm_shift,
|
|
bank_code_id: bank_code_id.id,
|
|
});
|
|
if(!existing) throw new NotFoundException('[SHIFT_STALE]- The shift was modified or deleted by someone else');
|
|
|
|
await tx.shifts.delete({ where: { id: existing.id } });
|
|
|
|
await this.helpersService.afterWriteOvertimeAndLog(tx, employee_id, date_only, 'delete');
|
|
const fresh_shift = await this.helpersService.getDayShifts(tx, timesheet_id, date_only);
|
|
return { day: await this.helpersService.mapDay(fresh_shift)};
|
|
});
|
|
}
|
|
}
|
|
|