targo-backend/src/modules/shifts/services/shifts-command.service.ts

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