refactor(shifts): refactor main upsert function to use shared utils and helpers
This commit is contained in:
parent
cc310e286d
commit
f6c5b2a73c
|
|
@ -42,8 +42,6 @@ export class EmployeesService {
|
|||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
async findOneProfile(email: string): Promise<EmployeeProfileItemDto> {
|
||||
const emp = await this.prisma.employees.findFirst({
|
||||
where: { user: { email } },
|
||||
|
|
|
|||
|
|
@ -3,28 +3,20 @@ import { Module } from "@nestjs/common";
|
|||
import { ExpensesQueryService } from "./services/expenses-query.service";
|
||||
import { BusinessLogicsModule } from "src/modules/business-logics/business-logics.module";
|
||||
import { ExpensesCommandService } from "./services/expenses-command.service";
|
||||
import { BankCodesRepo } from "./repos/bank-codes.repo";
|
||||
import { TimesheetsRepo } from "./repos/timesheets.repo";
|
||||
import { EmployeesRepo } from "./repos/employee.repo";
|
||||
import { ExpensesArchivalService } from "./services/expenses-archival.service";
|
||||
import { SharedModule } from "../shared/shared.module";
|
||||
|
||||
@Module({
|
||||
imports: [BusinessLogicsModule],
|
||||
imports: [BusinessLogicsModule, SharedModule],
|
||||
controllers: [ExpensesController],
|
||||
providers: [
|
||||
ExpensesQueryService,
|
||||
ExpensesArchivalService,
|
||||
ExpensesCommandService,
|
||||
BankCodesRepo,
|
||||
TimesheetsRepo,
|
||||
EmployeesRepo,
|
||||
],
|
||||
exports: [
|
||||
ExpensesQueryService,
|
||||
ExpensesArchivalService,
|
||||
BankCodesRepo,
|
||||
TimesheetsRepo,
|
||||
EmployeesRepo,
|
||||
],
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -2,16 +2,16 @@ import { BaseApprovalService } from "src/common/shared/base-approval.service";
|
|||
import { Expenses, Prisma } from "@prisma/client";
|
||||
import { PrismaService } from "src/prisma/prisma.service";
|
||||
import { UpsertExpenseDto } from "../dtos/upsert-expense.dto";
|
||||
import { BankCodesRepo } from "../repos/bank-codes.repo";
|
||||
import { TimesheetsRepo } from "../repos/timesheets.repo";
|
||||
import { EmployeesRepo } from "../repos/employee.repo";
|
||||
import { toDateOnlyUTC } from "src/modules/shifts/helpers/shifts-date-time-helpers";
|
||||
import { BankCodesResolver } from "src/modules/shared/utils/resolve-bank-type-id.utils";
|
||||
import { ExpenseResponse, UpsertAction } from "../types and interfaces/expenses.types.interfaces";
|
||||
import { EmployeeIdEmailResolver } from "src/modules/shared/utils/resolve-email-id.utils";
|
||||
import { EmployeeTimesheetResolver } from "src/modules/shared/utils/resolve-employee-timesheet.utils";
|
||||
import {
|
||||
BadRequestException,
|
||||
Injectable,
|
||||
NotFoundException
|
||||
} from "@nestjs/common";
|
||||
import { ExpenseResponse, UpsertAction } from "../types and interfaces/expenses.types.interfaces";
|
||||
import {
|
||||
assertAndTrimComment,
|
||||
computeAmountDecimal,
|
||||
|
|
@ -25,9 +25,9 @@ import {
|
|||
export class ExpensesCommandService extends BaseApprovalService<Expenses> {
|
||||
constructor(
|
||||
prisma: PrismaService,
|
||||
private readonly bankCodesRepo: BankCodesRepo,
|
||||
private readonly timesheetsRepo: TimesheetsRepo,
|
||||
private readonly employeesRepo: EmployeesRepo,
|
||||
private readonly bankCodesResolver: BankCodesResolver,
|
||||
private readonly timesheetsResolver: EmployeeTimesheetResolver,
|
||||
private readonly emailResolver: EmployeeIdEmailResolver,
|
||||
) { super(prisma); }
|
||||
|
||||
//_____________________________________________________________________________________________
|
||||
|
|
@ -56,27 +56,25 @@ export class ExpensesCommandService extends BaseApprovalService<Expenses> {
|
|||
|
||||
//validates if there is an existing expense, at least 1 old or new
|
||||
const { old_expense, new_expense } = dto ?? {};
|
||||
if(!old_expense && !new_expense) {
|
||||
throw new BadRequestException('At least one expense must be provided');
|
||||
}
|
||||
if(!old_expense && !new_expense) throw new BadRequestException('At least one expense must be provided');
|
||||
|
||||
//validate date format
|
||||
const date_only = toDateOnlyUTC(date);
|
||||
if(Number.isNaN(date_only.getTime())) {
|
||||
throw new BadRequestException('Invalid date format (expected: YYYY-MM-DD)');
|
||||
}
|
||||
if(Number.isNaN(date_only.getTime())) throw new BadRequestException('Invalid date format (expected: YYYY-MM-DD)');
|
||||
|
||||
//resolve employee_id by email
|
||||
const employee_id = await this.resolveEmployeeIdByEmail(email);
|
||||
const employee_id = await this.emailResolver.findIdByEmail(email);
|
||||
|
||||
//make sure a timesheet existes
|
||||
const timesheet_id = await this.ensureTimesheetForDate(employee_id, date_only);
|
||||
const timesheet_id = await this.timesheetsResolver.ensureForDate(employee_id, date_only);
|
||||
if(!timesheet_id) throw new NotFoundException(`no timesheet found for employee #${employee_id}`)
|
||||
const {id} = timesheet_id;
|
||||
|
||||
return this.prisma.$transaction(async (tx) => {
|
||||
const loadDay = async (): Promise<ExpenseResponse[]> => {
|
||||
const rows = await tx.expenses.findMany({
|
||||
where: {
|
||||
timesheet_id: timesheet_id,
|
||||
timesheet_id: id,
|
||||
date: date_only,
|
||||
},
|
||||
include: {
|
||||
|
|
@ -118,7 +116,7 @@ export class ExpensesCommandService extends BaseApprovalService<Expenses> {
|
|||
const comment = assertAndTrimComment(payload.comment);
|
||||
const attachment = parseAttachmentId(payload.attachment);
|
||||
|
||||
const { id: bank_code_id, modifier } = await this.resolveBankCodeIdByType(tx, type);
|
||||
const { id: bank_code_id, modifier } = await this.bankCodesResolver.findByType(type);
|
||||
let amount = computeAmountDecimal(type, payload, modifier);
|
||||
let mileage: number | null = null;
|
||||
|
||||
|
|
@ -139,11 +137,11 @@ export class ExpensesCommandService extends BaseApprovalService<Expenses> {
|
|||
}
|
||||
|
||||
if (attachment !== null) {
|
||||
const attachmentRow = await tx.attachments.findUnique({
|
||||
const attachment_row = await tx.attachments.findUnique({
|
||||
where: { id: attachment },
|
||||
select: { status: true },
|
||||
});
|
||||
if (!attachmentRow || attachmentRow.status !== 'ACTIVE') {
|
||||
if (!attachment_row || attachment_row.status !== 'ACTIVE') {
|
||||
throw new BadRequestException('Attachment not found or inactive');
|
||||
}
|
||||
}
|
||||
|
|
@ -167,7 +165,7 @@ export class ExpensesCommandService extends BaseApprovalService<Expenses> {
|
|||
}) => {
|
||||
return tx.expenses.findFirst({
|
||||
where: {
|
||||
timesheet_id: timesheet_id,
|
||||
timesheet_id: id,
|
||||
date: date_only,
|
||||
bank_code_id: norm.bank_code_id,
|
||||
amount: norm.amount,
|
||||
|
|
@ -184,8 +182,8 @@ export class ExpensesCommandService extends BaseApprovalService<Expenses> {
|
|||
// DELETE
|
||||
//_____________________________________________________________________________________________
|
||||
if(old_expense && !new_expense) {
|
||||
const oldNorm = await normalizePayload(old_expense);
|
||||
const existing = await findExactOld(oldNorm);
|
||||
const old_norm = await normalizePayload(old_expense);
|
||||
const existing = await findExactOld(old_norm);
|
||||
if(!existing) {
|
||||
throw new NotFoundException({
|
||||
error_code: 'EXPENSE_STALE',
|
||||
|
|
@ -202,7 +200,7 @@ export class ExpensesCommandService extends BaseApprovalService<Expenses> {
|
|||
const new_exp = await normalizePayload(new_expense);
|
||||
await tx.expenses.create({
|
||||
data: {
|
||||
timesheet_id: timesheet_id,
|
||||
timesheet_id: id,
|
||||
date: date_only,
|
||||
bank_code_id: new_exp.bank_code_id,
|
||||
amount: new_exp.amount,
|
||||
|
|
@ -218,8 +216,8 @@ export class ExpensesCommandService extends BaseApprovalService<Expenses> {
|
|||
// UPDATE
|
||||
//_____________________________________________________________________________________________
|
||||
else if(old_expense && new_expense) {
|
||||
const oldNorm = await normalizePayload(old_expense);
|
||||
const existing = await findExactOld(oldNorm);
|
||||
const old_norm = await normalizePayload(old_expense);
|
||||
const existing = await findExactOld(old_norm);
|
||||
if(!existing) {
|
||||
throw new NotFoundException({
|
||||
error_code: 'EXPENSE_STALE',
|
||||
|
|
@ -249,22 +247,4 @@ export class ExpensesCommandService extends BaseApprovalService<Expenses> {
|
|||
return { action, day };
|
||||
});
|
||||
}
|
||||
|
||||
//_____________________________________________________________________________________________
|
||||
// HELPERS
|
||||
//_____________________________________________________________________________________________
|
||||
|
||||
private readonly resolveEmployeeIdByEmail = async (email: string): Promise<number> =>
|
||||
this.employeesRepo.findIdByEmail(email);
|
||||
|
||||
private readonly ensureTimesheetForDate = async ( employee_id: number, date: Date
|
||||
): Promise<number> => {
|
||||
const { id } = await this.timesheetsRepo.ensureForDate(employee_id, date);
|
||||
return id;
|
||||
};
|
||||
|
||||
private readonly resolveBankCodeIdByType = async ( transaction: Prisma.TransactionClient, type: string
|
||||
): Promise<{id: number; modifier: number}> =>
|
||||
this.bankCodesRepo.findByType(type, transaction);
|
||||
|
||||
}
|
||||
|
|
@ -1,15 +1,15 @@
|
|||
import { Injectable, NotFoundException } from "@nestjs/common";
|
||||
import { PrismaService } from "src/prisma/prisma.service";
|
||||
import { DayExpensesDto, ExpenseDto } from "src/modules/timesheets/dtos/timesheet-period.dto";
|
||||
import { EmployeesRepo } from "../repos/employee.repo";
|
||||
import { round2, toUTCDateOnly } from "src/modules/timesheets/utils/timesheet.helpers";
|
||||
import { EXPENSE_TYPES } from "src/modules/timesheets/types/timesheet.types";
|
||||
import { EmployeeIdEmailResolver } from "src/modules/shared/utils/resolve-email-id.utils";
|
||||
|
||||
@Injectable()
|
||||
export class ExpensesQueryService {
|
||||
constructor(
|
||||
private readonly prisma: PrismaService,
|
||||
private readonly employeeRepo: EmployeesRepo,
|
||||
private readonly employeeRepo: EmployeeIdEmailResolver,
|
||||
) {}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -7,21 +7,16 @@ import { TimesheetsModule } from "../timesheets/timesheets.module";
|
|||
import { TimesheetsCommandService } from "../timesheets/services/timesheets-command.service";
|
||||
import { ExpensesCommandService } from "../expenses/services/expenses-command.service";
|
||||
import { ShiftsCommandService } from "../shifts/services/shifts-command.service";
|
||||
import { BankCodesRepo } from "../expenses/repos/bank-codes.repo";
|
||||
import { EmployeesRepo } from "../expenses/repos/employee.repo";
|
||||
import { TimesheetsRepo } from "../expenses/repos/timesheets.repo";
|
||||
import { SharedModule } from "../shared/shared.module";
|
||||
|
||||
@Module({
|
||||
imports: [PrismaModule, TimesheetsModule],
|
||||
imports: [PrismaModule, TimesheetsModule, SharedModule],
|
||||
providers: [
|
||||
PayPeriodsQueryService,
|
||||
PayPeriodsCommandService,
|
||||
TimesheetsCommandService,
|
||||
ExpensesCommandService,
|
||||
ShiftsCommandService,
|
||||
BankCodesRepo,
|
||||
TimesheetsRepo,
|
||||
EmployeesRepo,
|
||||
],
|
||||
controllers: [PayPeriodsController],
|
||||
exports: [
|
||||
|
|
|
|||
22
src/modules/shared/shared.module.ts
Normal file
22
src/modules/shared/shared.module.ts
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
import { Module } from "@nestjs/common";
|
||||
import { EmployeeIdEmailResolver } from "./utils/resolve-email-id.utils";
|
||||
import { EmployeeTimesheetResolver } from "./utils/resolve-employee-timesheet.utils";
|
||||
import { FullNameResolver } from "./utils/resolve-full-name.utils";
|
||||
import { BankCodesResolver } from "./utils/resolve-bank-type-id.utils";
|
||||
import { PrismaModule } from "src/prisma/prisma.module";
|
||||
|
||||
@Module({
|
||||
imports: [PrismaModule],
|
||||
providers: [
|
||||
FullNameResolver,
|
||||
EmployeeIdEmailResolver,
|
||||
BankCodesResolver,
|
||||
EmployeeTimesheetResolver,
|
||||
],
|
||||
exports: [
|
||||
FullNameResolver,
|
||||
EmployeeIdEmailResolver,
|
||||
BankCodesResolver,
|
||||
EmployeeTimesheetResolver,
|
||||
],
|
||||
}) export class SharedModule {}
|
||||
|
|
@ -2,11 +2,10 @@ import { Injectable, NotFoundException } from "@nestjs/common";
|
|||
import { Prisma, PrismaClient } from "@prisma/client";
|
||||
import { PrismaService } from "src/prisma/prisma.service";
|
||||
|
||||
|
||||
type Tx = Prisma.TransactionClient | PrismaClient;
|
||||
|
||||
@Injectable()
|
||||
export class BankCodesRepo {
|
||||
export class BankCodesResolver {
|
||||
constructor(private readonly prisma: PrismaService) {}
|
||||
|
||||
//find id and modifier by type
|
||||
|
|
@ -14,21 +13,11 @@ export class BankCodesRepo {
|
|||
): Promise<{id:number; modifier: number }> => {
|
||||
const db = client ?? this.prisma;
|
||||
const bank = await db.bankCodes.findFirst({
|
||||
where: {
|
||||
type,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
modifier: true,
|
||||
},
|
||||
where: { type },
|
||||
select: { id: true, modifier: true },
|
||||
});
|
||||
|
||||
if(!bank) {
|
||||
throw new NotFoundException(`Unknown bank code type: ${type}`);
|
||||
}
|
||||
return {
|
||||
id: bank.id,
|
||||
modifier: bank.modifier,
|
||||
};
|
||||
if(!bank) throw new NotFoundException(`Unknown bank code type: ${type}`);
|
||||
return { id: bank.id, modifier: bank.modifier };
|
||||
};
|
||||
}
|
||||
|
|
@ -2,31 +2,22 @@ import { Injectable, NotFoundException } from "@nestjs/common";
|
|||
import { Prisma, PrismaClient } from "@prisma/client";
|
||||
import { PrismaService } from "src/prisma/prisma.service";
|
||||
|
||||
|
||||
type Tx = Prisma.TransactionClient | PrismaClient;
|
||||
|
||||
@Injectable()
|
||||
export class EmployeesRepo {
|
||||
export class EmployeeIdEmailResolver {
|
||||
|
||||
constructor(private readonly prisma: PrismaService) {}
|
||||
|
||||
// find employee id by email
|
||||
// find employee_id using email
|
||||
readonly findIdByEmail = async ( email: string, client?: Tx
|
||||
): Promise<number> => {
|
||||
const db = client ?? this.prisma;
|
||||
const employee = await db.employees.findFirst({
|
||||
where: {
|
||||
user: {
|
||||
email,
|
||||
},
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
},
|
||||
where: { user: { email } },
|
||||
select: { id: true },
|
||||
});
|
||||
|
||||
if(!employee) {
|
||||
throw new NotFoundException(`Employee with email: ${email} not found`);
|
||||
}
|
||||
if(!employee)throw new NotFoundException(`Employee with email: ${email} not found`);
|
||||
return employee.id;
|
||||
}
|
||||
}
|
||||
|
|
@ -7,7 +7,7 @@ import { PrismaService } from "src/prisma/prisma.service";
|
|||
type Tx = Prisma.TransactionClient | PrismaClient;
|
||||
|
||||
@Injectable()
|
||||
export class TimesheetsRepo {
|
||||
export class EmployeeTimesheetResolver {
|
||||
constructor(private readonly prisma: PrismaService) {}
|
||||
|
||||
//find an existing timesheet linked to the employee
|
||||
22
src/modules/shared/utils/resolve-full-name.utils.ts
Normal file
22
src/modules/shared/utils/resolve-full-name.utils.ts
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
import { Injectable, NotFoundException } from "@nestjs/common";
|
||||
import { Prisma, PrismaClient } from "@prisma/client";
|
||||
import { PrismaService } from "src/prisma/prisma.service";
|
||||
|
||||
type Tx = Prisma.TransactionClient | PrismaClient;
|
||||
|
||||
@Injectable()
|
||||
export class FullNameResolver {
|
||||
constructor(private readonly prisma: PrismaService){}
|
||||
|
||||
readonly resolveFullName = async (employee_id: number, client?: Tx): Promise<string> =>{
|
||||
const db = client ?? this.prisma;
|
||||
const employee = await db.employees.findUnique({
|
||||
where: { id: employee_id },
|
||||
select: { user: { select: {first_name: true, last_name: true} } },
|
||||
});
|
||||
if(!employee) throw new NotFoundException(`Unknown user with name: ${employee_id}`)
|
||||
|
||||
const full_name = ( employee.user.first_name + " " + employee.user.last_name ) || " ";
|
||||
return full_name;
|
||||
}
|
||||
}
|
||||
|
|
@ -6,7 +6,7 @@ import { ShiftsCommandService } from "../services/shifts-command.service";
|
|||
import { ShiftsQueryService } from "../services/shifts-query.service";
|
||||
import { GetShiftsOverviewDto } from "../dtos/get-shift-overview.dto";
|
||||
import { UpsertShiftDto } from "../dtos/upsert-shift.dto";
|
||||
import { OverviewRow } from "../types and interfaces/shifts-overview-row.interface";
|
||||
import { OverviewRow } from "../types-and-interfaces/shifts-overview-row.interface";
|
||||
|
||||
@ApiTags('Shifts')
|
||||
@ApiBearerAuth('access-token')
|
||||
|
|
|
|||
|
|
@ -3,13 +3,11 @@ export function timeFromHHMMUTC(hhmm: string): Date {
|
|||
return new Date(Date.UTC(1970,0,1,hour, min,0));
|
||||
}
|
||||
|
||||
export function weekStartMondayUTC(date: Date): Date {
|
||||
const d = new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate()));
|
||||
export function weekStartSundayUTC(d: Date): Date {
|
||||
const day = d.getUTCDay();
|
||||
const diff = (day + 6) % 7;
|
||||
d.setUTCDate(d.getUTCDate() - diff);
|
||||
d.setUTCHours(0,0,0,0);
|
||||
return d;
|
||||
const start = new Date(Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate()));
|
||||
start.setUTCDate(start.getUTCDate()- day);
|
||||
return start;
|
||||
}
|
||||
|
||||
export function toDateOnlyUTC(input: string | Date): Date {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
import { BadRequestException, ConflictException, Injectable, NotFoundException, UnprocessableEntityException } from "@nestjs/common";
|
||||
import { formatHHmm, toDateOnlyUTC, weekStartMondayUTC } from "../helpers/shifts-date-time-helpers";
|
||||
import { normalizeShiftPayload, overlaps, resolveBankCodeByType } from "../utils/shifts.utils";
|
||||
import { DayShiftResponse, UpsertAction } from "../types and interfaces/shifts-upsert.types";
|
||||
import { formatHHmm, toDateOnlyUTC, weekStartSundayUTC } from "../helpers/shifts-date-time-helpers";
|
||||
import { normalizeShiftPayload, overlaps } from "../utils/shifts.utils";
|
||||
import { DayShiftResponse, UpsertAction } from "../types-and-interfaces/shifts-upsert.types";
|
||||
import { EmployeeIdEmailResolver } from "src/modules/shared/utils/resolve-email-id.utils";
|
||||
import { BankCodesResolver } from "src/modules/shared/utils/resolve-bank-type-id.utils";
|
||||
import { Prisma, Shifts } from "@prisma/client";
|
||||
import { UpsertShiftDto } from "../dtos/upsert-shift.dto";
|
||||
import { BaseApprovalService } from "src/common/shared/base-approval.service";
|
||||
|
|
@ -9,7 +11,11 @@ import { PrismaService } from "src/prisma/prisma.service";
|
|||
|
||||
@Injectable()
|
||||
export class ShiftsCommandService extends BaseApprovalService<Shifts> {
|
||||
constructor(prisma: PrismaService) { super(prisma); }
|
||||
constructor(
|
||||
prisma: PrismaService,
|
||||
private readonly emailResolver: EmployeeIdEmailResolver,
|
||||
private readonly bankTypeResolver: BankCodesResolver,
|
||||
) { super(prisma); }
|
||||
|
||||
//_____________________________________________________________________________________________
|
||||
// APPROVAL AND DELEGATE METHODS
|
||||
|
|
@ -40,88 +46,53 @@ export class ShiftsCommandService extends BaseApprovalService<Shifts> {
|
|||
}
|
||||
|
||||
const date_only = toDateOnlyUTC(date_string);
|
||||
const employee_id = await this.emailResolver.findIdByEmail(email);
|
||||
|
||||
//Resolve employee by email
|
||||
const employee = await this.prisma.employees.findFirst({
|
||||
where: { user: {email } },
|
||||
select: { id: true },
|
||||
});
|
||||
if(!employee) throw new NotFoundException(`Employee not found for email : ${ email }`);
|
||||
return this.prisma.$transaction(async (tx) => {
|
||||
const start_of_week = weekStartSundayUTC(date_only);
|
||||
|
||||
//making sure a timesheet exist in selected week
|
||||
const start_of_week = weekStartMondayUTC(date_only);
|
||||
let timesheet = await this.prisma.timesheets.findFirst({
|
||||
where: {
|
||||
employee_id: employee.id,
|
||||
start_date: start_of_week
|
||||
},
|
||||
select: {
|
||||
id: true
|
||||
},
|
||||
});
|
||||
if(!timesheet) {
|
||||
timesheet = await this.prisma.timesheets.create({
|
||||
data: {
|
||||
employee_id: employee.id,
|
||||
start_date: start_of_week
|
||||
},
|
||||
select: {
|
||||
id: true
|
||||
},
|
||||
const timesheet = await tx.timesheets.upsert({
|
||||
where: { employee_id_start_date: { employee_id, start_date: start_of_week } },
|
||||
update: {},
|
||||
create: { employee_id, start_date: start_of_week },
|
||||
select: { id: true },
|
||||
});
|
||||
}
|
||||
|
||||
//normalization of data to ensure a valid comparison between DB and payload
|
||||
const old_norm = dto.old_shift
|
||||
? normalizeShiftPayload(dto.old_shift)
|
||||
: undefined;
|
||||
const new_norm = dto.new_shift
|
||||
? normalizeShiftPayload(dto.new_shift)
|
||||
: undefined;
|
||||
//validation/sanitation
|
||||
//resolve bank_code_id using type
|
||||
const old_norm_shift = old_shift ? await normalizeShiftPayload(old_shift) : undefined;
|
||||
if (old_norm_shift && old_norm_shift.end_time.getTime() <= old_norm_shift.start_time.getTime()) {
|
||||
throw new UnprocessableEntityException(' old_shift.end_time must be > old_shift.start_time');
|
||||
}
|
||||
const old_bank_code_id: number | undefined = old_norm_shift ? (await this.bankTypeResolver.findByType(old_norm_shift.type, tx))?.id : undefined;
|
||||
|
||||
if (old_norm && old_norm.end_time.getTime() <= old_norm.start_time.getTime()) {
|
||||
throw new UnprocessableEntityException(' old_shift.end_time must be > old_shift.start_time');
|
||||
}
|
||||
if (new_norm && new_norm.end_time.getTime() <= new_norm.start_time.getTime()) {
|
||||
throw new UnprocessableEntityException(' new_shift.end_time must be > new_shift.start_time');
|
||||
}
|
||||
|
||||
//Resolve bank_code_id with type
|
||||
const old_bank_code_id = old_norm
|
||||
? await resolveBankCodeByType(old_norm.type)
|
||||
: undefined;
|
||||
const new_bank_code_id = new_norm
|
||||
? await resolveBankCodeByType(new_norm.type)
|
||||
: undefined;
|
||||
const new_norm_shift = new_shift ? await normalizeShiftPayload(new_shift) : undefined;
|
||||
if (new_norm_shift && new_norm_shift.end_time.getTime() <= new_norm_shift.start_time.getTime()) {
|
||||
throw new UnprocessableEntityException(' new_shift.end_time must be > new_shift.start_time');
|
||||
}
|
||||
const new_bank_code_id: number | undefined = new_norm_shift ? (await this.bankTypeResolver.findByType(new_norm_shift.type, tx))?.id : undefined;
|
||||
|
||||
//fetch all shifts in a single day
|
||||
const day_shifts = await this.prisma.shifts.findMany({
|
||||
where: {
|
||||
timesheet_id: timesheet.id,
|
||||
date: date_only
|
||||
},
|
||||
include: {
|
||||
bank_code: true
|
||||
},
|
||||
orderBy: {
|
||||
start_time: 'asc'
|
||||
},
|
||||
});
|
||||
|
||||
const result = await this.prisma.$transaction(async (transaction)=> {
|
||||
let action: UpsertAction;
|
||||
//fetch all shifts in a single day and verify possible overlaps
|
||||
const day_shifts = await tx.shifts.findMany({
|
||||
where: { timesheet_id: timesheet.id, date: date_only },
|
||||
include: { bank_code: true },
|
||||
orderBy: { start_time: 'asc'},
|
||||
});
|
||||
|
||||
|
||||
const findExactOldShift = async ()=> {
|
||||
if(!old_norm || old_bank_code_id === undefined) return undefined;
|
||||
const old_comment = old_norm.comment ?? null;
|
||||
if(!old_norm_shift || old_bank_code_id === undefined) return undefined;
|
||||
const old_comment = old_norm_shift.comment ?? null;
|
||||
|
||||
return transaction.shifts.findFirst({
|
||||
return await tx.shifts.findFirst({
|
||||
where: {
|
||||
timesheet_id: timesheet.id,
|
||||
date: date_only,
|
||||
start_time: old_norm.start_time,
|
||||
end_time: old_norm.end_time,
|
||||
is_remote: old_norm.is_remote,
|
||||
start_time: old_norm_shift.start_time,
|
||||
end_time: old_norm_shift.end_time,
|
||||
is_remote: old_norm_shift.is_remote,
|
||||
comment: old_comment,
|
||||
bank_code_id: old_bank_code_id,
|
||||
},
|
||||
|
|
@ -131,12 +102,12 @@ export class ShiftsCommandService extends BaseApprovalService<Shifts> {
|
|||
|
||||
//checks for overlaping shifts
|
||||
const assertNoOverlap = (exclude_shift_id?: number)=> {
|
||||
if (!new_norm) return;
|
||||
if (!new_norm_shift) return;
|
||||
const overlap_with = day_shifts.filter((shift)=> {
|
||||
if(exclude_shift_id && shift.id === exclude_shift_id) return false;
|
||||
return overlaps(
|
||||
new_norm.start_time.getTime(),
|
||||
new_norm.end_time.getTime(),
|
||||
new_norm_shift.start_time.getTime(),
|
||||
new_norm_shift.end_time.getTime(),
|
||||
shift.start_time.getTime(),
|
||||
shift.end_time.getTime(),
|
||||
);
|
||||
|
|
@ -148,100 +119,93 @@ export class ShiftsCommandService extends BaseApprovalService<Shifts> {
|
|||
end_time: formatHHmm(shift.end_time),
|
||||
type: shift.bank_code?.type ?? 'UNKNOWN',
|
||||
}));
|
||||
throw new ConflictException({
|
||||
error_code: 'SHIFT_OVERLAP',
|
||||
message: 'New shift overlaps with existing shift(s)',
|
||||
conflicts,
|
||||
});
|
||||
throw new ConflictException({ error_code: 'SHIFT_OVERLAP', message: 'New shift overlaps with existing shift(s)', conflicts});
|
||||
}
|
||||
};
|
||||
|
||||
//_____________________________________________________________________________________________
|
||||
// DELETE
|
||||
//_____________________________________________________________________________________________
|
||||
if ( old_shift && !new_shift ) {
|
||||
const existing = await findExactOldShift();
|
||||
if(!existing) {
|
||||
throw new NotFoundException({
|
||||
error_code: 'SHIFT_STALE',
|
||||
message: 'The shift was modified or deleted by someone else',
|
||||
});
|
||||
}
|
||||
await transaction.shifts.delete({ where: { id: existing.id } } );
|
||||
action = 'deleted';
|
||||
}
|
||||
//_____________________________________________________________________________________________
|
||||
// CREATE
|
||||
//_____________________________________________________________________________________________
|
||||
else if (!old_shift && new_shift) {
|
||||
assertNoOverlap();
|
||||
await transaction.shifts.create({
|
||||
data: {
|
||||
timesheet_id: timesheet.id,
|
||||
date: date_only,
|
||||
start_time: new_norm!.start_time,
|
||||
end_time: new_norm!.end_time,
|
||||
is_remote: new_norm!.is_remote,
|
||||
comment: new_norm!.comment ?? null,
|
||||
bank_code_id: new_bank_code_id!,
|
||||
},
|
||||
};
|
||||
let action: UpsertAction;
|
||||
//_____________________________________________________________________________________________
|
||||
// DELETE
|
||||
//_____________________________________________________________________________________________
|
||||
if ( old_shift && !new_shift ) {
|
||||
if (old_bank_code_id === undefined) throw new NotFoundException(`bank code not found for old_shift.type: ${old_norm_shift?.type ?? ''}`);
|
||||
const existing = await findExactOldShift();
|
||||
if(!existing) {
|
||||
throw new NotFoundException({
|
||||
error_code: 'SHIFT_STALE',
|
||||
message: 'The shift was modified or deleted by someone else',
|
||||
});
|
||||
action = 'created';
|
||||
}
|
||||
//_____________________________________________________________________________________________
|
||||
// UPDATE
|
||||
//_____________________________________________________________________________________________
|
||||
else if (old_shift && new_shift){
|
||||
await tx.shifts.delete({ where: { id: existing.id } } );
|
||||
action = 'deleted';
|
||||
}
|
||||
//_____________________________________________________________________________________________
|
||||
// CREATE
|
||||
//_____________________________________________________________________________________________
|
||||
else if (!old_shift && new_shift) {
|
||||
if (new_bank_code_id === undefined) throw new NotFoundException(`bank code not found for new_shift.type: ${new_norm_shift?.type ?? ''}`);
|
||||
assertNoOverlap();
|
||||
await tx.shifts.create({
|
||||
data: {
|
||||
timesheet_id: 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,
|
||||
comment: new_norm_shift!.comment ?? null,
|
||||
bank_code_id: new_bank_code_id!,
|
||||
},
|
||||
});
|
||||
action = 'created';
|
||||
}
|
||||
//_____________________________________________________________________________________________
|
||||
// UPDATE
|
||||
//_____________________________________________________________________________________________
|
||||
else if (old_shift && new_shift){
|
||||
if (old_bank_code_id === undefined) throw new NotFoundException(`bank code not found for old_shift.type: ${old_norm_shift?.type ?? ''}`);
|
||||
if (new_bank_code_id === undefined) throw new NotFoundException(`bank code not found for new_shift.type: ${new_norm_shift?.type ?? ''}`);
|
||||
const existing = await findExactOldShift();
|
||||
if(!existing) {
|
||||
throw new NotFoundException({
|
||||
error_code: 'SHIFT_STALE',
|
||||
message: 'The shift was modified or deleted by someone else',
|
||||
});
|
||||
}
|
||||
if(!existing) throw new NotFoundException({ error_code: 'SHIFT_STALE', message: 'The shift was modified or deleted by someone else'});
|
||||
assertNoOverlap(existing.id);
|
||||
await transaction.shifts.update({
|
||||
|
||||
await tx.shifts.update({
|
||||
where: {
|
||||
id: existing.id
|
||||
},
|
||||
data: {
|
||||
start_time: new_norm!.start_time,
|
||||
end_time: new_norm!.end_time,
|
||||
is_remote: new_norm!.is_remote,
|
||||
comment: new_norm!.comment ?? null,
|
||||
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,
|
||||
},
|
||||
});
|
||||
action = 'updated';
|
||||
} else {
|
||||
throw new BadRequestException('At least one of old_shift or new_shift must be provided');
|
||||
}
|
||||
} else throw new BadRequestException('At least one of old_shift or new_shift must be provided');
|
||||
|
||||
//Reload the day (truth source)
|
||||
const fresh_day = await transaction.shifts.findMany({
|
||||
where: {
|
||||
date: date_only,
|
||||
timesheet_id: timesheet.id,
|
||||
},
|
||||
include: {
|
||||
bank_code: true
|
||||
},
|
||||
orderBy: {
|
||||
start_time: 'asc'
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
action,
|
||||
day: fresh_day.map<DayShiftResponse>((shift)=> ({
|
||||
start_time: formatHHmm(shift.start_time),
|
||||
end_time: formatHHmm(shift.end_time),
|
||||
type: shift.bank_code?.type ?? 'UNKNOWN',
|
||||
is_remote: shift.is_remote,
|
||||
comment: shift.comment ?? null,
|
||||
})),
|
||||
};
|
||||
//Reload the day (truth source)
|
||||
const fresh_day = await tx.shifts.findMany({
|
||||
where: {
|
||||
date: date_only,
|
||||
timesheet_id: timesheet.id,
|
||||
},
|
||||
include: {
|
||||
bank_code: true
|
||||
},
|
||||
orderBy: {
|
||||
start_time: 'asc'
|
||||
},
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
return {
|
||||
action,
|
||||
day: fresh_day.map<DayShiftResponse>((shift)=> ({
|
||||
start_time: formatHHmm(shift.start_time),
|
||||
end_time: formatHHmm(shift.end_time),
|
||||
type: shift.bank_code?.type ?? 'UNKNOWN',
|
||||
is_remote: shift.is_remote,
|
||||
comment: shift.comment ?? null,
|
||||
})),
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -2,7 +2,7 @@ import { Injectable, NotFoundException } from "@nestjs/common";
|
|||
import { PrismaService } from "src/prisma/prisma.service";
|
||||
import { NotificationsService } from "src/modules/notifications/services/notifications.service";
|
||||
import { computeHours } from "src/common/utils/date-utils";
|
||||
import { OverviewRow } from "../types and interfaces/shifts-overview-row.interface";
|
||||
import { OverviewRow } from "../types-and-interfaces/shifts-overview-row.interface";
|
||||
|
||||
// const DAILY_LIMIT_HOURS = Number(process.env.DAILY_LIMIT_HOURS ?? 12);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,13 +1,12 @@
|
|||
import { BadRequestException, Body, Controller, Delete, Get, Param, ParseBoolPipe, ParseIntPipe, Patch, Post, Query } from '@nestjs/common';
|
||||
import { BadRequestException, Body, Controller, Get, Param, ParseIntPipe, Post, Query } from '@nestjs/common';
|
||||
import { TimesheetsQueryService } from '../services/timesheets-query.service';
|
||||
import { CreateTimesheetDto, CreateWeekShiftsDto } from '../dtos/create-timesheet.dto';
|
||||
import { Timesheets } from '@prisma/client';
|
||||
import { CreateWeekShiftsDto } from '../dtos/create-timesheet.dto';
|
||||
import { RolesAllowed } from "src/common/decorators/roles.decorators";
|
||||
import { Roles as RoleEnum } from '.prisma/client';
|
||||
import { ApiBearerAuth, ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger';
|
||||
import { ApiBearerAuth, ApiTags } from '@nestjs/swagger';
|
||||
import { TimesheetsCommandService } from '../services/timesheets-command.service';
|
||||
import { TimesheetPeriodDto } from '../dtos/timesheet-period.dto';
|
||||
import { TimesheetDto } from '../dtos/overview-timesheet.dto';
|
||||
import { TimesheetDto, TimesheetPeriodDto } from '../dtos/timesheet-period.dto';
|
||||
|
||||
|
||||
@ApiTags('Timesheets')
|
||||
@ApiBearerAuth('access-token')
|
||||
|
|
|
|||
|
|
@ -1,28 +0,0 @@
|
|||
export class TimesheetDto {
|
||||
is_approved: boolean;
|
||||
start_day: string;
|
||||
end_day: string;
|
||||
label: string;
|
||||
shifts: ShiftsDto[];
|
||||
expenses: ExpensesDto[]
|
||||
}
|
||||
|
||||
export class ShiftsDto {
|
||||
bank_type: string;
|
||||
date: string;
|
||||
start_time: string;
|
||||
end_time: string;
|
||||
comment: string;
|
||||
is_approved: boolean;
|
||||
is_remote: boolean;
|
||||
}
|
||||
|
||||
export class ExpensesDto {
|
||||
bank_type: string;
|
||||
date: string;
|
||||
amount: number;
|
||||
mileage: number;
|
||||
comment: string;
|
||||
supervisor_comment: string;
|
||||
is_approved: boolean;
|
||||
}
|
||||
|
|
@ -1,3 +1,12 @@
|
|||
export class TimesheetDto {
|
||||
start_day: string;
|
||||
end_day: string;
|
||||
label: string;
|
||||
shifts: ShiftDto[];
|
||||
expenses: ExpenseDto[]
|
||||
is_approved: boolean;
|
||||
}
|
||||
|
||||
export class ShiftDto {
|
||||
date: string;
|
||||
type: string;
|
||||
|
|
@ -31,7 +40,7 @@ export class DetailedShifts {
|
|||
}
|
||||
|
||||
export class DayExpensesDto {
|
||||
expenses: ExpenseDto[];
|
||||
expenses: ExpenseDto[] = [];
|
||||
total_mileage: number;
|
||||
total_expense: number;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +0,0 @@
|
|||
import { PartialType } from "@nestjs/swagger";
|
||||
import { CreateTimesheetDto } from "./create-timesheet.dto";
|
||||
|
||||
export class UpdateTimesheetDto extends PartialType(CreateTimesheetDto) {}
|
||||
|
|
@ -4,15 +4,21 @@ import { BaseApprovalService } from "src/common/shared/base-approval.service";
|
|||
import { PrismaService } from "src/prisma/prisma.service";
|
||||
import { TimesheetsQueryService } from "./timesheets-query.service";
|
||||
import { CreateTimesheetDto } from "../dtos/create-timesheet.dto";
|
||||
import { TimesheetDto } from "../dtos/overview-timesheet.dto";
|
||||
import { getWeekEnd, getWeekStart } from "src/common/utils/date-utils";
|
||||
import { parseISODate, parseHHmm } from "../utils/timesheet.helpers";
|
||||
import { TimesheetDto } from "../dtos/timesheet-period.dto";
|
||||
import { EmployeeIdEmailResolver } from "src/modules/shared/utils/resolve-email-id.utils";
|
||||
import { EmployeeTimesheetResolver } from "src/modules/shared/utils/resolve-employee-timesheet.utils";
|
||||
import { BankCodesResolver } from "src/modules/shared/utils/resolve-bank-type-id.utils";
|
||||
|
||||
@Injectable()
|
||||
export class TimesheetsCommandService extends BaseApprovalService<Timesheets>{
|
||||
constructor(
|
||||
prisma: PrismaService,
|
||||
private readonly query: TimesheetsQueryService,
|
||||
private readonly emailResolver: EmployeeIdEmailResolver,
|
||||
private readonly timesheetResolver: EmployeeTimesheetResolver,
|
||||
private readonly bankTypeResolver: BankCodesResolver,
|
||||
) {super(prisma);}
|
||||
//_____________________________________________________________________________________________
|
||||
// APPROVAL AND DELEGATE METHODS
|
||||
|
|
@ -33,17 +39,14 @@ export class TimesheetsCommandService extends BaseApprovalService<Timesheets>{
|
|||
|
||||
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 },
|
||||
});
|
||||
|
||||
await transaction.expenses.updateManyAndReturn({
|
||||
where: { timesheet_id: timesheetId },
|
||||
data: { is_approved: isApproved },
|
||||
});
|
||||
|
||||
return timesheet;
|
||||
}
|
||||
|
||||
|
|
@ -56,20 +59,9 @@ export class TimesheetsCommandService extends BaseApprovalService<Timesheets>{
|
|||
shifts: CreateTimesheetDto[],
|
||||
week_offset = 0,
|
||||
): Promise<TimesheetDto> {
|
||||
|
||||
//match user's email with email
|
||||
const user = await this.prisma.users.findUnique({
|
||||
where: { email },
|
||||
select: { id: true },
|
||||
});
|
||||
if(!user) throw new NotFoundException(`user with email ${email} not found`);
|
||||
|
||||
//fetchs employee matchint user's email
|
||||
const employee = await this.prisma.employees.findFirst({
|
||||
where: { user_id: user?.id },
|
||||
select: { id: true },
|
||||
});
|
||||
if(!employee) throw new NotFoundException(`employee for ${ email } not found`);
|
||||
const employee_id = await this.emailResolver.findIdByEmail(email);
|
||||
if(!employee_id) throw new NotFoundException(`employee for ${ email } not found`);
|
||||
|
||||
//insure that the week starts on sunday and finishes on saturday
|
||||
const base = new Date();
|
||||
|
|
@ -77,43 +69,27 @@ export class TimesheetsCommandService extends BaseApprovalService<Timesheets>{
|
|||
const start_week = getWeekStart(base, 0);
|
||||
const end_week = getWeekEnd(start_week);
|
||||
|
||||
const timesheet = await this.prisma.timesheets.upsert({
|
||||
where: {
|
||||
employee_id_start_date: {
|
||||
employee_id: employee.id,
|
||||
start_date: start_week,
|
||||
},
|
||||
},
|
||||
create: {
|
||||
employee_id: employee.id,
|
||||
start_date: start_week,
|
||||
is_approved: false,
|
||||
},
|
||||
update: {},
|
||||
select: { id: true },
|
||||
});
|
||||
const timesheet = await this.timesheetResolver.ensureForDate(employee_id, base)
|
||||
if(!timesheet) throw new NotFoundException(`no timesheet found for employe ${employee_id}`);
|
||||
|
||||
//validations and insertions
|
||||
for(const shift of shifts) {
|
||||
const date = parseISODate(shift.date);
|
||||
if (date < start_week || date > end_week) throw new BadRequestException(`date ${shift.date} not in current week`);
|
||||
|
||||
const bank_code = await this.prisma.bankCodes.findFirst({
|
||||
where: { type: shift.type },
|
||||
select: { id: true },
|
||||
});
|
||||
const bank_code = await this.bankTypeResolver.findByType(shift.type)
|
||||
if(!bank_code) throw new BadRequestException(`Invalid bank_code type: ${shift.type}`);
|
||||
|
||||
await this.prisma.shifts.create({
|
||||
data: {
|
||||
timesheet_id: timesheet.id,
|
||||
bank_code_id: bank_code.id,
|
||||
date: date,
|
||||
start_time: parseHHmm(shift.start_time),
|
||||
end_time: parseHHmm(shift.end_time),
|
||||
comment: shift.comment ?? null,
|
||||
is_approved: false,
|
||||
is_remote: false,
|
||||
date: date,
|
||||
start_time: parseHHmm(shift.start_time),
|
||||
end_time: parseHHmm(shift.end_time),
|
||||
comment: shift.comment ?? null,
|
||||
is_approved: false,
|
||||
is_remote: false,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,42 +2,29 @@ import { endOfDayUTC, toHHmm, toUTCDateOnly } from '../utils/timesheet.helpers';
|
|||
import { Injectable, NotFoundException } from '@nestjs/common';
|
||||
import { formatDateISO, getWeekEnd, getWeekStart } from 'src/common/utils/date-utils';
|
||||
import { PrismaService } from 'src/prisma/prisma.service';
|
||||
import { OvertimeService } from 'src/modules/business-logics/services/overtime.service';
|
||||
import { TimesheetDto } from '../dtos/overview-timesheet.dto';
|
||||
import { TimesheetPeriodDto } from '../dtos/timesheet-period.dto';
|
||||
import { TimesheetDto, TimesheetPeriodDto } from '../dtos/timesheet-period.dto';
|
||||
import { ShiftRow, ExpenseRow } from '../types/timesheet.types';
|
||||
import { buildPeriod } from '../utils/timesheet.utils';
|
||||
import { EmployeeIdEmailResolver } from 'src/modules/shared/utils/resolve-email-id.utils';
|
||||
import { FullNameResolver } from 'src/modules/shared/utils/resolve-full-name.utils';
|
||||
|
||||
|
||||
@Injectable()
|
||||
export class TimesheetsQueryService {
|
||||
constructor(
|
||||
private readonly prisma: PrismaService,
|
||||
// private readonly overtime: OvertimeService,
|
||||
private readonly emailResolver: EmployeeIdEmailResolver,
|
||||
private readonly fullNameResolver: FullNameResolver
|
||||
) {}
|
||||
|
||||
async findAll(year: number, period_no: number, email: string): Promise<TimesheetPeriodDto> {
|
||||
//finds the employee
|
||||
const employee = await this.prisma.employees.findFirst({
|
||||
where: {
|
||||
user: { is: { email } }
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
user_id: true,
|
||||
},
|
||||
});
|
||||
if(!employee) throw new NotFoundException(`no employee with email ${email} found`);
|
||||
//finds the employee using email
|
||||
const employee_id = await this.emailResolver.findIdByEmail(email);
|
||||
if(!employee_id) throw new NotFoundException(`employee with email : ${email} not found`);
|
||||
|
||||
//gets the employee's full name
|
||||
const user = await this.prisma.users.findFirst({
|
||||
where: { id: employee.user_id },
|
||||
select: {
|
||||
first_name: true,
|
||||
last_name: true,
|
||||
}
|
||||
});
|
||||
const employee_full_name: string = ( user?.first_name + " " + user?.last_name ) || " ";
|
||||
//finds the employee full name using employee_id
|
||||
const full_name = await this.fullNameResolver.resolveFullName(employee_id);
|
||||
if(!full_name) throw new NotFoundException(`employee with id: ${employee_id} not found`)
|
||||
|
||||
//finds the period
|
||||
const period = await this.prisma.payPeriods.findFirst({
|
||||
|
|
@ -57,7 +44,7 @@ export class TimesheetsQueryService {
|
|||
|
||||
const raw_shifts = await this.prisma.shifts.findMany({
|
||||
where: {
|
||||
timesheet: { is: { employee_id: employee.id } },
|
||||
timesheet: { is: { employee_id: employee_id } },
|
||||
date: { gte: from, lte: to },
|
||||
},
|
||||
select: {
|
||||
|
|
@ -74,7 +61,7 @@ export class TimesheetsQueryService {
|
|||
|
||||
const raw_expenses = await this.prisma.expenses.findMany({
|
||||
where: {
|
||||
timesheet: { is: { employee_id: employee.id } },
|
||||
timesheet: { is: { employee_id: employee_id } },
|
||||
date: { gte: from, lte: to },
|
||||
},
|
||||
select: {
|
||||
|
|
@ -115,24 +102,12 @@ export class TimesheetsQueryService {
|
|||
supervisor_comment: expense.supervisor_comment ?? '',
|
||||
}));
|
||||
|
||||
return buildPeriod(period.period_start, period.period_end, shifts , expenses, employee_full_name);
|
||||
return buildPeriod(period.period_start, period.period_end, shifts , expenses, full_name);
|
||||
}
|
||||
|
||||
async getTimesheetByEmail(email: string, week_offset = 0): Promise<TimesheetDto> {
|
||||
|
||||
//fetch user related to email
|
||||
const user = await this.prisma.users.findUnique({
|
||||
where: { email },
|
||||
select: { id: true },
|
||||
});
|
||||
if(!user) throw new NotFoundException(`user with email ${email} not found`);
|
||||
|
||||
//fetch employee_id matching the email
|
||||
const employee = await this.prisma.employees.findFirst({
|
||||
where: { user_id: user.id },
|
||||
select: { id: true },
|
||||
});
|
||||
if(!employee) throw new NotFoundException(`Employee with email: ${email} not found`);
|
||||
const employee_id = await this.emailResolver.findIdByEmail(email);
|
||||
if(!employee_id) throw new NotFoundException(`Employee with email: ${email} not found`);
|
||||
|
||||
//sets current week Sunday -> Saturday
|
||||
const base = new Date();
|
||||
|
|
@ -152,7 +127,7 @@ export class TimesheetsQueryService {
|
|||
const timesheet = await this.prisma.timesheets.findUnique({
|
||||
where: {
|
||||
employee_id_start_date: {
|
||||
employee_id: employee.id,
|
||||
employee_id: employee_id,
|
||||
start_date: start_date_week,
|
||||
},
|
||||
},
|
||||
|
|
@ -182,7 +157,7 @@ export class TimesheetsQueryService {
|
|||
|
||||
//maps all shifts of selected timesheet
|
||||
const shifts = timesheet.shift.map((shift_row) => ({
|
||||
bank_type: shift_row.bank_code?.type ?? '',
|
||||
type: shift_row.bank_code?.type ?? '',
|
||||
date: formatDateISO(shift_row.date),
|
||||
start_time: toHHmm(shift_row.start_time),
|
||||
end_time: toHHmm(shift_row.end_time),
|
||||
|
|
@ -193,7 +168,7 @@ export class TimesheetsQueryService {
|
|||
|
||||
//maps all expenses of selected timsheet
|
||||
const expenses = timesheet.expense.map((exp) => ({
|
||||
bank_type: exp.bank_code?.type ?? '',
|
||||
type: exp.bank_code?.type ?? '',
|
||||
date: formatDateISO(exp.date),
|
||||
amount: Number(exp.amount) || 0,
|
||||
mileage: exp.mileage != null ? Number(exp.mileage) : 0,
|
||||
|
|
@ -203,12 +178,12 @@ export class TimesheetsQueryService {
|
|||
}));
|
||||
|
||||
return {
|
||||
is_approved: timesheet.is_approved,
|
||||
start_day,
|
||||
end_day,
|
||||
label,
|
||||
shifts,
|
||||
expenses,
|
||||
is_approved: timesheet.is_approved,
|
||||
} as TimesheetDto;
|
||||
}
|
||||
//_____________________________________________________________________________________________
|
||||
|
|
|
|||
|
|
@ -5,24 +5,20 @@ import { TimesheetsCommandService } from './services/timesheets-command.service'
|
|||
import { ShiftsCommandService } from '../shifts/services/shifts-command.service';
|
||||
import { ExpensesCommandService } from '../expenses/services/expenses-command.service';
|
||||
import { BusinessLogicsModule } from 'src/modules/business-logics/business-logics.module';
|
||||
import { BankCodesRepo } from '../expenses/repos/bank-codes.repo';
|
||||
import { TimesheetsRepo } from '../expenses/repos/timesheets.repo';
|
||||
import { EmployeesRepo } from '../expenses/repos/employee.repo';
|
||||
import { Module } from '@nestjs/common';
|
||||
import { SharedModule } from '../shared/shared.module';
|
||||
import { Module } from '@nestjs/common';
|
||||
|
||||
@Module({
|
||||
imports: [BusinessLogicsModule],
|
||||
imports: [BusinessLogicsModule, SharedModule],
|
||||
controllers: [TimesheetsController],
|
||||
providers: [
|
||||
TimesheetsQueryService,
|
||||
TimesheetsCommandService,
|
||||
ShiftsCommandService,
|
||||
ExpensesCommandService,
|
||||
TimesheetArchiveService,
|
||||
BankCodesRepo,
|
||||
TimesheetsRepo,
|
||||
EmployeesRepo,
|
||||
],
|
||||
TimesheetsQueryService,
|
||||
TimesheetsCommandService,
|
||||
ShiftsCommandService,
|
||||
ExpensesCommandService,
|
||||
TimesheetArchiveService,
|
||||
|
||||
],
|
||||
exports: [
|
||||
TimesheetsQueryService,
|
||||
TimesheetArchiveService,
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user