From 062b9b4640e1c07e54498109f66847d5bb0c2116 Mon Sep 17 00:00:00 2001 From: Matthieu Haineault Date: Fri, 24 Oct 2025 16:15:54 -0400 Subject: [PATCH] refactor(expenses): major refactor of the CRUDs methods sing sessions data --- package-lock.json | 136 +++++++------- package.json | 4 +- prisma/schema.prisma | 62 +------ .../controllers/expense.controller.ts | 27 ++- src/modules/expenses/dtos/expense.dto.ts | 4 +- src/modules/expenses/dtos/get-expense.dto.ts | 3 +- src/modules/expenses/expenses.module.ts | 34 ++-- .../helpers/expenses-date-time-helpers.ts | 4 +- .../services/expense-upsert.service.ts | 174 ++++++++++++++++-- .../shifts/controllers/shift.controller.ts | 12 +- .../shifts/services/shifts-upsert.service.ts | 16 +- 11 files changed, 277 insertions(+), 199 deletions(-) diff --git a/package-lock.json b/package-lock.json index 1180132..b3363fc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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" diff --git a/package.json b/package.json index c8db806..589f94f 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 1d62fbd..60503eb 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -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 diff --git a/src/modules/expenses/controllers/expense.controller.ts b/src/modules/expenses/controllers/expense.controller.ts index bb37634..e5e5f56 100644 --- a/src/modules/expenses/controllers/expense.controller.ts +++ b/src/modules/expenses/controllers/expense.controller.ts @@ -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{ + return this.upsert_service.createExpense(timesheet_id, dto); + } - // @Post(':timesheet_id') - // create( - // @Param('timesheet_id', ParseIntPipe) timesheet_id: number, - // @Body() dto: ExpenseDto): Promise{ - // return this.upsert_service.createExpense(timesheet_id, dto); - // } + @Patch() + update( + @Body() body: { update :{ id: number; dto: updateExpenseDto }}): Promise{ + return this.upsert_service.updateExpense(body.update); + } + + @Delete(':expense_id') + remove(@Param('expense_id') expense_id: number) { + return this.upsert_service.deleteExpense(expense_id); + } } \ No newline at end of file diff --git a/src/modules/expenses/dtos/expense.dto.ts b/src/modules/expenses/dtos/expense.dto.ts index 51cc174..7e037e8 100644 --- a/src/modules/expenses/dtos/expense.dto.ts +++ b/src/modules/expenses/dtos/expense.dto.ts @@ -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; diff --git a/src/modules/expenses/dtos/get-expense.dto.ts b/src/modules/expenses/dtos/get-expense.dto.ts index 6c3056e..773a1a7 100644 --- a/src/modules/expenses/dtos/get-expense.dto.ts +++ b/src/modules/expenses/dtos/get-expense.dto.ts @@ -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; diff --git a/src/modules/expenses/expenses.module.ts b/src/modules/expenses/expenses.module.ts index 490ec9e..85f62cb 100644 --- a/src/modules/expenses/expenses.module.ts +++ b/src/modules/expenses/expenses.module.ts @@ -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 {} \ No newline at end of file +export class ExpensesModule {} \ No newline at end of file diff --git a/src/modules/expenses/helpers/expenses-date-time-helpers.ts b/src/modules/expenses/helpers/expenses-date-time-helpers.ts index ce14e69..ef81852 100644 --- a/src/modules/expenses/helpers/expenses-date-time-helpers.ts +++ b/src/modules/expenses/helpers/expenses-date-time-helpers.ts @@ -1,3 +1,5 @@ export const toDateFromString = (ymd: string): Date => { return new Date(`${ymd}T00:00:00:000Z`); -} \ No newline at end of file +} +export const toStringFromDate = (date: Date) => + date.toISOString().slice(0,10); \ No newline at end of file diff --git a/src/modules/expenses/services/expense-upsert.service.ts b/src/modules/expenses/services/expense-upsert.service.ts index 5e47a1e..d2a332c 100644 --- a/src/modules/expenses/services/expense-upsert.service.ts +++ b/src/modules/expenses/services/expense-upsert.service.ts @@ -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 { + 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 { + try { + //checks for modifications + const data: Record = {}; + 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 { + 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; + }; } \ No newline at end of file diff --git a/src/modules/shifts/controllers/shift.controller.ts b/src/modules/shifts/controllers/shift.controller.ts index bffa9cb..b35766d 100644 --- a/src/modules/shifts/controllers/shift.controller.ts +++ b/src/modules/shifts/controllers/shift.controller.ts @@ -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( diff --git a/src/modules/shifts/services/shifts-upsert.service.ts b/src/modules/shifts/services/shifts-upsert.service.ts index e63380b..cc0e790 100644 --- a/src/modules/shifts/services/shifts-upsert.service.ts +++ b/src/modules/shifts/services/shifts-upsert.service.ts @@ -336,20 +336,20 @@ 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); - return { - success: true, - overtime: summary + const summary = await this.overtime.getWeekOvertimeSummary(shift.timesheet_id, shift.date, tx); + return { + success: true, + overtime: summary }; }); }