refactor(timesheets): refactored findAll to return more data
This commit is contained in:
parent
9bc5c41de8
commit
994e02ba7f
|
|
@ -464,24 +464,36 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"get": {
|
"get": {
|
||||||
"operationId": "TimesheetsController_findAll",
|
"operationId": "TimesheetsController_getPeriodByQuery",
|
||||||
"parameters": [],
|
"parameters": [
|
||||||
"responses": {
|
{
|
||||||
"201": {
|
"name": "year",
|
||||||
"description": "List of timesheet found",
|
"required": true,
|
||||||
"content": {
|
"in": "query",
|
||||||
"application/json": {
|
|
||||||
"schema": {
|
"schema": {
|
||||||
"type": "array",
|
"type": "number"
|
||||||
"items": {
|
|
||||||
"$ref": "#/components/schemas/CreateTimesheetDto"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"400": {
|
{
|
||||||
"description": "List of timesheets not found"
|
"name": "period_no",
|
||||||
|
"required": true,
|
||||||
|
"in": "query",
|
||||||
|
"schema": {
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "email",
|
||||||
|
"required": true,
|
||||||
|
"in": "query",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": ""
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"security": [
|
"security": [
|
||||||
|
|
@ -489,7 +501,6 @@
|
||||||
"access-token": []
|
"access-token": []
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"summary": "Find all timesheets",
|
|
||||||
"tags": [
|
"tags": [
|
||||||
"Timesheets"
|
"Timesheets"
|
||||||
]
|
]
|
||||||
|
|
@ -618,7 +629,7 @@
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"/timesheets/{id}/approval": {
|
"/timesheets/approval/{id}": {
|
||||||
"patch": {
|
"patch": {
|
||||||
"operationId": "TimesheetsController_approve",
|
"operationId": "TimesheetsController_approve",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
|
|
@ -1206,7 +1217,7 @@
|
||||||
"schema": {
|
"schema": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"items": {
|
"items": {
|
||||||
"$ref": "#/components/schemas/CreateLeaveRequestsDto"
|
"$ref": "#/components/schemas/LeaveRequestViewDto"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1246,7 +1257,7 @@
|
||||||
"content": {
|
"content": {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
"schema": {
|
"schema": {
|
||||||
"$ref": "#/components/schemas/CreateLeaveRequestsDto"
|
"$ref": "#/components/schemas/LeaveRequestViewDto"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1293,7 +1304,7 @@
|
||||||
"content": {
|
"content": {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
"schema": {
|
"schema": {
|
||||||
"$ref": "#/components/schemas/CreateLeaveRequestsDto"
|
"$ref": "#/components/schemas/LeaveRequestViewDto"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1525,29 +1536,6 @@
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"/exports/csv": {
|
|
||||||
"get": {
|
|
||||||
"operationId": "CsvExportController_exportCsv",
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"name": "period",
|
|
||||||
"required": true,
|
|
||||||
"in": "query",
|
|
||||||
"schema": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": ""
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"tags": [
|
|
||||||
"CsvExport"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/customers": {
|
"/customers": {
|
||||||
"post": {
|
"post": {
|
||||||
"operationId": "CustomersController_create",
|
"operationId": "CustomersController_create",
|
||||||
|
|
@ -2293,7 +2281,7 @@
|
||||||
"description": "Employee`s email"
|
"description": "Employee`s email"
|
||||||
},
|
},
|
||||||
"phone_number": {
|
"phone_number": {
|
||||||
"type": "number",
|
"type": "string",
|
||||||
"example": "82538437464",
|
"example": "82538437464",
|
||||||
"description": "Employee`s phone number"
|
"description": "Employee`s phone number"
|
||||||
},
|
},
|
||||||
|
|
@ -2378,7 +2366,7 @@
|
||||||
"description": "Employee`s email"
|
"description": "Employee`s email"
|
||||||
},
|
},
|
||||||
"phone_number": {
|
"phone_number": {
|
||||||
"type": "number",
|
"type": "string",
|
||||||
"example": "82538437464",
|
"example": "82538437464",
|
||||||
"description": "Employee`s phone number"
|
"description": "Employee`s phone number"
|
||||||
},
|
},
|
||||||
|
|
@ -2646,16 +2634,6 @@
|
||||||
"CreateLeaveRequestsDto": {
|
"CreateLeaveRequestsDto": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"id": {
|
|
||||||
"type": "number",
|
|
||||||
"example": 1,
|
|
||||||
"description": "Leave request`s unique id(auto-incremented)"
|
|
||||||
},
|
|
||||||
"employee_id": {
|
|
||||||
"type": "number",
|
|
||||||
"example": "4655867",
|
|
||||||
"description": "Employee`s id"
|
|
||||||
},
|
|
||||||
"bank_code_id": {
|
"bank_code_id": {
|
||||||
"type": "number",
|
"type": "number",
|
||||||
"example": 7,
|
"example": 7,
|
||||||
|
|
@ -2667,13 +2645,11 @@
|
||||||
"description": "type of leave request for an accounting perception"
|
"description": "type of leave request for an accounting perception"
|
||||||
},
|
},
|
||||||
"start_date_time": {
|
"start_date_time": {
|
||||||
"format": "date-time",
|
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"example": "22/06/2463",
|
"example": "22/06/2463",
|
||||||
"description": "Leave request`s start date"
|
"description": "Leave request`s start date"
|
||||||
},
|
},
|
||||||
"end_date_time": {
|
"end_date_time": {
|
||||||
"format": "date-time",
|
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"example": "25/03/3019",
|
"example": "25/03/3019",
|
||||||
"description": "Leave request`s end date"
|
"description": "Leave request`s end date"
|
||||||
|
|
@ -2690,8 +2666,6 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
"id",
|
|
||||||
"employee_id",
|
|
||||||
"bank_code_id",
|
"bank_code_id",
|
||||||
"leave_type",
|
"leave_type",
|
||||||
"start_date_time",
|
"start_date_time",
|
||||||
|
|
@ -2700,19 +2674,13 @@
|
||||||
"approval_status"
|
"approval_status"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"LeaveRequestViewDto": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {}
|
||||||
|
},
|
||||||
"UpdateLeaveRequestsDto": {
|
"UpdateLeaveRequestsDto": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"id": {
|
|
||||||
"type": "number",
|
|
||||||
"example": 1,
|
|
||||||
"description": "Leave request`s unique id(auto-incremented)"
|
|
||||||
},
|
|
||||||
"employee_id": {
|
|
||||||
"type": "number",
|
|
||||||
"example": "4655867",
|
|
||||||
"description": "Employee`s id"
|
|
||||||
},
|
|
||||||
"bank_code_id": {
|
"bank_code_id": {
|
||||||
"type": "number",
|
"type": "number",
|
||||||
"example": 7,
|
"example": 7,
|
||||||
|
|
@ -2724,13 +2692,11 @@
|
||||||
"description": "type of leave request for an accounting perception"
|
"description": "type of leave request for an accounting perception"
|
||||||
},
|
},
|
||||||
"start_date_time": {
|
"start_date_time": {
|
||||||
"format": "date-time",
|
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"example": "22/06/2463",
|
"example": "22/06/2463",
|
||||||
"description": "Leave request`s start date"
|
"description": "Leave request`s start date"
|
||||||
},
|
},
|
||||||
"end_date_time": {
|
"end_date_time": {
|
||||||
"format": "date-time",
|
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"example": "25/03/3019",
|
"example": "25/03/3019",
|
||||||
"description": "Leave request`s end date"
|
"description": "Leave request`s end date"
|
||||||
|
|
@ -2845,7 +2811,7 @@
|
||||||
"description": "Customer`s email"
|
"description": "Customer`s email"
|
||||||
},
|
},
|
||||||
"phone_number": {
|
"phone_number": {
|
||||||
"type": "number",
|
"type": "string",
|
||||||
"example": "8436637464",
|
"example": "8436637464",
|
||||||
"description": "Customer`s phone number"
|
"description": "Customer`s phone number"
|
||||||
},
|
},
|
||||||
|
|
@ -2898,7 +2864,7 @@
|
||||||
"description": "Customer`s email"
|
"description": "Customer`s email"
|
||||||
},
|
},
|
||||||
"phone_number": {
|
"phone_number": {
|
||||||
"type": "number",
|
"type": "string",
|
||||||
"example": "8436637464",
|
"example": "8436637464",
|
||||||
"description": "Customer`s phone number"
|
"description": "Customer`s phone number"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { PrismaClient, Roles } from '@prisma/client';
|
import { PrismaClient, Roles } from '@prisma/client';
|
||||||
|
|
||||||
const prisma = new PrismaClient();
|
const prisma = new PrismaClient();
|
||||||
const BASE_PHONE = 1_100_000_000; // < 2_147_483_647
|
const BASE_PHONE = '1_100_000_000'; // < 2_147_483_647
|
||||||
|
|
||||||
function emailFor(i: number) {
|
function emailFor(i: number) {
|
||||||
return `user${i + 1}@example.test`;
|
return `user${i + 1}@example.test`;
|
||||||
|
|
@ -16,7 +16,7 @@ async function main() {
|
||||||
first_name: string;
|
first_name: string;
|
||||||
last_name: string;
|
last_name: string;
|
||||||
email: string;
|
email: string;
|
||||||
phone_number: number;
|
phone_number: string;
|
||||||
residence?: string | null;
|
residence?: string | null;
|
||||||
role: Roles;
|
role: Roles;
|
||||||
}[] = [];
|
}[] = [];
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ import { ArchivalModule } from './modules/archival/archival.module';
|
||||||
import { AuthenticationModule } from './modules/authentication/auth.module';
|
import { AuthenticationModule } from './modules/authentication/auth.module';
|
||||||
import { BankCodesModule } from './modules/bank-codes/bank-codes.module';
|
import { BankCodesModule } from './modules/bank-codes/bank-codes.module';
|
||||||
import { BusinessLogicsModule } from './modules/business-logics/business-logics.module';
|
import { BusinessLogicsModule } from './modules/business-logics/business-logics.module';
|
||||||
import { CsvExportModule } from './modules/exports/csv-exports.module';
|
// import { CsvExportModule } from './modules/exports/csv-exports.module';
|
||||||
import { CustomersModule } from './modules/customers/customers.module';
|
import { CustomersModule } from './modules/customers/customers.module';
|
||||||
import { EmployeesModule } from './modules/employees/employees.module';
|
import { EmployeesModule } from './modules/employees/employees.module';
|
||||||
import { ExpensesModule } from './modules/expenses/expenses.module';
|
import { ExpensesModule } from './modules/expenses/expenses.module';
|
||||||
|
|
@ -30,7 +30,7 @@ import { ConfigModule } from '@nestjs/config';
|
||||||
BankCodesModule,
|
BankCodesModule,
|
||||||
BusinessLogicsModule,
|
BusinessLogicsModule,
|
||||||
ConfigModule.forRoot({isGlobal: true}),
|
ConfigModule.forRoot({isGlobal: true}),
|
||||||
CsvExportModule,
|
// CsvExportModule,
|
||||||
CustomersModule,
|
CustomersModule,
|
||||||
EmployeesModule,
|
EmployeesModule,
|
||||||
ExpensesModule,
|
ExpensesModule,
|
||||||
|
|
|
||||||
|
|
@ -55,10 +55,10 @@ export class CreateCustomerDto {
|
||||||
example: '8436637464',
|
example: '8436637464',
|
||||||
description: 'Customer`s phone number',
|
description: 'Customer`s phone number',
|
||||||
})
|
})
|
||||||
@Type(() => Number)
|
@IsString()
|
||||||
@IsInt()
|
@IsInt()
|
||||||
@IsPositive()
|
@IsPositive()
|
||||||
phone_number: number;
|
phone_number: string;
|
||||||
|
|
||||||
@ApiProperty({
|
@ApiProperty({
|
||||||
example: '1 Ringbearer`s way, Mount Doom city, ME, T1R 1N6 ',
|
example: '1 Ringbearer`s way, Mount Doom city, ME, T1R 1N6 ',
|
||||||
|
|
|
||||||
|
|
@ -62,10 +62,8 @@ export class CreateEmployeeDto {
|
||||||
example: '82538437464',
|
example: '82538437464',
|
||||||
description: 'Employee`s phone number',
|
description: 'Employee`s phone number',
|
||||||
})
|
})
|
||||||
@Type(() => Number)
|
@IsString()
|
||||||
@IsInt()
|
phone_number: string;
|
||||||
@IsPositive()
|
|
||||||
phone_number: number;
|
|
||||||
|
|
||||||
@ApiProperty({
|
@ApiProperty({
|
||||||
example: '1 Bagshot Row, Hobbiton, The Shire, Middle-earth',
|
example: '1 Bagshot Row, Hobbiton, The Shire, Middle-earth',
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ export class EmployeeProfileItemDto {
|
||||||
company_name: number | null;
|
company_name: number | null;
|
||||||
job_title: string | null;
|
job_title: string | null;
|
||||||
email: string | null;
|
email: string | null;
|
||||||
phone_number: number;
|
phone_number: string;
|
||||||
first_work_day: string;
|
first_work_day: string;
|
||||||
last_work_day?: string | null;
|
last_work_day?: string | null;
|
||||||
residence: string | null;
|
residence: string | null;
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,6 @@ export class UpdateEmployeeDto extends PartialType(CreateEmployeeDto) {
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
supervisor_id?: number;
|
supervisor_id?: number;
|
||||||
|
|
||||||
@Max(2147483647)
|
@IsOptional()
|
||||||
phone_number: number;
|
phone_number: string;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ import { Controller, Get, Header, Query, UseGuards } from "@nestjs/common";
|
||||||
import { RolesGuard } from "src/common/guards/roles.guard";
|
import { RolesGuard } from "src/common/guards/roles.guard";
|
||||||
import { Roles as RoleEnum } from '.prisma/client';
|
import { Roles as RoleEnum } from '.prisma/client';
|
||||||
import { CsvExportService } from "../services/csv-exports.service";
|
import { CsvExportService } from "../services/csv-exports.service";
|
||||||
import { ExportCompany, ExportCsvOptionsDto, ExportType } from "../dtos/export-csv-options.dto";
|
// import { ExportCompany, ExportCsvOptionsDto, ExportType } from "../dtos/export-csv-options.dto";
|
||||||
import { RolesAllowed } from "src/common/decorators/roles.decorators";
|
import { RolesAllowed } from "src/common/decorators/roles.decorators";
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -11,34 +11,34 @@ import { RolesAllowed } from "src/common/decorators/roles.decorators";
|
||||||
export class CsvExportController {
|
export class CsvExportController {
|
||||||
constructor(private readonly csvService: CsvExportService) {}
|
constructor(private readonly csvService: CsvExportService) {}
|
||||||
|
|
||||||
@Get('csv/:year/:period_no')
|
// @Get('csv/:year/:period_no')
|
||||||
@Header('Content-Type', 'text/csv; charset=utf-8')
|
// @Header('Content-Type', 'text/csv; charset=utf-8')
|
||||||
@Header('Content-Disposition', 'attachment; filename="export.csv"')
|
// @Header('Content-Disposition', 'attachment; filename="export.csv"')
|
||||||
//@RolesAllowed(RoleEnum.ADMIN, RoleEnum.ACCOUNTING, RoleEnum.HR)
|
// //@RolesAllowed(RoleEnum.ADMIN, RoleEnum.ACCOUNTING, RoleEnum.HR)
|
||||||
async exportCsv(@Query() options: ExportCsvOptionsDto,
|
// async exportCsv(@Query() options: ExportCsvOptionsDto,
|
||||||
@Query('period') periodId: string ): Promise<Buffer> {
|
// @Query('period') periodId: string ): Promise<Buffer> {
|
||||||
//modify to accept year and period_number
|
// //modify to accept year and period_number
|
||||||
//sets default values
|
// //sets default values
|
||||||
const companies = options.companies && options.companies.length ? options.companies :
|
// const companies = options.companies && options.companies.length ? options.companies :
|
||||||
[ ExportCompany.TARGO, ExportCompany.SOLUCOM];
|
// [ ExportCompany.TARGO, ExportCompany.SOLUCOM];
|
||||||
const types = options.type && options.type.length ? options.type :
|
// const types = options.type && options.type.length ? options.type :
|
||||||
Object.values(ExportType);
|
// Object.values(ExportType);
|
||||||
|
|
||||||
//collects all
|
// //collects all
|
||||||
const all = await this.csvService.collectTransaction(Number(periodId), companies);
|
// const all = await this.csvService.collectTransaction(Number(periodId), companies);
|
||||||
|
|
||||||
//filters by type
|
// //filters by type
|
||||||
const filtered = all.filter(row => {
|
// const filtered = all.filter(row => {
|
||||||
switch (row.bank_code.toLocaleLowerCase()) {
|
// switch (row.bank_code.toLocaleLowerCase()) {
|
||||||
case 'holiday' : return types.includes(ExportType.HOLIDAY);
|
// case 'holiday' : return types.includes(ExportType.HOLIDAY);
|
||||||
case 'vacation' : return types.includes(ExportType.VACATION);
|
// case 'vacation' : return types.includes(ExportType.VACATION);
|
||||||
case 'expenses' : return types.includes(ExportType.EXPENSES);
|
// case 'expenses' : return types.includes(ExportType.EXPENSES);
|
||||||
default : return types.includes(ExportType.SHIFTS);
|
// default : return types.includes(ExportType.SHIFTS);
|
||||||
}
|
// }
|
||||||
});
|
// });
|
||||||
|
|
||||||
//generating the csv file
|
// //generating the csv file
|
||||||
return this.csvService.generateCsv(filtered);
|
// return this.csvService.generateCsv(filtered);
|
||||||
}
|
// }
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -31,38 +31,38 @@ type Filters = {
|
||||||
export class CsvExportService {
|
export class CsvExportService {
|
||||||
constructor(private readonly prisma: PrismaService) {}
|
constructor(private readonly prisma: PrismaService) {}
|
||||||
|
|
||||||
async collectTransaction(
|
// async collectTransaction(
|
||||||
year: number,
|
// year: number,
|
||||||
period_no: number,
|
// period_no: number,
|
||||||
filters: Filters,
|
// filters: Filters,
|
||||||
approved: boolean = true
|
// approved: boolean = true
|
||||||
): Promise<CsvRow[]> {
|
// ): Promise<CsvRow[]> {
|
||||||
//fetch period
|
//fetch period
|
||||||
const period = await this.prisma.payPeriods.findFirst({
|
// const period = await this.prisma.payPeriods.findFirst({
|
||||||
where: { pay_year: year, pay_period_no: period_no },
|
// where: { pay_year: year, pay_period_no: period_no },
|
||||||
select: { period_start: true, period_end: true },
|
// select: { period_start: true, period_end: true },
|
||||||
});
|
// });
|
||||||
if(!period) throw new NotFoundException(`Pay period ${ year }-${ period_no } not found`);
|
// if(!period) throw new NotFoundException(`Pay period ${ year }-${ period_no } not found`);
|
||||||
|
|
||||||
const start = period.period_start;
|
// const start = period.period_start;
|
||||||
const end = period.period_end;
|
// const end = period.period_end;
|
||||||
|
|
||||||
//fetch company codes from .env
|
// //fetch company codes from .env
|
||||||
const comapany_codes = this.resolveCompanyCodes(filters.companies);
|
// const comapany_codes = this.resolveCompanyCodes(filters.companies);
|
||||||
if(comapany_codes.length === 0) throw new BadRequestException('No company selected');
|
// if(comapany_codes.length === 0) throw new BadRequestException('No company selected');
|
||||||
|
|
||||||
//Flag types
|
// //Flag types
|
||||||
const { shifts: want_shifts, expenses: want_expense, holiday: want_holiday, vacation: want_vacation } = filters.types;
|
// const { shifts: want_shifts, expenses: want_expense, holiday: want_holiday, vacation: want_vacation } = filters.types;
|
||||||
if(!want_shifts && !want_expense && !want_holiday && !want_vacation) {
|
// if(!want_shifts && !want_expense && !want_holiday && !want_vacation) {
|
||||||
throw new BadRequestException(' No export type selected ');
|
// throw new BadRequestException(' No export type selected ');
|
||||||
}
|
// }
|
||||||
|
|
||||||
const approved_filter = filters.approved? { is_approved: true } : {};
|
// const approved_filter = filters.approved? { is_approved: true } : {};
|
||||||
|
|
||||||
//Prisma queries
|
// //Prisma queries
|
||||||
const [shifts, expenses] = await Promise.all([
|
// const [shifts, expenses] = await Promise.all([
|
||||||
want_shifts || want_expense || want_holiday || want_vacation
|
// want_shifts || want_expense || want_holiday || want_vacation
|
||||||
])
|
// ])
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -198,16 +198,16 @@ export class CsvExportService {
|
||||||
// }
|
// }
|
||||||
|
|
||||||
//Final Mapping and sorts
|
//Final Mapping and sorts
|
||||||
return rows.sort((a,b) => {
|
// return rows.sort((a,b) => {
|
||||||
if(a.external_payroll_id !== b.external_payroll_id) {
|
// if(a.external_payroll_id !== b.external_payroll_id) {
|
||||||
return a.external_payroll_id - b.external_payroll_id;
|
// return a.external_payroll_id - b.external_payroll_id;
|
||||||
}
|
// }
|
||||||
if(a.bank_code !== b.bank_code) {
|
// if(a.bank_code !== b.bank_code) {
|
||||||
return a.bank_code.localeCompare(b.bank_code);
|
// return a.bank_code.localeCompare(b.bank_code);
|
||||||
}
|
// }
|
||||||
return a.week_number - b.week_number;
|
// return a.week_number - b.week_number;
|
||||||
});
|
// });
|
||||||
}
|
// }
|
||||||
resolveCompanyCodes(companies: { targo: boolean; solucom: boolean; }) {
|
resolveCompanyCodes(companies: { targo: boolean; solucom: boolean; }) {
|
||||||
throw new Error("Method not implemented.");
|
throw new Error("Method not implemented.");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -36,8 +36,8 @@ export class TimesheetsController {
|
||||||
@Query('period_no', ParseIntPipe ) period_no: number,
|
@Query('period_no', ParseIntPipe ) period_no: number,
|
||||||
@Query('email') email?: string
|
@Query('email') email?: string
|
||||||
): Promise<TimesheetPeriodDto> {
|
): Promise<TimesheetPeriodDto> {
|
||||||
if(!email || !email.trim()) throw new BadRequestException('Query param "email" is mandatory for this route.');
|
if(!email || !(email = email.trim())) throw new BadRequestException('Query param "email" is mandatory for this route.');
|
||||||
return this.timesheetsQuery.findAll(year, period_no, email.trim());
|
return this.timesheetsQuery.findAll(year, period_no, email);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get(':id')
|
@Get(':id')
|
||||||
|
|
@ -70,7 +70,7 @@ export class TimesheetsController {
|
||||||
return this.timesheetsQuery.remove(id);
|
return this.timesheetsQuery.remove(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Patch(':id/approval')
|
@Patch('approval/:id')
|
||||||
//@RolesAllowed(RoleEnum.ACCOUNTING, RoleEnum.ADMIN, RoleEnum.HR, RoleEnum.SUPERVISOR)
|
//@RolesAllowed(RoleEnum.ACCOUNTING, RoleEnum.ADMIN, RoleEnum.HR, RoleEnum.SUPERVISOR)
|
||||||
async approve(@Param('id', ParseIntPipe) id: number, @Body('is_approved', ParseBoolPipe) isApproved: boolean) {
|
async approve(@Param('id', ParseIntPipe) id: number, @Body('is_approved', ParseBoolPipe) isApproved: boolean) {
|
||||||
return this.timesheetsCommand.updateApproval(id, isApproved);
|
return this.timesheetsCommand.updateApproval(id, isApproved);
|
||||||
|
|
|
||||||
|
|
@ -27,13 +27,13 @@ export class DayExpensesDto {
|
||||||
export class WeekDto {
|
export class WeekDto {
|
||||||
is_approved: boolean;
|
is_approved: boolean;
|
||||||
shifts: {
|
shifts: {
|
||||||
sun: DayShiftsDto;
|
sun: DetailedShifts;
|
||||||
mon: DayShiftsDto;
|
mon: DetailedShifts;
|
||||||
tue: DayShiftsDto;
|
tue: DetailedShifts;
|
||||||
wed: DayShiftsDto;
|
wed: DetailedShifts;
|
||||||
thu: DayShiftsDto;
|
thu: DetailedShifts;
|
||||||
fri: DayShiftsDto;
|
fri: DetailedShifts;
|
||||||
sat: DayShiftsDto;
|
sat: DetailedShifts;
|
||||||
}
|
}
|
||||||
expenses: {
|
expenses: {
|
||||||
sun: DayExpensesDto;
|
sun: DayExpensesDto;
|
||||||
|
|
|
||||||
|
|
@ -5,14 +5,10 @@ import { Timesheets, TimesheetsArchive } from '@prisma/client';
|
||||||
import { UpdateTimesheetDto } from '../dtos/update-timesheet.dto';
|
import { UpdateTimesheetDto } from '../dtos/update-timesheet.dto';
|
||||||
import { OvertimeService } from 'src/modules/business-logics/services/overtime.service';
|
import { OvertimeService } from 'src/modules/business-logics/services/overtime.service';
|
||||||
import { computeHours } from 'src/common/utils/date-utils';
|
import { computeHours } from 'src/common/utils/date-utils';
|
||||||
import { buildPrismaWhere } from 'src/common/shared/build-prisma-where';
|
|
||||||
import { SearchTimesheetDto } from '../dtos/search-timesheet.dto';
|
|
||||||
import { TimesheetPeriodDto } from '../dtos/timesheet-period.dto';
|
import { TimesheetPeriodDto } from '../dtos/timesheet-period.dto';
|
||||||
import { buildPeriod, endOfDayUTC, toUTCDateOnly } from '../utils/timesheet.helpers';
|
import { buildPeriod, endOfDayUTC, toUTCDateOnly } from '../utils/timesheet.helpers';
|
||||||
|
import type { ShiftRow, ExpenseRow } from '../utils/timesheet.helpers';
|
||||||
|
|
||||||
// deprecated (used with old findAll) const ROUND_TO = 5;
|
|
||||||
type ShiftRow = { date: Date; start_time: Date; end_time: Date; is_approved?: boolean };
|
|
||||||
type ExpenseRow = { date: Date; amount: number; type: string; is_approved?: boolean };
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class TimesheetsQueryService {
|
export class TimesheetsQueryService {
|
||||||
|
|
@ -21,8 +17,6 @@ export class TimesheetsQueryService {
|
||||||
private readonly overtime: OvertimeService,
|
private readonly overtime: OvertimeService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
async create(dto : CreateTimesheetDto): Promise<Timesheets> {
|
async create(dto : CreateTimesheetDto): Promise<Timesheets> {
|
||||||
const { employee_id, is_approved } = dto;
|
const { employee_id, is_approved } = dto;
|
||||||
return this.prisma.timesheets.create({
|
return this.prisma.timesheets.create({
|
||||||
|
|
@ -74,7 +68,7 @@ export class TimesheetsQueryService {
|
||||||
}),
|
}),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
//Shift data mapping
|
// data mapping
|
||||||
const shifts: ShiftRow[] = raw_shifts.map(shift => ({
|
const shifts: ShiftRow[] = raw_shifts.map(shift => ({
|
||||||
date: shift.date,
|
date: shift.date,
|
||||||
start_time: shift.start_time,
|
start_time: shift.start_time,
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
import { DayExpensesDto, DayShiftsDto, ShiftDto, TimesheetPeriodDto, WeekDto } from "../dtos/timesheet-period.dto";
|
import { DayExpensesDto, DayShiftsDto, DetailedShifts, ShiftDto, TimesheetPeriodDto, WeekDto } from "../dtos/timesheet-period.dto";
|
||||||
|
|
||||||
//makes the strings indexes for arrays
|
//makes the strings indexes for arrays
|
||||||
export const DAY_KEYS = ['sun','mon','tue','wed','thu','fri','sat'] as const;
|
export const DAY_KEYS = ['sun','mon','tue','wed','thu','fri','sat'] as const;
|
||||||
export type DayKey = 'sun'|'mon'|'tue'|'wed'|'thu'|'fri'|'sat';
|
export type DayKey = typeof DAY_KEYS[number];
|
||||||
|
|
||||||
//DB line types
|
//DB line types
|
||||||
type ShiftRow = { date: Date; start_time: Date; end_time: Date; is_approved?: boolean };
|
export type ShiftRow = { date: Date; start_time: Date; end_time: Date; is_approved?: boolean };
|
||||||
type ExpenseRow = { date: Date; amount: number; type: string; is_approved?: boolean };
|
export type ExpenseRow = { date: Date; amount: number; type: string; is_approved?: boolean };
|
||||||
|
|
||||||
export function dayKeyFromDate(date: Date, useUTC = true): DayKey {
|
export function dayKeyFromDate(date: Date, useUTC = true): DayKey {
|
||||||
const index = useUTC ? date.getUTCDay() : date.getDay(); // 0=Sunday..6=Saturday
|
const index = useUTC ? date.getUTCDay() : date.getDay(); // 0=Sunday..6=Saturday
|
||||||
|
|
@ -14,6 +14,7 @@ export function dayKeyFromDate(date: Date, useUTC = true): DayKey {
|
||||||
}
|
}
|
||||||
|
|
||||||
const MS_PER_DAY = 86_400_000;
|
const MS_PER_DAY = 86_400_000;
|
||||||
|
const MS_PER_HOUR = 3_600_000;
|
||||||
|
|
||||||
export function toUTCDateOnly(date: Date | string): Date {
|
export function toUTCDateOnly(date: Date | string): Date {
|
||||||
const d = new Date(date);
|
const d = new Date(date);
|
||||||
|
|
@ -34,12 +35,6 @@ export function isBetweenUTC(date: Date, start: Date, end_inclusive: Date): bool
|
||||||
return time >= start.getTime() && time <= end_inclusive.getTime();
|
return time >= start.getTime() && time <= end_inclusive.getTime();
|
||||||
}
|
}
|
||||||
|
|
||||||
export function dayIndex(week_start: Date, date: Date): 1|2|3|4|5|6|7 {
|
|
||||||
const diff = Math.floor((toUTCDateOnly(date).getTime() - toUTCDateOnly(week_start).getTime())/ MS_PER_DAY);
|
|
||||||
const index = Math.min(6, Math.max(0, diff)) + 1;
|
|
||||||
return index as 1|2|3|4|5|6|7;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function toTimeString(date: Date): string {
|
export function toTimeString(date: Date): string {
|
||||||
const hours = String(date.getUTCHours()).padStart(2,'0');
|
const hours = String(date.getUTCHours()).padStart(2,'0');
|
||||||
const minutes = String(date.getUTCMinutes()).padStart(2,'0');
|
const minutes = String(date.getUTCMinutes()).padStart(2,'0');
|
||||||
|
|
@ -50,21 +45,33 @@ export function round2(num: number) {
|
||||||
return Math.round(num * 100) / 100;
|
return Math.round(num * 100) / 100;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function makeEmptyDayShifts(): DayShiftsDto { return []; }
|
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 makeEmptyDayShifts(): DayShiftsDto { return []; }
|
||||||
|
|
||||||
export function makeEmptyDayExpenses(): DayExpensesDto { return {cash: [], km: []}; }
|
export function makeEmptyDayExpenses(): DayExpensesDto { return {cash: [], km: []}; }
|
||||||
|
|
||||||
export function makeEmptyWeek(): WeekDto {
|
export function makeEmptyWeek(week_start: Date): WeekDto {
|
||||||
|
const make_empty_shifts = (offset: number): DetailedShifts => ({
|
||||||
|
shifts: [],
|
||||||
|
total_hours: 0,
|
||||||
|
short_date: shortDate(addDays(week_start, offset)),
|
||||||
|
break_durations: undefined,
|
||||||
|
});
|
||||||
return {
|
return {
|
||||||
is_approved: true,
|
is_approved: true,
|
||||||
shifts: {
|
shifts: {
|
||||||
sun: makeEmptyDayShifts(),
|
sun: make_empty_shifts(0),
|
||||||
mon: makeEmptyDayShifts(),
|
mon: make_empty_shifts(1),
|
||||||
tue: makeEmptyDayShifts(),
|
tue: make_empty_shifts(2),
|
||||||
wed: makeEmptyDayShifts(),
|
wed: make_empty_shifts(3),
|
||||||
thu: makeEmptyDayShifts(),
|
thu: make_empty_shifts(4),
|
||||||
fri: makeEmptyDayShifts(),
|
fri: make_empty_shifts(5),
|
||||||
sat: makeEmptyDayShifts(),
|
sat: make_empty_shifts(6),
|
||||||
},
|
},
|
||||||
expenses: {
|
expenses: {
|
||||||
sun: makeEmptyDayExpenses(),
|
sun: makeEmptyDayExpenses(),
|
||||||
|
|
@ -79,7 +86,7 @@ export function makeEmptyWeek(): WeekDto {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function makeEmptyPeriod(): TimesheetPeriodDto {
|
export function makeEmptyPeriod(): TimesheetPeriodDto {
|
||||||
return { week1: makeEmptyWeek(), week2: makeEmptyWeek() };
|
return { week1: makeEmptyWeek(new Date()), week2: makeEmptyWeek(new Date()) };
|
||||||
}
|
}
|
||||||
|
|
||||||
//needs ajusting according to DB's data for expenses types
|
//needs ajusting according to DB's data for expenses types
|
||||||
|
|
@ -89,16 +96,27 @@ export function normalizeExpenseBucket(db_type: string): 'km' | 'cash' {
|
||||||
return 'cash';
|
return 'cash';
|
||||||
}
|
}
|
||||||
|
|
||||||
export function buildWeek( week_start: Date, week_end: Date, shifts: ShiftRow[], expenses: ExpenseRow[]): WeekDto {
|
export function buildWeek(
|
||||||
const week = makeEmptyWeek();
|
week_start: Date,
|
||||||
|
week_end: Date,
|
||||||
|
shifts: ShiftRow[],
|
||||||
|
expenses: ExpenseRow[],
|
||||||
|
): WeekDto {
|
||||||
|
const week = makeEmptyWeek(week_start);
|
||||||
let all_approved = true;
|
let all_approved = true;
|
||||||
|
|
||||||
|
//array of shifts per day ( to check for break_gaps and calculate daily total hours )
|
||||||
|
const dayTimes: Record<DayKey, Array<{start:Date; end: Date;}>> = {
|
||||||
|
sun: [], mon: [], tue: [], wed: [],thu: [], fri: [], sat: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
//Shifts mapped and filtered by dates
|
//Shifts mapped and filtered by dates
|
||||||
const week_shifts = shifts.filter(shift => isBetweenUTC(shift.date, week_start, week_end));
|
const week_shifts = shifts.filter(shift => isBetweenUTC(shift.date, week_start, week_end));
|
||||||
for (const shift of week_shifts) {
|
for (const shift of week_shifts) {
|
||||||
const key = dayKeyFromDate(shift.date, true);
|
const key = dayKeyFromDate(shift.date, true);
|
||||||
week.shifts[key].push({
|
dayTimes[key].push({start: shift.start_time, end:shift.end_time });
|
||||||
shifts: [],
|
week.shifts[key].shifts.push({
|
||||||
start: toTimeString(shift.start_time),
|
start: toTimeString(shift.start_time),
|
||||||
end : toTimeString(shift.end_time),
|
end : toTimeString(shift.end_time),
|
||||||
is_approved: shift.is_approved ?? true,
|
is_approved: shift.is_approved ?? true,
|
||||||
|
|
@ -118,11 +136,38 @@ export function buildWeek( week_start: Date, week_end: Date, shifts: ShiftRow[],
|
||||||
});
|
});
|
||||||
all_approved = all_approved && (expense.is_approved ?? true);
|
all_approved = all_approved && (expense.is_approved ?? true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (const key of DAY_KEYS) {
|
||||||
|
//sorts shifts in chronological order
|
||||||
|
const times = dayTimes[key].sort((a,b) => a.start.getTime() - b.start.getTime());
|
||||||
|
|
||||||
|
//daily total hours
|
||||||
|
const total = times.reduce((sum, time) => {
|
||||||
|
const duration = (time.end.getTime() - time.start.getTime()) / MS_PER_HOUR;
|
||||||
|
return sum + Math.max(0, duration);
|
||||||
|
}, 0);
|
||||||
|
week.shifts[key].total_hours = round2(total);
|
||||||
|
|
||||||
|
//break_duration
|
||||||
|
if (times.length >= 2) {
|
||||||
|
let break_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) break_gaps += gap;
|
||||||
|
}
|
||||||
|
if(break_gaps > 0) week.shifts[key].break_durations = round2(break_gaps);
|
||||||
|
}
|
||||||
|
}
|
||||||
week.is_approved = all_approved;
|
week.is_approved = all_approved;
|
||||||
return week;
|
return week;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function buildPeriod( period_start: Date, period_end: Date, shifts: ShiftRow[], expenses: ExpenseRow[]): TimesheetPeriodDto {
|
export function buildPeriod(
|
||||||
|
period_start: Date,
|
||||||
|
period_end: Date,
|
||||||
|
shifts: ShiftRow[],
|
||||||
|
expenses: ExpenseRow[]
|
||||||
|
): TimesheetPeriodDto {
|
||||||
const week1_start = toUTCDateOnly(period_start);
|
const week1_start = toUTCDateOnly(period_start);
|
||||||
const week1_end = endOfDayUTC(addDays(week1_start, 6));
|
const week1_end = endOfDayUTC(addDays(week1_start, 6));
|
||||||
const week2_start = toUTCDateOnly(addDays(week1_start, 7));
|
const week2_start = toUTCDateOnly(addDays(week1_start, 7));
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user