refactor(expenses): major refactor of the CRUDs methods sing sessions data

This commit is contained in:
Matthieu Haineault 2025-10-24 16:15:54 -04:00
parent 60aac39daa
commit 062b9b4640
11 changed files with 277 additions and 199 deletions

136
package-lock.json generated
View File

@ -17,7 +17,7 @@
"@nestjs/platform-express": "^11.1.6",
"@nestjs/schedule": "^6.0.0",
"@nestjs/swagger": "^11.2.0",
"@prisma/client": "^6.17.1",
"@prisma/client": "^6.18.0",
"bullmq": "^5.58.0",
"class-transformer": "^0.5.1",
"class-validator": "^0.14.2",
@ -54,7 +54,7 @@
"globals": "^16.0.0",
"jest": "^29.7.0",
"prettier": "^3.4.2",
"prisma": "^6.17.1",
"prisma": "^6.18.0",
"source-map-support": "^0.5.21",
"supertest": "^7.0.0",
"ts-jest": "^29.2.5",
@ -3268,13 +3268,13 @@
}
},
"node_modules/@nestjs/common": {
"version": "11.1.6",
"resolved": "https://registry.npmjs.org/@nestjs/common/-/common-11.1.6.tgz",
"integrity": "sha512-krKwLLcFmeuKDqngG2N/RuZHCs2ycsKcxWIDgcm7i1lf3sQ0iG03ci+DsP/r3FcT/eJDFsIHnKtNta2LIi7PzQ==",
"version": "11.1.7",
"resolved": "https://registry.npmjs.org/@nestjs/common/-/common-11.1.7.tgz",
"integrity": "sha512-lwlObwGgIlpXSXYOTpfzdCepUyWomz6bv9qzGzzvpgspUxkj0Uz0fUJcvD44V8Ps7QhKW3lZBoYbXrH25UZrbA==",
"dependencies": {
"file-type": "21.0.0",
"iterare": "1.2.1",
"load-esm": "1.0.2",
"load-esm": "1.0.3",
"tslib": "2.8.1",
"uid": "2.0.2"
},
@ -3312,15 +3312,15 @@
}
},
"node_modules/@nestjs/core": {
"version": "11.1.6",
"resolved": "https://registry.npmjs.org/@nestjs/core/-/core-11.1.6.tgz",
"integrity": "sha512-siWX7UDgErisW18VTeJA+x+/tpNZrJewjTBsRPF3JVxuWRuAB1kRoiJcxHgln8Lb5UY9NdvklITR84DUEXD0Cg==",
"version": "11.1.7",
"resolved": "https://registry.npmjs.org/@nestjs/core/-/core-11.1.7.tgz",
"integrity": "sha512-TyXFOwjhHv/goSgJ8i20K78jwTM0iSpk9GBcC2h3mf4MxNy+znI8m7nWjfoACjTkb89cTwDQetfTHtSfGLLaiA==",
"hasInstallScript": true,
"dependencies": {
"@nuxt/opencollective": "0.4.1",
"fast-safe-stringify": "2.1.1",
"iterare": "1.2.1",
"path-to-regexp": "8.2.0",
"path-to-regexp": "8.3.0",
"tslib": "2.8.1",
"uid": "2.0.2"
},
@ -3392,14 +3392,14 @@
}
},
"node_modules/@nestjs/platform-express": {
"version": "11.1.6",
"resolved": "https://registry.npmjs.org/@nestjs/platform-express/-/platform-express-11.1.6.tgz",
"integrity": "sha512-HErwPmKnk+loTq8qzu1up+k7FC6Kqa8x6lJ4cDw77KnTxLzsCaPt+jBvOq6UfICmfqcqCCf3dKXg+aObQp+kIQ==",
"version": "11.1.7",
"resolved": "https://registry.npmjs.org/@nestjs/platform-express/-/platform-express-11.1.7.tgz",
"integrity": "sha512-5T+GLdvTiGPKB4/P4PM9ftKUKNHJy8ThEFhZA3vQnXVL7Vf0rDr07TfVTySVu+XTh85m1lpFVuyFM6u6wLNsRA==",
"dependencies": {
"cors": "2.8.5",
"express": "5.1.0",
"multer": "2.0.2",
"path-to-regexp": "8.2.0",
"path-to-regexp": "8.3.0",
"tslib": "2.8.1"
},
"funding": {
@ -3547,19 +3547,10 @@
}
}
},
"node_modules/@nestjs/swagger/node_modules/path-to-regexp": {
"version": "8.3.0",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz",
"integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/@nestjs/testing": {
"version": "11.1.6",
"resolved": "https://registry.npmjs.org/@nestjs/testing/-/testing-11.1.6.tgz",
"integrity": "sha512-srYzzDNxGvVCe1j0SpTS9/ix75PKt6Sn6iMaH1rpJ6nj2g8vwNrhK0CoJJXvpCYgrnI+2WES2pprYnq8rAMYHA==",
"version": "11.1.7",
"resolved": "https://registry.npmjs.org/@nestjs/testing/-/testing-11.1.7.tgz",
"integrity": "sha512-QbtrgSlc3QVo6RHNxTTlyhaiobLLy8kvhOlgWHsoXRknybuRs7vZg4k5mo3ye6pITGeT3CrWIRpZjUsh5Wps5Q==",
"dev": true,
"dependencies": {
"tslib": "2.8.1"
@ -3667,9 +3658,9 @@
}
},
"node_modules/@prisma/client": {
"version": "6.17.1",
"resolved": "https://registry.npmjs.org/@prisma/client/-/client-6.17.1.tgz",
"integrity": "sha512-zL58jbLzYamjnNnmNA51IOZdbk5ci03KviXCuB0Tydc9btH2kDWsi1pQm2VecviRTM7jGia0OPPkgpGnT3nKvw==",
"version": "6.18.0",
"resolved": "https://registry.npmjs.org/@prisma/client/-/client-6.18.0.tgz",
"integrity": "sha512-jnL2I9gDnPnw4A+4h5SuNn8Gc+1mL1Z79U/3I9eE2gbxJG1oSA+62ByPW4xkeDgwE0fqMzzpAZ7IHxYnLZ4iQA==",
"hasInstallScript": true,
"engines": {
"node": ">=18.18"
@ -3688,60 +3679,60 @@
}
},
"node_modules/@prisma/config": {
"version": "6.17.1",
"resolved": "https://registry.npmjs.org/@prisma/config/-/config-6.17.1.tgz",
"integrity": "sha512-fs8wY6DsvOCzuiyWVckrVs1LOcbY4LZNz8ki4uUIQ28jCCzojTGqdLhN2Jl5lDnC1yI8/gNIKpsWDM8pLhOdwA==",
"version": "6.18.0",
"resolved": "https://registry.npmjs.org/@prisma/config/-/config-6.18.0.tgz",
"integrity": "sha512-rgFzspCpwsE+q3OF/xkp0fI2SJ3PfNe9LLMmuSVbAZ4nN66WfBiKqJKo/hLz3ysxiPQZf8h1SMf2ilqPMeWATQ==",
"devOptional": true,
"dependencies": {
"c12": "3.1.0",
"deepmerge-ts": "7.1.5",
"effect": "3.16.12",
"effect": "3.18.4",
"empathic": "2.0.0"
}
},
"node_modules/@prisma/debug": {
"version": "6.17.1",
"resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-6.17.1.tgz",
"integrity": "sha512-Vf7Tt5Wh9XcndpbmeotuqOMLWPTjEKCsgojxXP2oxE1/xYe7PtnP76hsouG9vis6fctX+TxgmwxTuYi/+xc7dQ==",
"version": "6.18.0",
"resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-6.18.0.tgz",
"integrity": "sha512-PMVPMmxPj0ps1VY75DIrT430MoOyQx9hmm174k6cmLZpcI95rAPXOQ+pp8ANQkJtNyLVDxnxVJ0QLbrm/ViBcg==",
"devOptional": true
},
"node_modules/@prisma/engines": {
"version": "6.17.1",
"resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-6.17.1.tgz",
"integrity": "sha512-D95Ik3GYZkqZ8lSR4EyFOJ/tR33FcYRP8kK61o+WMsyD10UfJwd7+YielflHfKwiGodcqKqoraWw8ElAgMDbPw==",
"version": "6.18.0",
"resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-6.18.0.tgz",
"integrity": "sha512-i5RzjGF/ex6AFgqEe2o1IW8iIxJGYVQJVRau13kHPYEL1Ck8Zvwuzamqed/1iIljs5C7L+Opiz5TzSsUebkriA==",
"devOptional": true,
"hasInstallScript": true,
"dependencies": {
"@prisma/debug": "6.17.1",
"@prisma/engines-version": "6.17.1-1.272a37d34178c2894197e17273bf937f25acdeac",
"@prisma/fetch-engine": "6.17.1",
"@prisma/get-platform": "6.17.1"
"@prisma/debug": "6.18.0",
"@prisma/engines-version": "6.18.0-8.34b5a692b7bd79939a9a2c3ef97d816e749cda2f",
"@prisma/fetch-engine": "6.18.0",
"@prisma/get-platform": "6.18.0"
}
},
"node_modules/@prisma/engines-version": {
"version": "6.17.1-1.272a37d34178c2894197e17273bf937f25acdeac",
"resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-6.17.1-1.272a37d34178c2894197e17273bf937f25acdeac.tgz",
"integrity": "sha512-17140E3huOuD9lMdJ9+SF/juOf3WR3sTJMVyyenzqUPbuH+89nPhSWcrY+Mf7tmSs6HvaO+7S+HkELinn6bhdg==",
"version": "6.18.0-8.34b5a692b7bd79939a9a2c3ef97d816e749cda2f",
"resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-6.18.0-8.34b5a692b7bd79939a9a2c3ef97d816e749cda2f.tgz",
"integrity": "sha512-T7Af4QsJQnSgWN1zBbX+Cha5t4qjHRxoeoWpK4JugJzG/ipmmDMY5S+O0N1ET6sCBNVkf6lz+Y+ZNO9+wFU8pQ==",
"devOptional": true
},
"node_modules/@prisma/fetch-engine": {
"version": "6.17.1",
"resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-6.17.1.tgz",
"integrity": "sha512-AYZiHOs184qkDMiTeshyJCtyL4yERkjfTkJiSJdYuSfc24m94lTNL5+GFinZ6vVz+ktX4NJzHKn1zIFzGTWrWg==",
"version": "6.18.0",
"resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-6.18.0.tgz",
"integrity": "sha512-TdaBvTtBwP3IoqVYoGIYpD4mWlk0pJpjTJjir/xLeNWlwog7Sl3bD2J0jJ8+5+q/6RBg+acb9drsv5W6lqae7A==",
"devOptional": true,
"dependencies": {
"@prisma/debug": "6.17.1",
"@prisma/engines-version": "6.17.1-1.272a37d34178c2894197e17273bf937f25acdeac",
"@prisma/get-platform": "6.17.1"
"@prisma/debug": "6.18.0",
"@prisma/engines-version": "6.18.0-8.34b5a692b7bd79939a9a2c3ef97d816e749cda2f",
"@prisma/get-platform": "6.18.0"
}
},
"node_modules/@prisma/get-platform": {
"version": "6.17.1",
"resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-6.17.1.tgz",
"integrity": "sha512-AKEn6fsfz0r482S5KRDFlIGEaq9wLNcgalD1adL+fPcFFblIKs1sD81kY/utrHdqKuVC6E1XSRpegDK3ZLL4Qg==",
"version": "6.18.0",
"resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-6.18.0.tgz",
"integrity": "sha512-uXNJCJGhxTCXo2B25Ta91Rk1/Nmlqg9p7G9GKh8TPhxvAyXCvMNQoogj4JLEUy+3ku8g59cpyQIKFhqY2xO2bg==",
"devOptional": true,
"dependencies": {
"@prisma/debug": "6.17.1"
"@prisma/debug": "6.18.0"
}
},
"node_modules/@scarf/scarf": {
@ -6953,9 +6944,9 @@
"integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="
},
"node_modules/effect": {
"version": "3.16.12",
"resolved": "https://registry.npmjs.org/effect/-/effect-3.16.12.tgz",
"integrity": "sha512-N39iBk0K71F9nb442TLbTkjl24FLUzuvx2i1I2RsEAQsdAdUTuUoW0vlfUXgkMTUOnYqKnWcFfqw4hK4Pw27hg==",
"version": "3.18.4",
"resolved": "https://registry.npmjs.org/effect/-/effect-3.18.4.tgz",
"integrity": "sha512-b1LXQJLe9D11wfnOKAk3PKxuqYshQ0Heez+y5pnkd3jLj1yx9QhM72zZ9uUrOQyNvrs2GZZd/3maL0ZV18YuDA==",
"devOptional": true,
"dependencies": {
"@standard-schema/spec": "^1.0.0",
@ -9542,9 +9533,9 @@
"dev": true
},
"node_modules/load-esm": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/load-esm/-/load-esm-1.0.2.tgz",
"integrity": "sha512-nVAvWk/jeyrWyXEAs84mpQCYccxRqgKY4OznLuJhJCa0XsPSfdOIr2zvBZEj3IHEHbX97jjscKRRV539bW0Gpw==",
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/load-esm/-/load-esm-1.0.3.tgz",
"integrity": "sha512-v5xlu8eHD1+6r8EHTg6hfmO97LN8ugKtiXcy5e6oN72iD2r6u0RPfLl6fxM+7Wnh2ZRq15o0russMst44WauPA==",
"funding": [
{
"type": "github",
@ -10468,11 +10459,12 @@
}
},
"node_modules/path-to-regexp": {
"version": "8.2.0",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.2.0.tgz",
"integrity": "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==",
"engines": {
"node": ">=16"
"version": "8.3.0",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz",
"integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/path-type": {
@ -10703,14 +10695,14 @@
}
},
"node_modules/prisma": {
"version": "6.17.1",
"resolved": "https://registry.npmjs.org/prisma/-/prisma-6.17.1.tgz",
"integrity": "sha512-ac6h0sM1Tg3zu8NInY+qhP/S9KhENVaw9n1BrGKQVFu05JT5yT5Qqqmb8tMRIE3ZXvVj4xcRA5yfrsy4X7Yy5g==",
"version": "6.18.0",
"resolved": "https://registry.npmjs.org/prisma/-/prisma-6.18.0.tgz",
"integrity": "sha512-bXWy3vTk8mnRmT+SLyZBQoC2vtV9Z8u7OHvEu+aULYxwiop/CPiFZ+F56KsNRNf35jw+8wcu8pmLsjxpBxAO9g==",
"devOptional": true,
"hasInstallScript": true,
"dependencies": {
"@prisma/config": "6.17.1",
"@prisma/engines": "6.17.1"
"@prisma/config": "6.18.0",
"@prisma/engines": "6.18.0"
},
"bin": {
"prisma": "build/index.js"

View File

@ -49,7 +49,7 @@
"@nestjs/platform-express": "^11.1.6",
"@nestjs/schedule": "^6.0.0",
"@nestjs/swagger": "^11.2.0",
"@prisma/client": "^6.17.1",
"@prisma/client": "^6.18.0",
"bullmq": "^5.58.0",
"class-transformer": "^0.5.1",
"class-validator": "^0.14.2",
@ -86,7 +86,7 @@
"globals": "^16.0.0",
"jest": "^29.7.0",
"prettier": "^3.4.2",
"prisma": "^6.17.1",
"prisma": "^6.18.0",
"source-map-support": "^0.5.21",
"supertest": "^7.0.0",
"ts-jest": "^29.2.5",

View File

@ -24,10 +24,7 @@ model Users {
role Roles @default(GUEST)
employee Employees? @relation("UserEmployee")
customer Customers? @relation("UserCustomer")
oauth_sessions OAuthSessions[] @relation("UserOAuthSessions")
employees_archive EmployeesArchive[] @relation("UsersToEmployeesToArchive")
customer_archive CustomersArchive[] @relation("UserToCustomersToArchive")
preferences Preferences? @relation("UserPreferences")
@@map("users")
@ -49,61 +46,13 @@ model Employees {
crew Employees[] @relation("EmployeeSupervisor")
archive EmployeesArchive[] @relation("EmployeeToArchive")
timesheet Timesheets[] @relation("TimesheetEmployee")
leave_request LeaveRequests[] @relation("LeaveRequestEmployee")
supervisor_archive EmployeesArchive[] @relation("EmployeeSupervisorToArchive")
schedule_presets SchedulePresets[] @relation("SchedulePreset")
@@map("employees")
}
model EmployeesArchive {
id Int @id @default(autoincrement())
employee Employees @relation("EmployeeToArchive", fields: [employee_id], references: [id])
employee_id Int
user_id String @db.Uuid
user Users @relation("UsersToEmployeesToArchive", fields: [user_id], references: [id])
supervisor Employees? @relation("EmployeeSupervisorToArchive", fields: [supervisor_id], references: [id])
supervisor_id Int?
archived_at DateTime @default(now())
first_name String
last_name String
job_title String?
is_supervisor Boolean
external_payroll_id Int
company_code Int
first_work_day DateTime @db.Date
last_work_day DateTime @db.Date
@@map("employees_archive")
}
model Customers {
id Int @id @default(autoincrement())
user Users @relation("UserCustomer", fields: [user_id], references: [id])
user_id String @unique @db.Uuid
invoice_id Int? @unique
archive CustomersArchive[] @relation("CustomerToArchive")
@@map("customers")
}
model CustomersArchive {
id Int @id @default(autoincrement())
customer Customers @relation("CustomerToArchive", fields: [customer_id], references: [id])
customer_id Int
user Users @relation("UserToCustomersToArchive", fields: [user_id], references: [id])
user_id String @db.Uuid
archived_at DateTime @default(now())
invoice_id Int? @unique
@@map("customers_archive")
}
model LeaveRequests {
id Int @id @default(autoincrement())
employee Employees @relation("LeaveRequestEmployee", fields: [employee_id], references: [id])
@ -216,13 +165,6 @@ model SchedulePresetShifts {
@@map("schedule_preset_shifts")
}
model Shifts {
id Int @id @default(autoincrement())
timesheet Timesheets @relation("ShiftTimesheet", fields: [timesheet_id], references: [id])
@ -283,7 +225,7 @@ model Expenses {
attachment Int?
date DateTime @db.Date
amount Decimal @db.Money
amount Decimal? @db.Decimal(12,2)
mileage Decimal? @db.Decimal(12,2)
comment String
supervisor_comment String?
@ -305,7 +247,7 @@ model ExpensesArchive {
archived_at DateTime @default(now())
bank_code_id Int
date DateTime @db.Date
amount Decimal? @db.Money
amount Decimal? @db.Decimal(12,2)
mileage Decimal? @db.Decimal(12,2)
comment String?
is_approved Boolean

View File

@ -1,7 +1,8 @@
import { Body, Controller, Param, ParseIntPipe, Post } from "@nestjs/common";
import { Body, Controller, Delete, Get, Param, ParseIntPipe, Patch, Post } from "@nestjs/common";
import { PrismaService } from "src/prisma/prisma.service";
import { ExpenseDto } from "../dtos/expense.dto";
import { CreateResult, ExpenseUpsertService } from "../services/expense-upsert.service";
import { CreateResult, ExpenseUpsertService, UpdateResult } from "../services/expense-upsert.service";
import { updateExpenseDto } from "src/modules/expenses/dtos/update-expense.dto";
@Controller('expense')
@ -11,11 +12,21 @@ export class ExpenseController {
private readonly upsert_service: ExpenseUpsertService,
){}
@Post(':timesheet_id')
create(
@Param('timesheet_id', ParseIntPipe) timesheet_id: number,
@Body() dto: ExpenseDto): Promise<CreateResult>{
return this.upsert_service.createExpense(timesheet_id, dto);
}
// @Post(':timesheet_id')
// create(
// @Param('timesheet_id', ParseIntPipe) timesheet_id: number,
// @Body() dto: ExpenseDto): Promise<CreateResult>{
// return this.upsert_service.createExpense(timesheet_id, dto);
// }
@Patch()
update(
@Body() body: { update :{ id: number; dto: updateExpenseDto }}): Promise<UpdateResult>{
return this.upsert_service.updateExpense(body.update);
}
@Delete(':expense_id')
remove(@Param('expense_id') expense_id: number) {
return this.upsert_service.deleteExpense(expense_id);
}
}

View File

@ -3,9 +3,9 @@ import { IsBoolean, IsInt, IsOptional, IsString, MaxLength } from "class-validat
export class ExpenseDto {
@IsInt() bank_code_id!: number;
@IsInt() timesheet_id!: number;
@IsString() @IsOptional() attachment?: string;
@IsInt() @IsOptional() attachment?: number;
@IsString() date!: string;
@IsString() date!: string;
@IsInt() @IsOptional() amount?: number;
@IsInt() @IsOptional() mileage?: number;
@IsString() @MaxLength(280) comment!: string;

View File

@ -1,7 +1,8 @@
export class GetExpenseDto {
id: number;
timesheet_id: number;
bank_code_id: number;
attachment?: string;
attachment?: number;
date: string;
comment: string;
mileage?: number;

View File

@ -1,23 +1,15 @@
// import { ExpensesController } from "./controllers/expenses.controller";
// import { Module } from "@nestjs/common";
// import { ExpensesQueryService } from "./services/expenses-query.service";
// import { BusinessLogicsModule } from "src/modules/business-logics/business-logics.module";
// import { ExpensesCommandService } from "./services/expenses-command.service";
// import { ExpensesArchivalService } from "./services/expenses-archival.service";
// import { SharedModule } from "../shared/shared.module";
import { ExpensesArchivalService } from "./services/expenses-archival.service";
import { BusinessLogicsModule } from "src/modules/business-logics/business-logics.module";
import { ExpenseUpsertService } from "src/modules/expenses/services/expense-upsert.service";
import { ExpenseController } from "src/modules/expenses/controllers/expense.controller";
import { SharedModule } from "../shared/shared.module";
import { Module } from "@nestjs/common";
// @Module({
// imports: [BusinessLogicsModule, SharedModule],
// controllers: [ExpensesController],
// providers: [
// ExpensesQueryService,
// ExpensesArchivalService,
// ExpensesCommandService,
// ],
// exports: [
// ExpensesQueryService,
// ExpensesArchivalService,
// ],
// })
@Module({
imports: [ BusinessLogicsModule, SharedModule ],
controllers: [ ExpenseController ],
providers: [ ExpenseUpsertService, ExpensesArchivalService ],
exports: [ ExpensesArchivalService ],
})
// export class ExpensesModule {}
export class ExpensesModule {}

View File

@ -1,3 +1,5 @@
export const toDateFromString = (ymd: string): Date => {
return new Date(`${ymd}T00:00:00:000Z`);
}
export const toStringFromDate = (date: Date) =>
date.toISOString().slice(0,10);

View File

@ -1,41 +1,171 @@
import { Injectable } from "@nestjs/common";
import { PrismaService } from "src/prisma/prisma.service";
import { GetExpenseDto } from "../dtos/get-expense.dto";
import { toDateFromString, toStringFromDate } from "../helpers/expenses-date-time-helpers";
import { Injectable, NotFoundException } from "@nestjs/common";
import { updateExpenseDto } from "../dtos/update-expense.dto";
import { GetExpenseDto } from "../dtos/get-expense.dto";
import { PrismaService } from "src/prisma/prisma.service";
import { ExpenseDto } from "../dtos/expense.dto";
import { toDateFromString } from "../helpers/expenses-date-time-helpers";
type Normalized = { date: Date; comment: string; supervisor_comment: string; };
type Normalized = { date: Date; comment: string; supervisor_comment?: string; };
export type CreateResult = { ok: true; data: GetExpenseDto } | { ok: false; error: any };
export type CreateResult = { ok: true; data: GetExpenseDto } | { ok: false; error: any };
export type UpdatePayload = { id: number; dto: updateExpenseDto };
export type UpdateResult = { ok: true; id: number; data: GetExpenseDto } | { ok: false; id: number; error: any };
export type DeleteResult = { ok: true; id: number; } | { ok: false; id: number; error: any };
type NormedOk = { dto: GetExpenseDto; normed: Normalized };
type NormedErr = { error: any };
export type UpdateResult = { ok: true; id: number; data: GetExpenseDto } | { ok: false; id: number; error: any };
export type DeleteResult = { ok: true; id: number; } | { ok: false; id: number; error: any };
@Injectable()
export class ExpenseUpsertService {
constructor(private readonly prisma: PrismaService){}
constructor(private readonly prisma: PrismaService) { }
//_________________________________________________________________
// CREATE
//_________________________________________________________________
//normalized frontend data to match DB
async createExpense(timesheet_id: number, dto: ExpenseDto){
const normed_expense = this.normalizeExpenseDto(dto)
async createExpense(timesheet_id: number, dto: ExpenseDto): Promise<CreateResult> {
try {
//normalize strings and dates
const normed_expense = this.normalizeExpenseDto(dto);
//parse numbers
const parsed_amount = this.parseOptionalNumber(dto.amount, "amount");
const parsed_mileage = this.parseOptionalNumber(dto.mileage, "mileage");
const parsed_attachment = this.parseOptionalNumber(dto.attachment, "attachment");
//create a new expense
const expense = await this.prisma.expenses.create({
data: {
timesheet_id,
bank_code_id: dto.bank_code_id,
attachment: parsed_attachment,
date: normed_expense.date,
amount: parsed_amount,
mileage: parsed_mileage,
comment: normed_expense.comment,
supervisor_comment: normed_expense.supervisor_comment,
is_approved: dto.is_approved,
},
//return the newly created expense with id
select: {
id: true,
timesheet_id: true,
bank_code_id: true,
attachment: true,
date: true,
amount: true,
mileage: true,
comment: true,
supervisor_comment: true,
is_approved: true,
},
});
//build an object to return to the frontend to display
const created: GetExpenseDto = {
id: expense.id,
timesheet_id: expense.timesheet_id,
bank_code_id: expense.bank_code_id,
attachment: expense.attachment ?? undefined,
date: toStringFromDate(expense.date),
amount: expense.amount?.toNumber(),
mileage: expense.mileage?.toNumber(),
comment: expense.comment,
supervisor_comment: expense.supervisor_comment ?? undefined,
is_approved: expense.is_approved,
};
return { ok: true, data: created }
} catch (error) {
return { ok: false, error: error }
}
}
//_________________________________________________________________
// UPDATE
//_________________________________________________________________
async updateExpense({id, dto}: UpdatePayload): Promise<UpdateResult> {
try {
//checks for modifications
const data: Record<string, unknown> = {};
if (dto.date !== undefined) data.date = toDateFromString(dto.date);
if (dto.comment !== undefined) data.comment = this.truncate280(dto.comment);
if (dto.attachment !== undefined) data.attachment = this.parseOptionalNumber(dto.attachment, "attachment");
if (dto.amount !== undefined) data.amount = this.parseOptionalNumber(dto.amount, "amount");
if (dto.mileage !== undefined) data.mileage = this.parseOptionalNumber(dto.mileage, "mileage");
if (dto.bank_code_id !== undefined) data.bank_code_id = dto.bank_code_id;
if (dto.supervisor_comment !== undefined) {
data.supervisor_comment = dto.supervisor_comment?.trim()
? this.truncate280(dto.supervisor_comment.trim())
: null;
}
//return an error if no fields needs an update
if(!Object.keys(data).length) {
return { ok: false, id, error: new Error("Nothing to update")};
}
const expense = await this.prisma.expenses.update({
where: { id },
data,
select: {
id: true,
timesheet_id: true,
bank_code_id: true,
attachment: true,
date: true,
amount: true,
mileage: true,
comment: true,
supervisor_comment: true,
is_approved: true,
},
});
const updated: GetExpenseDto = {
id: expense.id,
timesheet_id: expense.timesheet_id,
bank_code_id: expense.bank_code_id,
attachment: expense.attachment ?? undefined,
date: toStringFromDate(expense.date),
amount: expense.amount?.toNumber(),
mileage: expense.mileage?.toNumber(),
comment: expense.comment,
supervisor_comment: expense.supervisor_comment ?? undefined,
is_approved: expense.is_approved,
};
return { ok: true, id: expense.id, data: updated };
} catch (error) {
return { ok: false, id: id, error: error}
}
}
//_________________________________________________________________
// DELETE
//_________________________________________________________________
async deleteExpense(expense_id: number): Promise<DeleteResult> {
try {
await this.prisma.$transaction(async (tx) => {
const expense = await tx.expenses.findUnique({
where: { id: expense_id },
select: { id: true },
});
if(!expense) throw new NotFoundException(`Expense with id: ${expense_id} not found`);
await tx.expenses.delete({ where: { id: expense_id }});
return { success: true };
});
return { ok: true, id: expense_id };
} catch (error) {
return { ok: false, id: expense_id, error };
}
}
//_________________________________________________________________
// LOCAL HELPERS
//_________________________________________________________________
//makes sure that comments are the right length the date is of Date type
private normalizeExpenseDto(dto: ExpenseDto): Normalized {
const date = toDateFromString(dto.date);
const date = toDateFromString(dto.date);
const comment = this.truncate280(dto.comment);
const supervisor_comment = this.truncate280(dto.supervisor_comment? dto.supervisor_comment : '');
const supervisor_comment =
dto.supervisor_comment && dto.supervisor_comment.trim()
? this.truncate280(dto.supervisor_comment.trim())
: undefined;
return { date, comment, supervisor_comment };
}
@ -43,4 +173,12 @@ export class ExpenseUpsertService {
private truncate280 = (input: string): string => {
return input.length > 280 ? input.slice(0, 280) : input;
}
//makes sure that the type of data of numeric values is valid
private parseOptionalNumber = (value: unknown, field: string) => {
if (value == null) return undefined;
const parsed = Number(value);
if (Number.isNaN(parsed)) throw new Error(`Invalid value : ${value} for ${field}`);
return parsed;
};
}

View File

@ -12,12 +12,12 @@ export class ShiftController {
private readonly get_service: ShiftsGetService
){}
@Get()
async getShiftsByIds(
@Query("shift_ids") shift_ids: string) {
const parsed = shift_ids.split(/,\s*/).map(value => Number(value)).filter(Number.isFinite);
return this.get_service.getShiftByShiftId(parsed);
}
// @Get()
// async getShiftsByIds(
// @Query("shift_ids") shift_ids: string) {
// const parsed = shift_ids.split(/,\s*/).map(value => Number(value)).filter(Number.isFinite);
// return this.get_service.getShiftByShiftId(parsed);
// }
@Post(':timesheet_id')
createBatch(

View File

@ -336,17 +336,17 @@ export class ShiftsUpsertService {
//finds shifts using shit_ids
//recalc overtime shifts after delete
//blocs deletion if approved
async deleteShift(shift_id: number) {
return await this.prisma.$transaction(async (tx) =>{
async deleteShift(shift_id: number) {
return await this.prisma.$transaction(async (tx) => {
const shift = await tx.shifts.findUnique({
where: { id: shift_id },
where: { id: shift_id },
select: { id: true, date: true, timesheet_id: true },
});
if(!shift) throw new NotFoundException(`Shift with id #${shift_id} not found`);
if (!shift) throw new NotFoundException(`Shift with id #${shift_id} not found`);
await tx.shifts.delete({ where: { id: shift_id } });
const summary = await this.overtime.getWeekOvertimeSummary( shift.timesheet_id, shift.date, tx);
const summary = await this.overtime.getWeekOvertimeSummary(shift.timesheet_id, shift.date, tx);
return {
success: true,
overtime: summary