refactor(timesheets): dried findAll methods to centralize shareable methods
This commit is contained in:
parent
9efdafb20f
commit
2de8db6212
|
|
@ -1,8 +1,8 @@
|
|||
import { Injectable, NotFoundException } from "@nestjs/common";
|
||||
import { PrismaService } from "src/prisma/prisma.service";
|
||||
import { DayExpensesDto as ExpenseListResponseDto, ExpenseDto } from "src/modules/timesheets/dtos/timesheet-period.dto";
|
||||
import { round2, toUTCDateOnly } from "src/modules/timesheets/utils/timesheet.helpers";
|
||||
import { EXPENSE_TYPES } from "src/modules/timesheets/types/timesheet.types";
|
||||
import { round2, toUTCDateOnly } from "src/modules/timesheets/timesheet.helpers";
|
||||
import { EXPENSE_TYPES } from "src/modules/timesheets/timesheet.types";
|
||||
import { EmailToIdResolver } from "src/modules/shared/utils/resolve-email-id.utils";
|
||||
|
||||
@Injectable()
|
||||
|
|
|
|||
0
src/modules/shared/selects/employees.select.ts
Normal file
0
src/modules/shared/selects/employees.select.ts
Normal file
11
src/modules/shared/selects/expenses.select.ts
Normal file
11
src/modules/shared/selects/expenses.select.ts
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
export const EXPENSE_SELECT = {
|
||||
date: true,
|
||||
amount: true,
|
||||
mileage: true,
|
||||
comment: true,
|
||||
is_approved: true,
|
||||
supervisor_comment: true,
|
||||
bank_code: { select: { type: true } },
|
||||
} as const;
|
||||
|
||||
export const EXPENSE_ASC_ORDER = { date: 'asc' as const };
|
||||
4
src/modules/shared/selects/pay-periods.select.ts
Normal file
4
src/modules/shared/selects/pay-periods.select.ts
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
export const PAY_PERIOD_SELECT = {
|
||||
period_start: true,
|
||||
period_end: true,
|
||||
} as const;
|
||||
12
src/modules/shared/selects/shifts.select.ts
Normal file
12
src/modules/shared/selects/shifts.select.ts
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
export const SHIFT_SELECT = {
|
||||
date: true,
|
||||
start_time: true,
|
||||
end_time: true,
|
||||
comment: true,
|
||||
is_approved: true,
|
||||
is_remote: true,
|
||||
bank_code: {select: { type: true } },
|
||||
} as const;
|
||||
|
||||
export const SHIFT_ASC_ORDER = [{date: 'asc' as const}, {start_time: 'asc' as const}];
|
||||
|
||||
0
src/modules/shared/selects/timesheets.select.ts
Normal file
0
src/modules/shared/selects/timesheets.select.ts
Normal file
|
|
@ -5,7 +5,7 @@ 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 "../utils/timesheet.helpers";
|
||||
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";
|
||||
|
|
|
|||
|
|
@ -1,12 +1,14 @@
|
|||
import { endOfDayUTC, toHHmm, toUTCDateOnly } from '../utils/timesheet.helpers';
|
||||
import { endOfDayUTC, toHHmm, toNum, toRangeFromPeriod, toUTCDateOnly } from '../timesheet.helpers';
|
||||
import { Injectable, NotFoundException } from '@nestjs/common';
|
||||
import { formatDateISO, getWeekEnd, getWeekStart } from 'src/common/utils/date-utils';
|
||||
import { PrismaService } from 'src/prisma/prisma.service';
|
||||
import { TimesheetDto, TimesheetPeriodDto } from '../dtos/timesheet-period.dto';
|
||||
import { ShiftRow, ExpenseRow } from '../types/timesheet.types';
|
||||
import { buildPeriod } from '../utils/timesheet.utils';
|
||||
import { ShiftRow, ExpenseRow } from '../timesheet.types';
|
||||
import { buildPeriod } from '../timesheet.utils';
|
||||
import { EmailToIdResolver } from 'src/modules/shared/utils/resolve-email-id.utils';
|
||||
import { FullNameResolver } from 'src/modules/shared/utils/resolve-full-name.utils';
|
||||
import { TimesheetSelectors } from '../timesheet.selectors';
|
||||
import { mapExpenseRow, mapShiftRow } from '../timesheet.mappers';
|
||||
|
||||
|
||||
@Injectable()
|
||||
|
|
@ -14,93 +16,32 @@ export class TimesheetsQueryService {
|
|||
constructor(
|
||||
private readonly prisma: PrismaService,
|
||||
private readonly emailResolver: EmailToIdResolver,
|
||||
private readonly fullNameResolver: FullNameResolver
|
||||
private readonly fullNameResolver: FullNameResolver,
|
||||
private readonly selectors: TimesheetSelectors,
|
||||
) {}
|
||||
|
||||
async findAll(year: number, period_no: number, email: string): Promise<TimesheetPeriodDto> {
|
||||
//finds the employee using email
|
||||
const employee_id = await this.emailResolver.findIdByEmail(email);
|
||||
if(!employee_id) throw new NotFoundException(`employee with email : ${email} not found`);
|
||||
|
||||
|
||||
//finds the employee full name using employee_id
|
||||
const full_name = await this.fullNameResolver.resolveFullName(employee_id);
|
||||
if(!full_name) throw new NotFoundException(`employee with id: ${employee_id} not found`)
|
||||
|
||||
//finds the period
|
||||
const period = await this.prisma.payPeriods.findFirst({
|
||||
where: {
|
||||
pay_year: year,
|
||||
pay_period_no: period_no
|
||||
},
|
||||
select: {
|
||||
period_start: true,
|
||||
period_end: true
|
||||
},
|
||||
});
|
||||
if(!period) throw new NotFoundException(`Period ${year}-${period_no} not found`);
|
||||
|
||||
const from = toUTCDateOnly(period.period_start);
|
||||
const to = endOfDayUTC(period.period_end);
|
||||
|
||||
const raw_shifts = await this.prisma.shifts.findMany({
|
||||
where: {
|
||||
timesheet: { is: { employee_id: employee_id } },
|
||||
date: { gte: from, lte: to },
|
||||
},
|
||||
select: {
|
||||
date: true,
|
||||
start_time: true,
|
||||
end_time: true,
|
||||
comment: true,
|
||||
is_approved: true,
|
||||
is_remote: true,
|
||||
bank_code: { select: { type: true } },
|
||||
},
|
||||
orderBy:[ { date:'asc'}, { start_time: 'asc'} ],
|
||||
});
|
||||
|
||||
const raw_expenses = await this.prisma.expenses.findMany({
|
||||
where: {
|
||||
timesheet: { is: { employee_id: employee_id } },
|
||||
date: { gte: from, lte: to },
|
||||
},
|
||||
select: {
|
||||
date: true,
|
||||
amount: true,
|
||||
mileage: true,
|
||||
comment: true,
|
||||
is_approved: true,
|
||||
supervisor_comment: true,
|
||||
bank_code: { select: { type: true } },
|
||||
},
|
||||
orderBy: { date: 'asc' },
|
||||
});
|
||||
|
||||
const toNum = (value: any) =>
|
||||
value && typeof value.toNumber === 'function' ? value.toNumber() :
|
||||
typeof value === 'number' ? value :
|
||||
value ? Number(value) : 0;
|
||||
//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
|
||||
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: ShiftRow[] = raw_shifts.map(shift => ({
|
||||
date: shift.date,
|
||||
start_time: shift.start_time,
|
||||
end_time: shift.end_time,
|
||||
comment: shift.comment ?? '',
|
||||
is_approved: shift.is_approved ?? true,
|
||||
is_remote: shift.is_remote ?? true,
|
||||
type: String(shift.bank_code?.type ?? '').toUpperCase(),
|
||||
}));
|
||||
|
||||
const expenses: ExpenseRow[] = raw_expenses.map(expense => ({
|
||||
type: String(expense.bank_code?.type ?? '').toUpperCase(),
|
||||
date: expense.date,
|
||||
amount: toNum(expense.amount),
|
||||
mileage: toNum(expense.mileage),
|
||||
comment: expense.comment ?? '',
|
||||
is_approved: expense.is_approved ?? true,
|
||||
supervisor_comment: expense.supervisor_comment ?? '',
|
||||
}));
|
||||
const shifts = raw_shifts.map(mapShiftRow);
|
||||
const expenses = raw_expenses.map(mapExpenseRow);
|
||||
|
||||
return buildPeriod(period.period_start, period.period_end, shifts , expenses, full_name);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
import { MS_PER_DAY, DayKey, DAY_KEYS } from "../types/timesheet.types";
|
||||
|
||||
import { MS_PER_DAY, DayKey, DAY_KEYS } from "./timesheet.types";
|
||||
|
||||
export function toUTCDateOnly(date: Date | string): Date {
|
||||
const d = new Date(date);
|
||||
|
|
@ -43,7 +42,6 @@ export function dayKeyFromDate(date: Date, useUTC = true): DayKey {
|
|||
|
||||
export const toHHmm = (date: Date) => date.toISOString().slice(11, 16);
|
||||
|
||||
//create shifts within timesheet's week - employee overview functions
|
||||
export function parseISODate(iso: string): Date {
|
||||
const [ y, m, d ] = iso.split('-').map(Number);
|
||||
return new Date(y, (m ?? 1) - 1, d ?? 1);
|
||||
|
|
@ -54,3 +52,15 @@ export function parseHHmm(t: string): Date {
|
|||
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,6 +1,46 @@
|
|||
import { DayExpensesDto, WeekDto, DetailedShifts, TimesheetPeriodDto } from "../dtos/timesheet-period.dto";
|
||||
import { ExpensesAmount } from "../types/timesheet.types";
|
||||
import { addDays, shortDate } from "../utils/timesheet.helpers";
|
||||
import { DayExpensesDto, WeekDto, DetailedShifts, TimesheetPeriodDto } from "./dtos/timesheet-period.dto";
|
||||
import { ExpenseRow, ExpensesAmount, ShiftRow } 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 {
|
||||
35
src/modules/timesheets/timesheet.selectors.ts
Normal file
35
src/modules/timesheets/timesheet.selectors.ts
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
import { Injectable, NotFoundException } from "@nestjs/common";
|
||||
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()
|
||||
export class TimesheetSelectors {
|
||||
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,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -1,13 +1,13 @@
|
|||
import {
|
||||
DayKey, DAY_KEYS, EXPENSE_TYPES, ExpenseRow, MS_PER_HOUR,
|
||||
SHIFT_TYPES, ShiftRow, make_hours, ShiftsHours, ExpensesAmount
|
||||
} from "../types/timesheet.types";
|
||||
} 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 { makeAmounts, makeEmptyWeek } from "../mappers/timesheet.mappers";
|
||||
import { WeekDto, ShiftDto, TimesheetPeriodDto, DayExpensesDto, ExpenseDto } from "./dtos/timesheet-period.dto";
|
||||
import { makeAmounts, makeEmptyWeek } from "./timesheet.mappers";
|
||||
import { toDateString } from "src/modules/pay-periods/utils/pay-year.util";
|
||||
|
||||
export function buildWeek(
|
||||
Loading…
Reference in New Issue
Block a user