refactor(timesheets): refacatored query-service
This commit is contained in:
parent
2de8db6212
commit
408e52b4f5
|
|
@ -5,7 +5,8 @@ import { EmployeesArchivalService } from './services/employees-archival.service'
|
||||||
import { SharedModule } from '../shared/shared.module';
|
import { SharedModule } from '../shared/shared.module';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
controllers: [EmployeesController, SharedModule],
|
imports: [SharedModule],
|
||||||
|
controllers: [EmployeesController],
|
||||||
providers: [EmployeesService, EmployeesArchivalService],
|
providers: [EmployeesService, EmployeesArchivalService],
|
||||||
exports: [EmployeesService, EmployeesArchivalService],
|
exports: [EmployeesService, EmployeesArchivalService],
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
import { Injectable, NotFoundException } from "@nestjs/common";
|
import { Injectable, NotFoundException } from "@nestjs/common";
|
||||||
import { PrismaService } from "src/prisma/prisma.service";
|
import { PrismaService } from "src/prisma/prisma.service";
|
||||||
import { DayExpensesDto as ExpenseListResponseDto, ExpenseDto } from "src/modules/timesheets/dtos/timesheet-period.dto";
|
import { DayExpensesDto as ExpenseListResponseDto, ExpenseDto } from "src/modules/timesheets/dtos/timesheet-period.dto";
|
||||||
import { round2, toUTCDateOnly } from "src/modules/timesheets/timesheet.helpers";
|
import { round2, toUTCDateOnly } from "src/modules/timesheets/utils-helpers-others/timesheet.helpers";
|
||||||
import { EXPENSE_TYPES } from "src/modules/timesheets/timesheet.types";
|
import { EXPENSE_TYPES } from "src/modules/timesheets/utils-helpers-others/timesheet.types";
|
||||||
import { EmailToIdResolver } from "src/modules/shared/utils/resolve-email-id.utils";
|
import { EmailToIdResolver } from "src/modules/shared/utils/resolve-email-id.utils";
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
|
|
|
||||||
2
src/modules/shared/constants/date-time.constant.ts
Normal file
2
src/modules/shared/constants/date-time.constant.ts
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
export const MS_PER_DAY = 86_400_000;
|
||||||
|
export const MS_PER_HOUR = 3_600_000;
|
||||||
|
|
@ -5,7 +5,8 @@ import { RolesAllowed } from "src/common/decorators/roles.decorators";
|
||||||
import { Roles as RoleEnum } from '.prisma/client';
|
import { Roles as RoleEnum } from '.prisma/client';
|
||||||
import { ApiBearerAuth, ApiTags } from '@nestjs/swagger';
|
import { ApiBearerAuth, ApiTags } from '@nestjs/swagger';
|
||||||
import { TimesheetsCommandService } from '../services/timesheets-command.service';
|
import { TimesheetsCommandService } from '../services/timesheets-command.service';
|
||||||
import { TimesheetDto, TimesheetPeriodDto } from '../dtos/timesheet-period.dto';
|
import { TimesheetMap } from '../utils-helpers-others/timesheet.types';
|
||||||
|
import { TimesheetPeriodDto } from '../dtos/timesheet-period.dto';
|
||||||
|
|
||||||
|
|
||||||
@ApiTags('Timesheets')
|
@ApiTags('Timesheets')
|
||||||
|
|
@ -33,7 +34,7 @@ export class TimesheetsController {
|
||||||
async getByEmail(
|
async getByEmail(
|
||||||
@Param('email') email: string,
|
@Param('email') email: string,
|
||||||
@Query('offset') offset?: string,
|
@Query('offset') offset?: string,
|
||||||
): Promise<TimesheetDto> {
|
): Promise<TimesheetMap> {
|
||||||
const week_offset = Number.isFinite(Number(offset)) ? Number(offset) : 0;
|
const week_offset = Number.isFinite(Number(offset)) ? Number(offset) : 0;
|
||||||
return this.timesheetsQuery.getTimesheetByEmail(email, week_offset);
|
return this.timesheetsQuery.getTimesheetByEmail(email, week_offset);
|
||||||
}
|
}
|
||||||
|
|
@ -43,37 +44,8 @@ export class TimesheetsController {
|
||||||
@Param('email') email: string,
|
@Param('email') email: string,
|
||||||
@Body() dto: CreateWeekShiftsDto,
|
@Body() dto: CreateWeekShiftsDto,
|
||||||
@Query('offset') offset?: string,
|
@Query('offset') offset?: string,
|
||||||
): Promise<TimesheetDto> {
|
): Promise<TimesheetMap> {
|
||||||
const week_offset = Number.isFinite(Number(offset)) ? Number(offset) : 0;
|
const week_offset = Number.isFinite(Number(offset)) ? Number(offset) : 0;
|
||||||
return this.timesheetsCommand.createWeekShiftsAndReturnOverview(email, dto.shifts, week_offset);
|
return this.timesheetsCommand.createWeekShiftsAndReturnOverview(email, dto.shifts, week_offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
//_____________________________________________________________________________________________
|
|
||||||
// Deprecated or unused methods
|
|
||||||
//_____________________________________________________________________________________________
|
|
||||||
|
|
||||||
// @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.timesheetsCommand.updateApproval(id, isApproved);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// @Get(':id')
|
|
||||||
// //@RolesAllowed(RoleEnum.ACCOUNTING, RoleEnum.ADMIN, RoleEnum.EMPLOYEE, RoleEnum.HR, RoleEnum.SUPERVISOR)
|
|
||||||
// @ApiOperation({ summary: 'Find timesheet' })
|
|
||||||
// @ApiResponse({ status: 201, description: 'Timesheet found', type: CreateTimesheetDto })
|
|
||||||
// @ApiResponse({ status: 400, description: 'Timesheet not found' })
|
|
||||||
// findOne(@Param('id', ParseIntPipe) id: number): Promise<Timesheets> {
|
|
||||||
// return this.timesheetsQuery.findOne(id);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// @Delete(':id')
|
|
||||||
// // @RolesAllowed(RoleEnum.ACCOUNTING, RoleEnum.ADMIN, RoleEnum.HR, RoleEnum.SUPERVISOR)
|
|
||||||
// @ApiOperation({ summary: 'Delete timesheet' })
|
|
||||||
// @ApiResponse({ status: 201, description: 'Timesheet deleted', type: CreateTimesheetDto })
|
|
||||||
// @ApiResponse({ status: 400, description: 'Timesheet not found' })
|
|
||||||
// remove(@Param('id', ParseIntPipe) id: number): Promise<Timesheets> {
|
|
||||||
// return this.timesheetsQuery.remove(id);
|
|
||||||
// }
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
import { TimesheetsArchive } from "@prisma/client";
|
import { TimesheetsArchive } from "@prisma/client";
|
||||||
import { PrismaService } from "src/prisma/prisma.service";
|
import { PrismaService } from "src/prisma/prisma.service";
|
||||||
|
|
||||||
|
|
||||||
export class TimesheetArchiveService {
|
export class TimesheetArchiveService {
|
||||||
constructor(private readonly prisma: PrismaService){}
|
constructor(private readonly prisma: PrismaService){}
|
||||||
|
|
||||||
|
|
@ -21,9 +20,7 @@ export class TimesheetArchiveService {
|
||||||
is_approved: true,
|
is_approved: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
if( oldSheets.length === 0) {
|
if( oldSheets.length === 0) return;
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
//preping data for archivation
|
//preping data for archivation
|
||||||
const archiveDate = oldSheets.map(sheet => ({
|
const archiveDate = oldSheets.map(sheet => ({
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,15 @@
|
||||||
import { BadRequestException, Injectable, NotFoundException } from "@nestjs/common";
|
import { BadRequestException, Injectable, NotFoundException } from "@nestjs/common";
|
||||||
import { Prisma, Timesheets } from "@prisma/client";
|
|
||||||
import { BaseApprovalService } from "src/common/shared/base-approval.service";
|
|
||||||
import { PrismaService } from "src/prisma/prisma.service";
|
|
||||||
import { TimesheetsQueryService } from "./timesheets-query.service";
|
|
||||||
import { CreateTimesheetDto } from "../dtos/create-timesheet.dto";
|
|
||||||
import { getWeekEnd, getWeekStart } from "src/common/utils/date-utils";
|
|
||||||
import { parseISODate, parseHHmm } from "../timesheet.helpers";
|
|
||||||
import { TimesheetDto } from "../dtos/timesheet-period.dto";
|
|
||||||
import { EmailToIdResolver } from "src/modules/shared/utils/resolve-email-id.utils";
|
|
||||||
import { EmployeeTimesheetResolver } from "src/modules/shared/utils/resolve-employee-timesheet.utils";
|
import { EmployeeTimesheetResolver } from "src/modules/shared/utils/resolve-employee-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 "../dtos/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 { 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";
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class TimesheetsCommandService extends BaseApprovalService<Timesheets>{
|
export class TimesheetsCommandService extends BaseApprovalService<Timesheets>{
|
||||||
|
|
@ -58,7 +58,7 @@ export class TimesheetsCommandService extends BaseApprovalService<Timesheets>{
|
||||||
email:string,
|
email:string,
|
||||||
shifts: CreateTimesheetDto[],
|
shifts: CreateTimesheetDto[],
|
||||||
week_offset = 0,
|
week_offset = 0,
|
||||||
): Promise<TimesheetDto> {
|
): Promise<TimesheetMap> {
|
||||||
//fetchs employee matchint user's email
|
//fetchs employee matchint user's email
|
||||||
const employee_id = await this.emailResolver.findIdByEmail(email);
|
const employee_id = await this.emailResolver.findIdByEmail(email);
|
||||||
if(!employee_id) throw new NotFoundException(`employee for ${ email } not found`);
|
if(!employee_id) throw new NotFoundException(`employee for ${ email } not found`);
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,13 @@
|
||||||
import { endOfDayUTC, toHHmm, toNum, toRangeFromPeriod, toUTCDateOnly } from '../timesheet.helpers';
|
import { makeEmptyTimesheet, mapExpenseRow, mapShiftRow } from '../utils-helpers-others/timesheet.mappers';
|
||||||
import { Injectable, NotFoundException } from '@nestjs/common';
|
import { buildPeriod, computeWeekRange } from '../utils-helpers-others/timesheet.utils';
|
||||||
import { formatDateISO, getWeekEnd, getWeekStart } from 'src/common/utils/date-utils';
|
import { TimesheetSelectorsService } from '../utils-helpers-others/timesheet.selectors';
|
||||||
import { PrismaService } from 'src/prisma/prisma.service';
|
import { TimesheetPeriodDto } from '../dtos/timesheet-period.dto';
|
||||||
import { TimesheetDto, TimesheetPeriodDto } from '../dtos/timesheet-period.dto';
|
import { toRangeFromPeriod } from '../utils-helpers-others/timesheet.helpers';
|
||||||
import { ShiftRow, ExpenseRow } from '../timesheet.types';
|
|
||||||
import { buildPeriod } from '../timesheet.utils';
|
|
||||||
import { EmailToIdResolver } from 'src/modules/shared/utils/resolve-email-id.utils';
|
import { EmailToIdResolver } from 'src/modules/shared/utils/resolve-email-id.utils';
|
||||||
import { FullNameResolver } from 'src/modules/shared/utils/resolve-full-name.utils';
|
import { FullNameResolver } from 'src/modules/shared/utils/resolve-full-name.utils';
|
||||||
import { TimesheetSelectors } from '../timesheet.selectors';
|
import { PrismaService } from 'src/prisma/prisma.service';
|
||||||
import { mapExpenseRow, mapShiftRow } from '../timesheet.mappers';
|
import { TimesheetMap } from '../utils-helpers-others/timesheet.types';
|
||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
|
|
@ -17,28 +16,19 @@ export class TimesheetsQueryService {
|
||||||
private readonly prisma: PrismaService,
|
private readonly prisma: PrismaService,
|
||||||
private readonly emailResolver: EmailToIdResolver,
|
private readonly emailResolver: EmailToIdResolver,
|
||||||
private readonly fullNameResolver: FullNameResolver,
|
private readonly fullNameResolver: FullNameResolver,
|
||||||
private readonly selectors: TimesheetSelectors,
|
private readonly selectors: TimesheetSelectorsService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async findAll(year: number, period_no: number, email: string): Promise<TimesheetPeriodDto> {
|
async findAll(year: number, period_no: number, email: string): Promise<TimesheetPeriodDto> {
|
||||||
//finds the employee using email
|
const employee_id = await this.emailResolver.findIdByEmail(email); //finds the employee using email
|
||||||
const employee_id = await this.emailResolver.findIdByEmail(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
|
||||||
//finds the employee full name using employee_id
|
const{ from, to } = toRangeFromPeriod(period); //finds start and end dates
|
||||||
const full_name = await this.fullNameResolver.resolveFullName(employee_id);
|
|
||||||
|
|
||||||
//finds the pay period using year and period_no
|
|
||||||
const period = await this.selectors.getPayPeriod(year, period_no);
|
|
||||||
|
|
||||||
//finds start and end dates
|
|
||||||
const{ from, to } = toRangeFromPeriod(period);
|
|
||||||
|
|
||||||
//finds all shifts from selected period
|
//finds all shifts from selected period
|
||||||
const [raw_shifts, raw_expenses] = await Promise.all([
|
const [raw_shifts, raw_expenses] = await Promise.all([
|
||||||
this.selectors.getShifts(employee_id, from, to),
|
this.selectors.getShifts(employee_id, from, to),
|
||||||
this.selectors.getExpenses(employee_id, from, to),
|
this.selectors.getExpenses(employee_id, from, to),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// data mapping
|
// data mapping
|
||||||
const shifts = raw_shifts.map(mapShiftRow);
|
const shifts = raw_shifts.map(mapShiftRow);
|
||||||
const expenses = raw_expenses.map(mapExpenseRow);
|
const expenses = raw_expenses.map(mapExpenseRow);
|
||||||
|
|
@ -46,121 +36,19 @@ export class TimesheetsQueryService {
|
||||||
return buildPeriod(period.period_start, period.period_end, shifts , expenses, full_name);
|
return buildPeriod(period.period_start, period.period_end, shifts , expenses, full_name);
|
||||||
}
|
}
|
||||||
|
|
||||||
async getTimesheetByEmail(email: string, week_offset = 0): Promise<TimesheetDto> {
|
|
||||||
const employee_id = await this.emailResolver.findIdByEmail(email);
|
|
||||||
if(!employee_id) throw new NotFoundException(`Employee with email: ${email} not found`);
|
|
||||||
|
|
||||||
//sets current week Sunday -> Saturday
|
|
||||||
const base = new Date();
|
|
||||||
const offset = new Date(base);
|
|
||||||
offset.setDate(offset.getDate() + (week_offset * 7));
|
|
||||||
|
|
||||||
const start_date_week = getWeekStart(offset, 0);
|
async getTimesheetByEmail(email: string, week_offset = 0): Promise<TimesheetMap> {
|
||||||
const end_date_week = getWeekEnd(start_date_week);
|
const employee_id = await this.emailResolver.findIdByEmail(email); //finds the employee using email
|
||||||
const start_day = formatDateISO(start_date_week);
|
const { start, start_day, end_day, label } = computeWeekRange(week_offset);
|
||||||
const end_day = formatDateISO(end_date_week);
|
const timesheet = await this.selectors.getTimesheetWithShiftsAndExpenses(employee_id, start); //fetch timesheet shifts and expenses
|
||||||
|
if(!timesheet) return makeEmptyTimesheet({ start_day, end_day, label});
|
||||||
//build the label MM/DD/YYYY.MM/DD.YYYY
|
|
||||||
const mm_dd = (date: Date) => `${String(date.getFullYear())}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2,'0')}`;
|
|
||||||
const label = `${mm_dd(start_date_week)}.${mm_dd(end_date_week)}`;
|
|
||||||
|
|
||||||
//fetch timesheet shifts and expenses
|
|
||||||
const timesheet = await this.prisma.timesheets.findUnique({
|
|
||||||
where: {
|
|
||||||
employee_id_start_date: {
|
|
||||||
employee_id: employee_id,
|
|
||||||
start_date: start_date_week,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
include: {
|
|
||||||
shift: {
|
|
||||||
include: { bank_code: true },
|
|
||||||
orderBy: [{ date: 'asc'}, { start_time: 'asc'}],
|
|
||||||
},
|
|
||||||
expense: {
|
|
||||||
include: { bank_code: true },
|
|
||||||
orderBy: [{date: 'asc'}],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
//returns an empty timesheet if not found
|
|
||||||
if(!timesheet) {
|
|
||||||
return {
|
|
||||||
is_approved: false,
|
|
||||||
start_day,
|
|
||||||
end_day,
|
|
||||||
label,
|
|
||||||
shifts:[],
|
|
||||||
expenses: [],
|
|
||||||
} as TimesheetDto;
|
|
||||||
}
|
|
||||||
|
|
||||||
//maps all shifts of selected timesheet
|
//maps all shifts of selected timesheet
|
||||||
const shifts = timesheet.shift.map((shift_row) => ({
|
const shifts = timesheet.shift.map(mapShiftRow);
|
||||||
type: shift_row.bank_code?.type ?? '',
|
const expenses = timesheet.expense.map(mapExpenseRow);
|
||||||
date: formatDateISO(shift_row.date),
|
|
||||||
start_time: toHHmm(shift_row.start_time),
|
|
||||||
end_time: toHHmm(shift_row.end_time),
|
|
||||||
comment: shift_row.comment ?? '',
|
|
||||||
is_approved: shift_row.is_approved ?? false,
|
|
||||||
is_remote: shift_row.is_remote ?? false,
|
|
||||||
}));
|
|
||||||
|
|
||||||
//maps all expenses of selected timsheet
|
|
||||||
const expenses = timesheet.expense.map((exp) => ({
|
|
||||||
type: exp.bank_code?.type ?? '',
|
|
||||||
date: formatDateISO(exp.date),
|
|
||||||
amount: Number(exp.amount) || 0,
|
|
||||||
mileage: exp.mileage != null ? Number(exp.mileage) : 0,
|
|
||||||
comment: exp.comment ?? '',
|
|
||||||
is_approved: exp.is_approved ?? false,
|
|
||||||
supervisor_comment: exp.supervisor_comment ?? '',
|
|
||||||
}));
|
|
||||||
|
|
||||||
return {
|
return { start_day, end_day, label, shifts, expenses, is_approved: timesheet.is_approved};
|
||||||
start_day,
|
|
||||||
end_day,
|
|
||||||
label,
|
|
||||||
shifts,
|
|
||||||
expenses,
|
|
||||||
is_approved: timesheet.is_approved,
|
|
||||||
} as TimesheetDto;
|
|
||||||
}
|
}
|
||||||
//_____________________________________________________________________________________________
|
|
||||||
// Deprecated or unused methods
|
|
||||||
//_____________________________________________________________________________________________
|
|
||||||
|
|
||||||
// async findOne(id: number): Promise<any> {
|
|
||||||
// const timesheet = await this.prisma.timesheets.findUnique({
|
|
||||||
// where: { id },
|
|
||||||
// include: {
|
|
||||||
// shift: { include: { bank_code: true } },
|
|
||||||
// expense: { include: { bank_code: true } },
|
|
||||||
// employee: { include: { user: true } },
|
|
||||||
// },
|
|
||||||
// });
|
|
||||||
// if(!timesheet) {
|
|
||||||
// throw new NotFoundException(`Timesheet #${id} not found`);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// const detailedShifts = timesheet.shift.map( s => {
|
|
||||||
// const hours = computeHours(s.start_time, s.end_time);
|
|
||||||
// const regularHours = Math.min(8, hours);
|
|
||||||
// const dailyOvertime = this.overtime.getDailyOvertimeHours(s.start_time, s.end_time);
|
|
||||||
// const payRegular = regularHours * s.bank_code.modifier;
|
|
||||||
// const payOvertime = this.overtime.calculateOvertimePay(dailyOvertime, s.bank_code.modifier);
|
|
||||||
// return { ...s, hours, payRegular, payOvertime };
|
|
||||||
// });
|
|
||||||
// const weeklyOvertimeHours = detailedShifts.length
|
|
||||||
// ? await this.overtime.getWeeklyOvertimeHours(
|
|
||||||
// timesheet.employee_id,
|
|
||||||
// timesheet.shift[0].date): 0;
|
|
||||||
// return { ...timesheet, shift: detailedShifts, weeklyOvertimeHours };
|
|
||||||
// }
|
|
||||||
|
|
||||||
// async remove(id: number): Promise<Timesheets> {
|
|
||||||
// await this.findOne(id);
|
|
||||||
// return this.prisma.timesheets.delete({ where: { id } });
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,11 +7,12 @@ import { ExpensesCommandService } from '../expenses/services/expenses-command.
|
||||||
import { BusinessLogicsModule } from 'src/modules/business-logics/business-logics.module';
|
import { BusinessLogicsModule } from 'src/modules/business-logics/business-logics.module';
|
||||||
import { SharedModule } from '../shared/shared.module';
|
import { SharedModule } from '../shared/shared.module';
|
||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
|
import { TimesheetSelectorsService } from './utils-helpers-others/timesheet.selectors';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
BusinessLogicsModule,
|
BusinessLogicsModule,
|
||||||
SharedModule
|
SharedModule,
|
||||||
],
|
],
|
||||||
controllers: [TimesheetsController],
|
controllers: [TimesheetsController],
|
||||||
providers: [
|
providers: [
|
||||||
|
|
@ -20,6 +21,7 @@ import { Module } from '@nestjs/common';
|
||||||
ShiftsCommandService,
|
ShiftsCommandService,
|
||||||
ExpensesCommandService,
|
ExpensesCommandService,
|
||||||
TimesheetArchiveService,
|
TimesheetArchiveService,
|
||||||
|
TimesheetSelectorsService,
|
||||||
],
|
],
|
||||||
exports: [
|
exports: [
|
||||||
TimesheetsQueryService,
|
TimesheetsQueryService,
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import { MS_PER_DAY, DayKey, DAY_KEYS } from "./timesheet.types";
|
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 {
|
export function toUTCDateOnly(date: Date | string): Date {
|
||||||
const d = new Date(date);
|
const d = new Date(date);
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { DayExpensesDto, WeekDto, DetailedShifts, TimesheetPeriodDto } from "./dtos/timesheet-period.dto";
|
import { DayExpensesDto, WeekDto, DetailedShifts, TimesheetPeriodDto } from "../dtos/timesheet-period.dto";
|
||||||
import { ExpenseRow, ExpensesAmount, ShiftRow } from "./timesheet.types";
|
import { ShiftRow, ExpenseRow, ExpensesAmount, TimesheetMap } from "./timesheet.types";
|
||||||
import { addDays, shortDate, toNum, upper } from "./timesheet.helpers";
|
import { addDays, shortDate, toNum, upper } from "./timesheet.helpers";
|
||||||
import { Prisma } from "@prisma/client";
|
import { Prisma } from "@prisma/client";
|
||||||
|
|
||||||
|
|
@ -41,7 +41,6 @@ export const mapExpenseRow = (expense: {
|
||||||
type: upper(expense.bank_code.type),
|
type: upper(expense.bank_code.type),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
// Factories
|
// Factories
|
||||||
export function makeEmptyDayExpenses(): DayExpensesDto {
|
export function makeEmptyDayExpenses(): DayExpensesDto {
|
||||||
return {
|
return {
|
||||||
|
|
@ -93,3 +92,20 @@ export const makeAmounts = (): ExpensesAmount => ({
|
||||||
expense: 0,
|
expense: 0,
|
||||||
mileage: 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,11 +1,11 @@
|
||||||
|
import { EXPENSE_ASC_ORDER, EXPENSE_SELECT } from "../../shared/selects/expenses.select";
|
||||||
import { Injectable, NotFoundException } from "@nestjs/common";
|
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";
|
import { PrismaService } from "src/prisma/prisma.service";
|
||||||
import { SHIFT_ASC_ORDER, SHIFT_SELECT } from "../shared/selects/shifts.select";
|
|
||||||
import { PAY_PERIOD_SELECT } from "../shared/selects/pay-periods.select";
|
|
||||||
import { EXPENSE_ASC_ORDER, EXPENSE_SELECT } from "../shared/selects/expenses.select";
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class TimesheetSelectors {
|
export class TimesheetSelectorsService {
|
||||||
constructor(readonly prisma: PrismaService){}
|
constructor(readonly prisma: PrismaService){}
|
||||||
|
|
||||||
async getPayPeriod(pay_year: number, pay_period_no: number) {
|
async getPayPeriod(pay_year: number, pay_period_no: number) {
|
||||||
|
|
@ -32,4 +32,15 @@ export class TimesheetSelectors {
|
||||||
orderBy: EXPENSE_ASC_ORDER,
|
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 },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -17,9 +17,14 @@ export type ExpenseRow = {
|
||||||
supervisor_comment: string;
|
supervisor_comment: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
//Date & Format
|
export type TimesheetMap = {
|
||||||
export const MS_PER_DAY = 86_400_000;
|
start_day: string;
|
||||||
export const MS_PER_HOUR = 3_600_000;
|
end_day: string;
|
||||||
|
label: string;
|
||||||
|
shifts: ShiftRow[];
|
||||||
|
expenses: ExpenseRow[]
|
||||||
|
is_approved: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
// Types
|
// Types
|
||||||
export const SHIFT_TYPES = {
|
export const SHIFT_TYPES = {
|
||||||
|
|
@ -1,14 +1,31 @@
|
||||||
import {
|
import {
|
||||||
DayKey, DAY_KEYS, EXPENSE_TYPES, ExpenseRow, MS_PER_HOUR,
|
DayKey, DAY_KEYS, EXPENSE_TYPES, ExpenseRow,
|
||||||
SHIFT_TYPES, ShiftRow, make_hours, ShiftsHours, ExpensesAmount
|
SHIFT_TYPES, ShiftRow, make_hours, ShiftsHours, ExpensesAmount
|
||||||
} from "./timesheet.types";
|
} from "./timesheet.types";
|
||||||
import {
|
import {
|
||||||
isBetweenUTC, dayKeyFromDate, toTimeString, round2,
|
isBetweenUTC, dayKeyFromDate, toTimeString, round2,
|
||||||
toUTCDateOnly, endOfDayUTC, addDays
|
toUTCDateOnly, endOfDayUTC, addDays
|
||||||
} from "./timesheet.helpers";
|
} from "./timesheet.helpers";
|
||||||
import { WeekDto, ShiftDto, TimesheetPeriodDto, DayExpensesDto, ExpenseDto } from "./dtos/timesheet-period.dto";
|
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 { makeAmounts, makeEmptyWeek } from "./timesheet.mappers";
|
||||||
import { toDateString } from "src/modules/pay-periods/utils/pay-year.util";
|
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(
|
export function buildWeek(
|
||||||
week_start: Date,
|
week_start: Date,
|
||||||
Loading…
Reference in New Issue
Block a user