refactor(shifts): removed email from param of create shift and used req-user data instead
This commit is contained in:
parent
bb60887a0d
commit
c274550a91
|
|
@ -1,4 +1,4 @@
|
||||||
import { BadRequestException, Body, Controller, Delete, Param, Patch, Post } from "@nestjs/common";
|
import { BadRequestException, Body, Controller, Delete, Param, Patch, Post, Req } from "@nestjs/common";
|
||||||
import { CreateShiftResult, UpdateShiftResult } from "src/time-and-attendance/utils/type.utils";
|
import { CreateShiftResult, UpdateShiftResult } from "src/time-and-attendance/utils/type.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";
|
||||||
import { UpdateShiftDto } from "src/time-and-attendance/modules/time-tracker/shifts/dtos/shift-update.dto";
|
import { UpdateShiftDto } from "src/time-and-attendance/modules/time-tracker/shifts/dtos/shift-update.dto";
|
||||||
|
|
@ -11,10 +11,12 @@ export class ShiftController {
|
||||||
|
|
||||||
@Post('create')
|
@Post('create')
|
||||||
createBatch(
|
createBatch(
|
||||||
|
@Req() req,
|
||||||
@Body()dtos: ShiftDto[]): Promise<CreateShiftResult[]> {
|
@Body()dtos: ShiftDto[]): Promise<CreateShiftResult[]> {
|
||||||
|
const email = req.user?.email;
|
||||||
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(dtos)
|
return this.upsert_service.createShifts(email, dtos)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { CreateShiftResult, NormedOk, NormedErr, UpdateShiftResult, UpdateShiftPayload, UpdateShiftChanges, Normalized } from "src/time-and-attendance/utils/type.utils";
|
import { CreateShiftResult, NormedOk, NormedErr, UpdateShiftResult, UpdateShiftPayload, UpdateShiftChanges, Normalized } from "src/time-and-attendance/utils/type.utils";
|
||||||
import { overlaps, toStringFromHHmm, toStringFromDate, toDateFromString, toHHmmFromString } from "src/time-and-attendance/utils/date-time.utils";
|
import { overlaps, toStringFromHHmm, toStringFromDate, toDateFromString, toHHmmFromString, weekStartSunday } from "src/time-and-attendance/utils/date-time.utils";
|
||||||
import { Injectable, BadRequestException, ConflictException, NotFoundException } from "@nestjs/common";
|
import { Injectable, BadRequestException, ConflictException, NotFoundException } from "@nestjs/common";
|
||||||
import { OvertimeService } from "src/time-and-attendance/domains/services/overtime.service";
|
import { OvertimeService } from "src/time-and-attendance/domains/services/overtime.service";
|
||||||
import { UpdateShiftDto } from "src/time-and-attendance/modules/time-tracker/shifts/dtos/shift-update.dto";
|
import { UpdateShiftDto } from "src/time-and-attendance/modules/time-tracker/shifts/dtos/shift-update.dto";
|
||||||
|
|
@ -7,6 +7,7 @@ import { PrismaService } from "src/prisma/prisma.service";
|
||||||
import { shift_select } from "src/time-and-attendance/utils/selects.utils";
|
import { shift_select } from "src/time-and-attendance/utils/selects.utils";
|
||||||
import { GetShiftDto } from "src/time-and-attendance/modules/time-tracker/shifts/dtos/shift-get.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 { ShiftDto } from "src/time-and-attendance/modules/time-tracker/shifts/dtos/shift-create.dto";
|
||||||
|
import { EmailToIdResolver } from "src/time-and-attendance/modules/shared/utils/resolve-email-id.utils";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -15,6 +16,7 @@ export class ShiftsUpsertService {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly prisma: PrismaService,
|
private readonly prisma: PrismaService,
|
||||||
private readonly overtime: OvertimeService,
|
private readonly overtime: OvertimeService,
|
||||||
|
private readonly emailResolver: EmailToIdResolver,
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
//_________________________________________________________________
|
//_________________________________________________________________
|
||||||
|
|
@ -25,76 +27,140 @@ export class ShiftsUpsertService {
|
||||||
//checks for overlaping shifts
|
//checks for overlaping shifts
|
||||||
//create new shifts
|
//create new shifts
|
||||||
//calculate overtime
|
//calculate overtime
|
||||||
async createShifts(dtos: ShiftDto[]): Promise<CreateShiftResult[]> {
|
async createShifts(email: string, dtos: ShiftDto[]): Promise<CreateShiftResult[]> {
|
||||||
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 employee_id = await this.emailResolver.findIdByEmail(email);
|
||||||
|
|
||||||
|
const normed_shifts = await Promise.all(
|
||||||
|
dtos.map(async (dto, index) => {
|
||||||
try {
|
try {
|
||||||
const normed = this.normalizeShiftDto(dto);
|
const normed = this.normalizeShiftDto(dto);
|
||||||
if (normed.end_time <= normed.start_time) {
|
if (normed.end_time <= normed.start_time) {
|
||||||
return { index, error: new BadRequestException(`end_time must be greater than start_time (index ${index})`) };
|
return {
|
||||||
|
index,
|
||||||
|
error: new BadRequestException(
|
||||||
|
`end_time must be greater than start_time (index ${index})`
|
||||||
|
),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
return { index, dto, normed };
|
|
||||||
|
const start_date = weekStartSunday(normed.date);
|
||||||
|
|
||||||
|
const timesheet = await this.prisma.timesheets.findFirst({
|
||||||
|
where: { start_date, employee_id },
|
||||||
|
select: { id: true },
|
||||||
|
});
|
||||||
|
if (!timesheet) {
|
||||||
|
return {
|
||||||
|
index,
|
||||||
|
error: new NotFoundException(`Timesheet not found`),
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
index,
|
||||||
|
dto,
|
||||||
|
normed,
|
||||||
|
timesheet_id: timesheet.id,
|
||||||
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return { index, error };
|
return { index, error };
|
||||||
}
|
}
|
||||||
});
|
}));
|
||||||
const ok_items = normed_shift.filter((x): x is NormedOk => "normed" in x);
|
|
||||||
|
|
||||||
const regroup_by_date = new Map<number, number[]>();
|
const ok_items = normed_shifts.filter(
|
||||||
|
(item): item is NormedOk & { timesheet_id: number } => "normed" in item);
|
||||||
|
|
||||||
ok_items.forEach(({ index, normed }) => {
|
const regroup_by_date = new Map<string, number[]>();
|
||||||
const d = normed.date;
|
ok_items.forEach(({ index, normed, timesheet_id }) => {
|
||||||
const key = new Date(d.getFullYear(), d.getMonth(), d.getDate()).getTime();
|
const day = new Date(normed.date.getFullYear(), normed.date.getMonth(), normed.date.getDate()).getTime();
|
||||||
|
const key = `${timesheet_id}|${day}`;
|
||||||
if (!regroup_by_date.has(key)) regroup_by_date.set(key, []);
|
if (!regroup_by_date.has(key)) regroup_by_date.set(key, []);
|
||||||
regroup_by_date.get(key)!.push(index);
|
regroup_by_date.get(key)!.push(index);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const timesheet_keys = Array.from(regroup_by_date.keys()).map((raw) => {
|
||||||
|
const [timesheet, day] = raw.split('|');
|
||||||
|
return {
|
||||||
|
timesheet_id: Number(timesheet),
|
||||||
|
day: Number(day),
|
||||||
|
key: raw,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
for (const indices of regroup_by_date.values()) {
|
for (const indices of regroup_by_date.values()) {
|
||||||
const ordered = indices
|
const ordered = indices
|
||||||
.map(index => {
|
.map(index => {
|
||||||
const item = normed_shift[index] as NormedOk;
|
const item = normed_shifts[index] as NormedOk & { timesheet_id: number };
|
||||||
return { index: index, start: item.normed.start_time, end: item.normed.end_time };
|
return {
|
||||||
|
index: index,
|
||||||
|
start: item.normed.start_time,
|
||||||
|
end: item.normed.end_time
|
||||||
|
};
|
||||||
})
|
})
|
||||||
.sort((a, b) => a.start.getTime() - b.start.getTime());
|
.sort((a, b) => a.start.getTime() - b.start.getTime());
|
||||||
|
|
||||||
for (let j = 1; j < ordered.length; j++) {
|
for (let j = 1; j < ordered.length; j++) {
|
||||||
if (overlaps({ start: ordered[j - 1].start, end: ordered[j - 1].end }, { start: ordered[j].start, end: ordered[j].end })) {
|
if (
|
||||||
const err = new ConflictException({
|
overlaps(
|
||||||
|
{ start: ordered[j - 1].start, end: ordered[j - 1].end },
|
||||||
|
{ start: ordered[j].start, end: ordered[j].end }
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
const error = new ConflictException({
|
||||||
error_code: 'SHIFT_OVERLAP_BATCH',
|
error_code: 'SHIFT_OVERLAP_BATCH',
|
||||||
message: 'New shift overlaps with another shift in the same batch (same day).',
|
message: 'New shift overlaps with another shift in the same batch (same day).',
|
||||||
});
|
});
|
||||||
return dtos.map((_dto, key) =>
|
return dtos.map((_dto, key) =>
|
||||||
indices.includes(key)
|
indices.includes(key)
|
||||||
? ({ ok: false, error: err } as CreateShiftResult)
|
? ({
|
||||||
: ({ ok: false, error: new BadRequestException('Batch aborted due to overlaps in another date group') })
|
ok: false,
|
||||||
|
error
|
||||||
|
} as CreateShiftResult)
|
||||||
|
: ({
|
||||||
|
ok: false,
|
||||||
|
error: new BadRequestException(
|
||||||
|
'Batch aborted due to overlaps in another date group'
|
||||||
|
),
|
||||||
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return this.prisma.$transaction(async (tx) => {
|
return this.prisma.$transaction(async (tx) => {
|
||||||
const results: CreateShiftResult[] = Array.from({ length: dtos.length }, () => ({ ok: false, error: new Error('uninitialized') }));
|
const results: CreateShiftResult[] = Array.from(
|
||||||
|
{ length: dtos.length },
|
||||||
|
() => ({ ok: false, error: new Error('uninitialized') }));
|
||||||
|
|
||||||
|
const existing_map = new Map<string, { start_time: Date; end_time: Date }[]>();
|
||||||
|
|
||||||
normed_shift.forEach((x, i) => {
|
for (const { timesheet_id, day, key } of timesheet_keys) {
|
||||||
|
const day_date = new Date(day);
|
||||||
|
const rows = await tx.shifts.findMany({
|
||||||
|
where: { timesheet_id, date: day_date },
|
||||||
|
select: { start_time: true, end_time: true },
|
||||||
|
});
|
||||||
|
existing_map.set(
|
||||||
|
key,
|
||||||
|
rows.map((row) => ({ start_time: row.start_time, end_time: row.end_time })),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
normed_shifts.forEach((x, i) => {
|
||||||
if ("error" in x) results[i] = { ok: false, error: x.error };
|
if ("error" in x) results[i] = { ok: false, error: x.error };
|
||||||
});
|
});
|
||||||
|
|
||||||
const unique_dates = Array.from(regroup_by_date.keys()).map(ms => new Date(ms));
|
|
||||||
const existing_date = new Map<number, { start_time: Date; end_time: Date }[]>();
|
|
||||||
for (const d of unique_dates) {
|
|
||||||
const rows = await tx.shifts.findMany({
|
|
||||||
where: { date: d },
|
|
||||||
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 })));
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const item of ok_items) {
|
for (const item of ok_items) {
|
||||||
const { index, dto, normed } = item;
|
const { index, dto, normed, timesheet_id } = item;
|
||||||
const dayKey = new Date(normed.date.getFullYear(), normed.date.getMonth(), normed.date.getDate()).getTime();
|
const day_key = new Date(normed.date.getFullYear(), normed.date.getMonth(), normed.date.getDate()).getTime();
|
||||||
const existing = existing_date.get(dayKey) ?? [];
|
const map_key = `${timesheet_id}|${day_key}`;
|
||||||
|
let existing = existing_map.get(map_key);
|
||||||
|
if(!existing) {
|
||||||
|
existing = [];
|
||||||
|
existing_map.set(map_key, existing);
|
||||||
|
}
|
||||||
const hit = existing.find(e => overlaps({ start: e.start_time, end: e.end_time }, { start: normed.start_time, end: normed.end_time }));
|
const hit = existing.find(e => overlaps({ start: e.start_time, end: e.end_time }, { start: normed.start_time, end: normed.end_time }));
|
||||||
if (hit) {
|
if (hit) {
|
||||||
results[index] = {
|
results[index] = {
|
||||||
|
|
@ -114,7 +180,7 @@ export class ShiftsUpsertService {
|
||||||
|
|
||||||
const row = await tx.shifts.create({
|
const row = await tx.shifts.create({
|
||||||
data: {
|
data: {
|
||||||
timesheet_id: dto.timesheet_id,
|
timesheet_id: 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,
|
||||||
|
|
@ -126,10 +192,11 @@ 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);
|
||||||
|
|
||||||
const summary = await this.overtime.getWeekOvertimeSummary(dto.timesheet_id, normed.date, tx);
|
const summary = await this.overtime.getWeekOvertimeSummary(timesheet_id, normed.date, tx);
|
||||||
const shift: GetShiftDto = {
|
const shift: GetShiftDto = {
|
||||||
timesheet_id: row.timesheet_id,
|
timesheet_id: timesheet_id,
|
||||||
bank_code_id: row.bank_code_id,
|
bank_code_id: row.bank_code_id,
|
||||||
date: toStringFromDate(row.date),
|
date: toStringFromDate(row.date),
|
||||||
start_time: toStringFromHHmm(row.start_time),
|
start_time: toStringFromHHmm(row.start_time),
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user