refactor(expenses): commented old files, set up create update and delete expenses. set up findAll method and ajust query params of the timesheet controller
This commit is contained in:
parent
9270033f24
commit
af9d89da01
|
|
@ -57,6 +57,20 @@
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/auth/me": {
|
||||||
|
"get": {
|
||||||
|
"operationId": "AuthController_getProfile",
|
||||||
|
"parameters": [],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tags": [
|
||||||
|
"Auth"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
"/employees/employee-list": {
|
"/employees/employee-list": {
|
||||||
"get": {
|
"get": {
|
||||||
"operationId": "EmployeesController_findListEmployees",
|
"operationId": "EmployeesController_findListEmployees",
|
||||||
|
|
@ -153,8 +167,6 @@
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
<<<<<<< HEAD
|
|
||||||
=======
|
|
||||||
"/employees/profile/{email}": {
|
"/employees/profile/{email}": {
|
||||||
"get": {
|
"get": {
|
||||||
"operationId": "EmployeesController_findOneProfile",
|
"operationId": "EmployeesController_findOneProfile",
|
||||||
|
|
@ -195,381 +207,6 @@
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"/timesheets": {
|
|
||||||
"get": {
|
|
||||||
"operationId": "TimesheetsController_getPeriodByQuery",
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"name": "year",
|
|
||||||
"required": true,
|
|
||||||
"in": "query",
|
|
||||||
"schema": {
|
|
||||||
"type": "number"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "period_no",
|
|
||||||
"required": true,
|
|
||||||
"in": "query",
|
|
||||||
"schema": {
|
|
||||||
"type": "number"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "email",
|
|
||||||
"required": true,
|
|
||||||
"in": "query",
|
|
||||||
"schema": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": ""
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"security": [
|
|
||||||
{
|
|
||||||
"access-token": []
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"tags": [
|
|
||||||
"Timesheets"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/timesheets/{email}": {
|
|
||||||
"get": {
|
|
||||||
"operationId": "TimesheetsController_getByEmail",
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"name": "email",
|
|
||||||
"required": true,
|
|
||||||
"in": "path",
|
|
||||||
"schema": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "offset",
|
|
||||||
"required": true,
|
|
||||||
"in": "query",
|
|
||||||
"schema": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": ""
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"security": [
|
|
||||||
{
|
|
||||||
"access-token": []
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"tags": [
|
|
||||||
"Timesheets"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/timesheets/shifts/{email}": {
|
|
||||||
"post": {
|
|
||||||
"operationId": "TimesheetsController_createTimesheetShifts",
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"name": "email",
|
|
||||||
"required": true,
|
|
||||||
"in": "path",
|
|
||||||
"schema": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "offset",
|
|
||||||
"required": true,
|
|
||||||
"in": "query",
|
|
||||||
"schema": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"requestBody": {
|
|
||||||
"required": true,
|
|
||||||
"content": {
|
|
||||||
"application/json": {
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/components/schemas/CreateWeekShiftsDto"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"responses": {
|
|
||||||
"201": {
|
|
||||||
"description": ""
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"security": [
|
|
||||||
{
|
|
||||||
"access-token": []
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"tags": [
|
|
||||||
"Timesheets"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/Expenses/upsert/{email}/{date}": {
|
|
||||||
"put": {
|
|
||||||
"operationId": "ExpensesController_upsert_by_date",
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"name": "email",
|
|
||||||
"required": true,
|
|
||||||
"in": "path",
|
|
||||||
"schema": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "date",
|
|
||||||
"required": true,
|
|
||||||
"in": "path",
|
|
||||||
"schema": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"requestBody": {
|
|
||||||
"required": true,
|
|
||||||
"content": {
|
|
||||||
"application/json": {
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/components/schemas/UpsertExpenseDto"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": ""
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"security": [
|
|
||||||
{
|
|
||||||
"access-token": []
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"tags": [
|
|
||||||
"Expenses"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/Expenses/list/{email}/{year}/{period_no}": {
|
|
||||||
"get": {
|
|
||||||
"operationId": "ExpensesController_findExpenseListByPayPeriodAndEmail",
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"name": "email",
|
|
||||||
"required": true,
|
|
||||||
"in": "path",
|
|
||||||
"schema": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "year",
|
|
||||||
"required": true,
|
|
||||||
"in": "path",
|
|
||||||
"schema": {
|
|
||||||
"type": "number"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "period_no",
|
|
||||||
"required": true,
|
|
||||||
"in": "path",
|
|
||||||
"schema": {
|
|
||||||
"type": "number"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": ""
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"security": [
|
|
||||||
{
|
|
||||||
"access-token": []
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"tags": [
|
|
||||||
"Expenses"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/shifts/upsert/{email}": {
|
|
||||||
"put": {
|
|
||||||
"operationId": "ShiftsController_upsert_by_date",
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"name": "email",
|
|
||||||
"required": true,
|
|
||||||
"in": "path",
|
|
||||||
"schema": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "action",
|
|
||||||
"required": true,
|
|
||||||
"in": "query",
|
|
||||||
"schema": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"requestBody": {
|
|
||||||
"required": true,
|
|
||||||
"content": {
|
|
||||||
"application/json": {
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/components/schemas/UpsertShiftDto"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": ""
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"security": [
|
|
||||||
{
|
|
||||||
"access-token": []
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"tags": [
|
|
||||||
"Shifts"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/shifts/delete/{email}/{date}": {
|
|
||||||
"delete": {
|
|
||||||
"operationId": "ShiftsController_remove",
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"name": "email",
|
|
||||||
"required": true,
|
|
||||||
"in": "path",
|
|
||||||
"schema": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "date",
|
|
||||||
"required": true,
|
|
||||||
"in": "path",
|
|
||||||
"schema": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"requestBody": {
|
|
||||||
"required": true,
|
|
||||||
"content": {
|
|
||||||
"application/json": {
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/components/schemas/UpsertShiftDto"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": ""
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"security": [
|
|
||||||
{
|
|
||||||
"access-token": []
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"tags": [
|
|
||||||
"Shifts"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/shifts/approval/{id}": {
|
|
||||||
"patch": {
|
|
||||||
"operationId": "ShiftsController_approve",
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"name": "id",
|
|
||||||
"required": true,
|
|
||||||
"in": "path",
|
|
||||||
"schema": {
|
|
||||||
"type": "number"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": ""
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"security": [
|
|
||||||
{
|
|
||||||
"access-token": []
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"tags": [
|
|
||||||
"Shifts"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/shifts/summary": {
|
|
||||||
"get": {
|
|
||||||
"operationId": "ShiftsController_getSummary",
|
|
||||||
"parameters": [],
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": ""
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"security": [
|
|
||||||
{
|
|
||||||
"access-token": []
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"tags": [
|
|
||||||
"Shifts"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/shifts/export.csv": {
|
|
||||||
"get": {
|
|
||||||
"operationId": "ShiftsController_exportCsv",
|
|
||||||
"parameters": [],
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": ""
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"security": [
|
|
||||||
{
|
|
||||||
"access-token": []
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"tags": [
|
|
||||||
"Shifts"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
>>>>>>> 88f7c0cb0e3824f6faed91058a7d55a9cca048a7
|
|
||||||
"/notifications/summary": {
|
"/notifications/summary": {
|
||||||
"get": {
|
"get": {
|
||||||
"operationId": "NotificationsController_summary",
|
"operationId": "NotificationsController_summary",
|
||||||
|
|
@ -598,80 +235,6 @@
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
<<<<<<< HEAD
|
|
||||||
=======
|
|
||||||
"/leave-requests/upsert": {
|
|
||||||
"post": {
|
|
||||||
"operationId": "LeaveRequestController_upsertLeaveRequest",
|
|
||||||
"parameters": [],
|
|
||||||
"requestBody": {
|
|
||||||
"required": true,
|
|
||||||
"content": {
|
|
||||||
"application/json": {
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/components/schemas/UpsertLeaveRequestDto"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"responses": {
|
|
||||||
"201": {
|
|
||||||
"description": ""
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"security": [
|
|
||||||
{
|
|
||||||
"access-token": []
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"tags": [
|
|
||||||
"Leave Requests"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/auth/v1/login": {
|
|
||||||
"get": {
|
|
||||||
"operationId": "AuthController_login",
|
|
||||||
"parameters": [],
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": ""
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"tags": [
|
|
||||||
"Auth"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/auth/callback": {
|
|
||||||
"get": {
|
|
||||||
"operationId": "AuthController_loginCallback",
|
|
||||||
"parameters": [],
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": ""
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"tags": [
|
|
||||||
"Auth"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/auth/me": {
|
|
||||||
"get": {
|
|
||||||
"operationId": "AuthController_getProfile",
|
|
||||||
"parameters": [],
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": ""
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"tags": [
|
|
||||||
"Auth"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
>>>>>>> 88f7c0cb0e3824f6faed91058a7d55a9cca048a7
|
|
||||||
"/oauth-sessions": {
|
"/oauth-sessions": {
|
||||||
"post": {
|
"post": {
|
||||||
"operationId": "OauthSessionsController_create",
|
"operationId": "OauthSessionsController_create",
|
||||||
|
|
@ -1327,29 +890,10 @@
|
||||||
"first_work_day"
|
"first_work_day"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
<<<<<<< HEAD
|
|
||||||
=======
|
|
||||||
"EmployeeProfileItemDto": {
|
"EmployeeProfileItemDto": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {}
|
"properties": {}
|
||||||
},
|
},
|
||||||
"CreateWeekShiftsDto": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {}
|
|
||||||
},
|
|
||||||
"UpsertExpenseDto": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {}
|
|
||||||
},
|
|
||||||
"UpsertShiftDto": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {}
|
|
||||||
},
|
|
||||||
"UpsertLeaveRequestDto": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {}
|
|
||||||
},
|
|
||||||
>>>>>>> 88f7c0cb0e3824f6faed91058a7d55a9cca048a7
|
|
||||||
"CreateOauthSessionDto": {
|
"CreateOauthSessionDto": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
|
|
||||||
10
src/modules/expenses/controllers/expense.controller.ts
Normal file
10
src/modules/expenses/controllers/expense.controller.ts
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
import { Controller } from "@nestjs/common";
|
||||||
|
import { PrismaService } from "src/prisma/prisma.service";
|
||||||
|
|
||||||
|
|
||||||
|
@Controller('expense')
|
||||||
|
export class ExpenseController {
|
||||||
|
constructor(private readonly prisma: PrismaService){}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -1,66 +0,0 @@
|
||||||
import { ApiProperty } from "@nestjs/swagger";
|
|
||||||
import { Type } from "class-transformer";
|
|
||||||
import { Allow, IsBoolean, IsDate, IsDateString, IsInt, IsNumber, IsOptional, IsString } from "class-validator";
|
|
||||||
|
|
||||||
export class CreateExpenseDto {
|
|
||||||
@ApiProperty({
|
|
||||||
example: 1,
|
|
||||||
description: 'Unique ID of the expense (auto-generated)',
|
|
||||||
})
|
|
||||||
@Allow()
|
|
||||||
id?: number;
|
|
||||||
|
|
||||||
@ApiProperty({
|
|
||||||
example: 101,
|
|
||||||
description: 'ID number for a set timesheet',
|
|
||||||
})
|
|
||||||
@Type(()=> Number)
|
|
||||||
@IsInt()
|
|
||||||
timesheet_id: number;
|
|
||||||
|
|
||||||
@ApiProperty({
|
|
||||||
example: 7,
|
|
||||||
description: 'ID number of an bank code (link with bank-codes)',
|
|
||||||
})
|
|
||||||
@Type(() => Number)
|
|
||||||
@IsInt()
|
|
||||||
bank_code_id: number;
|
|
||||||
|
|
||||||
@ApiProperty({
|
|
||||||
example: '3018-10-20T00:00:00.000Z',
|
|
||||||
description: 'Date where the expense was made',
|
|
||||||
})
|
|
||||||
@IsDateString()
|
|
||||||
date: string;
|
|
||||||
|
|
||||||
@ApiProperty({
|
|
||||||
example: 17.82,
|
|
||||||
description: 'amount in $ for a refund',
|
|
||||||
})
|
|
||||||
@Type(() => Number)
|
|
||||||
@IsNumber()
|
|
||||||
amount: number;
|
|
||||||
|
|
||||||
@ApiProperty({
|
|
||||||
example:'Spent for mileage between A and B',
|
|
||||||
description:'explain`s why the expense was made'
|
|
||||||
})
|
|
||||||
@IsString()
|
|
||||||
comment: string;
|
|
||||||
|
|
||||||
@ApiProperty({
|
|
||||||
example: 'DENIED, APPROUVED, PENDING, etc...',
|
|
||||||
description: 'validation status',
|
|
||||||
})
|
|
||||||
@IsOptional()
|
|
||||||
@IsBoolean()
|
|
||||||
is_approved?: boolean;
|
|
||||||
|
|
||||||
@ApiProperty({
|
|
||||||
example:'Asked X to go there as an emergency response',
|
|
||||||
description:'Supervisro`s justification for the spending of an employee'
|
|
||||||
})
|
|
||||||
@IsString()
|
|
||||||
@IsOptional()
|
|
||||||
supervisor_comment?: string;
|
|
||||||
}
|
|
||||||
0
src/modules/expenses/dtos/expense.dto.ts
Normal file
0
src/modules/expenses/dtos/expense.dto.ts
Normal file
0
src/modules/expenses/dtos/get-expense.dto.ts
Normal file
0
src/modules/expenses/dtos/get-expense.dto.ts
Normal file
|
|
@ -1,26 +0,0 @@
|
||||||
import { Type } from "class-transformer";
|
|
||||||
import { IsDateString, IsInt, IsOptional, IsString } from "class-validator";
|
|
||||||
|
|
||||||
export class SearchExpensesDto {
|
|
||||||
@IsOptional()
|
|
||||||
@Type(()=> Number)
|
|
||||||
@IsInt()
|
|
||||||
timesheet_id?: number;
|
|
||||||
|
|
||||||
@IsOptional()
|
|
||||||
@Type(()=> Number)
|
|
||||||
@IsInt()
|
|
||||||
bank_code_id?: number;
|
|
||||||
|
|
||||||
@IsOptional()
|
|
||||||
@IsString()
|
|
||||||
comment_contains?: string;
|
|
||||||
|
|
||||||
@IsOptional()
|
|
||||||
@IsDateString()
|
|
||||||
start_date: string;
|
|
||||||
|
|
||||||
@IsOptional()
|
|
||||||
@IsDateString()
|
|
||||||
end_date: string;
|
|
||||||
}
|
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
import { PartialType } from "@nestjs/swagger";
|
|
||||||
import { CreateExpenseDto } from "./create-expense.dto";
|
|
||||||
|
|
||||||
export class UpdateExpenseDto extends PartialType(CreateExpenseDto) {}
|
|
||||||
|
|
@ -1,59 +0,0 @@
|
||||||
import { Transform, Type } from "class-transformer";
|
|
||||||
import {
|
|
||||||
IsNumber,
|
|
||||||
IsOptional,
|
|
||||||
IsString,
|
|
||||||
Matches,
|
|
||||||
MaxLength,
|
|
||||||
Min,
|
|
||||||
ValidateIf,
|
|
||||||
ValidateNested
|
|
||||||
} from "class-validator";
|
|
||||||
|
|
||||||
export class ExpensePayloadDto {
|
|
||||||
@IsString()
|
|
||||||
type!: string;
|
|
||||||
|
|
||||||
@ValidateIf(o => (o.type ?? '').toUpperCase() !== 'MILEAGE')
|
|
||||||
@IsNumber()
|
|
||||||
@Min(0)
|
|
||||||
amount?: number;
|
|
||||||
|
|
||||||
@ValidateIf(o => (o.type ?? '').toUpperCase() === 'MILEAGE')
|
|
||||||
@IsNumber()
|
|
||||||
@Min(0)
|
|
||||||
mileage?: number;
|
|
||||||
|
|
||||||
@IsString()
|
|
||||||
@MaxLength(280)
|
|
||||||
@Transform(({ value }) => (typeof value === 'string' ? value.trim() : value))
|
|
||||||
comment!: string;
|
|
||||||
|
|
||||||
@IsOptional()
|
|
||||||
@Transform(({ value }) => {
|
|
||||||
if (value === null || value === undefined || value === '') return undefined;
|
|
||||||
if (typeof value === 'number') return value.toString();
|
|
||||||
if (typeof value === 'string') {
|
|
||||||
const trimmed = value.trim();
|
|
||||||
return trimmed.length ? trimmed : undefined;
|
|
||||||
}
|
|
||||||
return undefined;
|
|
||||||
})
|
|
||||||
@IsString()
|
|
||||||
@Matches(/^\d+$/)
|
|
||||||
@MaxLength(255)
|
|
||||||
attachment?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
export class UpsertExpenseDto {
|
|
||||||
@IsOptional()
|
|
||||||
@ValidateNested()
|
|
||||||
@Type(()=> ExpensePayloadDto)
|
|
||||||
old_expense?: ExpensePayloadDto;
|
|
||||||
|
|
||||||
@IsOptional()
|
|
||||||
@ValidateNested()
|
|
||||||
@Type(()=> ExpensePayloadDto)
|
|
||||||
new_expense?: ExpensePayloadDto;
|
|
||||||
}
|
|
||||||
|
|
@ -1,14 +0,0 @@
|
||||||
export type UpsertAction = 'create' | 'update' | 'delete';
|
|
||||||
|
|
||||||
export interface ExpenseResponse {
|
|
||||||
date: string;
|
|
||||||
type: string;
|
|
||||||
amount: number;
|
|
||||||
comment: string;
|
|
||||||
is_approved: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type UpsertExpenseResult = {
|
|
||||||
action: UpsertAction;
|
|
||||||
day: ExpenseResponse[]
|
|
||||||
};
|
|
||||||
|
|
@ -1,111 +0,0 @@
|
||||||
import { BadRequestException } from "@nestjs/common";
|
|
||||||
import { ExpenseResponse } from "../types and interfaces/expenses.types.interfaces";
|
|
||||||
import { Prisma } from "@prisma/client";
|
|
||||||
|
|
||||||
//uppercase and trim for validation
|
|
||||||
export function normalizeType(type: string): string {
|
|
||||||
return (type ?? '').trim().toUpperCase();
|
|
||||||
};
|
|
||||||
|
|
||||||
//required comment after trim
|
|
||||||
export function assertAndTrimComment(comment: string): string {
|
|
||||||
const cmt = (comment ?? '').trim();
|
|
||||||
if(cmt.length === 0) {
|
|
||||||
throw new BadRequestException('A comment is required');
|
|
||||||
}
|
|
||||||
return cmt;
|
|
||||||
};
|
|
||||||
|
|
||||||
//rounding $ to 2 decimals
|
|
||||||
export function roundMoney2(num: number): number {
|
|
||||||
return Math.round((num + Number.EPSILON) * 100)/ 100;
|
|
||||||
};
|
|
||||||
|
|
||||||
export function computeMileageAmount(km: number, modifier: number): number {
|
|
||||||
if(km < 0) throw new BadRequestException('mileage must be positive');
|
|
||||||
if(modifier < 0) throw new BadRequestException('modifier must be positive');
|
|
||||||
return roundMoney2(km * modifier);
|
|
||||||
};
|
|
||||||
|
|
||||||
//compat. types with Prisma.Decimal. work around Prisma import in utils.
|
|
||||||
export type DecimalLike =
|
|
||||||
| number
|
|
||||||
| string
|
|
||||||
| { toNumber?: () => number }
|
|
||||||
| { toString?: () => string };
|
|
||||||
|
|
||||||
|
|
||||||
//safe conversion to number
|
|
||||||
export function toNumberSafe(value: DecimalLike): number {
|
|
||||||
if(typeof value === 'number') return value;
|
|
||||||
if(value && typeof (value as any).toNumber === 'function') return (value as any).toNumber();
|
|
||||||
return Number(
|
|
||||||
typeof (value as any)?.toString === 'function'
|
|
||||||
? (value as any).toString()
|
|
||||||
: value,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export const parseAttachmentId = (value: unknown): number | null => {
|
|
||||||
if (value == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof value === 'number') {
|
|
||||||
if (!Number.isInteger(value) || value <= 0) {
|
|
||||||
throw new BadRequestException('Invalid attachment id');
|
|
||||||
}
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof value === 'string') {
|
|
||||||
|
|
||||||
const trimmed = value.trim();
|
|
||||||
if (!trimmed.length) return null;
|
|
||||||
if (!/^\d+$/.test(trimmed)) throw new BadRequestException('Invalid attachment id');
|
|
||||||
|
|
||||||
const parsed = Number(trimmed);
|
|
||||||
if (parsed <= 0) throw new BadRequestException('Invalid attachment id');
|
|
||||||
|
|
||||||
return parsed;
|
|
||||||
}
|
|
||||||
throw new BadRequestException('Invalid attachment id');
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
//map of a row for DayExpenseResponse
|
|
||||||
export function mapDbExpenseToDayResponse(row: {
|
|
||||||
date: Date;
|
|
||||||
amount: Prisma.Decimal | number | string | null;
|
|
||||||
mileage?: Prisma.Decimal | number | string | null;
|
|
||||||
comment: string;
|
|
||||||
is_approved: boolean;
|
|
||||||
bank_code?: { type?: string | null } | null;
|
|
||||||
}): ExpenseResponse {
|
|
||||||
const yyyyMmDd = row.date.toISOString().slice(0,10);
|
|
||||||
const toNum = (value: any)=> (value == null ? 0 : Number(value));
|
|
||||||
return {
|
|
||||||
date: yyyyMmDd,
|
|
||||||
type: normalizeType(row.bank_code?.type ?? 'UNKNOWN'),
|
|
||||||
amount: toNum(row.amount),
|
|
||||||
comment: row.comment,
|
|
||||||
is_approved: row.is_approved,
|
|
||||||
...(row.mileage !== null ? { mileage: toNum(row.mileage) }: {}),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export const computeAmountDecimal = (
|
|
||||||
type: string,
|
|
||||||
payload: {
|
|
||||||
amount?: number;
|
|
||||||
mileage?: number;
|
|
||||||
},
|
|
||||||
modifier: number,
|
|
||||||
): Prisma.Decimal => {
|
|
||||||
if(type === 'MILEAGE') {
|
|
||||||
const km = payload.mileage ?? 0;
|
|
||||||
const amountNumber = computeMileageAmount(km, modifier);
|
|
||||||
return new Prisma.Decimal(amountNumber);
|
|
||||||
}
|
|
||||||
return new Prisma.Decimal(payload.amount!);
|
|
||||||
};
|
|
||||||
|
|
@ -0,0 +1,66 @@
|
||||||
|
// import { ApiProperty } from "@nestjs/swagger";
|
||||||
|
// import { Type } from "class-transformer";
|
||||||
|
// import { Allow, IsBoolean, IsDate, IsDateString, IsInt, IsNumber, IsOptional, IsString } from "class-validator";
|
||||||
|
|
||||||
|
// export class CreateExpenseDto {
|
||||||
|
// @ApiProperty({
|
||||||
|
// example: 1,
|
||||||
|
// description: 'Unique ID of the expense (auto-generated)',
|
||||||
|
// })
|
||||||
|
// @Allow()
|
||||||
|
// id?: number;
|
||||||
|
|
||||||
|
// @ApiProperty({
|
||||||
|
// example: 101,
|
||||||
|
// description: 'ID number for a set timesheet',
|
||||||
|
// })
|
||||||
|
// @Type(()=> Number)
|
||||||
|
// @IsInt()
|
||||||
|
// timesheet_id: number;
|
||||||
|
|
||||||
|
// @ApiProperty({
|
||||||
|
// example: 7,
|
||||||
|
// description: 'ID number of an bank code (link with bank-codes)',
|
||||||
|
// })
|
||||||
|
// @Type(() => Number)
|
||||||
|
// @IsInt()
|
||||||
|
// bank_code_id: number;
|
||||||
|
|
||||||
|
// @ApiProperty({
|
||||||
|
// example: '3018-10-20T00:00:00.000Z',
|
||||||
|
// description: 'Date where the expense was made',
|
||||||
|
// })
|
||||||
|
// @IsDateString()
|
||||||
|
// date: string;
|
||||||
|
|
||||||
|
// @ApiProperty({
|
||||||
|
// example: 17.82,
|
||||||
|
// description: 'amount in $ for a refund',
|
||||||
|
// })
|
||||||
|
// @Type(() => Number)
|
||||||
|
// @IsNumber()
|
||||||
|
// amount: number;
|
||||||
|
|
||||||
|
// @ApiProperty({
|
||||||
|
// example:'Spent for mileage between A and B',
|
||||||
|
// description:'explain`s why the expense was made'
|
||||||
|
// })
|
||||||
|
// @IsString()
|
||||||
|
// comment: string;
|
||||||
|
|
||||||
|
// @ApiProperty({
|
||||||
|
// example: 'DENIED, APPROUVED, PENDING, etc...',
|
||||||
|
// description: 'validation status',
|
||||||
|
// })
|
||||||
|
// @IsOptional()
|
||||||
|
// @IsBoolean()
|
||||||
|
// is_approved?: boolean;
|
||||||
|
|
||||||
|
// @ApiProperty({
|
||||||
|
// example:'Asked X to go there as an emergency response',
|
||||||
|
// description:'Supervisro`s justification for the spending of an employee'
|
||||||
|
// })
|
||||||
|
// @IsString()
|
||||||
|
// @IsOptional()
|
||||||
|
// supervisor_comment?: string;
|
||||||
|
// }
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
// export type UpsertAction = 'create' | 'update' | 'delete';
|
||||||
|
|
||||||
|
// export interface ExpenseResponse {
|
||||||
|
// date: string;
|
||||||
|
// type: string;
|
||||||
|
// amount: number;
|
||||||
|
// comment: string;
|
||||||
|
// is_approved: boolean;
|
||||||
|
// };
|
||||||
|
|
||||||
|
// export type UpsertExpenseResult = {
|
||||||
|
// action: UpsertAction;
|
||||||
|
// day: ExpenseResponse[]
|
||||||
|
// };
|
||||||
111
src/modules/expenses/~misc_deprecated-files/expenses.utils.ts
Normal file
111
src/modules/expenses/~misc_deprecated-files/expenses.utils.ts
Normal file
|
|
@ -0,0 +1,111 @@
|
||||||
|
// import { BadRequestException } from "@nestjs/common";
|
||||||
|
// import { ExpenseResponse } from "../types and interfaces/expenses.types.interfaces";
|
||||||
|
// import { Prisma } from "@prisma/client";
|
||||||
|
|
||||||
|
// //uppercase and trim for validation
|
||||||
|
// export function normalizeType(type: string): string {
|
||||||
|
// return (type ?? '').trim().toUpperCase();
|
||||||
|
// };
|
||||||
|
|
||||||
|
// //required comment after trim
|
||||||
|
// export function assertAndTrimComment(comment: string): string {
|
||||||
|
// const cmt = (comment ?? '').trim();
|
||||||
|
// if(cmt.length === 0) {
|
||||||
|
// throw new BadRequestException('A comment is required');
|
||||||
|
// }
|
||||||
|
// return cmt;
|
||||||
|
// };
|
||||||
|
|
||||||
|
// //rounding $ to 2 decimals
|
||||||
|
// export function roundMoney2(num: number): number {
|
||||||
|
// return Math.round((num + Number.EPSILON) * 100)/ 100;
|
||||||
|
// };
|
||||||
|
|
||||||
|
// export function computeMileageAmount(km: number, modifier: number): number {
|
||||||
|
// if(km < 0) throw new BadRequestException('mileage must be positive');
|
||||||
|
// if(modifier < 0) throw new BadRequestException('modifier must be positive');
|
||||||
|
// return roundMoney2(km * modifier);
|
||||||
|
// };
|
||||||
|
|
||||||
|
// //compat. types with Prisma.Decimal. work around Prisma import in utils.
|
||||||
|
// export type DecimalLike =
|
||||||
|
// | number
|
||||||
|
// | string
|
||||||
|
// | { toNumber?: () => number }
|
||||||
|
// | { toString?: () => string };
|
||||||
|
|
||||||
|
|
||||||
|
// //safe conversion to number
|
||||||
|
// export function toNumberSafe(value: DecimalLike): number {
|
||||||
|
// if(typeof value === 'number') return value;
|
||||||
|
// if(value && typeof (value as any).toNumber === 'function') return (value as any).toNumber();
|
||||||
|
// return Number(
|
||||||
|
// typeof (value as any)?.toString === 'function'
|
||||||
|
// ? (value as any).toString()
|
||||||
|
// : value,
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
|
||||||
|
// export const parseAttachmentId = (value: unknown): number | null => {
|
||||||
|
// if (value == null) {
|
||||||
|
// return null;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if (typeof value === 'number') {
|
||||||
|
// if (!Number.isInteger(value) || value <= 0) {
|
||||||
|
// throw new BadRequestException('Invalid attachment id');
|
||||||
|
// }
|
||||||
|
// return value;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if (typeof value === 'string') {
|
||||||
|
|
||||||
|
// const trimmed = value.trim();
|
||||||
|
// if (!trimmed.length) return null;
|
||||||
|
// if (!/^\d+$/.test(trimmed)) throw new BadRequestException('Invalid attachment id');
|
||||||
|
|
||||||
|
// const parsed = Number(trimmed);
|
||||||
|
// if (parsed <= 0) throw new BadRequestException('Invalid attachment id');
|
||||||
|
|
||||||
|
// return parsed;
|
||||||
|
// }
|
||||||
|
// throw new BadRequestException('Invalid attachment id');
|
||||||
|
// };
|
||||||
|
|
||||||
|
|
||||||
|
// //map of a row for DayExpenseResponse
|
||||||
|
// export function mapDbExpenseToDayResponse(row: {
|
||||||
|
// date: Date;
|
||||||
|
// amount: Prisma.Decimal | number | string | null;
|
||||||
|
// mileage?: Prisma.Decimal | number | string | null;
|
||||||
|
// comment: string;
|
||||||
|
// is_approved: boolean;
|
||||||
|
// bank_code?: { type?: string | null } | null;
|
||||||
|
// }): ExpenseResponse {
|
||||||
|
// const yyyyMmDd = row.date.toISOString().slice(0,10);
|
||||||
|
// const toNum = (value: any)=> (value == null ? 0 : Number(value));
|
||||||
|
// return {
|
||||||
|
// date: yyyyMmDd,
|
||||||
|
// type: normalizeType(row.bank_code?.type ?? 'UNKNOWN'),
|
||||||
|
// amount: toNum(row.amount),
|
||||||
|
// comment: row.comment,
|
||||||
|
// is_approved: row.is_approved,
|
||||||
|
// ...(row.mileage !== null ? { mileage: toNum(row.mileage) }: {}),
|
||||||
|
// };
|
||||||
|
// }
|
||||||
|
|
||||||
|
// export const computeAmountDecimal = (
|
||||||
|
// type: string,
|
||||||
|
// payload: {
|
||||||
|
// amount?: number;
|
||||||
|
// mileage?: number;
|
||||||
|
// },
|
||||||
|
// modifier: number,
|
||||||
|
// ): Prisma.Decimal => {
|
||||||
|
// if(type === 'MILEAGE') {
|
||||||
|
// const km = payload.mileage ?? 0;
|
||||||
|
// const amountNumber = computeMileageAmount(km, modifier);
|
||||||
|
// return new Prisma.Decimal(amountNumber);
|
||||||
|
// }
|
||||||
|
// return new Prisma.Decimal(payload.amount!);
|
||||||
|
// };
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
// import { Type } from "class-transformer";
|
||||||
|
// import { IsDateString, IsInt, IsOptional, IsString } from "class-validator";
|
||||||
|
|
||||||
|
// export class SearchExpensesDto {
|
||||||
|
// @IsOptional()
|
||||||
|
// @Type(()=> Number)
|
||||||
|
// @IsInt()
|
||||||
|
// timesheet_id?: number;
|
||||||
|
|
||||||
|
// @IsOptional()
|
||||||
|
// @Type(()=> Number)
|
||||||
|
// @IsInt()
|
||||||
|
// bank_code_id?: number;
|
||||||
|
|
||||||
|
// @IsOptional()
|
||||||
|
// @IsString()
|
||||||
|
// comment_contains?: string;
|
||||||
|
|
||||||
|
// @IsOptional()
|
||||||
|
// @IsDateString()
|
||||||
|
// start_date: string;
|
||||||
|
|
||||||
|
// @IsOptional()
|
||||||
|
// @IsDateString()
|
||||||
|
// end_date: string;
|
||||||
|
// }
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
// import { PartialType } from "@nestjs/swagger";
|
||||||
|
// import { CreateExpenseDto } from "./create-expense.dto";
|
||||||
|
|
||||||
|
// export class UpdateExpenseDto extends PartialType(CreateExpenseDto) {}
|
||||||
|
|
@ -0,0 +1,59 @@
|
||||||
|
// import { Transform, Type } from "class-transformer";
|
||||||
|
// import {
|
||||||
|
// IsNumber,
|
||||||
|
// IsOptional,
|
||||||
|
// IsString,
|
||||||
|
// Matches,
|
||||||
|
// MaxLength,
|
||||||
|
// Min,
|
||||||
|
// ValidateIf,
|
||||||
|
// ValidateNested
|
||||||
|
// } from "class-validator";
|
||||||
|
|
||||||
|
// export class ExpensePayloadDto {
|
||||||
|
// @IsString()
|
||||||
|
// type!: string;
|
||||||
|
|
||||||
|
// @ValidateIf(o => (o.type ?? '').toUpperCase() !== 'MILEAGE')
|
||||||
|
// @IsNumber()
|
||||||
|
// @Min(0)
|
||||||
|
// amount?: number;
|
||||||
|
|
||||||
|
// @ValidateIf(o => (o.type ?? '').toUpperCase() === 'MILEAGE')
|
||||||
|
// @IsNumber()
|
||||||
|
// @Min(0)
|
||||||
|
// mileage?: number;
|
||||||
|
|
||||||
|
// @IsString()
|
||||||
|
// @MaxLength(280)
|
||||||
|
// @Transform(({ value }) => (typeof value === 'string' ? value.trim() : value))
|
||||||
|
// comment!: string;
|
||||||
|
|
||||||
|
// @IsOptional()
|
||||||
|
// @Transform(({ value }) => {
|
||||||
|
// if (value === null || value === undefined || value === '') return undefined;
|
||||||
|
// if (typeof value === 'number') return value.toString();
|
||||||
|
// if (typeof value === 'string') {
|
||||||
|
// const trimmed = value.trim();
|
||||||
|
// return trimmed.length ? trimmed : undefined;
|
||||||
|
// }
|
||||||
|
// return undefined;
|
||||||
|
// })
|
||||||
|
// @IsString()
|
||||||
|
// @Matches(/^\d+$/)
|
||||||
|
// @MaxLength(255)
|
||||||
|
// attachment?: string;
|
||||||
|
// }
|
||||||
|
|
||||||
|
|
||||||
|
// export class UpsertExpenseDto {
|
||||||
|
// @IsOptional()
|
||||||
|
// @ValidateNested()
|
||||||
|
// @Type(()=> ExpensePayloadDto)
|
||||||
|
// old_expense?: ExpensePayloadDto;
|
||||||
|
|
||||||
|
// @IsOptional()
|
||||||
|
// @ValidateNested()
|
||||||
|
// @Type(()=> ExpensePayloadDto)
|
||||||
|
// new_expense?: ExpensePayloadDto;
|
||||||
|
// }
|
||||||
|
|
@ -1,17 +1,26 @@
|
||||||
|
import { EmailToIdResolver } from "src/modules/shared/utils/resolve-email-id.utils";
|
||||||
import { GetTimesheetsOverviewService } from "../services/timesheet-get-overview.service";
|
import { GetTimesheetsOverviewService } from "../services/timesheet-get-overview.service";
|
||||||
import { Controller, Get, Query} from "@nestjs/common";
|
import { BadRequestException, Controller, Get, Query} from "@nestjs/common";
|
||||||
|
|
||||||
@Controller('timesheets')
|
@Controller('timesheets')
|
||||||
export class TimesheetController {
|
export class TimesheetController {
|
||||||
constructor(private readonly timesheetOverview: GetTimesheetsOverviewService){}
|
constructor(
|
||||||
|
private readonly timesheetOverview: GetTimesheetsOverviewService,
|
||||||
|
private readonly emailResolver: EmailToIdResolver,
|
||||||
|
){}
|
||||||
|
|
||||||
@Get()
|
@Get()
|
||||||
async getTimesheetByIds(
|
async getTimesheetByIds(
|
||||||
@Query('timesheet_ids') timesheet_ids: string ) {
|
@Query('employee_email') employee_email: string,
|
||||||
const parsed = timesheet_ids.split(/,\s*/).map(value => Number(value)).filter(Number.isFinite);
|
@Query('year') year: string,
|
||||||
return this.timesheetOverview.getTimesheetsByIds(parsed);
|
@Query('period_number') period_number: string,
|
||||||
|
) {
|
||||||
|
if (!employee_email || !year || !period_number) {
|
||||||
|
throw new BadRequestException('Query params "employee_email", "year" and eriod_number" are required.');
|
||||||
|
}
|
||||||
|
const employee_id = await this.emailResolver.findIdByEmail(employee_email);
|
||||||
|
const pay_year = Number(year);
|
||||||
|
const period_num = Number(period_number);
|
||||||
|
return this.timesheetOverview.getTimesheetsForEmployeeByPeriod(employee_id, pay_year, period_num);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
|
import { sevenDaysFrom, toDateFromString, toHHmmFromDate, toStringFromDate } from "../helpers/timesheets-date-time-helpers";
|
||||||
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 { sevenDaysFrom, toDateFromString, toHHmmFromDate, toStringFromDate } from "../helpers/timesheets-date-time-helpers";
|
|
||||||
|
|
||||||
type TotalHours = {
|
type TotalHours = {
|
||||||
regular: number;
|
regular: number;
|
||||||
|
|
@ -23,26 +23,31 @@ type TotalExpenses = {
|
||||||
export class GetTimesheetsOverviewService {
|
export class GetTimesheetsOverviewService {
|
||||||
constructor(private readonly prisma: PrismaService) { }
|
constructor(private readonly prisma: PrismaService) { }
|
||||||
|
|
||||||
async getTimesheetsByIds(timesheet_ids: number[]) {
|
async getTimesheetsForEmployeeByPeriod(employee_id: number, pay_year: number, pay_period_no: number) {
|
||||||
if (!Array.isArray(timesheet_ids) || timesheet_ids.length === 0) throw new NotFoundException(`Timesheet_ids are missing`);
|
//find period using year and period_no
|
||||||
|
const period = await this.prisma.payPeriods.findFirst({
|
||||||
|
where: { pay_year, pay_period_no },
|
||||||
|
});
|
||||||
|
if (!period) throw new NotFoundException(`Pay period ${pay_year}-${pay_period_no} not found`);
|
||||||
|
|
||||||
//fetch all needed data using timesheet ids
|
//loads the timesheets related to the fetched pay-period
|
||||||
const rows = await this.prisma.timesheets.findMany({
|
const rows = await this.loadTimesheets({
|
||||||
where: { id: { in: timesheet_ids } },
|
employee_id,
|
||||||
include: {
|
start_date: { gte: period.period_start, lte: period.period_end },
|
||||||
employee: { include: { user: true } },
|
|
||||||
shift: { include: { bank_code: true } },
|
|
||||||
expense: { include: { bank_code: true, attachment_record: true } },
|
|
||||||
},
|
|
||||||
orderBy: { start_date: 'asc' },
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (rows.length === 0) throw new NotFoundException('Timesheet(s) not found');
|
//find user infos using the employee_id
|
||||||
|
const employee = await this.prisma.employees.findUnique({
|
||||||
//build full name
|
where: { id: employee_id },
|
||||||
const user = rows[0].employee.user;
|
include: { user: true },
|
||||||
|
});
|
||||||
|
if (!employee) throw new NotFoundException(`Employee #${employee_id} not found`);
|
||||||
|
|
||||||
|
//builds employee full name
|
||||||
|
const user = employee.user;
|
||||||
const employee_fullname = `${user.first_name} ${user.last_name}`.trim();
|
const employee_fullname = `${user.first_name} ${user.last_name}`.trim();
|
||||||
|
|
||||||
|
//maps all timesheet's infos
|
||||||
const timesheets = rows.map((timesheet) => this.mapOneTimesheet(timesheet));
|
const timesheets = rows.map((timesheet) => this.mapOneTimesheet(timesheet));
|
||||||
return { employee_fullname, timesheets };
|
return { employee_fullname, timesheets };
|
||||||
}
|
}
|
||||||
|
|
@ -51,16 +56,29 @@ export class GetTimesheetsOverviewService {
|
||||||
//-----------------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------------
|
||||||
// MAPPERS & HELPERS
|
// MAPPERS & HELPERS
|
||||||
//-----------------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------------
|
||||||
|
//fetch timesheet's infos
|
||||||
|
private async loadTimesheets(where: any) {
|
||||||
|
return this.prisma.timesheets.findMany({
|
||||||
|
where,
|
||||||
|
include: {
|
||||||
|
employee: { include: { user: true } },
|
||||||
|
shift: { include: { bank_code: true } },
|
||||||
|
expense: { include: { bank_code: true, attachment_record: true } },
|
||||||
|
},
|
||||||
|
orderBy: { start_date: 'asc' },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private mapOneTimesheet(timesheet: any) {
|
private mapOneTimesheet(timesheet: any) {
|
||||||
//converts string to UTC date format
|
//converts string to UTC date format
|
||||||
const start = toDateFromString(timesheet.start_date);
|
const start = toDateFromString(timesheet.start_date);
|
||||||
const day_dates = sevenDaysFrom(start);
|
const day_dates = sevenDaysFrom(start);
|
||||||
|
|
||||||
//map of shifts by days
|
//map of shifts by days
|
||||||
const shifts_by_date = new Map<string, any[]>();
|
const shifts_by_date = new Map<string, any[]>();
|
||||||
for (const shift of timesheet.shift) {
|
for (const shift of timesheet.shift) {
|
||||||
const date = toStringFromDate(shift.date);
|
const date = toStringFromDate(shift.date);
|
||||||
const arr = shifts_by_date.get(date) ?? [];
|
const arr = shifts_by_date.get(date) ?? [];
|
||||||
arr.push(shift);
|
arr.push(shift);
|
||||||
shifts_by_date.set(date, arr);
|
shifts_by_date.set(date, arr);
|
||||||
}
|
}
|
||||||
|
|
@ -68,40 +86,40 @@ export class GetTimesheetsOverviewService {
|
||||||
const expenses_by_date = new Map<string, any[]>();
|
const expenses_by_date = new Map<string, any[]>();
|
||||||
for (const expense of timesheet.expense) {
|
for (const expense of timesheet.expense) {
|
||||||
const date = toStringFromDate(expense.date);
|
const date = toStringFromDate(expense.date);
|
||||||
const arr = expenses_by_date.get(date) ?? [];
|
const arr = expenses_by_date.get(date) ?? [];
|
||||||
arr.push(expense);
|
arr.push(expense);
|
||||||
expenses_by_date.set(date, arr);
|
expenses_by_date.set(date, arr);
|
||||||
}
|
}
|
||||||
//weekly totals
|
//weekly totals
|
||||||
const weekly_hours: TotalHours[] = [emptyHours()];
|
const weekly_hours: TotalHours[] = [emptyHours()];
|
||||||
const weekly_expenses: TotalExpenses[] = [emptyExpenses()];
|
const weekly_expenses: TotalExpenses[] = [emptyExpenses()];
|
||||||
|
|
||||||
//map of days
|
//map of days
|
||||||
const days = day_dates.map((date) => {
|
const days = day_dates.map((date) => {
|
||||||
const date_iso = toStringFromDate(date);
|
const date_iso = toStringFromDate(date);
|
||||||
const shifts_source = shifts_by_date.get(date_iso) ?? [];
|
const shifts_source = shifts_by_date.get(date_iso) ?? [];
|
||||||
const expenses_source = expenses_by_date.get(date_iso) ?? [];
|
const expenses_source = expenses_by_date.get(date_iso) ?? [];
|
||||||
//inner map of shifts
|
//inner map of shifts
|
||||||
const shifts = shifts_source.map((shift) => ({
|
const shifts = shifts_source.map((shift) => ({
|
||||||
date: toStringFromDate(shift.date),
|
date: toStringFromDate(shift.date),
|
||||||
start_time: toHHmmFromDate(shift.start_time),
|
start_time: toHHmmFromDate(shift.start_time),
|
||||||
end_time: toHHmmFromDate(shift.end_time),
|
end_time: toHHmmFromDate(shift.end_time),
|
||||||
type: shift.bank_code?.type ?? '',
|
type: shift.bank_code?.type ?? '',
|
||||||
is_remote: shift.is_remote ?? false,
|
is_remote: shift.is_remote ?? false,
|
||||||
is_approved: shift.is_approved ?? false,
|
is_approved: shift.is_approved ?? false,
|
||||||
shift_id: shift.id ?? null,
|
shift_id: shift.id ?? null,
|
||||||
comment: shift.comment ?? null,
|
comment: shift.comment ?? null,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
//inner map of expenses
|
//inner map of expenses
|
||||||
const expenses = expenses_source.map((expense) => ({
|
const expenses = expenses_source.map((expense) => ({
|
||||||
date: toStringFromDate(expense.date),
|
date: toStringFromDate(expense.date),
|
||||||
amount: expense.amount ? Number(expense.amount) : undefined,
|
amount: expense.amount ? Number(expense.amount) : undefined,
|
||||||
mileage: expense.mileage ? Number(expense.mileage) : undefined,
|
mileage: expense.mileage ? Number(expense.mileage) : undefined,
|
||||||
expense_id: expense.id ?? null,
|
expense_id: expense.id ?? null,
|
||||||
attachment: expense.attachment_record ? String(expense.attachment_record.id) : undefined,
|
attachment: expense.attachment_record ? String(expense.attachment_record.id) : undefined,
|
||||||
is_approved: expense.is_approved ?? false,
|
is_approved: expense.is_approved ?? false,
|
||||||
comment: expense.comment ?? '',
|
comment: expense.comment ?? '',
|
||||||
supervisor_comment: expense.supervisor_comment,
|
supervisor_comment: expense.supervisor_comment,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|
@ -113,7 +131,7 @@ export class GetTimesheetsOverviewService {
|
||||||
for (const shift of shifts_source) {
|
for (const shift of shifts_source) {
|
||||||
const hours = diffOfHours(shift.start_time, shift.end_time);
|
const hours = diffOfHours(shift.start_time, shift.end_time);
|
||||||
const subgroup = hoursSubGroupFromBankCode(shift.bank_code);
|
const subgroup = hoursSubGroupFromBankCode(shift.bank_code);
|
||||||
daily_hours[0][subgroup] += hours;
|
daily_hours[0][subgroup] += hours;
|
||||||
weekly_hours[0][subgroup] += hours;
|
weekly_hours[0][subgroup] += hours;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -148,7 +166,7 @@ export class GetTimesheetsOverviewService {
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
timesheet_id: timesheet.id,
|
timesheet_id: timesheet.id,
|
||||||
is_approved: timesheet.is_approved ?? false,
|
is_approved: timesheet.is_approved ?? false,
|
||||||
days,
|
days,
|
||||||
weekly_hours,
|
weekly_hours,
|
||||||
weekly_expenses,
|
weekly_expenses,
|
||||||
|
|
@ -156,37 +174,36 @@ export class GetTimesheetsOverviewService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const emptyHours = (): TotalHours => {
|
//filled array with default values
|
||||||
return { regular: 0, evening: 0, emergency: 0, overtime: 0, vacation: 0, holiday: 0, sick: 0 };
|
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 } };
|
||||||
const emptyExpenses = (): TotalExpenses => {
|
|
||||||
return { expenses: 0, per_diem: 0, on_call: 0, mileage: 0 };
|
|
||||||
}
|
|
||||||
|
|
||||||
|
//calculate the differences of hours
|
||||||
const diffOfHours = (a: Date, b: Date): number => {
|
const diffOfHours = (a: Date, b: Date): number => {
|
||||||
const ms = new Date(b).getTime() - new Date(a).getTime();
|
const ms = new Date(b).getTime() - new Date(a).getTime();
|
||||||
return Math.max(0, Math.round((ms / 36e5) * 1000) / 1000);
|
return Math.max(0, Math.round((ms / 36e5) * 1000) / 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
const num = (value: any): number => {
|
//validate numeric values
|
||||||
return value ? Number(value) : 0;
|
const num = (value: any): number => { return value ? Number(value) : 0 };
|
||||||
}
|
|
||||||
|
|
||||||
|
// shift's subgroup types
|
||||||
const hoursSubGroupFromBankCode = (bank_code: any): keyof TotalHours => {
|
const hoursSubGroupFromBankCode = (bank_code: any): keyof TotalHours => {
|
||||||
const type = bank_code.type;
|
const type = bank_code.type;
|
||||||
if (type.includes('EVENING')) return 'evening';
|
if (type.includes('EVENING')) return 'evening';
|
||||||
if (type.includes('EMERGENCY')) return 'emergency';
|
if (type.includes('EMERGENCY')) return 'emergency';
|
||||||
if (type.includes('OVERTIME')) return 'overtime';
|
if (type.includes('OVERTIME')) return 'overtime';
|
||||||
if (type.includes('VACATION')) return 'vacation';
|
if (type.includes('VACATION')) return 'vacation';
|
||||||
if (type.includes('HOLIDAY')) return 'holiday';
|
if (type.includes('HOLIDAY')) return 'holiday';
|
||||||
if (type.includes('SICK')) return 'sick';
|
if (type.includes('SICK')) return 'sick';
|
||||||
return 'regular'
|
return 'regular'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// expense's subgroup types
|
||||||
const expenseSubgroupFromBankCode = (bank_code: any): keyof TotalExpenses => {
|
const expenseSubgroupFromBankCode = (bank_code: any): keyof TotalExpenses => {
|
||||||
const type = bank_code.type;
|
const type = bank_code.type;
|
||||||
if (type.includes('MILEAGE')) return 'mileage';
|
if (type.includes('MILEAGE')) return 'mileage';
|
||||||
if (type.includes('PER_DIEM')) return 'per_diem';
|
if (type.includes('PER_DIEM')) return 'per_diem';
|
||||||
if (type.includes('ON_CALL')) return 'on_call';
|
if (type.includes('ON_CALL')) return 'on_call';
|
||||||
return 'expenses';
|
return 'expenses';
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,9 +16,8 @@ import { Module } from '@nestjs/common';
|
||||||
providers: [
|
providers: [
|
||||||
TimesheetArchiveService,
|
TimesheetArchiveService,
|
||||||
GetTimesheetsOverviewService,
|
GetTimesheetsOverviewService,
|
||||||
|
SharedModule,
|
||||||
],
|
],
|
||||||
exports: [
|
exports: [],
|
||||||
|
|
||||||
],
|
|
||||||
})
|
})
|
||||||
export class TimesheetsModule {}
|
export class TimesheetsModule {}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user