clean(modules): modules file cleaning
This commit is contained in:
parent
13962a8496
commit
6d3ff46c35
|
|
@ -1,79 +0,0 @@
|
|||
// import { BadRequestException, Injectable, Logger } from "@nestjs/common";
|
||||
// import { PrismaService } from "../prisma/prisma.service";
|
||||
|
||||
|
||||
// //THIS SERVICE IS NOT USED, RULES TO BE DETERMINED WITH MIKE/HR/ACCOUNTING
|
||||
// @Injectable()
|
||||
// export class AfterHoursService {
|
||||
// private readonly logger = new Logger(AfterHoursService.name);
|
||||
// private static readonly BUSINESS_START = 7;
|
||||
// private static readonly BUSINESS_END = 18;
|
||||
// private static readonly ROUND_MINUTES = 15;
|
||||
|
||||
// constructor(private readonly prisma: PrismaService) {}
|
||||
|
||||
|
||||
// private getPreBusinessMinutes(start: Date, end: Date): number {
|
||||
// const biz_start = new Date(start);
|
||||
// biz_start.setHours(AfterHoursService.BUSINESS_START, 0,0,0);
|
||||
|
||||
// if (end>= start || start >= biz_start) {
|
||||
// return 0;
|
||||
// }
|
||||
|
||||
// const segment_end = end < biz_start ? end : biz_start;
|
||||
// const minutes = (segment_end.getTime() - start.getTime()) / 60000;
|
||||
|
||||
// this.logger.debug(`getPreBusinessMintutes -> ${minutes.toFixed(1)}min`);
|
||||
// return minutes;
|
||||
|
||||
// }
|
||||
|
||||
// private getPostBusinessMinutes(start: Date, end: Date): number {
|
||||
// const biz_end = new Date(start);
|
||||
// biz_end.setHours(AfterHoursService.BUSINESS_END,0,0,0);
|
||||
|
||||
// if( end <= biz_end ) {
|
||||
// return 0;
|
||||
// }
|
||||
|
||||
// const segment_start = start > biz_end ? start : biz_end;
|
||||
// const minutes = (end.getTime() - segment_start.getTime()) / 60000;
|
||||
|
||||
// this.logger.debug(`getPostBusinessMintutes -> ${minutes.toFixed(1)}min`);
|
||||
// return minutes;
|
||||
|
||||
// }
|
||||
|
||||
// private roundToNearestQUarterMinute(minutes: number): number {
|
||||
// const rounded = Math.round(minutes / AfterHoursService.ROUND_MINUTES)
|
||||
// * AfterHoursService.ROUND_MINUTES;
|
||||
// this.logger.debug(`roundToNearestQuarterMinute -> raw=${minutes.toFixed(1)}min, rounded= ${rounded}min`);
|
||||
// return rounded;
|
||||
// }
|
||||
|
||||
// public computeAfterHours(start: Date, end:Date): number {
|
||||
// if(end.getTime() <= start.getTime()) {
|
||||
// throw new BadRequestException('The end cannot be before the starting of the shift');
|
||||
// }
|
||||
|
||||
// if (start.toDateString() !== end.toDateString()) {
|
||||
// throw new BadRequestException('you cannot enter a shift that start in a day and end in the next' +
|
||||
// 'You must create 2 instances, one on the first day and the second during the next day.');
|
||||
// }
|
||||
|
||||
// const pre_min = this.getPreBusinessMinutes(start, end);
|
||||
// const post_min = this.getPostBusinessMinutes(start, end);
|
||||
// const raw_aftermin = pre_min + post_min;
|
||||
|
||||
// const rounded_min = this.roundToNearestQUarterMinute(raw_aftermin);
|
||||
|
||||
// const hours = rounded_min / 60;
|
||||
// const result = parseFloat(hours.toFixed(2));
|
||||
|
||||
// this.logger.debug(`computeAfterHours -> raw_aftermin = ${raw_aftermin.toFixed(1)}min, +
|
||||
// rounded = ${rounded_min}min, hours = ${result.toFixed(2)}`);
|
||||
// return result;
|
||||
// }
|
||||
// }
|
||||
|
||||
|
|
@ -1,66 +0,0 @@
|
|||
// import { ApiProperty } from "@nestjs/swagger";
|
||||
// import { Type } from "class-transformer";
|
||||
// import { Allow, IsBoolean, IsDate, IsDateString, IsInt, IsNumber, IsOptional, IsString } from "class-validator";
|
||||
|
||||
// export class CreateExpenseDto {
|
||||
// @ApiProperty({
|
||||
// example: 1,
|
||||
// description: 'Unique ID of the expense (auto-generated)',
|
||||
// })
|
||||
// @Allow()
|
||||
// id?: number;
|
||||
|
||||
// @ApiProperty({
|
||||
// example: 101,
|
||||
// description: 'ID number for a set timesheet',
|
||||
// })
|
||||
// @Type(()=> Number)
|
||||
// @IsInt()
|
||||
// timesheet_id: number;
|
||||
|
||||
// @ApiProperty({
|
||||
// example: 7,
|
||||
// description: 'ID number of an bank code (link with bank-codes)',
|
||||
// })
|
||||
// @Type(() => Number)
|
||||
// @IsInt()
|
||||
// bank_code_id: number;
|
||||
|
||||
// @ApiProperty({
|
||||
// example: '3018-10-20T00:00:00.000Z',
|
||||
// description: 'Date where the expense was made',
|
||||
// })
|
||||
// @IsDateString()
|
||||
// date: string;
|
||||
|
||||
// @ApiProperty({
|
||||
// example: 17.82,
|
||||
// description: 'amount in $ for a refund',
|
||||
// })
|
||||
// @Type(() => Number)
|
||||
// @IsNumber()
|
||||
// amount: number;
|
||||
|
||||
// @ApiProperty({
|
||||
// example:'Spent for mileage between A and B',
|
||||
// description:'explain`s why the expense was made'
|
||||
// })
|
||||
// @IsString()
|
||||
// comment: string;
|
||||
|
||||
// @ApiProperty({
|
||||
// example: 'DENIED, APPROUVED, PENDING, etc...',
|
||||
// description: 'validation status',
|
||||
// })
|
||||
// @IsOptional()
|
||||
// @IsBoolean()
|
||||
// is_approved?: boolean;
|
||||
|
||||
// @ApiProperty({
|
||||
// example:'Asked X to go there as an emergency response',
|
||||
// description:'Supervisro`s justification for the spending of an employee'
|
||||
// })
|
||||
// @IsString()
|
||||
// @IsOptional()
|
||||
// supervisor_comment?: string;
|
||||
// }
|
||||
|
|
@ -1,33 +0,0 @@
|
|||
// import { Type } from "class-transformer";
|
||||
// import { IsArray, IsOptional, IsString, Length, Matches, ValidateNested } from "class-validator";
|
||||
|
||||
// export class CreateTimesheetDto {
|
||||
|
||||
// @IsString()
|
||||
// @Matches(/^\d{4}-\d{2}-\d{2}$/)
|
||||
// date!: string;
|
||||
|
||||
// @IsString()
|
||||
// @Length(1,64)
|
||||
// type!: string;
|
||||
|
||||
// @IsString()
|
||||
// @Matches(/^\d{2}:\d{2}$/)
|
||||
// start_time!: string;
|
||||
|
||||
// @IsString()
|
||||
// @Matches(/^\d{2}:\d{2}$/)
|
||||
// end_time!: string;
|
||||
|
||||
// @IsOptional()
|
||||
// @IsString()
|
||||
// @Length(0,512)
|
||||
// comment?: string;
|
||||
// }
|
||||
|
||||
// export class CreateWeekShiftsDto {
|
||||
// @IsArray()
|
||||
// @ValidateNested({each:true})
|
||||
// @Type(()=> CreateTimesheetDto)
|
||||
// shifts!: CreateTimesheetDto[];
|
||||
// }
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
// export const MS_PER_DAY = 86_400_000;
|
||||
// export const MS_PER_HOUR = 3_600_000;
|
||||
|
|
@ -1,249 +0,0 @@
|
|||
// 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 { BankCodesResolver } from "src/modules/shared/utils/resolve-bank-type-id.utils";
|
||||
// import { ExpenseResponse, UpsertAction } from "../types and interfaces/expenses.types.interfaces";
|
||||
// import { EmailToIdResolver } from "src/modules/shared/utils/resolve-email-id.utils";
|
||||
// import { EmployeeTimesheetResolver } from "src/modules/shared/utils/resolve-timesheet.utils";
|
||||
// import {
|
||||
// BadRequestException,
|
||||
// Injectable,
|
||||
// NotFoundException
|
||||
// } from "@nestjs/common";
|
||||
// import {
|
||||
// assertAndTrimComment,
|
||||
// computeAmountDecimal,
|
||||
// computeMileageAmount,
|
||||
// mapDbExpenseToDayResponse,
|
||||
// normalizeType,
|
||||
// parseAttachmentId
|
||||
// } from "../utils/expenses.utils";
|
||||
|
||||
// @Injectable()
|
||||
// export class ExpensesCommandService extends BaseApprovalService<Expenses> {
|
||||
// constructor(
|
||||
// prisma: PrismaService,
|
||||
// private readonly bankCodesResolver: BankCodesResolver,
|
||||
// private readonly timesheetsResolver: EmployeeTimesheetResolver,
|
||||
// private readonly emailResolver: EmailToIdResolver,
|
||||
// ) { super(prisma); }
|
||||
|
||||
// //_____________________________________________________________________________________________
|
||||
// // APPROVAL TX-DELEGATE METHODS
|
||||
// //_____________________________________________________________________________________________
|
||||
|
||||
// protected get delegate() {
|
||||
// return this.prisma.expenses;
|
||||
// }
|
||||
|
||||
// protected delegateFor(transaction: Prisma.TransactionClient){
|
||||
// return transaction.expenses;
|
||||
// }
|
||||
|
||||
// async updateApproval(id: number, isApproved: boolean): Promise<Expenses> {
|
||||
// return this.prisma.$transaction((transaction) =>
|
||||
// this.updateApprovalWithTransaction(transaction, id, isApproved),
|
||||
// );
|
||||
// }
|
||||
|
||||
// //_____________________________________________________________________________________________
|
||||
// // MASTER CRUD FUNCTION
|
||||
// //_____________________________________________________________________________________________
|
||||
// readonly upsertExpensesByDate = async (email: string, date: string, dto: UpsertExpenseDto,
|
||||
// ): Promise<{ action:UpsertAction; day: ExpenseResponse[] }> => {
|
||||
|
||||
// //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');
|
||||
|
||||
// //validate date format
|
||||
// const date_only = toDateOnly(date);
|
||||
// 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.emailResolver.findIdByEmail(email);
|
||||
|
||||
// //make sure a timesheet existes
|
||||
// const timesheet_id = await this.timesheetsResolver.findTimesheetIdByEmail(email, 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: id,
|
||||
// date: date_only,
|
||||
// },
|
||||
// include: {
|
||||
// bank_code: {
|
||||
// select: {
|
||||
// type: true,
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
// orderBy: [{ date: 'asc' }, { id: 'asc' }],
|
||||
// });
|
||||
|
||||
// return rows.map((r) =>
|
||||
// mapDbExpenseToDayResponse({
|
||||
// date: r.date,
|
||||
// amount: r.amount ?? 0,
|
||||
// mileage: r.mileage ?? 0,
|
||||
// comment: r.comment,
|
||||
// is_approved: r.is_approved,
|
||||
// bank_code: r.bank_code,
|
||||
// }));
|
||||
// };
|
||||
|
||||
// const normalizePayload = async (payload: {
|
||||
// type: string;
|
||||
// amount?: number;
|
||||
// mileage?: number;
|
||||
// comment: string;
|
||||
// attachment?: string | number;
|
||||
// }): Promise<{
|
||||
// type: string;
|
||||
// bank_code_id: number;
|
||||
// amount: Prisma.Decimal;
|
||||
// mileage: number | null;
|
||||
// comment: string;
|
||||
// attachment: number | null;
|
||||
// }> => {
|
||||
// const type = normalizeType(payload.type);
|
||||
// const comment = assertAndTrimComment(payload.comment);
|
||||
// const attachment = parseAttachmentId(payload.attachment);
|
||||
|
||||
// const { id: bank_code_id, modifier } = await this.bankCodesResolver.findByType(type);
|
||||
// let amount = computeAmountDecimal(type, payload, modifier);
|
||||
// let mileage: number | null = null;
|
||||
|
||||
// if (type === 'MILEAGE') {
|
||||
// mileage = Number(payload.mileage ?? 0);
|
||||
// if (!(mileage > 0)) {
|
||||
// throw new BadRequestException('Mileage required and must be > 0 for type MILEAGE');
|
||||
// }
|
||||
|
||||
// const amountNumber = computeMileageAmount(mileage, modifier);
|
||||
// amount = new Prisma.Decimal(amountNumber);
|
||||
|
||||
// } else {
|
||||
// if (!(typeof payload.amount === 'number' && payload.amount >= 0)) {
|
||||
// throw new BadRequestException('Amount required for non-MILEAGE expense');
|
||||
// }
|
||||
// amount = new Prisma.Decimal(payload.amount);
|
||||
// }
|
||||
|
||||
// if (attachment !== null) {
|
||||
// const attachment_row = await tx.attachments.findUnique({
|
||||
// where: { id: attachment },
|
||||
// select: { status: true },
|
||||
// });
|
||||
// if (!attachment_row || attachment_row.status !== 'ACTIVE') {
|
||||
// throw new BadRequestException('Attachment not found or inactive');
|
||||
// }
|
||||
// }
|
||||
|
||||
// return {
|
||||
// type,
|
||||
// bank_code_id,
|
||||
// amount,
|
||||
// mileage,
|
||||
// comment,
|
||||
// attachment
|
||||
// };
|
||||
// };
|
||||
|
||||
// const findExactOld = async (norm: {
|
||||
// bank_code_id: number;
|
||||
// amount: Prisma.Decimal;
|
||||
// mileage: number | null;
|
||||
// comment: string;
|
||||
// attachment: number | null;
|
||||
// }) => {
|
||||
// return tx.expenses.findFirst({
|
||||
// where: {
|
||||
// timesheet_id: id,
|
||||
// date: date_only,
|
||||
// bank_code_id: norm.bank_code_id,
|
||||
// amount: norm.amount,
|
||||
// comment: norm.comment,
|
||||
// attachment: norm.attachment,
|
||||
// ...(norm.mileage !== null ? { mileage: norm.mileage } : { mileage: null }),
|
||||
// },
|
||||
// select: { id: true },
|
||||
// });
|
||||
// };
|
||||
|
||||
// let action : UpsertAction;
|
||||
// //_____________________________________________________________________________________________
|
||||
// // DELETE
|
||||
// //_____________________________________________________________________________________________
|
||||
// if(old_expense && !new_expense) {
|
||||
// const old_norm = await normalizePayload(old_expense);
|
||||
// const existing = await findExactOld(old_norm);
|
||||
// if(!existing) {
|
||||
// throw new NotFoundException({
|
||||
// error_code: 'EXPENSE_STALE',
|
||||
// message: 'The expense was modified or deleted by someone else',
|
||||
// });
|
||||
// }
|
||||
// await tx.expenses.delete({where: { id: existing.id } });
|
||||
// action = 'delete';
|
||||
// }
|
||||
// //_____________________________________________________________________________________________
|
||||
// // CREATE
|
||||
// //_____________________________________________________________________________________________
|
||||
// else if (!old_expense && new_expense) {
|
||||
// const new_exp = await normalizePayload(new_expense);
|
||||
// await tx.expenses.create({
|
||||
// data: {
|
||||
// timesheet_id: id,
|
||||
// date: date_only,
|
||||
// bank_code_id: new_exp.bank_code_id,
|
||||
// amount: new_exp.amount,
|
||||
// mileage: new_exp.mileage,
|
||||
// comment: new_exp.comment,
|
||||
// attachment: new_exp.attachment,
|
||||
// is_approved: false,
|
||||
// },
|
||||
// });
|
||||
// action = 'create';
|
||||
// }
|
||||
// //_____________________________________________________________________________________________
|
||||
// // UPDATE
|
||||
// //_____________________________________________________________________________________________
|
||||
// else if(old_expense && new_expense) {
|
||||
// const old_norm = await normalizePayload(old_expense);
|
||||
// const existing = await findExactOld(old_norm);
|
||||
// if(!existing) {
|
||||
// throw new NotFoundException({
|
||||
// error_code: 'EXPENSE_STALE',
|
||||
// message: 'The expense was modified or deleted by someone else',
|
||||
// });
|
||||
// }
|
||||
|
||||
// const new_exp = await normalizePayload(new_expense);
|
||||
// await tx.expenses.update({
|
||||
// where: { id: existing.id },
|
||||
// data: {
|
||||
// bank_code_id: new_exp.bank_code_id,
|
||||
// amount: new_exp.amount,
|
||||
// mileage: new_exp.mileage,
|
||||
// comment: new_exp.comment,
|
||||
// attachment: new_exp.attachment,
|
||||
// },
|
||||
// });
|
||||
// action = 'update';
|
||||
// }
|
||||
// else {
|
||||
// throw new BadRequestException('Invalid upsert combination');
|
||||
// }
|
||||
|
||||
// const day = await loadDay();
|
||||
|
||||
// return { action, day };
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
|
|
@ -1,171 +0,0 @@
|
|||
// import { Injectable, NotFoundException } from "@nestjs/common";
|
||||
// import { PrismaService } from "src/prisma/prisma.service";
|
||||
// import { EmailToIdResolver } from "src/modules/shared/utils/resolve-email-id.utils";
|
||||
|
||||
// @Injectable()
|
||||
// export class ExpensesQueryService {
|
||||
// constructor(
|
||||
// private readonly prisma: PrismaService,
|
||||
// private readonly employeeRepo: EmailToIdResolver,
|
||||
// ) {}
|
||||
|
||||
|
||||
// //fetchs all expenses for a selected employee using email, pay-period-year and number
|
||||
// async findExpenseListByPayPeriodAndEmail(
|
||||
// email: string,
|
||||
// year: number,
|
||||
// period_no: number
|
||||
// ): Promise<ExpenseListResponseDto> {
|
||||
// //fetch employe_id using email
|
||||
// const employee_id = await this.employeeRepo.findIdByEmail(email);
|
||||
// if(!employee_id) throw new NotFoundException(`Employee with email: ${email} not found`);
|
||||
|
||||
// //fetch pay-period using year and period_no
|
||||
// const pay_period = await this.prisma.payPeriods.findFirst({
|
||||
// where: {
|
||||
// pay_year: year,
|
||||
// pay_period_no: period_no
|
||||
// },
|
||||
// select: { period_start: true, period_end: true },
|
||||
// });
|
||||
// if(!pay_period) throw new NotFoundException(`Pay period ${year}- ${period_no} not found`);
|
||||
|
||||
// const start = toUTCDateOnly(pay_period.period_start);
|
||||
// const end = toUTCDateOnly(pay_period.period_end);
|
||||
|
||||
// //sets rows data
|
||||
// const rows = await this.prisma.expenses.findMany({
|
||||
// where: {
|
||||
// date: { gte: start, lte: end },
|
||||
// timesheet: { is: { employee_id } },
|
||||
// },
|
||||
// orderBy: { date: 'asc'},
|
||||
// select: {
|
||||
// amount: true,
|
||||
// mileage: true,
|
||||
// comment: true,
|
||||
// is_approved: true,
|
||||
// supervisor_comment: true,
|
||||
// bank_code: {select: { type: true } },
|
||||
// },
|
||||
// });
|
||||
|
||||
// //declare return values
|
||||
// const expenses: ExpenseDto[] = [];
|
||||
// let total_amount = 0;
|
||||
// let total_mileage = 0;
|
||||
|
||||
// //set rows
|
||||
// for(const row of rows) {
|
||||
// const type = (row.bank_code?.type ?? '').toUpperCase();
|
||||
// const amount = round2(Number(row.amount ?? 0));
|
||||
// const mileage = round2(Number(row.mileage ?? 0));
|
||||
|
||||
// if(type === EXPENSE_TYPES.MILEAGE) {
|
||||
// total_mileage += mileage;
|
||||
// } else {
|
||||
// total_amount += amount;
|
||||
// }
|
||||
|
||||
// //fills rows array
|
||||
// expenses.push({
|
||||
// type,
|
||||
// amount,
|
||||
// mileage,
|
||||
// comment: row.comment ?? '',
|
||||
// is_approved: row.is_approved ?? false,
|
||||
// supervisor_comment: row.supervisor_comment ?? '',
|
||||
// });
|
||||
// }
|
||||
|
||||
// return {
|
||||
// expenses,
|
||||
// total_expense: round2(total_amount),
|
||||
// total_mileage: round2(total_mileage),
|
||||
// };
|
||||
// }
|
||||
|
||||
// //_____________________________________________________________________________________________
|
||||
// // Deprecated or unused methods
|
||||
// //_____________________________________________________________________________________________
|
||||
|
||||
// // async create(dto: CreateExpenseDto): Promise<Expenses> {
|
||||
// // const { timesheet_id, bank_code_id, date, amount:rawAmount,
|
||||
// // comment, is_approved,supervisor_comment} = dto;
|
||||
// // //fetches type and modifier
|
||||
// // const bank_code = await this.prisma.bankCodes.findUnique({
|
||||
// // where: { id: bank_code_id },
|
||||
// // select: { type: true, modifier: true },
|
||||
// // });
|
||||
// // if(!bank_code) throw new NotFoundException(`bank_code #${bank_code_id} not found`);
|
||||
|
||||
// // //if mileage -> service, otherwise the ratio is amount:1
|
||||
// // let final_amount: number;
|
||||
// // if(bank_code.type === 'mileage') {
|
||||
// // final_amount = await this.mileageService.calculateReimbursement(rawAmount, bank_code_id);
|
||||
// // }else {
|
||||
// // final_amount = parseFloat( (rawAmount * bank_code.modifier).toFixed(2));
|
||||
// // }
|
||||
|
||||
// // return this.prisma.expenses.create({
|
||||
// // data: {
|
||||
// // timesheet_id,
|
||||
// // bank_code_id,
|
||||
// // date,
|
||||
// // amount: final_amount,
|
||||
// // comment,
|
||||
// // is_approved,
|
||||
// // supervisor_comment
|
||||
// // },
|
||||
// // include: { timesheet: { include: { employee: { include: { user: true }}}},
|
||||
// // bank_code: true,
|
||||
// // },
|
||||
// // })
|
||||
// // }
|
||||
|
||||
// // async findAll(filters: SearchExpensesDto): Promise<Expenses[]> {
|
||||
// const where = buildPrismaWhere(filters);
|
||||
// // const expenses = await this.prisma.expenses.findMany({ where })
|
||||
// // return expenses;
|
||||
// // }
|
||||
|
||||
// // async findOne(id: number): Promise<Expenses> {
|
||||
// // const expense = await this.prisma.expenses.findUnique({
|
||||
// // where: { id },
|
||||
// // include: { timesheet: { include: { employee: { include: { user:true } } } },
|
||||
// // bank_code: true,
|
||||
// // },
|
||||
// // });
|
||||
// // if (!expense) {
|
||||
// // throw new NotFoundException(`Expense #${id} not found`);
|
||||
// // }
|
||||
// // return expense;
|
||||
// // }
|
||||
|
||||
// // async update(id: number, dto: UpdateExpenseDto): Promise<Expenses> {
|
||||
// // await this.findOne(id);
|
||||
// // const { timesheet_id, bank_code_id, date, amount,
|
||||
// // comment, is_approved, supervisor_comment} = dto;
|
||||
// // return this.prisma.expenses.update({
|
||||
// // where: { id },
|
||||
// // data: {
|
||||
// // ...(timesheet_id !== undefined && { timesheet_id}),
|
||||
// // ...(bank_code_id !== undefined && { bank_code_id }),
|
||||
// // ...(date !== undefined && { date }),
|
||||
// // ...(amount !== undefined && { amount }),
|
||||
// // ...(comment !== undefined && { comment }),
|
||||
// // ...(is_approved !== undefined && { is_approved }),
|
||||
// // ...(supervisor_comment !== undefined && { supervisor_comment }),
|
||||
// // },
|
||||
// // include: { timesheet: { include: { employee: { include: { user: true } } } },
|
||||
// // bank_code: true,
|
||||
// // },
|
||||
// // });
|
||||
// // }
|
||||
|
||||
// // async remove(id: number): Promise<Expenses> {
|
||||
// // await this.findOne(id);
|
||||
// // return this.prisma.expenses.delete({ where: { id } });
|
||||
// // }
|
||||
|
||||
// }
|
||||
|
|
@ -1,95 +0,0 @@
|
|||
// import { Body, Controller, Get, Param, Put, } from "@nestjs/common";
|
||||
// import { Roles as RoleEnum } from '.prisma/client';
|
||||
// import { ApiBearerAuth, ApiTags } from "@nestjs/swagger";
|
||||
// import { RolesAllowed } from "src/common/decorators/roles.decorators";
|
||||
// import { ExpensesCommandService } from "../services/expenses-command.service";
|
||||
// import { UpsertExpenseDto } from "../dtos/upsert-expense.dto";
|
||||
// import { UpsertExpenseResult } from "../types and interfaces/expenses.types.interfaces";
|
||||
// import { DayExpensesDto } from "src/modules/timesheets/~misc_deprecated-files/timesheet-period.dto";
|
||||
// import { ExpensesQueryService } from "../services/expenses-query.service";
|
||||
|
||||
// @ApiTags('Expenses')
|
||||
// @ApiBearerAuth('access-token')
|
||||
// // @UseGuards()
|
||||
// @Controller('Expenses')
|
||||
// export class ExpensesController {
|
||||
// constructor(
|
||||
// private readonly query: ExpensesQueryService,
|
||||
// private readonly command: ExpensesCommandService,
|
||||
// ) {}
|
||||
|
||||
// @Put('upsert/:email/:date')
|
||||
// async upsert_by_date(
|
||||
// @Param('email') email: string,
|
||||
// @Param('date') date: string,
|
||||
// @Body() dto: UpsertExpenseDto,
|
||||
// ): Promise<UpsertExpenseResult> {
|
||||
// return this.command.upsertExpensesByDate(email, date, dto);
|
||||
// }
|
||||
|
||||
// @Get('list/:email/:year/:period_no')
|
||||
// async findExpenseListByPayPeriodAndEmail(
|
||||
// @Param('email') email:string,
|
||||
// @Param('year') year: number,
|
||||
// @Param('period_no') period_no: number,
|
||||
// ): Promise<DayExpensesDto> {
|
||||
// return this.query.findExpenseListByPayPeriodAndEmail(email, year, period_no);
|
||||
// }
|
||||
|
||||
// //_____________________________________________________________________________________________
|
||||
// // Deprecated or unused methods
|
||||
// //_____________________________________________________________________________________________
|
||||
|
||||
// // @Post()
|
||||
// // //@RolesAllowed(RoleEnum.ACCOUNTING, RoleEnum.ADMIN, RoleEnum.EMPLOYEE, RoleEnum.HR, RoleEnum.SUPERVISOR)
|
||||
// // @ApiOperation({ summary: 'Create expense' })
|
||||
// // @ApiResponse({ status: 201, description: 'Expense created',type: CreateExpenseDto })
|
||||
// // @ApiResponse({ status: 400, description: 'Incomplete task or invalid data' })
|
||||
// // create(@Body() dto: CreateExpenseDto): Promise<Expenses> {
|
||||
// // return this.query.create(dto);
|
||||
// // }
|
||||
|
||||
// // @Get()
|
||||
// // //@RolesAllowed(RoleEnum.ACCOUNTING, RoleEnum.ADMIN, RoleEnum.EMPLOYEE, RoleEnum.HR, RoleEnum.SUPERVISOR)
|
||||
// // @ApiOperation({ summary: 'Find all expenses' })
|
||||
// // @ApiResponse({ status: 201, description: 'List of expenses found',type: CreateExpenseDto, isArray: true })
|
||||
// // @ApiResponse({ status: 400, description: 'List of expenses not found' })
|
||||
// // @UsePipes(new ValidationPipe({ transform: true, whitelist: true }))
|
||||
// // findAll(@Query() filters: SearchExpensesDto): Promise<Expenses[]> {
|
||||
// // return this.query.findAll(filters);
|
||||
// // }
|
||||
|
||||
// // @Get(':id')
|
||||
// // //@RolesAllowed(RoleEnum.ACCOUNTING, RoleEnum.ADMIN, RoleEnum.EMPLOYEE, RoleEnum.HR, RoleEnum.SUPERVISOR)
|
||||
// // @ApiOperation({ summary: 'Find expense' })
|
||||
// // @ApiResponse({ status: 201, description: 'Expense found',type: CreateExpenseDto })
|
||||
// // @ApiResponse({ status: 400, description: 'Expense not found' })
|
||||
// // findOne(@Param('id', ParseIntPipe) id: number): Promise <Expenses> {
|
||||
// // return this.query.findOne(id);
|
||||
// // }
|
||||
|
||||
// // @Patch(':id')
|
||||
// // //@RolesAllowed(RoleEnum.ACCOUNTING, RoleEnum.ADMIN, RoleEnum.EMPLOYEE, RoleEnum.HR, RoleEnum.SUPERVISOR)
|
||||
// // @ApiOperation({ summary: 'Expense shift' })
|
||||
// // @ApiResponse({ status: 201, description: 'Expense updated',type: CreateExpenseDto })
|
||||
// // @ApiResponse({ status: 400, description: 'Expense not found' })
|
||||
// // update(@Param('id', ParseIntPipe) id: number, @Body() dto: UpdateExpenseDto) {
|
||||
// // return this.query.update(id,dto);
|
||||
// // }
|
||||
|
||||
// // @Delete(':id')
|
||||
// // //@RolesAllowed(RoleEnum.ACCOUNTING, RoleEnum.ADMIN, RoleEnum.EMPLOYEE, RoleEnum.HR, RoleEnum.SUPERVISOR)
|
||||
// // @ApiOperation({ summary: 'Delete expense' })
|
||||
// // @ApiResponse({ status: 201, description: 'Expense deleted',type: CreateExpenseDto })
|
||||
// // @ApiResponse({ status: 400, description: 'Expense not found' })
|
||||
// // remove(@Param('id', ParseIntPipe) id: number): Promise<Expenses> {
|
||||
// // return this.query.remove(id);
|
||||
// // }
|
||||
|
||||
// // @Patch('approval/:id')
|
||||
// // //@RolesAllowed(RoleEnum.ACCOUNTING, RoleEnum.ADMIN, RoleEnum.HR, RoleEnum.SUPERVISOR)
|
||||
// // async approve(@Param('id', ParseIntPipe) id: number, @Body('is_approved', ParseBoolPipe) isApproved: boolean) {
|
||||
// // return this.command.updateApproval(id, isApproved);
|
||||
// // }
|
||||
|
||||
// }
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
// export type UpsertAction = 'create' | 'update' | 'delete';
|
||||
|
||||
// export interface ExpenseResponse {
|
||||
// date: string;
|
||||
// type: string;
|
||||
// amount: number;
|
||||
// comment: string;
|
||||
// is_approved: boolean;
|
||||
// };
|
||||
|
||||
// export type UpsertExpenseResult = {
|
||||
// action: UpsertAction;
|
||||
// day: ExpenseResponse[]
|
||||
// };
|
||||
|
|
@ -1,111 +0,0 @@
|
|||
// import { BadRequestException } from "@nestjs/common";
|
||||
// import { ExpenseResponse } from "../types and interfaces/expenses.types.interfaces";
|
||||
// import { Prisma } from "@prisma/client";
|
||||
|
||||
// //uppercase and trim for validation
|
||||
// export function normalizeType(type: string): string {
|
||||
// return (type ?? '').trim().toUpperCase();
|
||||
// };
|
||||
|
||||
// //required comment after trim
|
||||
// export function assertAndTrimComment(comment: string): string {
|
||||
// const cmt = (comment ?? '').trim();
|
||||
// if(cmt.length === 0) {
|
||||
// throw new BadRequestException('A comment is required');
|
||||
// }
|
||||
// return cmt;
|
||||
// };
|
||||
|
||||
// //rounding $ to 2 decimals
|
||||
// export function roundMoney2(num: number): number {
|
||||
// return Math.round((num + Number.EPSILON) * 100)/ 100;
|
||||
// };
|
||||
|
||||
// export function computeMileageAmount(km: number, modifier: number): number {
|
||||
// if(km < 0) throw new BadRequestException('mileage must be positive');
|
||||
// if(modifier < 0) throw new BadRequestException('modifier must be positive');
|
||||
// return roundMoney2(km * modifier);
|
||||
// };
|
||||
|
||||
// //compat. types with Prisma.Decimal. work around Prisma import in utils.
|
||||
// export type DecimalLike =
|
||||
// | number
|
||||
// | string
|
||||
// | { toNumber?: () => number }
|
||||
// | { toString?: () => string };
|
||||
|
||||
|
||||
// //safe conversion to number
|
||||
// export function toNumberSafe(value: DecimalLike): number {
|
||||
// if(typeof value === 'number') return value;
|
||||
// if(value && typeof (value as any).toNumber === 'function') return (value as any).toNumber();
|
||||
// return Number(
|
||||
// typeof (value as any)?.toString === 'function'
|
||||
// ? (value as any).toString()
|
||||
// : value,
|
||||
// );
|
||||
// }
|
||||
|
||||
// export const parseAttachmentId = (value: unknown): number | null => {
|
||||
// if (value == null) {
|
||||
// return null;
|
||||
// }
|
||||
|
||||
// if (typeof value === 'number') {
|
||||
// if (!Number.isInteger(value) || value <= 0) {
|
||||
// throw new BadRequestException('Invalid attachment id');
|
||||
// }
|
||||
// return value;
|
||||
// }
|
||||
|
||||
// if (typeof value === 'string') {
|
||||
|
||||
// const trimmed = value.trim();
|
||||
// if (!trimmed.length) return null;
|
||||
// if (!/^\d+$/.test(trimmed)) throw new BadRequestException('Invalid attachment id');
|
||||
|
||||
// const parsed = Number(trimmed);
|
||||
// if (parsed <= 0) throw new BadRequestException('Invalid attachment id');
|
||||
|
||||
// return parsed;
|
||||
// }
|
||||
// throw new BadRequestException('Invalid attachment id');
|
||||
// };
|
||||
|
||||
|
||||
// //map of a row for DayExpenseResponse
|
||||
// export function mapDbExpenseToDayResponse(row: {
|
||||
// date: Date;
|
||||
// amount: Prisma.Decimal | number | string | null;
|
||||
// mileage?: Prisma.Decimal | number | string | null;
|
||||
// comment: string;
|
||||
// is_approved: boolean;
|
||||
// bank_code?: { type?: string | null } | null;
|
||||
// }): ExpenseResponse {
|
||||
// const yyyyMmDd = row.date.toISOString().slice(0,10);
|
||||
// const toNum = (value: any)=> (value == null ? 0 : Number(value));
|
||||
// return {
|
||||
// date: yyyyMmDd,
|
||||
// type: normalizeType(row.bank_code?.type ?? 'UNKNOWN'),
|
||||
// amount: toNum(row.amount),
|
||||
// comment: row.comment,
|
||||
// is_approved: row.is_approved,
|
||||
// ...(row.mileage !== null ? { mileage: toNum(row.mileage) }: {}),
|
||||
// };
|
||||
// }
|
||||
|
||||
// export const computeAmountDecimal = (
|
||||
// type: string,
|
||||
// payload: {
|
||||
// amount?: number;
|
||||
// mileage?: number;
|
||||
// },
|
||||
// modifier: number,
|
||||
// ): Prisma.Decimal => {
|
||||
// if(type === 'MILEAGE') {
|
||||
// const km = payload.mileage ?? 0;
|
||||
// const amountNumber = computeMileageAmount(km, modifier);
|
||||
// return new Prisma.Decimal(amountNumber);
|
||||
// }
|
||||
// return new Prisma.Decimal(payload.amount!);
|
||||
// };
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
// import { Type } from "class-transformer";
|
||||
// import { IsInt, Min, Max } from "class-validator";
|
||||
|
||||
// export class GetShiftsOverviewDto {
|
||||
// @Type(()=> Number)
|
||||
// @IsInt()
|
||||
// @Min(1)
|
||||
// @Max(26)
|
||||
// period_id: number;
|
||||
// }
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
// import { Prisma } from "@prisma/client";
|
||||
|
||||
// //custom prisma select to avoid employee_id exposure
|
||||
// export const leaveRequestsSelect = {
|
||||
// id: true,
|
||||
// bank_code_id: true,
|
||||
// leave_type: true,
|
||||
// date: true,
|
||||
// payable_hours: true,
|
||||
// requested_hours: true,
|
||||
// comment: true,
|
||||
// approval_status: true,
|
||||
// employee: { select: {
|
||||
// id: true,
|
||||
// user: { select: {
|
||||
// email: true,
|
||||
// first_name: true,
|
||||
// last_name: true,
|
||||
// }},
|
||||
// }},
|
||||
// } satisfies Prisma.LeaveRequestsSelect;
|
||||
|
||||
// export type LeaveRequestRow = Prisma.LeaveRequestsGetPayload<{ select: typeof leaveRequestsSelect}>;
|
||||
|
|
@ -1 +0,0 @@
|
|||
|
||||
|
|
@ -1,126 +0,0 @@
|
|||
// import { Injectable } from "@nestjs/common";
|
||||
// import { Weekday, Prisma } from "@prisma/client";
|
||||
// import { DATE_ISO_FORMAT, WEEKDAY } from "src/common/utils/constants.utils";
|
||||
// import { PrismaService } from "src/prisma/prisma.service";
|
||||
// import { ApplyResult } from "src/time-and-attendance/utils/type.utils";
|
||||
// import { EmailToIdResolver } from "src/common/mappers/email-id.mapper";
|
||||
// import { Result } from "src/common/errors/result-error.factory";
|
||||
|
||||
|
||||
// @Injectable()
|
||||
// export class SchedulePresetsApplyService {
|
||||
// constructor(
|
||||
// private readonly prisma: PrismaService,
|
||||
// private readonly emailResolver: EmailToIdResolver
|
||||
// ) { }
|
||||
|
||||
// async applyToTimesheet(email: string, id: number, start_date_iso: string): Promise<Result<ApplyResult, string>> {
|
||||
// if (!DATE_ISO_FORMAT.test(start_date_iso)) return { success: false, error: 'INVALID_PRESET' };
|
||||
|
||||
// const employee_id = await this.emailResolver.findIdByEmail(email);
|
||||
// if (!employee_id.success) return { success: false, error: employee_id.error }
|
||||
|
||||
// const preset = await this.prisma.schedulePresets.findFirst({
|
||||
// where: { id },
|
||||
// include: {
|
||||
// shifts: {
|
||||
// orderBy: [{ week_day: 'asc' }, { sort_order: 'asc' }],
|
||||
// select: {
|
||||
// id: true,
|
||||
// week_day: true,
|
||||
// sort_order: true,
|
||||
// start_time: true,
|
||||
// end_time: true,
|
||||
// is_remote: true,
|
||||
// bank_code_id: true,
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
// });
|
||||
// if (!preset) return { success: false, error: `PRESET_NOT_FOUND` };
|
||||
|
||||
|
||||
// const start_date = new Date(`${start_date_iso}T00:00:00.000Z`);
|
||||
// const timesheet = await this.prisma.timesheets.upsert({
|
||||
// where: { employee_id_start_date: { employee_id: employee_id.data, start_date: start_date } },
|
||||
// update: {},
|
||||
// create: { employee_id: employee_id.data, start_date: start_date },
|
||||
// select: { id: true },
|
||||
// });
|
||||
|
||||
// //index shifts by weekday
|
||||
// const index_by_day = new Map<Weekday, typeof preset.shifts>();
|
||||
// for (const shift of preset.shifts) {
|
||||
// const list = index_by_day.get(shift.week_day) ?? [];
|
||||
// list.push(shift);
|
||||
// index_by_day.set(shift.week_day, list);
|
||||
// }
|
||||
|
||||
// const addDays = (date: Date, days: number) =>
|
||||
// new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate() + days));
|
||||
|
||||
// const overlaps = (aStart: Date, aEnd: Date, bStart: Date, bEnd: Date) =>
|
||||
// aStart.getTime() < bEnd.getTime() && aEnd.getTime() > bStart.getTime();
|
||||
|
||||
// let created = 0;
|
||||
// let skipped = 0;
|
||||
|
||||
// await this.prisma.$transaction(async (tx) => {
|
||||
// for (let i = 0; i < 7; i++) {
|
||||
// const date = addDays(start_date, i);
|
||||
// const week_day = WEEKDAY[date.getUTCDay()];
|
||||
// const shifts = index_by_day.get(week_day) ?? [];
|
||||
|
||||
// if (shifts.length === 0) continue;
|
||||
|
||||
// const existing = await tx.shifts.findMany({
|
||||
// where: { timesheet_id: timesheet.id, date: date },
|
||||
// orderBy: { start_time: 'asc' },
|
||||
// select: {
|
||||
// start_time: true,
|
||||
// end_time: true,
|
||||
// bank_code_id: true,
|
||||
// is_remote: true,
|
||||
// comment: true,
|
||||
// },
|
||||
// });
|
||||
|
||||
// const payload: Prisma.ShiftsCreateManyInput[] = [];
|
||||
|
||||
// for (const shift of shifts) {
|
||||
// if (shift.end_time.getTime() <= shift.start_time.getTime()) {
|
||||
// return {
|
||||
// success: false,
|
||||
// error: `INVALID_PRESET_SHIFT`
|
||||
// };
|
||||
// }
|
||||
// const conflict = existing.find((existe) => overlaps(
|
||||
// shift.start_time, shift.end_time,
|
||||
// existe.start_time, existe.end_time,
|
||||
// ));
|
||||
// if (conflict)
|
||||
// return {
|
||||
// success: false,
|
||||
// error: `OVERLAPING_SHIFT`
|
||||
// };
|
||||
|
||||
// payload.push({
|
||||
// timesheet_id: timesheet.id,
|
||||
// date: date,
|
||||
// start_time: shift.start_time,
|
||||
// end_time: shift.end_time,
|
||||
// is_remote: shift.is_remote,
|
||||
// comment: null,
|
||||
// bank_code_id: shift.bank_code_id,
|
||||
// });
|
||||
// }
|
||||
// if (payload.length) {
|
||||
// const response = await tx.shifts.createMany({ data: payload, skipDuplicates: true });
|
||||
// created += response.count;
|
||||
// skipped += payload.length - response.count;
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
// return { success: true, data: { timesheet_id: timesheet.id, created, skipped } };
|
||||
// }
|
||||
// }
|
||||
|
|
@ -1,26 +0,0 @@
|
|||
// import { Type } from "class-transformer";
|
||||
// import { IsDateString, IsInt, IsOptional, IsString } from "class-validator";
|
||||
|
||||
// export class SearchExpensesDto {
|
||||
// @IsOptional()
|
||||
// @Type(()=> Number)
|
||||
// @IsInt()
|
||||
// timesheet_id?: number;
|
||||
|
||||
// @IsOptional()
|
||||
// @Type(()=> Number)
|
||||
// @IsInt()
|
||||
// bank_code_id?: number;
|
||||
|
||||
// @IsOptional()
|
||||
// @IsString()
|
||||
// comment_contains?: string;
|
||||
|
||||
// @IsOptional()
|
||||
// @IsDateString()
|
||||
// start_date: string;
|
||||
|
||||
// @IsOptional()
|
||||
// @IsDateString()
|
||||
// end_date: string;
|
||||
// }
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
// import { Type } from "class-transformer";
|
||||
// import { IsBoolean, IsInt, IsOptional } from "class-validator";
|
||||
|
||||
|
||||
// export class SearchTimesheetDto {
|
||||
// @IsOptional()
|
||||
// @Type(() => Number)
|
||||
// @IsInt()
|
||||
// timesheet_id?: number;
|
||||
|
||||
// @IsOptional()
|
||||
// @Type(()=> Number)
|
||||
// @IsInt()
|
||||
// employee_id?: number;
|
||||
|
||||
// @IsOptional()
|
||||
// @Type(()=> Boolean)
|
||||
// @IsBoolean()
|
||||
// is_approved?: boolean;
|
||||
// }
|
||||
|
|
@ -1,194 +0,0 @@
|
|||
// 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),
|
||||
// );
|
||||
// }
|
||||
|
||||
|
||||
// //TODO: modifier le Master Crud pour recevoir l'ensemble des shifts de la pay-period et trier sur l'action 'create'| 'update' | 'delete'
|
||||
// //_____________________________________________________________________________________________
|
||||
// // 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 timesheet = await this.helpersService.ensureTimesheet(tx, employee_id, date_only);
|
||||
// if(!timesheet) throw new NotFoundException('Timesheet not found')
|
||||
// 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: 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);
|
||||
// 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 timesheet = await this.helpersService.ensureTimesheet(tx, employee_id, date_only);
|
||||
// if(!timesheet) throw new NotFoundException('Timesheet not found')
|
||||
|
||||
// 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 = await this.typeResolver.findByType(old_norm_shift.type);
|
||||
// const new_bank_code = await this.typeResolver.findByType(new_norm_shift.type);
|
||||
|
||||
// const day_shifts = await this.helpersService.getDayShifts(tx, timesheet.id, date_only);
|
||||
// const existing = await this.helpersService.findExactOldShift(tx, {
|
||||
// timesheet_id: 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);
|
||||
// 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,
|
||||
// ){
|
||||
// return this.prisma.$transaction(async (tx) => {
|
||||
// const date_only = toDateOnly(date_iso); //converts to Date format
|
||||
// const employee_id = await this.emailResolver.findIdByEmail(email);
|
||||
|
||||
// const timesheet = await this.helpersService.ensureTimesheet(tx, employee_id, date_only);
|
||||
// if(!timesheet) throw new NotFoundException('Timesheet not found')
|
||||
// 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: 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);
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
// export interface OverviewRow {
|
||||
// full_name: string;
|
||||
// supervisor: string;
|
||||
// total_regular_hrs: number;
|
||||
// total_evening_hrs: number;
|
||||
// total_overtime_hrs: number;
|
||||
// total_expenses: number;
|
||||
// total_mileage: number;
|
||||
// is_approved: boolean;
|
||||
// }
|
||||
|
|
@ -1,114 +0,0 @@
|
|||
// 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";
|
||||
|
||||
// // const DAILY_LIMIT_HOURS = Number(process.env.DAILY_LIMIT_HOURS ?? 12);
|
||||
|
||||
// @Injectable()
|
||||
// export class ShiftsQueryService {
|
||||
// constructor(
|
||||
// private readonly prisma: PrismaService,
|
||||
// private readonly notifs: NotificationsService,
|
||||
// ) {}
|
||||
|
||||
// async getSummary(period_id: number): Promise<OverviewRow[]> {
|
||||
// //fetch pay-period to display
|
||||
// const period = await this.prisma.payPeriods.findFirst({
|
||||
// where: { pay_period_no: period_id },
|
||||
// });
|
||||
// if(!period) {
|
||||
// throw new NotFoundException(`pay-period ${period_id} not found`);
|
||||
// }
|
||||
// const { period_start, period_end } = period;
|
||||
|
||||
// //prepare shifts and expenses for display
|
||||
// const shifts = await this.prisma.shifts.findMany({
|
||||
// where: { date: { gte: period_start, lte: period_end } },
|
||||
// include: {
|
||||
// bank_code: true,
|
||||
// timesheet: { include: {
|
||||
// employee: { include: {
|
||||
// user:true,
|
||||
// supervisor: { include: { user: true } },
|
||||
// } },
|
||||
// } },
|
||||
// },
|
||||
// });
|
||||
|
||||
// const expenses = await this.prisma.expenses.findMany({
|
||||
// where: { date: { gte: period_start, lte: period_end } },
|
||||
// include: {
|
||||
// bank_code: true,
|
||||
// timesheet: { include: { employee: {
|
||||
// include: { user:true,
|
||||
// supervisor: { include: { user:true } },
|
||||
// } },
|
||||
// } },
|
||||
// },
|
||||
// });
|
||||
|
||||
// const mapRow = new Map<string, OverviewRow>();
|
||||
|
||||
// for(const shift of shifts) {
|
||||
// const employeeId = shift.timesheet.employee.user_id;
|
||||
// const user = shift.timesheet.employee.user;
|
||||
// const sup = shift.timesheet.employee.supervisor?.user;
|
||||
|
||||
// let row = mapRow.get(employeeId);
|
||||
// if(!row) {
|
||||
// row = {
|
||||
// full_name: `${user.first_name} ${user.last_name}`,
|
||||
// supervisor: sup? `${sup.first_name} ${sup.last_name }` : '',
|
||||
// total_regular_hrs: 0,
|
||||
// total_evening_hrs: 0,
|
||||
// total_overtime_hrs: 0,
|
||||
// total_expenses: 0,
|
||||
// total_mileage: 0,
|
||||
// is_approved: false,
|
||||
// };
|
||||
// }
|
||||
// const hours = computeHours(shift.start_time, shift.end_time);
|
||||
|
||||
// switch(shift.bank_code.type) {
|
||||
// case 'regular' : row.total_regular_hrs += hours;
|
||||
// break;
|
||||
// case 'evening' : row.total_evening_hrs += hours;
|
||||
// break;
|
||||
// case 'overtime' : row.total_overtime_hrs += hours;
|
||||
// break;
|
||||
// default: row.total_regular_hrs += hours;
|
||||
// }
|
||||
// mapRow.set(employeeId, row);
|
||||
// }
|
||||
|
||||
// for(const exp of expenses) {
|
||||
// const employee_id = exp.timesheet.employee.user_id;
|
||||
// const user = exp.timesheet.employee.user;
|
||||
// const sup = exp.timesheet.employee.supervisor?.user;
|
||||
|
||||
// let row = mapRow.get(employee_id);
|
||||
// if(!row) {
|
||||
// row = {
|
||||
// full_name: `${user.first_name} ${user.last_name}`,
|
||||
// supervisor: sup? `${sup.first_name} ${sup.last_name }` : '',
|
||||
// total_regular_hrs: 0,
|
||||
// total_evening_hrs: 0,
|
||||
// total_overtime_hrs: 0,
|
||||
// total_expenses: 0,
|
||||
// total_mileage: 0,
|
||||
// is_approved: false,
|
||||
// };
|
||||
// }
|
||||
// const amount = Number(exp.amount);
|
||||
// row.total_expenses += amount;
|
||||
// if(exp.bank_code.type === 'mileage') {
|
||||
// row.total_mileage += amount;
|
||||
// }
|
||||
// mapRow.set(employee_id, row);
|
||||
// }
|
||||
// //return by default the list of employee in ascending alphabetical order
|
||||
// return Array.from(mapRow.values()).sort((a,b) => a.full_name.localeCompare(b.full_name));
|
||||
// }
|
||||
// }
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
// export type DayShiftResponse = {
|
||||
// start_time: string;
|
||||
// end_time: string;
|
||||
// type: string;
|
||||
// is_remote: boolean;
|
||||
// comment: string | null;
|
||||
// }
|
||||
|
||||
// export type ShiftPayload = {
|
||||
// date: string;
|
||||
// start_time: string;
|
||||
// end_time: string;
|
||||
// type: string;
|
||||
// is_remote: boolean;
|
||||
// is_approved: boolean;
|
||||
// comment?: string | null;
|
||||
// }
|
||||
|
|
@ -1,87 +0,0 @@
|
|||
// import { Body, Controller, Delete, Get, Header, Param, ParseBoolPipe, ParseIntPipe, Patch, Put, Query, } from "@nestjs/common";
|
||||
// import { RolesAllowed } from "src/common/decorators/roles.decorators";
|
||||
// import { Roles as RoleEnum } from '.prisma/client';
|
||||
// import { ApiBearerAuth, ApiTags } from "@nestjs/swagger";
|
||||
// import { ShiftsCommandService } from "../services/shifts-command.service";
|
||||
// import { ShiftsQueryService } from "../services/shifts-query.service";
|
||||
// import { GetShiftsOverviewDto } from "../dtos/get-shift-overview.dto";
|
||||
// import { ShiftPayloadDto, UpsertShiftDto } from "../dtos/upsert-shift.dto";
|
||||
// import { OverviewRow } from "../types-and-interfaces/shifts-overview-row.interface";
|
||||
// import { UpsertAction } from "src/modules/shared/types/upsert-actions.types";
|
||||
|
||||
// @ApiTags('Shifts')
|
||||
// @ApiBearerAuth('access-token')
|
||||
// // @UseGuards()
|
||||
// @Controller('shifts')
|
||||
// export class ShiftsController {
|
||||
// constructor(
|
||||
// private readonly shiftsService: ShiftsQueryService,
|
||||
// private readonly shiftsCommandService: ShiftsCommandService,
|
||||
// ){}
|
||||
|
||||
// @Put('upsert/:email')
|
||||
// async upsert_by_date(
|
||||
// @Param('email') email_param: string,
|
||||
// @Query('action') action: UpsertAction,
|
||||
// @Body() payload: UpsertShiftDto,
|
||||
// ) {
|
||||
// return this.shiftsCommandService.upsertShifts(email_param, action, payload);
|
||||
// }
|
||||
|
||||
// @Delete('delete/:email/:date')
|
||||
// async remove(
|
||||
// @Param('email') email: string,
|
||||
// @Param('date') date: string,
|
||||
// @Body() payload: UpsertShiftDto,
|
||||
// ) {
|
||||
// return this.shiftsCommandService.deleteShift(email, date, payload);
|
||||
// }
|
||||
|
||||
// @Patch('approval/:id')
|
||||
// //@RolesAllowed(RoleEnum.ACCOUNTING, RoleEnum.ADMIN, RoleEnum.HR, RoleEnum.SUPERVISOR)
|
||||
// async approve(@Param('id', ParseIntPipe) id: number, @Body('is_approved', ParseBoolPipe) isApproved: boolean) {
|
||||
// return this.shiftsCommandService.updateApproval(id, isApproved);
|
||||
// }
|
||||
|
||||
// @Get('summary')
|
||||
// async getSummary( @Query() query: GetShiftsOverviewDto): Promise<OverviewRow[]> {
|
||||
// return this.shiftsService.getSummary(query.period_id);
|
||||
// }
|
||||
|
||||
// @Get('export.csv')
|
||||
// @Header('Content-Type', 'text/csv; charset=utf-8')
|
||||
// @Header('Content-Disposition', 'attachment; filename="shifts-validation.csv"')
|
||||
// async exportCsv(@Query() query: GetShiftsOverviewDto): Promise<Buffer>{
|
||||
// const rows = await this.shiftsService.getSummary(query.period_id);
|
||||
// //CSV Headers
|
||||
// const header = [
|
||||
// 'full_name',
|
||||
// 'supervisor',
|
||||
// 'total_regular_hrs',
|
||||
// 'total_evening_hrs',
|
||||
// 'total_overtime_hrs',
|
||||
// 'total_expenses',
|
||||
// 'total_mileage',
|
||||
// 'is_validated'
|
||||
// ].join(',') + '\n';
|
||||
|
||||
// //CSV rows
|
||||
// const body = rows.map(r => {
|
||||
// const esc = (str: string) => `"${str.replace(/"/g, '""')}"`;
|
||||
|
||||
// return [
|
||||
// esc(r.full_name),
|
||||
// esc(r.supervisor),
|
||||
// r.total_regular_hrs.toFixed(2),
|
||||
// r.total_evening_hrs.toFixed(2),
|
||||
// r.total_overtime_hrs.toFixed(2),
|
||||
// r.total_expenses.toFixed(2),
|
||||
// r.total_mileage.toFixed(2),
|
||||
// r.is_approved,
|
||||
// ].join(',');
|
||||
// }).join('\n');
|
||||
|
||||
// return Buffer.from('\uFEFF' + header + body, 'utf8');
|
||||
// }
|
||||
|
||||
// }
|
||||
|
|
@ -1,103 +0,0 @@
|
|||
// import { BadRequestException, UnprocessableEntityException, NotFoundException, ConflictException } from "@nestjs/common";
|
||||
// import { Prisma, Shifts } from "@prisma/client";
|
||||
// import { BankCodesResolver } from "src/modules/shared/utils/resolve-bank-type-id.utils";
|
||||
// import { OvertimeService } from "src/modules/business-logics/services/overtime.service";
|
||||
|
||||
// export type Tx = Prisma.TransactionClient;
|
||||
// export type Normalized = Awaited<ReturnType<typeof normalizeShiftPayload>>;
|
||||
|
||||
// export class ShiftsHelpersService {
|
||||
|
||||
// constructor(
|
||||
// private readonly bankTypeResolver: BankCodesResolver,
|
||||
// private readonly overtimeService: OvertimeService,
|
||||
// ) { }
|
||||
|
||||
// async ensureTimesheet(tx: Tx, employee_id: number, date_only: Date) {
|
||||
// const start_of_week = weekStartSunday(date_only);
|
||||
// return tx.timesheets.findUnique({
|
||||
// where: { employee_id_start_date: { employee_id, start_date: start_of_week } },
|
||||
// select: { id: true },
|
||||
// });
|
||||
// }
|
||||
|
||||
// async normalizeRequired(
|
||||
// raw: UpsertShiftDto['new_shift'] | UpsertShiftDto['old_shift'] | undefined | null,
|
||||
// label: 'old_shift' | 'new_shift' = 'new_shift',
|
||||
// ): Promise<Normalized> {
|
||||
// if (!raw) throw new BadRequestException(`${label} is required`);
|
||||
// const norm = await normalizeShiftPayload(raw);
|
||||
// if (norm.end_time.getTime() <= norm.start_time.getTime()) {
|
||||
// throw new UnprocessableEntityException(` ${label}.end_time must be > ${label}.start_time`);
|
||||
// }
|
||||
// return norm;
|
||||
// }
|
||||
|
||||
// async resolveBankIdRequired(tx: Tx, type: string, label: 'old_shift' | 'new_shift'): Promise<number> {
|
||||
// const found = await this.bankTypeResolver.findByType(type, tx);
|
||||
// const id = found?.id;
|
||||
// if (typeof id !== 'number') {
|
||||
// throw new NotFoundException(`bank code not found for ${label}.type: ${type ?? ''}`);
|
||||
// }
|
||||
// return id;
|
||||
// }
|
||||
|
||||
// async getDayShifts(tx: Tx, timesheet_id: number, date_only: Date) {
|
||||
// return tx.shifts.findMany({
|
||||
// where: { timesheet_id, date: date_only },
|
||||
// include: { bank_code: true },
|
||||
// orderBy: { start_time: 'asc' },
|
||||
// });
|
||||
// }
|
||||
|
||||
// async findExactOldShift(
|
||||
// tx: Tx,
|
||||
// params: {
|
||||
// timesheet_id: number;
|
||||
// date_only: Date;
|
||||
// norm: Normalized;
|
||||
// bank_code_id: number;
|
||||
// comment?: string;
|
||||
// },
|
||||
// ) {
|
||||
// const { timesheet_id, date_only, norm, bank_code_id } = params;
|
||||
// return tx.shifts.findFirst({
|
||||
// where: {
|
||||
// timesheet_id,
|
||||
// date: date_only,
|
||||
// start_time: norm.start_time,
|
||||
// end_time: norm.end_time,
|
||||
// is_remote: norm.is_remote,
|
||||
// is_approved: norm.is_approved,
|
||||
// comment: norm.comment ?? null,
|
||||
// bank_code_id,
|
||||
// },
|
||||
// select: { id: true },
|
||||
// });
|
||||
// }
|
||||
|
||||
// async afterWriteOvertimeAndLog(tx: Tx, employee_id: number, date_only: Date) {
|
||||
// // Switch regular → weekly overtime si > 40h
|
||||
// await this.overtimeService.transformRegularHoursToWeeklyOvertime(employee_id, date_only, tx);
|
||||
// const daily = await this.overtimeService.getDailyOvertimeHours(employee_id, date_only);
|
||||
// const weekly = await this.overtimeService.getWeeklyOvertimeHours(employee_id, date_only);
|
||||
// // const [daily, weekly] = await Promise.all([
|
||||
// // this.overtimeService.getDailyOvertimeHoursForDay(employee_id, date_only),
|
||||
// // this.overtimeService.getWeeklyOvertimeHours(employee_id, date_only),
|
||||
// // ]);
|
||||
// return { daily, weekly };
|
||||
// }
|
||||
|
||||
// async mapDay(
|
||||
// fresh: Array<Shifts & { bank_code: { type: string } | null }>,
|
||||
// ): Promise<DayShiftResponse[]> {
|
||||
// return fresh.map((s) => ({
|
||||
// start_time: toStringFromHHmm(s.start_time),
|
||||
// end_time: toStringFromHHmm(s.end_time),
|
||||
// type: s.bank_code?.type ?? 'UNKNOWN',
|
||||
// is_remote: s.is_remote,
|
||||
// comment: s.comment ?? null,
|
||||
// }));
|
||||
// }
|
||||
// }
|
||||
|
||||
|
|
@ -1,58 +0,0 @@
|
|||
// import { NotFoundException } from "@nestjs/common";
|
||||
|
||||
// export function overlaps(
|
||||
// a_start_ms: number,
|
||||
// a_end_ms: number,
|
||||
// b_start_ms: number,
|
||||
// b_end_ms: number,
|
||||
// ): boolean {
|
||||
// return a_start_ms < b_end_ms && b_start_ms < a_end_ms;
|
||||
// }
|
||||
|
||||
// export function resolveBankCodeByType(type: string): Promise<number> {
|
||||
// const bank = this.prisma.bankCodes.findFirst({
|
||||
// where: { type },
|
||||
// select: { id: true },
|
||||
// });
|
||||
// if (!bank) {
|
||||
// throw new NotFoundException({ error_code: 'SHIFT_TYPE_UNKNOWN', message: `unknown shift type: ${type}` });
|
||||
// }
|
||||
// return bank.id;
|
||||
// }
|
||||
|
||||
// export function normalizeShiftPayload(payload: {
|
||||
// date: string,
|
||||
// start_time: string,
|
||||
// end_time: string,
|
||||
// type: string,
|
||||
// is_remote: boolean,
|
||||
// is_approved: boolean,
|
||||
// comment?: string | null,
|
||||
// }) {
|
||||
// //normalize shift's infos
|
||||
// const date = payload.date?.trim();
|
||||
// const m = /^(\d{4})-(\d{2})-(\d{2})$/.exec(date ?? '');
|
||||
// if (!m) throw new Error(`Invalid date format (expected YYYY-MM-DD): "${payload.date}"`);
|
||||
// const year = Number(m[1]), mo = Number(m[2]), d = Number(m[3]);
|
||||
|
||||
// const asLocalDateOn = (input: string): Date => {
|
||||
// // HH:mm ?
|
||||
// const hm = /^(\d{2}):(\d{2})$/.exec((input ?? '').trim());
|
||||
// if (hm) return new Date(year, mo - 1, d, Number(hm[1]), Number(hm[2]), 0, 0);
|
||||
// const iso = new Date(input);
|
||||
// if (isNaN(iso.getTime())) throw new Error(`Invalid time: "${input}"`);
|
||||
// return new Date(year, mo - 1, d, iso.getHours(), iso.getMinutes(), iso.getSeconds(), iso.getMilliseconds());
|
||||
// };
|
||||
|
||||
// const start_time = asLocalDateOn(payload.start_time);
|
||||
// const end_time = asLocalDateOn(payload.end_time);
|
||||
|
||||
// const type = (payload.type || '').trim().toUpperCase();
|
||||
// const is_remote = payload.is_remote;
|
||||
// const is_approved = payload.is_approved;
|
||||
// //normalize comment
|
||||
// const trimmed = typeof payload.comment === 'string' ? payload.comment.trim() : null;
|
||||
// const comment = trimmed && trimmed.length > 0 ? trimmed : null;
|
||||
|
||||
// return { date, start_time, end_time, type, is_remote, is_approved, comment };
|
||||
// }
|
||||
|
|
@ -1,75 +0,0 @@
|
|||
// 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;
|
||||
// start_time: string;
|
||||
// end_time : string;
|
||||
// comment: string;
|
||||
// is_approved: boolean;
|
||||
// is_remote: boolean;
|
||||
// }
|
||||
|
||||
// export class ExpenseDto {
|
||||
// type: string;
|
||||
// amount: number;
|
||||
// mileage: number;
|
||||
// comment: string;
|
||||
// is_approved: boolean;
|
||||
// supervisor_comment: string;
|
||||
// }
|
||||
|
||||
// export type DayShiftsDto = ShiftDto[];
|
||||
|
||||
// export class DetailedShifts {
|
||||
// shifts: DayShiftsDto;
|
||||
// regular_hours: number;
|
||||
// evening_hours: number;
|
||||
// overtime_hours: number;
|
||||
// emergency_hours: number;
|
||||
// comment: string;
|
||||
// short_date: string;
|
||||
// break_durations?: number;
|
||||
// }
|
||||
|
||||
// export class DayExpensesDto {
|
||||
// expenses: ExpenseDto[] = [];
|
||||
// total_mileage: number;
|
||||
// total_expense: number;
|
||||
// }
|
||||
|
||||
// export class WeekDto {
|
||||
// is_approved: boolean;
|
||||
// shifts: {
|
||||
// sun: DetailedShifts;
|
||||
// mon: DetailedShifts;
|
||||
// tue: DetailedShifts;
|
||||
// wed: DetailedShifts;
|
||||
// thu: DetailedShifts;
|
||||
// fri: DetailedShifts;
|
||||
// sat: DetailedShifts;
|
||||
// }
|
||||
// expenses: {
|
||||
// sun: DayExpensesDto;
|
||||
// mon: DayExpensesDto;
|
||||
// tue: DayExpensesDto;
|
||||
// wed: DayExpensesDto;
|
||||
// thu: DayExpensesDto;
|
||||
// fri: DayExpensesDto;
|
||||
// sat: DayExpensesDto;
|
||||
// }
|
||||
// }
|
||||
|
||||
// export class TimesheetPeriodDto {
|
||||
// weeks: WeekDto[];
|
||||
// employee_full_name: string;
|
||||
// }
|
||||
|
||||
|
||||
|
|
@ -1,67 +0,0 @@
|
|||
// import { MS_PER_DAY } from "src/modules/shared/constants/date-time.constant";
|
||||
// import { DAY_KEYS, DayKey } from "./timesheet.types";
|
||||
|
||||
// export function toUTCDateOnly(date: Date | string): Date {
|
||||
// const d = new Date(date);
|
||||
// return new Date(Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate()));
|
||||
// }
|
||||
|
||||
// export function addDays(date:Date, days: number): Date {
|
||||
// return new Date(date.getTime() + days * MS_PER_DAY);
|
||||
// }
|
||||
|
||||
// export function endOfDayUTC(date: Date | string): Date {
|
||||
// const d = toUTCDateOnly(date);
|
||||
// return new Date(d.getTime() + MS_PER_DAY - 1);
|
||||
// }
|
||||
|
||||
// export function isBetweenUTC(date: Date, start: Date, end_inclusive: Date): boolean {
|
||||
// const time = date.getTime();
|
||||
// return time >= start.getTime() && time <= end_inclusive.getTime();
|
||||
// }
|
||||
|
||||
// export function toTimeString(date: Date): string {
|
||||
// const hours = String(date.getUTCHours()).padStart(2,'0');
|
||||
// const minutes = String(date.getUTCMinutes()).padStart(2,'0');
|
||||
// return `${hours}:${minutes}`;
|
||||
// }
|
||||
|
||||
// export function round2(num: number) {
|
||||
// return Math.round(num * 100) / 100;
|
||||
// }
|
||||
|
||||
// export function shortDate(date:Date): string {
|
||||
// const mm = String(date.getUTCMonth()+1).padStart(2,'0');
|
||||
// const dd = String(date.getUTCDate()).padStart(2,'0');
|
||||
// return `${mm}/${dd}`;
|
||||
// }
|
||||
|
||||
// export function dayKeyFromDate(date: Date, useUTC = true): DayKey {
|
||||
// const index = useUTC ? date.getUTCDay() : date.getDay(); // 0=Sunday..6=Saturday
|
||||
// return DAY_KEYS[index];
|
||||
// }
|
||||
|
||||
// export const toHHmm = (date: Date) => date.toISOString().slice(11, 16);
|
||||
|
||||
// export function parseISODate(iso: string): Date {
|
||||
// const [ y, m, d ] = iso.split('-').map(Number);
|
||||
// return new Date(y, (m ?? 1) - 1, d ?? 1);
|
||||
// }
|
||||
|
||||
// export function parseHHmm(t: string): Date {
|
||||
// const [ hh, mm ] = t.split(':').map(Number);
|
||||
// return new Date(1970, 0, 1, hh || 0, mm || 0, 0, 0);
|
||||
// }
|
||||
|
||||
// export const toNum = (value: any) =>
|
||||
// value && typeof value.toNumber === 'function' ? value.toNumber() :
|
||||
// typeof value === 'number' ? value :
|
||||
// value ? Number(value) : 0;
|
||||
|
||||
|
||||
// export const upper = (s?: string | null) => String(s ?? '').toUpperCase();
|
||||
|
||||
// export const toRangeFromPeriod = (period: { period_start: Date; period_end: Date }) => ({
|
||||
// from: toUTCDateOnly(period.period_start),
|
||||
// to: endOfDayUTC(period.period_end),
|
||||
// });
|
||||
|
|
@ -1,111 +0,0 @@
|
|||
// import { DayExpensesDto, WeekDto, DetailedShifts, TimesheetPeriodDto } from "../dtos/timesheet-period.dto";
|
||||
// import { ShiftRow, ExpenseRow, ExpensesAmount, TimesheetMap } from "./timesheet.types";
|
||||
// import { addDays, shortDate, toNum, upper } from "./timesheet.helpers";
|
||||
// import { Prisma } from "@prisma/client";
|
||||
|
||||
|
||||
// //mappers
|
||||
// export const mapShiftRow = (shift: {
|
||||
// date: Date;
|
||||
// start_time: Date;
|
||||
// end_time: Date;
|
||||
// comment?: string | null;
|
||||
// is_approved: boolean;
|
||||
// is_remote: boolean;
|
||||
// bank_code: { type: string };
|
||||
// }): ShiftRow => ({
|
||||
// date: shift.date,
|
||||
// start_time: shift.start_time,
|
||||
// end_time: shift.end_time,
|
||||
// comment: shift.comment ?? '',
|
||||
// is_approved: shift.is_approved,
|
||||
// is_remote: shift.is_remote,
|
||||
// type: upper(shift.bank_code.type),
|
||||
// });
|
||||
|
||||
// export const mapExpenseRow = (expense: {
|
||||
// date: Date;
|
||||
// amount: Prisma.Decimal | number | null;
|
||||
// mileage: Prisma.Decimal | number | null;
|
||||
// comment?: string | null;
|
||||
// is_approved: boolean;
|
||||
// supervisor_comment?: string|null;
|
||||
// bank_code: { type: string },
|
||||
// }): ExpenseRow => ({
|
||||
// date: expense.date,
|
||||
// amount: toNum(expense.amount),
|
||||
// mileage: toNum(expense.mileage),
|
||||
// comment: expense.comment ?? '',
|
||||
// is_approved: expense.is_approved,
|
||||
// supervisor_comment: expense.supervisor_comment ?? '',
|
||||
// type: upper(expense.bank_code.type),
|
||||
// });
|
||||
|
||||
// // Factories
|
||||
// export function makeEmptyDayExpenses(): DayExpensesDto {
|
||||
// return {
|
||||
// expenses: [],
|
||||
// total_expense: -1,
|
||||
// total_mileage: -1,
|
||||
// };
|
||||
// }
|
||||
|
||||
// export function makeEmptyWeek(week_start: Date): WeekDto {
|
||||
// const make_empty_shifts = (offset: number): DetailedShifts => ({
|
||||
// shifts: [],
|
||||
// regular_hours: 0,
|
||||
// evening_hours: 0,
|
||||
// emergency_hours: 0,
|
||||
// overtime_hours: 0,
|
||||
// comment: '',
|
||||
// short_date: shortDate(addDays(week_start, offset)),
|
||||
// break_durations: 0,
|
||||
// });
|
||||
// return {
|
||||
// is_approved: true,
|
||||
// shifts: {
|
||||
// sun: make_empty_shifts(0),
|
||||
// mon: make_empty_shifts(1),
|
||||
// tue: make_empty_shifts(2),
|
||||
// wed: make_empty_shifts(3),
|
||||
// thu: make_empty_shifts(4),
|
||||
// fri: make_empty_shifts(5),
|
||||
// sat: make_empty_shifts(6),
|
||||
// },
|
||||
// expenses: {
|
||||
// sun: makeEmptyDayExpenses(),
|
||||
// mon: makeEmptyDayExpenses(),
|
||||
// tue: makeEmptyDayExpenses(),
|
||||
// wed: makeEmptyDayExpenses(),
|
||||
// thu: makeEmptyDayExpenses(),
|
||||
// fri: makeEmptyDayExpenses(),
|
||||
// sat: makeEmptyDayExpenses(),
|
||||
// },
|
||||
// };
|
||||
// }
|
||||
|
||||
// export function makeEmptyPeriod(): TimesheetPeriodDto {
|
||||
// return { weeks: [makeEmptyWeek(new Date()), makeEmptyWeek(new Date())], employee_full_name: '' };
|
||||
// }
|
||||
|
||||
// export const makeAmounts = (): ExpensesAmount => ({
|
||||
// expense: 0,
|
||||
// mileage: 0,
|
||||
// });
|
||||
|
||||
// export function makeEmptyTimesheet(params: {
|
||||
// start_day: string;
|
||||
// end_day: string;
|
||||
// label: string;
|
||||
// is_approved?: boolean;
|
||||
// }): TimesheetMap {
|
||||
// const { start_day, end_day, label, is_approved = false } = params;
|
||||
// return {
|
||||
// start_day,
|
||||
// end_day,
|
||||
// label,
|
||||
// shifts: [],
|
||||
// expenses: [],
|
||||
// is_approved,
|
||||
// };
|
||||
// }
|
||||
|
|
@ -1,46 +0,0 @@
|
|||
// import { EXPENSE_ASC_ORDER, EXPENSE_SELECT } from "../../../shared/selects/expenses.select";
|
||||
// import { Injectable, NotFoundException } from "@nestjs/common";
|
||||
// import { SHIFT_ASC_ORDER, SHIFT_SELECT } from "../../../shared/selects/shifts.select";
|
||||
// import { PAY_PERIOD_SELECT } from "../../../shared/selects/pay-periods.select";
|
||||
// import { PrismaService } from "src/prisma/prisma.service";
|
||||
|
||||
// @Injectable()
|
||||
// export class TimesheetSelectorsService {
|
||||
// constructor(readonly prisma: PrismaService){}
|
||||
|
||||
// async getPayPeriod(pay_year: number, pay_period_no: number) {
|
||||
// const period = await this.prisma.payPeriods.findFirst({
|
||||
// where: { pay_year, pay_period_no },
|
||||
// select: PAY_PERIOD_SELECT ,
|
||||
// });
|
||||
// if(!period) throw new NotFoundException(`period ${pay_year}-${pay_period_no} not found`);
|
||||
// return period;
|
||||
// }
|
||||
|
||||
// async getShifts(employee_id: number, from: Date, to: Date) {
|
||||
// return this.prisma.shifts.findMany({
|
||||
// where: {timesheet: { is: { employee_id } }, date: { gte: from, lte: to } },
|
||||
// select: SHIFT_SELECT,
|
||||
// orderBy: SHIFT_ASC_ORDER,
|
||||
// });
|
||||
// }
|
||||
|
||||
// async getExpenses(employee_id: number, from: Date, to: Date) {
|
||||
// return this.prisma.expenses.findMany({
|
||||
// where: { timesheet: {is: { employee_id } }, date: { gte: from, lte: to } },
|
||||
// select: EXPENSE_SELECT,
|
||||
// orderBy: EXPENSE_ASC_ORDER,
|
||||
// });
|
||||
// }
|
||||
|
||||
// async getTimesheetWithShiftsAndExpenses(employee_id: number, start_date_week: Date) {
|
||||
// return this.prisma.timesheets.findUnique({
|
||||
// where: { employee_id_start_date: { employee_id, start_date: start_date_week } },
|
||||
// select: {
|
||||
// is_approved: true,
|
||||
// shift: { select: SHIFT_SELECT, orderBy: SHIFT_ASC_ORDER },
|
||||
// expense: { select: EXPENSE_SELECT, orderBy: EXPENSE_ASC_ORDER },
|
||||
// },
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
|
|
@ -1,74 +0,0 @@
|
|||
// export type ShiftRow = {
|
||||
// date: Date;
|
||||
// start_time: Date;
|
||||
// end_time: Date;
|
||||
// comment: string;
|
||||
// is_approved?: boolean;
|
||||
// is_remote: boolean;
|
||||
// type: string
|
||||
// };
|
||||
// export type ExpenseRow = {
|
||||
// date: Date;
|
||||
// amount: number;
|
||||
// mileage?: number | null;
|
||||
// comment: string;
|
||||
// type: string;
|
||||
// is_approved?: boolean;
|
||||
// supervisor_comment: string;
|
||||
// };
|
||||
|
||||
// export type TimesheetMap = {
|
||||
// start_day: string;
|
||||
// end_day: string;
|
||||
// label: string;
|
||||
// shifts: ShiftRow[];
|
||||
// expenses: ExpenseRow[]
|
||||
// is_approved: boolean;
|
||||
// }
|
||||
|
||||
// // Types
|
||||
// export const SHIFT_TYPES = {
|
||||
// REGULAR: 'REGULAR',
|
||||
// EVENING: 'EVENING',
|
||||
// OVERTIME: 'OVERTIME',
|
||||
// EMERGENCY: 'EMERGENCY',
|
||||
// HOLIDAY: 'HOLIDAY',
|
||||
// VACATION: 'VACATION',
|
||||
// SICK: 'SICK',
|
||||
// } as const;
|
||||
|
||||
// export const EXPENSE_TYPES = {
|
||||
// MILEAGE: 'MILEAGE',
|
||||
// EXPENSE: 'EXPENSES',
|
||||
// PER_DIEM: 'PER_DIEM',
|
||||
// ON_CALL: 'ON_CALL',
|
||||
// } as const;
|
||||
|
||||
// //makes the strings indexes for arrays
|
||||
// export const DAY_KEYS = ['sun','mon','tue','wed','thu','fri','sat'] as const;
|
||||
// export type DayKey = typeof DAY_KEYS[number];
|
||||
|
||||
// //shifts's hour by type
|
||||
// export type ShiftsHours = {
|
||||
// regular: number;
|
||||
// evening: number;
|
||||
// overtime: number;
|
||||
// emergency: number;
|
||||
// sick: number;
|
||||
// vacation: number;
|
||||
// holiday: number;
|
||||
// };
|
||||
// export const make_hours = (): ShiftsHours => ({
|
||||
// regular: 0,
|
||||
// evening: 0,
|
||||
// overtime: 0,
|
||||
// emergency: 0,
|
||||
// sick: 0,
|
||||
// vacation: 0,
|
||||
// holiday: 0,
|
||||
// });
|
||||
|
||||
// export type ExpensesAmount = {
|
||||
// expense: number;
|
||||
// mileage: number;
|
||||
// };
|
||||
|
|
@ -1,171 +0,0 @@
|
|||
// import {
|
||||
// DayKey, DAY_KEYS, EXPENSE_TYPES, ExpenseRow,
|
||||
// SHIFT_TYPES, ShiftRow, make_hours, ShiftsHours, ExpensesAmount
|
||||
// } from "./timesheet.types";
|
||||
// import {
|
||||
// isBetweenUTC, dayKeyFromDate, toTimeString, round2,
|
||||
// toUTCDateOnly, endOfDayUTC, addDays
|
||||
// } from "./timesheet.helpers";
|
||||
// import { WeekDto, ShiftDto, TimesheetPeriodDto, DayExpensesDto, ExpenseDto } from "../dtos/timesheet-period.dto";
|
||||
// import { getWeekStart, getWeekEnd, formatDateISO } from "src/common/utils/date-utils";
|
||||
// import { makeAmounts, makeEmptyWeek } from "./timesheet.mappers";
|
||||
// import { toDateString } from "src/modules/pay-periods/utils/pay-year.util";
|
||||
// import { MS_PER_HOUR } from "src/modules/shared/constants/date-time.constant";
|
||||
|
||||
// export function computeWeekRange(week_offset = 0){
|
||||
// //sets current week Sunday -> Saturday
|
||||
// const base = new Date();
|
||||
// const offset = new Date(base);
|
||||
// offset.setDate(offset.getDate() + (week_offset * 7));
|
||||
|
||||
// const start = getWeekStart(offset, 0);
|
||||
// const end = getWeekEnd(start);
|
||||
// const start_day = formatDateISO(start);
|
||||
// const end_day = formatDateISO(end);
|
||||
// const label = `${(start_day)}.${(end_day)}`;
|
||||
|
||||
// return { start, end, start_day, end_day, label }
|
||||
// };
|
||||
|
||||
// export function buildWeek(
|
||||
// week_start: Date,
|
||||
// week_end: Date,
|
||||
// shifts: ShiftRow[],
|
||||
// expenses: ExpenseRow[],
|
||||
// ): WeekDto {
|
||||
// const week = makeEmptyWeek(week_start);
|
||||
// let all_approved = true;
|
||||
|
||||
// const day_times: Record<DayKey, Array<{ start: Date; end: Date }>> = DAY_KEYS.reduce((acc, key) => {
|
||||
// acc[key] = []; return acc;
|
||||
// }, {} as Record<DayKey, Array<{ start: Date; end: Date}>>);
|
||||
|
||||
// const day_hours: Record<DayKey, ShiftsHours> = DAY_KEYS.reduce((acc, key) => {
|
||||
// acc[key] = make_hours(); return acc;
|
||||
// }, {} as Record<DayKey, ShiftsHours>);
|
||||
|
||||
// const day_amounts: Record<DayKey, ExpensesAmount> = DAY_KEYS.reduce((acc, key) => {
|
||||
// acc[key] = makeAmounts(); return acc;
|
||||
// }, {} as Record<DayKey, ExpensesAmount>);
|
||||
|
||||
// const day_expense_rows: Record<DayKey, DayExpensesDto> = DAY_KEYS.reduce((acc, key) => {
|
||||
// acc[key] = {
|
||||
// expenses: [{
|
||||
// type: '',
|
||||
// amount: -1,
|
||||
// mileage: -1,
|
||||
// comment: '',
|
||||
// is_approved: false,
|
||||
// supervisor_comment: '',
|
||||
// }],
|
||||
// total_expense: -1,
|
||||
// total_mileage: -1,
|
||||
// };
|
||||
// return acc;
|
||||
// }, {} as Record<DayKey, DayExpensesDto>);
|
||||
|
||||
// //regroup hours per type of shifts
|
||||
// const week_shifts = shifts.filter(shift => isBetweenUTC(shift.date, week_start, week_end));
|
||||
// for (const shift of week_shifts) {
|
||||
// const key = dayKeyFromDate(shift.date, true);
|
||||
// week.shifts[key].shifts.push({
|
||||
// date: toDateString(shift.date),
|
||||
// type: shift.type,
|
||||
// start_time: toTimeString(shift.start_time),
|
||||
// end_time: toTimeString(shift.end_time),
|
||||
// comment: shift.comment,
|
||||
// is_approved: shift.is_approved ?? true,
|
||||
// is_remote: shift.is_remote,
|
||||
// } as ShiftDto);
|
||||
|
||||
// day_times[key].push({ start: shift.start_time, end: shift.end_time});
|
||||
|
||||
// const duration = Math.max(0, (shift.end_time.getTime() - shift.start_time.getTime())/ MS_PER_HOUR);
|
||||
// const type = (shift.type || '').toUpperCase();
|
||||
|
||||
// if ( type === SHIFT_TYPES.REGULAR) day_hours[key].regular += duration;
|
||||
// else if( type === SHIFT_TYPES.EVENING) day_hours[key].evening += duration;
|
||||
// else if( type === SHIFT_TYPES.EMERGENCY) day_hours[key].emergency += duration;
|
||||
// else if( type === SHIFT_TYPES.OVERTIME) day_hours[key].overtime += duration;
|
||||
// else if( type === SHIFT_TYPES.SICK) day_hours[key].sick += duration;
|
||||
// else if( type === SHIFT_TYPES.VACATION) day_hours[key].vacation += duration;
|
||||
// else if( type === SHIFT_TYPES.HOLIDAY) day_hours[key].holiday += duration;
|
||||
|
||||
// all_approved = all_approved && (shift.is_approved ?? true );
|
||||
// }
|
||||
|
||||
// //regroupe amounts to type of expenses
|
||||
// const week_expenses = expenses.filter(expense => isBetweenUTC(expense.date, week_start, week_end));
|
||||
// for (const expense of week_expenses) {
|
||||
// const key = dayKeyFromDate(expense.date, true);
|
||||
// const type = (expense.type || '').toUpperCase();
|
||||
|
||||
// const row: ExpenseDto = {
|
||||
// type,
|
||||
// amount: round2(expense.amount ?? 0),
|
||||
// mileage: round2(expense.mileage ?? 0),
|
||||
// comment: expense.comment ?? '',
|
||||
// is_approved: expense.is_approved ?? true,
|
||||
// supervisor_comment: expense.supervisor_comment ?? '',
|
||||
// };
|
||||
|
||||
// day_expense_rows[key].expenses.push(row);
|
||||
|
||||
// if(type === EXPENSE_TYPES.MILEAGE) {
|
||||
// day_amounts[key].mileage += row.mileage ?? 0;
|
||||
// } else {
|
||||
// day_amounts[key].expense += row.amount;
|
||||
// }
|
||||
|
||||
// all_approved = all_approved && row.is_approved;
|
||||
// }
|
||||
|
||||
// for (const key of DAY_KEYS) {
|
||||
// //return exposed dto data
|
||||
// week.shifts[key].regular_hours = round2(day_hours[key].regular);
|
||||
// week.shifts[key].evening_hours = round2(day_hours[key].evening);
|
||||
// week.shifts[key].overtime_hours = round2(day_hours[key].overtime);
|
||||
// week.shifts[key].emergency_hours = round2(day_hours[key].emergency);
|
||||
|
||||
// //calculate gaps between shifts
|
||||
// const times = day_times[key].sort((a,b) => a.start.getTime() - b.start.getTime());
|
||||
// let gaps = 0;
|
||||
// for (let i = 1; i < times.length; i++) {
|
||||
// const gap = (times[i].start.getTime() - times[i - 1].end.getTime()) / MS_PER_HOUR;
|
||||
// if(gap > 0) gaps += gap;
|
||||
// }
|
||||
// week.shifts[key].break_durations = round2(gaps);
|
||||
|
||||
// //daily totals
|
||||
// const totals = day_amounts[key];
|
||||
|
||||
// day_expense_rows[key].total_mileage = round2(totals.mileage);
|
||||
// day_expense_rows[key].total_expense = round2(totals.expense);
|
||||
// }
|
||||
|
||||
// week.is_approved = all_approved;
|
||||
// return week;
|
||||
// }
|
||||
|
||||
// export function buildPeriod(
|
||||
// period_start: Date,
|
||||
// period_end: Date,
|
||||
// shifts: ShiftRow[],
|
||||
// expenses: ExpenseRow[],
|
||||
// employeeFullName = ''
|
||||
// ): TimesheetPeriodDto {
|
||||
// const week1_start = toUTCDateOnly(period_start);
|
||||
// const week1_end = endOfDayUTC(addDays(week1_start, 6));
|
||||
// const week2_start = toUTCDateOnly(addDays(week1_start, 7));
|
||||
// const week2_end = endOfDayUTC(period_end);
|
||||
|
||||
// const weeks: WeekDto[] = [
|
||||
// buildWeek(week1_start, week1_end, shifts, expenses),
|
||||
// buildWeek(week2_start, week2_end, shifts, expenses),
|
||||
// ];
|
||||
|
||||
// return {
|
||||
// weeks,
|
||||
// employee_full_name: employeeFullName,
|
||||
// };
|
||||
// }
|
||||
|
|
@ -1,137 +0,0 @@
|
|||
// import { BadRequestException, Injectable, NotFoundException } from "@nestjs/common";
|
||||
// import { EmployeeTimesheetResolver } from "src/modules/shared/utils/resolve-timesheet.utils";
|
||||
// import { getWeekEnd, getWeekStart } from "src/common/utils/date-utils";
|
||||
// import { parseISODate, parseHHmm } from "./utils-helpers-others/timesheet.helpers";
|
||||
// import { TimesheetsQueryService } from "./timesheets-query.service";
|
||||
// import { BaseApprovalService } from "src/common/shared/base-approval.service";
|
||||
// import { Prisma, Timesheets } from "@prisma/client";
|
||||
// import { CreateTimesheetDto } from "./create-timesheet.dto";
|
||||
// import { EmailToIdResolver } from "src/modules/shared/utils/resolve-email-id.utils";
|
||||
// import { BankCodesResolver } from "src/modules/shared/utils/resolve-bank-type-id.utils";
|
||||
// import { PrismaService } from "src/prisma/prisma.service";
|
||||
// import { TimesheetMap } from "./utils-helpers-others/timesheet.types";
|
||||
// import { Shift, Expense } from "../dtos/timesheet.dto";
|
||||
|
||||
// @Injectable()
|
||||
// export class TimesheetsCommandService extends BaseApprovalService<Timesheets>{
|
||||
// constructor(
|
||||
// prisma: PrismaService,
|
||||
// private readonly query: TimesheetsQueryService,
|
||||
// private readonly emailResolver: EmailToIdResolver,
|
||||
// private readonly timesheetResolver: EmployeeTimesheetResolver,
|
||||
// private readonly bankTypeResolver: BankCodesResolver,
|
||||
// ) {super(prisma);}
|
||||
// //_____________________________________________________________________________________________
|
||||
// // APPROVAL AND DELEGATE METHODS
|
||||
// //_____________________________________________________________________________________________
|
||||
// protected get delegate() {
|
||||
// return this.prisma.timesheets;
|
||||
// }
|
||||
|
||||
// protected delegateFor(transaction: Prisma.TransactionClient) {
|
||||
// return transaction.timesheets;
|
||||
// }
|
||||
|
||||
// async updateApproval(id: number, isApproved: boolean): Promise<Timesheets> {
|
||||
// return this.prisma.$transaction((transaction) =>
|
||||
// this.updateApprovalWithTransaction(transaction, id, isApproved),
|
||||
// );
|
||||
// }
|
||||
|
||||
// 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;
|
||||
// }
|
||||
|
||||
// /**_____________________________________________________________________________________________
|
||||
// create/update/delete shifts and expenses from 1 or many timesheet(s)
|
||||
|
||||
// -this function receives an email and an array of timesheets
|
||||
|
||||
// -this function will find the timesheets with all shifts and expenses
|
||||
// -this function will calculate total hours, total expenses, filtered by types,
|
||||
// cumulate in daily and weekly.
|
||||
|
||||
// -the timesheet_id will be determined using the employee email
|
||||
// -with the timesheet_id, all shifts and expenses will be fetched
|
||||
|
||||
// -with shift_id and expense_id, this function will compare both
|
||||
// datas from the DB and from the body of the function and then:
|
||||
// -it will create a shift if no shift is found in the DB
|
||||
// -it will update a shift if a shift is found in the DB
|
||||
// -it will delete a shift if a shift is found and no data is received from the frontend
|
||||
|
||||
// This function will be used for the Timesheet Page for an employee to enter, modify or delete and entry
|
||||
// This function will also be used in the modal of the timesheet validation page to
|
||||
// allow a supervisor to enter, modify or delete and entry of a selected employee
|
||||
// _____________________________________________________________________________________________*/
|
||||
|
||||
// async findTimesheetsByEmailAndPayPeriod(email: string, year: number, period_no: number, timesheets: Timesheets[]): Promise<Timesheets[]> {
|
||||
// const employee_id = await this.emailResolver.findIdByEmail(email);
|
||||
|
||||
|
||||
// return timesheets;
|
||||
// }
|
||||
|
||||
// async upsertOrDeleteShiftsByEmailAndDate(email:string, shift_ids: Shift[]) {}
|
||||
|
||||
// async upsertOrDeleteExpensesByEmailAndDate(email:string, expenses_id: Expense[]) {}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// //_____________________________________________________________________________________________
|
||||
// //
|
||||
// //_____________________________________________________________________________________________
|
||||
|
||||
// async createWeekShiftsAndReturnOverview(
|
||||
// email:string,
|
||||
// shifts: CreateTimesheetDto[],
|
||||
// week_offset = 0,
|
||||
// ): Promise<TimesheetMap> {
|
||||
// //fetchs employee matchint user's email
|
||||
// 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();
|
||||
// base.setDate(base.getDate() + week_offset * 7);
|
||||
// const start_week = getWeekStart(base, 0);
|
||||
// const end_week = getWeekEnd(start_week);
|
||||
|
||||
// const timesheet = await this.timesheetResolver.findTimesheetIdByEmail(email, 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.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,
|
||||
// },
|
||||
// });
|
||||
// }
|
||||
// return this.query.getTimesheetByEmail(email, week_offset);
|
||||
// }
|
||||
// }
|
||||
|
|
@ -1,54 +0,0 @@
|
|||
// import { makeEmptyTimesheet, mapExpenseRow, mapShiftRow } from './utils-helpers-others/timesheet.mappers';
|
||||
// import { buildPeriod, computeWeekRange } from './utils-helpers-others/timesheet.utils';
|
||||
// import { TimesheetSelectorsService } from './utils-helpers-others/timesheet.selectors';
|
||||
// import { TimesheetPeriodDto } from './timesheet-period.dto';
|
||||
// import { toRangeFromPeriod } from './utils-helpers-others/timesheet.helpers';
|
||||
// import { EmailToIdResolver } from 'src/modules/shared/utils/resolve-email-id.utils';
|
||||
// import { FullNameResolver } from 'src/modules/shared/utils/resolve-full-name.utils';
|
||||
// import { PrismaService } from 'src/prisma/prisma.service';
|
||||
// import { TimesheetMap } from './utils-helpers-others/timesheet.types';
|
||||
// import { Injectable } from '@nestjs/common';
|
||||
|
||||
|
||||
// @Injectable()
|
||||
// export class TimesheetsQueryService {
|
||||
// constructor(
|
||||
// private readonly prisma: PrismaService,
|
||||
// private readonly emailResolver: EmailToIdResolver,
|
||||
// private readonly fullNameResolver: FullNameResolver,
|
||||
// private readonly selectors: TimesheetSelectorsService,
|
||||
// ) {}
|
||||
|
||||
// async findAll(year: number, period_no: number, email: string): Promise<TimesheetPeriodDto> {
|
||||
// const employee_id = await this.emailResolver.findIdByEmail(email); //finds the employee using email
|
||||
// const full_name = await this.fullNameResolver.resolveFullName(employee_id); //finds the employee full name using employee_id
|
||||
// const period = await this.selectors.getPayPeriod(year, period_no);//finds the pay period using year and period_no
|
||||
// const{ from, to } = toRangeFromPeriod(period); //finds start and end dates
|
||||
// //finds all shifts from selected period
|
||||
// const [raw_shifts, raw_expenses] = await Promise.all([
|
||||
// this.selectors.getShifts(employee_id, from, to),
|
||||
// this.selectors.getExpenses(employee_id, from, to),
|
||||
// ]);
|
||||
// // data mapping
|
||||
// const shifts = raw_shifts.map(mapShiftRow);
|
||||
// const expenses = raw_expenses.map(mapExpenseRow);
|
||||
|
||||
// return buildPeriod(period.period_start, period.period_end, shifts , expenses, full_name);
|
||||
// }
|
||||
|
||||
|
||||
|
||||
// async getTimesheetByEmail(email: string, week_offset = 0): Promise<TimesheetMap> {
|
||||
// const employee_id = await this.emailResolver.findIdByEmail(email); //finds the employee using email
|
||||
// const { start, start_day, end_day, label } = computeWeekRange(week_offset);
|
||||
// const timesheet = await this.selectors.getTimesheetWithShiftsAndExpenses(employee_id, start); //fetch timesheet shifts and expenses
|
||||
// if(!timesheet) return makeEmptyTimesheet({ start_day, end_day, label});
|
||||
|
||||
// //maps all shifts of selected timesheet
|
||||
// const shifts = timesheet.shift.map(mapShiftRow);
|
||||
// const expenses = timesheet.expense.map(mapExpenseRow);
|
||||
|
||||
|
||||
// return { start_day, end_day, label, shifts, expenses, is_approved: timesheet.is_approved};
|
||||
// }
|
||||
// }
|
||||
|
|
@ -1,51 +0,0 @@
|
|||
// import { BadRequestException, Body, Controller, Get, Param, ParseIntPipe, Post, Query } from '@nestjs/common';
|
||||
// import { TimesheetsQueryService } from './timesheets-query.service';
|
||||
// import { CreateWeekShiftsDto } from './create-timesheet.dto';
|
||||
// import { RolesAllowed } from "src/common/decorators/roles.decorators";
|
||||
// import { Roles as RoleEnum } from '.prisma/client';
|
||||
// import { ApiBearerAuth, ApiTags } from '@nestjs/swagger';
|
||||
// import { TimesheetsCommandService } from './timesheets-command.service';
|
||||
// import { TimesheetPeriodDto } from './timesheet-period.dto';
|
||||
// import { TimesheetMap } from './timesheet.types';
|
||||
|
||||
|
||||
// @ApiTags('Timesheets')
|
||||
// @ApiBearerAuth('access-token')
|
||||
// // @UseGuards()
|
||||
// @Controller('timesheets')
|
||||
// export class TimesheetsController {
|
||||
// constructor(
|
||||
// private readonly timesheetsQuery: TimesheetsQueryService,
|
||||
// private readonly timesheetsCommand: TimesheetsCommandService,
|
||||
// ) {}
|
||||
|
||||
// @Get()
|
||||
// //@RolesAllowed(RoleEnum.ACCOUNTING, RoleEnum.ADMIN, RoleEnum.EMPLOYEE, RoleEnum.HR, RoleEnum.SUPERVISOR)
|
||||
// async getPeriodByQuery(
|
||||
// @Query('year', ParseIntPipe ) year: number,
|
||||
// @Query('period_no', ParseIntPipe ) period_no: number,
|
||||
// @Query('email') email?: string
|
||||
// ): Promise<TimesheetPeriodDto> {
|
||||
// if(!email || !(email = email.trim())) throw new BadRequestException('Query param "email" is mandatory for this route.');
|
||||
// return this.timesheetsQuery.findAll(year, period_no, email);
|
||||
// }
|
||||
|
||||
// @Get('/:email')
|
||||
// async getByEmail(
|
||||
// @Param('email') email: string,
|
||||
// @Query('offset') offset?: string,
|
||||
// ): Promise<TimesheetMap> {
|
||||
// const week_offset = Number.isFinite(Number(offset)) ? Number(offset) : 0;
|
||||
// return this.timesheetsQuery.getTimesheetByEmail(email, week_offset);
|
||||
// }
|
||||
|
||||
// @Post('shifts/:email')
|
||||
// async createTimesheetShifts(
|
||||
// @Param('email') email: string,
|
||||
// @Body() dto: CreateWeekShiftsDto,
|
||||
// @Query('offset') offset?: string,
|
||||
// ): Promise<TimesheetMap> {
|
||||
// const week_offset = Number.isFinite(Number(offset)) ? Number(offset) : 0;
|
||||
// return this.timesheetsCommand.createWeekShiftsAndReturnOverview(email, dto.shifts, week_offset);
|
||||
// }
|
||||
// }
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
// import { PartialType } from "@nestjs/swagger";
|
||||
// import { CreateExpenseDto } from "./create-expense.dto";
|
||||
|
||||
// export class UpdateExpenseDto extends PartialType(CreateExpenseDto) {}
|
||||
|
|
@ -1,59 +0,0 @@
|
|||
// import { Transform, Type } from "class-transformer";
|
||||
// import {
|
||||
// IsNumber,
|
||||
// IsOptional,
|
||||
// IsString,
|
||||
// Matches,
|
||||
// MaxLength,
|
||||
// Min,
|
||||
// ValidateIf,
|
||||
// ValidateNested
|
||||
// } from "class-validator";
|
||||
|
||||
// export class ExpensePayloadDto {
|
||||
// @IsString()
|
||||
// type!: string;
|
||||
|
||||
// @ValidateIf(o => (o.type ?? '').toUpperCase() !== 'MILEAGE')
|
||||
// @IsNumber()
|
||||
// @Min(0)
|
||||
// amount?: number;
|
||||
|
||||
// @ValidateIf(o => (o.type ?? '').toUpperCase() === 'MILEAGE')
|
||||
// @IsNumber()
|
||||
// @Min(0)
|
||||
// mileage?: number;
|
||||
|
||||
// @IsString()
|
||||
// @MaxLength(280)
|
||||
// @Transform(({ value }) => (typeof value === 'string' ? value.trim() : value))
|
||||
// comment!: string;
|
||||
|
||||
// @IsOptional()
|
||||
// @Transform(({ value }) => {
|
||||
// if (value === null || value === undefined || value === '') return undefined;
|
||||
// if (typeof value === 'number') return value.toString();
|
||||
// if (typeof value === 'string') {
|
||||
// const trimmed = value.trim();
|
||||
// return trimmed.length ? trimmed : undefined;
|
||||
// }
|
||||
// return undefined;
|
||||
// })
|
||||
// @IsString()
|
||||
// @Matches(/^\d+$/)
|
||||
// @MaxLength(255)
|
||||
// attachment?: string;
|
||||
// }
|
||||
|
||||
|
||||
// export class UpsertExpenseDto {
|
||||
// @IsOptional()
|
||||
// @ValidateNested()
|
||||
// @Type(()=> ExpensePayloadDto)
|
||||
// old_expense?: ExpensePayloadDto;
|
||||
|
||||
// @IsOptional()
|
||||
// @ValidateNested()
|
||||
// @Type(()=> ExpensePayloadDto)
|
||||
// new_expense?: ExpensePayloadDto;
|
||||
// }
|
||||
|
|
@ -1,43 +0,0 @@
|
|||
// import { Type } from "class-transformer";
|
||||
// import { IsBoolean, IsOptional, IsString, Matches, MaxLength, ValidateNested } from "class-validator";
|
||||
|
||||
// export const COMMENT_MAX_LENGTH = 280;
|
||||
|
||||
// export class ShiftPayloadDto {
|
||||
|
||||
// @Matches(/^\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])$/)
|
||||
// date!: string;
|
||||
|
||||
// @Matches(/^([01]\d|2[0-3]):([0-5]\d)$/)
|
||||
// start_time!: string;
|
||||
|
||||
// @Matches(/^([01]\d|2[0-3]):([0-5]\d)$/)
|
||||
// end_time!: string;
|
||||
|
||||
// @IsString()
|
||||
// type!: string;
|
||||
|
||||
// @IsBoolean()
|
||||
// is_remote!: boolean;
|
||||
|
||||
// @IsBoolean()
|
||||
// is_approved!: boolean;
|
||||
|
||||
// @IsOptional()
|
||||
// @IsString()
|
||||
// @MaxLength(COMMENT_MAX_LENGTH)
|
||||
// comment?: string;
|
||||
// };
|
||||
|
||||
// export class UpsertShiftDto {
|
||||
|
||||
// @IsOptional()
|
||||
// @ValidateNested()
|
||||
// @Type(()=> ShiftPayloadDto)
|
||||
// old_shift?: ShiftPayloadDto;
|
||||
|
||||
// @IsOptional()
|
||||
// @ValidateNested()
|
||||
// @Type(()=> ShiftPayloadDto)
|
||||
// new_shift?: ShiftPayloadDto;
|
||||
// };
|
||||
|
|
@ -1 +0,0 @@
|
|||
// export const COMMENT_MAX_LENGTH = 280;
|
||||
Loading…
Reference in New Issue
Block a user