fix(timesheets): added type to expense in return
This commit is contained in:
parent
73a2a755e4
commit
e067e15bb1
|
|
@ -248,10 +248,27 @@
|
|||
]
|
||||
}
|
||||
},
|
||||
"/timesheets": {
|
||||
"/timesheets/{year}/{period_number}": {
|
||||
"get": {
|
||||
"operationId": "TimesheetController_getTimesheetByPayPeriod",
|
||||
"parameters": [],
|
||||
"parameters": [
|
||||
{
|
||||
"name": "year",
|
||||
"required": true,
|
||||
"in": "path",
|
||||
"schema": {
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "period_number",
|
||||
"required": true,
|
||||
"in": "path",
|
||||
"schema": {
|
||||
"type": "number"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": ""
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import {
|
|||
} from 'class-validator';
|
||||
import { Type } from 'class-transformer';
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { UserDto } from 'src/identity-and-account/users-management/dtos/user.dto';
|
||||
|
||||
export class CreateEmployeeDto {
|
||||
@ApiProperty({
|
||||
|
|
@ -115,4 +116,6 @@ export class CreateEmployeeDto {
|
|||
@IsDateString()
|
||||
@IsOptional()
|
||||
last_work_day?: string;
|
||||
|
||||
user?: UserDto;
|
||||
}
|
||||
|
|
|
|||
7
src/modules/bank-codes/dtos/bank-code-entity.ts
Normal file
7
src/modules/bank-codes/dtos/bank-code-entity.ts
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
export class BankCodeEntity {
|
||||
id: number;
|
||||
type: string;
|
||||
categorie: string;
|
||||
modifier: number;
|
||||
bank_code: string;
|
||||
}
|
||||
|
|
@ -1,13 +1,14 @@
|
|||
import { Prisma } from "@prisma/client";
|
||||
|
||||
export class ExpenseEntity {
|
||||
id: number;
|
||||
timesheet_id: number;
|
||||
bank_code_id: number;
|
||||
attachment?:number;
|
||||
attachment?:number | null;
|
||||
date: Date;
|
||||
amount?: number;
|
||||
mileage?:number;
|
||||
amount?: number | Prisma.Decimal | null;
|
||||
mileage?:number | Prisma.Decimal | null;
|
||||
comment: string;
|
||||
supervisor_comment?:string;
|
||||
supervisor_comment?:string | null;
|
||||
is_approved: boolean;
|
||||
}
|
||||
|
|
@ -1,3 +1,5 @@
|
|||
import { BankCodeEntity } from "src/modules/bank-codes/dtos/bank-code-entity";
|
||||
|
||||
export class ShiftEntity {
|
||||
id: number;
|
||||
timesheet_id: number;
|
||||
|
|
@ -7,5 +9,6 @@ export class ShiftEntity {
|
|||
end_time: Date;
|
||||
is_remote: boolean;
|
||||
is_approved: boolean;
|
||||
comment?: string;
|
||||
comment?: string | null ;
|
||||
bank_code?: BankCodeEntity;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { Body, Controller, Get, ParseBoolPipe, ParseIntPipe, Patch, Req, UnauthorizedException } from "@nestjs/common";
|
||||
import { Body, Controller, Get, Param, ParseBoolPipe, ParseIntPipe, Patch, Req, UnauthorizedException } from "@nestjs/common";
|
||||
import { RolesAllowed } from "src/common/decorators/roles.decorators";
|
||||
import { GetTimesheetsOverviewService } from "src/time-and-attendance/time-tracker/timesheets/services/timesheet-get-overview.service";
|
||||
import { TimesheetApprovalService } from "src/time-and-attendance/time-tracker/timesheets/services/timesheet-approval.service";
|
||||
|
|
@ -13,11 +13,11 @@ export class TimesheetController {
|
|||
private readonly approvalService: TimesheetApprovalService,
|
||||
) { }
|
||||
|
||||
@Get()
|
||||
@Get(':year/:period_number')
|
||||
getTimesheetByPayPeriod(
|
||||
@Req() req,
|
||||
@Body('year', ParseIntPipe) year: number,
|
||||
@Body('period_number', ParseIntPipe) period_number: number
|
||||
@Param('year', ParseIntPipe) year: number,
|
||||
@Param('period_number', ParseIntPipe) period_number: number
|
||||
) {
|
||||
const email = req.user?.email;
|
||||
if (!email) throw new UnauthorizedException('Unauthorized User');
|
||||
|
|
|
|||
|
|
@ -1,3 +1,10 @@
|
|||
export class TimesheetEntity {
|
||||
id: number;
|
||||
employee_id: number;
|
||||
start_date: Date;
|
||||
is_approved: boolean;
|
||||
}
|
||||
|
||||
export class Timesheets {
|
||||
employee_fullname: string;
|
||||
timesheets: Timesheet[];
|
||||
|
|
@ -50,6 +57,7 @@ export class Shift {
|
|||
export class Expense {
|
||||
date: string;
|
||||
is_approved: boolean;
|
||||
type: string;
|
||||
comment: string;
|
||||
amount?: number;
|
||||
mileage?: number;
|
||||
|
|
|
|||
|
|
@ -3,8 +3,14 @@ import { NUMBER_OF_TIMESHEETS_TO_RETURN } from "src/time-and-attendance/utils/co
|
|||
import { Injectable } from "@nestjs/common";
|
||||
import { PrismaService } from "src/prisma/prisma.service";
|
||||
import { EmailToIdResolver } from "src/time-and-attendance/utils/resolve-email-id.utils";
|
||||
import { Timesheets } from "src/time-and-attendance/time-tracker/timesheets/dtos/timesheet.dto";
|
||||
import { Timesheet, TimesheetEntity, Timesheets } from "src/time-and-attendance/time-tracker/timesheets/dtos/timesheet.dto";
|
||||
import { Result } from "src/common/errors/result-error.factory";
|
||||
import { Users } from "@prisma/client";
|
||||
import { ShiftDto } from "src/time-and-attendance/time-tracker/shifts/dtos/shift-create.dto";
|
||||
import { ExpenseDto } from "src/time-and-attendance/expenses/dtos/expense-create.dto";
|
||||
import { ShiftEntity } from "src/time-and-attendance/time-tracker/shifts/dtos/shift-entity.dto";
|
||||
import { BankCodesResolver } from "src/time-and-attendance/utils/resolve-bank-type-id.utils";
|
||||
import { ExpenseEntity } from "src/time-and-attendance/expenses/dtos/expense-entity.dto";
|
||||
|
||||
export type TotalHours = {
|
||||
regular: number;
|
||||
|
|
@ -28,6 +34,7 @@ export class GetTimesheetsOverviewService {
|
|||
constructor(
|
||||
private readonly prisma: PrismaService,
|
||||
private readonly emailResolver: EmailToIdResolver,
|
||||
private readonly typeResolver: BankCodesResolver,
|
||||
) { }
|
||||
|
||||
//-----------------------------------------------------------------------------------
|
||||
|
|
@ -74,9 +81,11 @@ export class GetTimesheetsOverviewService {
|
|||
//builds employee full name
|
||||
const user = employee.user;
|
||||
const employee_fullname = `${user.first_name} ${user.last_name}`.trim();
|
||||
|
||||
|
||||
//maps all timesheet's infos
|
||||
const timesheets = await Promise.all(rows.map((timesheet) => this.mapOneTimesheet(timesheet)));
|
||||
if(!timesheets) return { success: false, error: 'an error occured during the mapping of a timesheet'}
|
||||
|
||||
return { success: true, data: { employee_fullname, timesheets } };
|
||||
} catch (error) {
|
||||
|
|
@ -87,127 +96,125 @@ export class GetTimesheetsOverviewService {
|
|||
//-----------------------------------------------------------------------------------
|
||||
// MAPPERS & HELPERS
|
||||
//-----------------------------------------------------------------------------------
|
||||
// const timesheet_range = { employee_id: employee_id.data, start_date: { gte: period.period_start, lte: period.period_end } };
|
||||
//fetch timesheet's infos
|
||||
private async loadTimesheets(employee_id: number, period_start: Date, period_end: Date) {
|
||||
return this.prisma.timesheets.findMany({
|
||||
where: { employee_id , start_date: { gte: period_start, lte: period_end } },
|
||||
where: { employee_id, start_date: { gte: period_start, lte: period_end } },
|
||||
include: {
|
||||
employee: { include: { user: true } },
|
||||
shift: { include: { bank_code: true } },
|
||||
expense: { include: { bank_code: true, attachment_record: true } },
|
||||
expense: { include: { bank_code: true } },
|
||||
},
|
||||
orderBy: { start_date: 'asc' },
|
||||
});
|
||||
}
|
||||
|
||||
private mapOneTimesheet(timesheet: any) {
|
||||
//converts string to UTC date format
|
||||
const start = toDateFromString(timesheet.start_date);
|
||||
const day_dates = sevenDaysFrom(start);
|
||||
private async mapOneTimesheet(timesheet: TimesheetResult): Promise<Timesheet> {
|
||||
//converts string to UTC date format
|
||||
const start = toDateFromString(timesheet.start_date);
|
||||
const day_dates = sevenDaysFrom(start);
|
||||
|
||||
//map of shifts by days
|
||||
const shifts_by_date = new Map<string, any[]>();
|
||||
for (const shift of timesheet.shift) {
|
||||
const date = toStringFromDate(shift.date);
|
||||
const arr = shifts_by_date.get(date) ?? [];
|
||||
arr.push(shift);
|
||||
shifts_by_date.set(date, arr);
|
||||
}
|
||||
//map of expenses by days
|
||||
const expenses_by_date = new Map<string, any[]>();
|
||||
for (const expense of timesheet.expense) {
|
||||
const date = toStringFromDate(expense.date);
|
||||
const arr = expenses_by_date.get(date) ?? [];
|
||||
arr.push(expense);
|
||||
expenses_by_date.set(date, arr);
|
||||
}
|
||||
//weekly totals
|
||||
const weekly_hours: TotalHours[] = [emptyHours()];
|
||||
const weekly_expenses: TotalExpenses[] = [emptyExpenses()];
|
||||
|
||||
|
||||
|
||||
//map of days
|
||||
const days = day_dates.map((date) => {
|
||||
const date_iso = toStringFromDate(date);
|
||||
const shifts_source = shifts_by_date.get(date_iso) ?? [];
|
||||
const expenses_source = expenses_by_date.get(date_iso) ?? [];
|
||||
|
||||
//inner map of shifts
|
||||
const shifts = shifts_source.map((shift) => ({
|
||||
timesheet_id: shift.timesheet_id,
|
||||
date: toStringFromDate(shift.date),
|
||||
start_time: toHHmmFromDate(shift.start_time),
|
||||
end_time: toHHmmFromDate(shift.end_time),
|
||||
type: shift.bank_code?.type ?? '',
|
||||
is_remote: shift.is_remote ?? false,
|
||||
is_approved: shift.is_approved ?? false,
|
||||
id: shift.id ?? null,
|
||||
comment: shift.comment ?? null,
|
||||
}));
|
||||
|
||||
//inner map of expenses
|
||||
const expenses = expenses_source.map((expense) => ({
|
||||
date: toStringFromDate(expense.date),
|
||||
amount: expense.amount ? Number(expense.amount) : undefined,
|
||||
mileage: expense.mileage ? Number(expense.mileage) : undefined,
|
||||
expense_id: expense.id ?? null,
|
||||
attachment: expense.attachment_record ? String(expense.attachment_record.id) : undefined,
|
||||
is_approved: expense.is_approved ?? false,
|
||||
comment: expense.comment ?? '',
|
||||
supervisor_comment: expense.supervisor_comment,
|
||||
type: expense.type,
|
||||
}));
|
||||
|
||||
//daily totals
|
||||
const daily_hours = [emptyHours()];
|
||||
const daily_expenses = [emptyExpenses()];
|
||||
|
||||
//totals by shift types
|
||||
for (const shift of shifts_source) {
|
||||
const hours = diffOfHours(shift.start_time, shift.end_time);
|
||||
const subgroup = hoursSubGroupFromBankCode(shift.bank_code);
|
||||
daily_hours[0][subgroup] += hours;
|
||||
weekly_hours[0][subgroup] += hours;
|
||||
//map of shifts by days
|
||||
const shifts_by_date = new Map<string, any[]>();
|
||||
for (const shift of timesheet.shift) {
|
||||
const date_string = toStringFromDate(shift.date);
|
||||
const arr = shifts_by_date.get(date_string) ?? [];
|
||||
arr.push(shift);
|
||||
shifts_by_date.set(date_string, arr);
|
||||
}
|
||||
//map of expenses by days
|
||||
const expenses_by_date = new Map<string, any[]>();
|
||||
for (const expense of timesheet.expense) {
|
||||
const date_string = toStringFromDate(expense.date);
|
||||
const arr = expenses_by_date.get(date_string) ?? [];
|
||||
arr.push(expense);
|
||||
expenses_by_date.set(date_string, arr);
|
||||
}
|
||||
//weekly totals
|
||||
const weekly_hours: TotalHours[] = [emptyHours()];
|
||||
const weekly_expenses: TotalExpenses[] = [emptyExpenses()];
|
||||
|
||||
//totals by expense types
|
||||
for (const expense of expenses_source) {
|
||||
const subgroup = expenseSubgroupFromBankCode(expense.bank_code);
|
||||
if (subgroup === 'mileage') {
|
||||
const mileage = num(expense.mileage);
|
||||
daily_expenses[0].mileage += mileage;
|
||||
weekly_expenses[0].mileage += mileage;
|
||||
} else if (subgroup === 'per_diem') {
|
||||
const amount = num(expense.amount);
|
||||
daily_expenses[0].per_diem += amount;
|
||||
weekly_expenses[0].per_diem += amount;
|
||||
} else if (subgroup === 'on_call') {
|
||||
const amount = num(expense.amount);
|
||||
daily_expenses[0].on_call += amount;
|
||||
weekly_expenses[0].on_call += amount;
|
||||
} else {
|
||||
const amount = num(expense.amount);
|
||||
daily_expenses[0].expenses += amount;
|
||||
weekly_expenses[0].expenses += amount;
|
||||
//map of days
|
||||
const days = day_dates.map((date) => {
|
||||
const date_iso = toStringFromDate(date);
|
||||
const shifts_source = shifts_by_date.get(date_iso) ?? [];
|
||||
const expenses_source = expenses_by_date.get(date_iso) ?? [];
|
||||
|
||||
//inner map of shifts
|
||||
const shifts = shifts_source.map((shift) => ({
|
||||
timesheet_id: shift.timesheet_id,
|
||||
date: toStringFromDate(shift.date),
|
||||
start_time: toHHmmFromDate(shift.start_time),
|
||||
end_time: toHHmmFromDate(shift.end_time),
|
||||
type: shift.bank_code?.type ?? '',
|
||||
is_remote: shift.is_remote ?? false,
|
||||
is_approved: shift.is_approved ?? false,
|
||||
id: shift.id ?? null,
|
||||
comment: shift.comment ?? null,
|
||||
}));
|
||||
|
||||
//inner map of expenses
|
||||
const expenses = expenses_source.map((expense) => ({
|
||||
date: toStringFromDate(expense.date),
|
||||
amount: expense.amount != null ? Number(expense.amount) : undefined,
|
||||
mileage: expense.mileage != null ? Number(expense.mileage) : undefined,
|
||||
expense_id: expense.id ?? null,
|
||||
attachment: expense.attachment_record ? String(expense.attachment_record.id) : undefined,
|
||||
is_approved: expense.is_approved ?? false,
|
||||
comment: expense.comment ?? '',
|
||||
supervisor_comment: expense.supervisor_comment,
|
||||
type: expense.type,
|
||||
}));
|
||||
|
||||
//daily totals
|
||||
const daily_hours = [emptyHours()];
|
||||
const daily_expenses = [emptyExpenses()];
|
||||
|
||||
//totals by shift types
|
||||
for (const shift of shifts_source) {
|
||||
const hours = diffOfHours(shift.start_time, shift.end_time);
|
||||
const subgroup = hoursSubGroupFromBankCode(shift.bank_code);
|
||||
daily_hours[0][subgroup] += hours;
|
||||
weekly_hours[0][subgroup] += hours;
|
||||
}
|
||||
}
|
||||
|
||||
//totals by expense types
|
||||
for (const expense of expenses_source) {
|
||||
const subgroup = expenseSubgroupFromBankCode(expense.bank_code);
|
||||
if (subgroup === 'mileage') {
|
||||
const mileage = num(expense.mileage);
|
||||
daily_expenses[0].mileage += mileage;
|
||||
weekly_expenses[0].mileage += mileage;
|
||||
} else if (subgroup === 'per_diem') {
|
||||
const amount = num(expense.amount);
|
||||
daily_expenses[0].per_diem += amount;
|
||||
weekly_expenses[0].per_diem += amount;
|
||||
} else if (subgroup === 'on_call') {
|
||||
const amount = num(expense.amount);
|
||||
daily_expenses[0].on_call += amount;
|
||||
weekly_expenses[0].on_call += amount;
|
||||
} else {
|
||||
const amount = num(expense.amount);
|
||||
daily_expenses[0].expenses += amount;
|
||||
weekly_expenses[0].expenses += amount;
|
||||
}
|
||||
}
|
||||
return {
|
||||
date: date_iso,
|
||||
shifts,
|
||||
expenses,
|
||||
daily_hours,
|
||||
daily_expenses,
|
||||
};
|
||||
});
|
||||
|
||||
return {
|
||||
date: date_iso,
|
||||
shifts,
|
||||
expenses,
|
||||
daily_hours,
|
||||
daily_expenses,
|
||||
timesheet_id: timesheet.id,
|
||||
is_approved: timesheet.is_approved ?? false,
|
||||
days,
|
||||
weekly_hours,
|
||||
weekly_expenses,
|
||||
};
|
||||
});
|
||||
return {
|
||||
timesheet_id: timesheet.id,
|
||||
is_approved: timesheet.is_approved ?? false,
|
||||
days,
|
||||
weekly_hours,
|
||||
weekly_expenses,
|
||||
};
|
||||
}
|
||||
|
||||
private ensureTimesheet = async (employee_id: number, start_date: Date | string) => {
|
||||
|
|
@ -236,13 +243,21 @@ export class GetTimesheetsOverviewService {
|
|||
include: {
|
||||
employee: { include: { user: true } },
|
||||
shift: { include: { bank_code: true } },
|
||||
expense: { include: { bank_code: true, attachment_record: true } },
|
||||
expense: { include: { bank_code: true, attachment_record: true, } },
|
||||
},
|
||||
});
|
||||
return row!;
|
||||
}
|
||||
}
|
||||
|
||||
interface TimesheetResult extends TimesheetEntity {
|
||||
employee: {
|
||||
user: Users
|
||||
},
|
||||
shift: ShiftEntity[],
|
||||
expense: ExpenseEntity[],
|
||||
}
|
||||
|
||||
//filled array with default values
|
||||
const emptyHours = (): TotalHours => { return { regular: 0, evening: 0, emergency: 0, overtime: 0, vacation: 0, holiday: 0, sick: 0 } };
|
||||
const emptyExpenses = (): TotalExpenses => { return { expenses: 0, per_diem: 0, on_call: 0, mileage: 0 } };
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user