Merge branch 'main' of git.targo.ca:Targo/targo_backend
This commit is contained in:
commit
e7213c62ed
|
|
@ -537,7 +537,7 @@
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"/preferences/update_preferences": {
|
"/preferences/update": {
|
||||||
"patch": {
|
"patch": {
|
||||||
"operationId": "PreferencesController_updatePreferences",
|
"operationId": "PreferencesController_updatePreferences",
|
||||||
"parameters": [],
|
"parameters": [],
|
||||||
|
|
@ -560,6 +560,99 @@
|
||||||
"Preferences"
|
"Preferences"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"/preferences": {
|
||||||
|
"get": {
|
||||||
|
"operationId": "PreferencesController_findPreferences",
|
||||||
|
"parameters": [],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tags": [
|
||||||
|
"Preferences"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/module_access": {
|
||||||
|
"get": {
|
||||||
|
"operationId": "ModuleAccessController_findAccess",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "employee_email",
|
||||||
|
"required": true,
|
||||||
|
"in": "query",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tags": [
|
||||||
|
"ModuleAccess"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/module_access/update": {
|
||||||
|
"patch": {
|
||||||
|
"operationId": "ModuleAccessController_updateAccess",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "employee_email",
|
||||||
|
"required": true,
|
||||||
|
"in": "query",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"requestBody": {
|
||||||
|
"required": true,
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/ModuleAccess"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tags": [
|
||||||
|
"ModuleAccess"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/module_access/revoke": {
|
||||||
|
"patch": {
|
||||||
|
"operationId": "ModuleAccessController_revokeModuleAccess",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "employee_email",
|
||||||
|
"required": true,
|
||||||
|
"in": "query",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tags": [
|
||||||
|
"ModuleAccess"
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"info": {
|
"info": {
|
||||||
|
|
@ -778,6 +871,10 @@
|
||||||
"PreferencesDto": {
|
"PreferencesDto": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {}
|
"properties": {}
|
||||||
|
},
|
||||||
|
"ModuleAccess": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
197
prisma-legacy/schema.prisma
Normal file
197
prisma-legacy/schema.prisma
Normal file
|
|
@ -0,0 +1,197 @@
|
||||||
|
generator client {
|
||||||
|
provider = "prisma-client-js"
|
||||||
|
output = "../node_modules/@prisma/client-legacy"
|
||||||
|
}
|
||||||
|
|
||||||
|
datasource db {
|
||||||
|
provider = "postgresql"
|
||||||
|
url = env("DATABASE_URL_LEGACY")
|
||||||
|
}
|
||||||
|
|
||||||
|
model codeDesjardins {
|
||||||
|
id String @id @map("_id") @db.VarChar(50)
|
||||||
|
code String @db.VarChar(50)
|
||||||
|
label String @db.VarChar(50)
|
||||||
|
description String @db.VarChar(250)
|
||||||
|
}
|
||||||
|
|
||||||
|
model customers {
|
||||||
|
id String @id @map("_id") @db.Uuid
|
||||||
|
user_id String? @db.VarChar(50)
|
||||||
|
email String? @db.VarChar(50)
|
||||||
|
first_name String? @db.VarChar(50)
|
||||||
|
last_name String? @db.VarChar(50)
|
||||||
|
phone_number String? @db.VarChar(50)
|
||||||
|
address String? @db.VarChar(255)
|
||||||
|
created_at BigInt?
|
||||||
|
updated_at BigInt?
|
||||||
|
created_by String? @db.VarChar(50)
|
||||||
|
}
|
||||||
|
|
||||||
|
model dealers {
|
||||||
|
id String @id @map("_id") @db.Uuid
|
||||||
|
user_id String? @db.VarChar(50)
|
||||||
|
email String? @db.VarChar(50)
|
||||||
|
first_name String? @db.VarChar(50)
|
||||||
|
last_name String? @db.VarChar(50)
|
||||||
|
phone_number String? @db.VarChar(50)
|
||||||
|
created_at BigInt?
|
||||||
|
updated_at BigInt?
|
||||||
|
created_by String? @db.VarChar(50)
|
||||||
|
}
|
||||||
|
|
||||||
|
model employee_shift_template {
|
||||||
|
id String @id @map("_id") @db.Uuid
|
||||||
|
employee_id String @db.VarChar
|
||||||
|
day_of_the_week String @db.VarChar
|
||||||
|
start_time BigInt
|
||||||
|
end_time BigInt
|
||||||
|
created_at BigInt
|
||||||
|
updated_at BigInt
|
||||||
|
}
|
||||||
|
|
||||||
|
model employees {
|
||||||
|
id String @id @map("_id") @db.Uuid
|
||||||
|
user_id String? @db.VarChar(50)
|
||||||
|
employee_number String? @db.VarChar(50)
|
||||||
|
email String? @db.VarChar(50)
|
||||||
|
first_name String? @db.VarChar(50)
|
||||||
|
last_name String? @db.VarChar(50)
|
||||||
|
phone_number String? @db.VarChar(50)
|
||||||
|
job_title String? @db.VarChar(50)
|
||||||
|
company Int?
|
||||||
|
supervisor String? @db.VarChar(50)
|
||||||
|
is_supervisor Boolean?
|
||||||
|
onboarding BigInt?
|
||||||
|
offboarding BigInt?
|
||||||
|
regular_hours_day Float? @db.Real
|
||||||
|
hours_bank_max Int?
|
||||||
|
created_at BigInt?
|
||||||
|
updated_at BigInt?
|
||||||
|
created_by String? @db.VarChar
|
||||||
|
}
|
||||||
|
|
||||||
|
model expenses {
|
||||||
|
id String @id @map("_id") @db.Uuid
|
||||||
|
time_sheet_id String? @db.VarChar(50)
|
||||||
|
date String? @db.VarChar(50)
|
||||||
|
code String? @db.VarChar(50)
|
||||||
|
value Float? @db.Real
|
||||||
|
description String? @db.VarChar
|
||||||
|
evidence_id String? @db.VarChar
|
||||||
|
status Boolean?
|
||||||
|
created_at BigInt?
|
||||||
|
updated_at BigInt?
|
||||||
|
supervisor_note String? @db.VarChar(255)
|
||||||
|
}
|
||||||
|
|
||||||
|
model hours_bank {
|
||||||
|
id String @id @map("_id") @db.Uuid
|
||||||
|
employee_id String? @db.VarChar(50)
|
||||||
|
hours Float? @db.Real
|
||||||
|
created_at BigInt?
|
||||||
|
updated_at BigInt?
|
||||||
|
}
|
||||||
|
|
||||||
|
model mileage_bank {
|
||||||
|
id String @id @map("_id") @db.Uuid
|
||||||
|
employee_id String? @db.VarChar(50)
|
||||||
|
mileage Int?
|
||||||
|
year Int?
|
||||||
|
}
|
||||||
|
|
||||||
|
model shifts {
|
||||||
|
id String @id @map("_id") @db.Uuid
|
||||||
|
time_sheet_id String? @db.VarChar(50)
|
||||||
|
code String? @db.VarChar(50)
|
||||||
|
type String? @db.VarChar(50)
|
||||||
|
date DateTime? @db.Date
|
||||||
|
start_time BigInt?
|
||||||
|
end_time BigInt?
|
||||||
|
comment String? @db.VarChar(255)
|
||||||
|
status Boolean?
|
||||||
|
created_at BigInt?
|
||||||
|
updated_at BigInt?
|
||||||
|
supervisor_note String? @db.VarChar(255)
|
||||||
|
}
|
||||||
|
|
||||||
|
model shifts_of_template {
|
||||||
|
id String @id @map("_id") @db.Uuid
|
||||||
|
model_id String @db.Uuid
|
||||||
|
day_of_the_week String @db.VarChar(50)
|
||||||
|
start_time BigInt
|
||||||
|
end_time BigInt
|
||||||
|
created_at BigInt
|
||||||
|
updated_at BigInt
|
||||||
|
}
|
||||||
|
|
||||||
|
model sick_leave {
|
||||||
|
id String @id @map("_id") @db.Uuid
|
||||||
|
employee_id String? @db.VarChar(50)
|
||||||
|
accumulated Float? @db.Real
|
||||||
|
consumed Float? @db.Real
|
||||||
|
year Int?
|
||||||
|
created_at BigInt?
|
||||||
|
updated_at BigInt?
|
||||||
|
}
|
||||||
|
|
||||||
|
model time_sheet_periods {
|
||||||
|
id String @id @map("_id") @db.Uuid
|
||||||
|
start_date DateTime? @db.Date
|
||||||
|
end_date DateTime? @db.Date
|
||||||
|
payment_date DateTime? @db.Date
|
||||||
|
period_number Int?
|
||||||
|
year Int?
|
||||||
|
}
|
||||||
|
|
||||||
|
model time_sheet_template {
|
||||||
|
id String @id @map("_id") @db.Uuid
|
||||||
|
title String @db.VarChar
|
||||||
|
description String? @db.VarChar
|
||||||
|
created_at BigInt
|
||||||
|
updated_at BigInt
|
||||||
|
}
|
||||||
|
|
||||||
|
model time_sheets {
|
||||||
|
id String @id @map("_id") @db.Uuid
|
||||||
|
employee_id String? @db.VarChar
|
||||||
|
start_date DateTime? @db.Date
|
||||||
|
end_date DateTime? @db.Date
|
||||||
|
status Boolean?
|
||||||
|
banked_hours Float? @db.Real
|
||||||
|
consumed_vacation Float? @db.Real
|
||||||
|
consumed_sick Float? @db.Real
|
||||||
|
period_id String? @db.VarChar(50)
|
||||||
|
period_number Int?
|
||||||
|
created_at BigInt?
|
||||||
|
updated_at BigInt?
|
||||||
|
blocked_week Boolean? @default(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
model users {
|
||||||
|
id String @id @map("_id") @db.VarChar(50)
|
||||||
|
email String @unique @db.VarChar(50)
|
||||||
|
password String @db.VarChar(255)
|
||||||
|
type String @db.VarChar(50)
|
||||||
|
role String @db.VarChar(50)
|
||||||
|
is_verified Boolean?
|
||||||
|
verification_token String?
|
||||||
|
otp_token String?
|
||||||
|
refresh_token String?
|
||||||
|
created_at BigInt?
|
||||||
|
updated_at BigInt?
|
||||||
|
created_by String @db.VarChar(255)
|
||||||
|
last_login BigInt
|
||||||
|
}
|
||||||
|
|
||||||
|
model vacation_leave {
|
||||||
|
id String @id @map("_id") @db.Uuid
|
||||||
|
employee_id String? @db.VarChar(50)
|
||||||
|
accumulated Float? @db.Real
|
||||||
|
consumed Float? @db.Real
|
||||||
|
created_at BigInt?
|
||||||
|
updated_at BigInt?
|
||||||
|
start_year Int?
|
||||||
|
end_year Int?
|
||||||
|
max_hours_per_year Float? @db.Real
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,67 @@
|
||||||
|
/*
|
||||||
|
Warnings:
|
||||||
|
|
||||||
|
- You are about to drop the column `patch` on the `attachment_variants` table. All the data in the column will be lost.
|
||||||
|
- You are about to alter the column `amount` on the `expenses` table. The data in that column could be lost. The data in that column will be cast from `Money` to `Decimal(12,2)`.
|
||||||
|
- You are about to alter the column `amount` on the `expenses_archive` table. The data in that column could be lost. The data in that column will be cast from `Money` to `Decimal(12,2)`.
|
||||||
|
- You are about to drop the column `date` on the `leave_requests` table. All the data in the column will be lost.
|
||||||
|
- You are about to drop the `customers` table. If the table is not empty, all the data it contains will be lost.
|
||||||
|
- You are about to drop the `customers_archive` table. If the table is not empty, all the data it contains will be lost.
|
||||||
|
- You are about to drop the `employees_archive` table. If the table is not empty, all the data it contains will be lost.
|
||||||
|
- A unique constraint covering the columns `[employee_id,leave_type,dates]` on the table `leave_requests` will be added. If there are existing duplicate values, this will fail.
|
||||||
|
- Added the required column `path` to the `attachment_variants` table without a default value. This is not possible if the table is not empty.
|
||||||
|
|
||||||
|
*/
|
||||||
|
-- DropForeignKey
|
||||||
|
ALTER TABLE "public"."customers" DROP CONSTRAINT "customers_user_id_fkey";
|
||||||
|
|
||||||
|
-- DropForeignKey
|
||||||
|
ALTER TABLE "public"."customers_archive" DROP CONSTRAINT "customers_archive_customer_id_fkey";
|
||||||
|
|
||||||
|
-- DropForeignKey
|
||||||
|
ALTER TABLE "public"."customers_archive" DROP CONSTRAINT "customers_archive_user_id_fkey";
|
||||||
|
|
||||||
|
-- DropForeignKey
|
||||||
|
ALTER TABLE "public"."employees_archive" DROP CONSTRAINT "employees_archive_employee_id_fkey";
|
||||||
|
|
||||||
|
-- DropForeignKey
|
||||||
|
ALTER TABLE "public"."employees_archive" DROP CONSTRAINT "employees_archive_supervisor_id_fkey";
|
||||||
|
|
||||||
|
-- DropForeignKey
|
||||||
|
ALTER TABLE "public"."employees_archive" DROP CONSTRAINT "employees_archive_user_id_fkey";
|
||||||
|
|
||||||
|
-- DropIndex
|
||||||
|
DROP INDEX "public"."leave_requests_employee_id_date_idx";
|
||||||
|
|
||||||
|
-- DropIndex
|
||||||
|
DROP INDEX "public"."leave_requests_employee_id_leave_type_date_key";
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "attachment_variants" DROP COLUMN "patch",
|
||||||
|
ADD COLUMN "path" TEXT NOT NULL;
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "expenses" ALTER COLUMN "amount" DROP NOT NULL,
|
||||||
|
ALTER COLUMN "amount" SET DATA TYPE DECIMAL(12,2);
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "expenses_archive" ALTER COLUMN "amount" SET DATA TYPE DECIMAL(12,2);
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "leave_requests" DROP COLUMN "date",
|
||||||
|
ADD COLUMN "dates" DATE[];
|
||||||
|
|
||||||
|
-- DropTable
|
||||||
|
DROP TABLE "public"."customers";
|
||||||
|
|
||||||
|
-- DropTable
|
||||||
|
DROP TABLE "public"."customers_archive";
|
||||||
|
|
||||||
|
-- DropTable
|
||||||
|
DROP TABLE "public"."employees_archive";
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "leave_requests_employee_id_dates_idx" ON "leave_requests"("employee_id", "dates");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "leave_requests_employee_id_leave_type_dates_key" ON "leave_requests"("employee_id", "leave_type", "dates");
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
-- DropIndex
|
||||||
|
DROP INDEX "public"."users_phone_number_key";
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
/*
|
||||||
|
Warnings:
|
||||||
|
|
||||||
|
- A unique constraint covering the columns `[timesheet_id,date,amount,mileage]` on the table `expenses` will be added. If there are existing duplicate values, this will fail.
|
||||||
|
- A unique constraint covering the columns `[timesheet_id,date,start_time]` on the table `shifts` will be added. If there are existing duplicate values, this will fail.
|
||||||
|
|
||||||
|
*/
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "expenses_timesheet_id_date_amount_mileage_key" ON "expenses"("timesheet_id", "date", "amount", "mileage");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "shifts_timesheet_id_date_start_time_key" ON "shifts"("timesheet_id", "date", "start_time");
|
||||||
|
|
@ -19,24 +19,40 @@ model Users {
|
||||||
first_name String
|
first_name String
|
||||||
last_name String
|
last_name String
|
||||||
email String @unique
|
email String @unique
|
||||||
phone_number String @unique
|
phone_number String
|
||||||
residence String?
|
residence String?
|
||||||
role Roles @default(GUEST)
|
role Roles @default(GUEST)
|
||||||
|
|
||||||
employee Employees? @relation("UserEmployee")
|
employee Employees? @relation("UserEmployee")
|
||||||
oauth_sessions OAuthSessions[] @relation("UserOAuthSessions")
|
oauth_sessions OAuthSessions[] @relation("UserOAuthSessions")
|
||||||
preferences Preferences? @relation("UserPreferences")
|
preferences Preferences? @relation("UserPreferences")
|
||||||
|
user_module_access userModuleAccess? @relation("UserModuleAccess")
|
||||||
|
|
||||||
@@map("users")
|
@@map("users")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
model userModuleAccess {
|
||||||
|
id Int @id @default(autoincrement())
|
||||||
|
user Users @relation("UserModuleAccess", fields: [user_id], references: [id])
|
||||||
|
user_id String @unique @db.Uuid
|
||||||
|
|
||||||
|
timesheets Boolean @default(false) //wich allows an employee to enter shifts and expenses
|
||||||
|
timesheets_approval Boolean @default(false) //wich allows the approbation of timesheets by a supervisor or above
|
||||||
|
employee_list Boolean @default(false) //wich shows the lists of employee to show names, emails, titles and profile picture
|
||||||
|
employee_management Boolean @default(false) //wich offers CRUD for employees, schedule_presets and manage module access
|
||||||
|
personnal_profile Boolean @default(false) //wich governs profile details, preferances and dashboard access
|
||||||
|
blocked Boolean @default(false)
|
||||||
|
|
||||||
|
@@map("user_module_access")
|
||||||
|
}
|
||||||
|
|
||||||
model Employees {
|
model Employees {
|
||||||
id Int @id @default(autoincrement())
|
id Int @id @default(autoincrement())
|
||||||
user Users @relation("UserEmployee", fields: [user_id], references: [id])
|
user Users @relation("UserEmployee", fields: [user_id], references: [id])
|
||||||
user_id String @unique @db.Uuid
|
user_id String @unique @db.Uuid
|
||||||
supervisor Employees? @relation("EmployeeSupervisor", fields: [supervisor_id], references: [id])
|
supervisor Employees? @relation("EmployeeSupervisor", fields: [supervisor_id], references: [id])
|
||||||
supervisor_id Int?
|
supervisor_id Int?
|
||||||
|
|
||||||
external_payroll_id Int
|
external_payroll_id Int
|
||||||
company_code Int
|
company_code Int
|
||||||
first_work_day DateTime @db.Date
|
first_work_day DateTime @db.Date
|
||||||
|
|
@ -44,11 +60,10 @@ model Employees {
|
||||||
job_title String?
|
job_title String?
|
||||||
is_supervisor Boolean @default(false)
|
is_supervisor Boolean @default(false)
|
||||||
|
|
||||||
|
crew Employees[] @relation("EmployeeSupervisor")
|
||||||
crew Employees[] @relation("EmployeeSupervisor")
|
timesheet Timesheets[] @relation("TimesheetEmployee")
|
||||||
timesheet Timesheets[] @relation("TimesheetEmployee")
|
leave_request LeaveRequests[] @relation("LeaveRequestEmployee")
|
||||||
leave_request LeaveRequests[] @relation("LeaveRequestEmployee")
|
schedule_presets SchedulePresets[] @relation("SchedulePreset")
|
||||||
schedule_presets SchedulePresets[] @relation("SchedulePreset")
|
|
||||||
|
|
||||||
@@map("employees")
|
@@map("employees")
|
||||||
}
|
}
|
||||||
|
|
@ -65,7 +80,7 @@ model LeaveRequests {
|
||||||
payable_hours Decimal? @db.Decimal(5, 2)
|
payable_hours Decimal? @db.Decimal(5, 2)
|
||||||
requested_hours Decimal? @db.Decimal(5, 2)
|
requested_hours Decimal? @db.Decimal(5, 2)
|
||||||
approval_status LeaveApprovalStatus @default(PENDING)
|
approval_status LeaveApprovalStatus @default(PENDING)
|
||||||
leave_type LeaveTypes
|
leave_type LeaveTypes
|
||||||
|
|
||||||
archive LeaveRequestsArchive[] @relation("LeaveRequestToArchive")
|
archive LeaveRequestsArchive[] @relation("LeaveRequestToArchive")
|
||||||
|
|
||||||
|
|
@ -79,14 +94,14 @@ model LeaveRequestsArchive {
|
||||||
leave_request LeaveRequests @relation("LeaveRequestToArchive", fields: [leave_request_id], references: [id])
|
leave_request LeaveRequests @relation("LeaveRequestToArchive", fields: [leave_request_id], references: [id])
|
||||||
leave_request_id Int
|
leave_request_id Int
|
||||||
|
|
||||||
archived_at DateTime @default(now())
|
archived_at DateTime @default(now())
|
||||||
employee_id Int
|
employee_id Int
|
||||||
date DateTime @db.Date
|
date DateTime @db.Date
|
||||||
payable_hours Decimal? @db.Decimal(5, 2)
|
payable_hours Decimal? @db.Decimal(5, 2)
|
||||||
requested_hours Decimal? @db.Decimal(5, 2)
|
requested_hours Decimal? @db.Decimal(5, 2)
|
||||||
comment String
|
comment String
|
||||||
leave_type LeaveTypes
|
leave_type LeaveTypes
|
||||||
approval_status LeaveApprovalStatus
|
approval_status LeaveApprovalStatus
|
||||||
|
|
||||||
@@unique([leave_request_id])
|
@@unique([leave_request_id])
|
||||||
@@index([employee_id, date])
|
@@index([employee_id, date])
|
||||||
|
|
@ -126,9 +141,9 @@ model TimesheetsArchive {
|
||||||
timesheet Timesheets @relation("TimesheetsToArchive", fields: [timesheet_id], references: [id])
|
timesheet Timesheets @relation("TimesheetsToArchive", fields: [timesheet_id], references: [id])
|
||||||
timesheet_id Int
|
timesheet_id Int
|
||||||
|
|
||||||
employee_id Int
|
employee_id Int
|
||||||
is_approved Boolean
|
is_approved Boolean
|
||||||
archive_at DateTime @default(now())
|
archive_at DateTime @default(now())
|
||||||
|
|
||||||
@@map("timesheets_archive")
|
@@map("timesheets_archive")
|
||||||
}
|
}
|
||||||
|
|
@ -139,7 +154,7 @@ model SchedulePresets {
|
||||||
employee_id Int
|
employee_id Int
|
||||||
|
|
||||||
name String
|
name String
|
||||||
is_default Boolean @default(false)
|
is_default Boolean @default(false)
|
||||||
|
|
||||||
shifts SchedulePresetShifts[] @relation("SchedulePresetShiftsSchedulePreset")
|
shifts SchedulePresetShifts[] @relation("SchedulePresetShiftsSchedulePreset")
|
||||||
|
|
||||||
|
|
@ -149,9 +164,9 @@ model SchedulePresets {
|
||||||
|
|
||||||
model SchedulePresetShifts {
|
model SchedulePresetShifts {
|
||||||
id Int @id @default(autoincrement())
|
id Int @id @default(autoincrement())
|
||||||
preset SchedulePresets @relation("SchedulePresetShiftsSchedulePreset",fields: [preset_id], references: [id])
|
preset SchedulePresets @relation("SchedulePresetShiftsSchedulePreset", fields: [preset_id], references: [id])
|
||||||
preset_id Int
|
preset_id Int
|
||||||
bank_code BankCodes @relation("SchedulePresetShiftsBankCodes",fields: [bank_code_id], references: [id])
|
bank_code BankCodes @relation("SchedulePresetShiftsBankCodes", fields: [bank_code_id], references: [id])
|
||||||
bank_code_id Int
|
bank_code_id Int
|
||||||
|
|
||||||
sort_order Int
|
sort_order Int
|
||||||
|
|
@ -181,12 +196,13 @@ model Shifts {
|
||||||
|
|
||||||
archive ShiftsArchive[] @relation("ShiftsToArchive")
|
archive ShiftsArchive[] @relation("ShiftsToArchive")
|
||||||
|
|
||||||
|
@@unique([timesheet_id, date, start_time], name: "unique_ts_id_date_start_time")
|
||||||
@@map("shifts")
|
@@map("shifts")
|
||||||
}
|
}
|
||||||
|
|
||||||
model ShiftsArchive {
|
model ShiftsArchive {
|
||||||
id Int @id @default(autoincrement())
|
id Int @id @default(autoincrement())
|
||||||
shift Shifts @relation("ShiftsToArchive", fields: [shift_id], references: [id])
|
shift Shifts @relation("ShiftsToArchive", fields: [shift_id], references: [id])
|
||||||
shift_id Int
|
shift_id Int
|
||||||
|
|
||||||
date DateTime @db.Date
|
date DateTime @db.Date
|
||||||
|
|
@ -216,39 +232,40 @@ model BankCodes {
|
||||||
}
|
}
|
||||||
|
|
||||||
model Expenses {
|
model Expenses {
|
||||||
id Int @id @default(autoincrement())
|
id Int @id @default(autoincrement())
|
||||||
timesheet Timesheets @relation("ExpensesTimesheet", fields: [timesheet_id], references: [id])
|
timesheet Timesheets @relation("ExpensesTimesheet", fields: [timesheet_id], references: [id])
|
||||||
timesheet_id Int
|
timesheet_id Int
|
||||||
bank_code BankCodes @relation("ExpenseBankCodes", fields: [bank_code_id], references: [id])
|
bank_code BankCodes @relation("ExpenseBankCodes", fields: [bank_code_id], references: [id])
|
||||||
bank_code_id Int
|
bank_code_id Int
|
||||||
attachment_record Attachments? @relation("ExpenseAttachment", fields: [attachment], references: [id], onDelete: SetNull)
|
attachment_record Attachments? @relation("ExpenseAttachment", fields: [attachment], references: [id], onDelete: SetNull)
|
||||||
attachment Int?
|
attachment Int?
|
||||||
|
|
||||||
date DateTime @db.Date
|
date DateTime @db.Date
|
||||||
amount Decimal? @db.Decimal(12,2)
|
amount Decimal? @db.Decimal(12, 2)
|
||||||
mileage Decimal? @db.Decimal(12,2)
|
mileage Decimal? @db.Decimal(12, 2)
|
||||||
comment String
|
comment String
|
||||||
supervisor_comment String?
|
supervisor_comment String?
|
||||||
is_approved Boolean @default(false)
|
is_approved Boolean @default(false)
|
||||||
|
|
||||||
archive ExpensesArchive[] @relation("ExpensesToArchive")
|
archive ExpensesArchive[] @relation("ExpensesToArchive")
|
||||||
|
|
||||||
|
@@unique([timesheet_id, date, amount, mileage], name: "unique_ts_id_date_amount_mileage")
|
||||||
@@map("expenses")
|
@@map("expenses")
|
||||||
}
|
}
|
||||||
|
|
||||||
model ExpensesArchive {
|
model ExpensesArchive {
|
||||||
id Int @id @default(autoincrement())
|
id Int @id @default(autoincrement())
|
||||||
expense Expenses @relation("ExpensesToArchive", fields: [expense_id], references: [id])
|
expense Expenses @relation("ExpensesToArchive", fields: [expense_id], references: [id])
|
||||||
expense_id Int
|
expense_id Int
|
||||||
attachment_record Attachments? @relation("ExpenseArchiveAttachment", fields: [attachment], references: [id], onDelete: SetNull)
|
attachment_record Attachments? @relation("ExpenseArchiveAttachment", fields: [attachment], references: [id], onDelete: SetNull)
|
||||||
attachment Int?
|
attachment Int?
|
||||||
|
|
||||||
timesheet_id Int
|
timesheet_id Int
|
||||||
archived_at DateTime @default(now())
|
archived_at DateTime @default(now())
|
||||||
bank_code_id Int
|
bank_code_id Int
|
||||||
date DateTime @db.Date
|
date DateTime @db.Date
|
||||||
amount Decimal? @db.Decimal(12,2)
|
amount Decimal? @db.Decimal(12, 2)
|
||||||
mileage Decimal? @db.Decimal(12,2)
|
mileage Decimal? @db.Decimal(12, 2)
|
||||||
comment String?
|
comment String?
|
||||||
is_approved Boolean
|
is_approved Boolean
|
||||||
supervisor_comment String?
|
supervisor_comment String?
|
||||||
|
|
@ -289,7 +306,7 @@ model Blobs {
|
||||||
|
|
||||||
model Attachments {
|
model Attachments {
|
||||||
id Int @id @default(autoincrement())
|
id Int @id @default(autoincrement())
|
||||||
blob Blobs @relation("AttachmnentBlob",fields: [sha256], references: [sha256], onUpdate: Cascade)
|
blob Blobs @relation("AttachmnentBlob", fields: [sha256], references: [sha256], onUpdate: Cascade)
|
||||||
sha256 String @db.Char(64)
|
sha256 String @db.Char(64)
|
||||||
|
|
||||||
owner_type String //EXPENSES ou éventuellement autre chose comme scan ONU ou photos d'employés, etc
|
owner_type String //EXPENSES ou éventuellement autre chose comme scan ONU ou photos d'employés, etc
|
||||||
|
|
@ -304,7 +321,7 @@ model Attachments {
|
||||||
expenses_archive ExpensesArchive[] @relation("ExpenseArchiveAttachment")
|
expenses_archive ExpensesArchive[] @relation("ExpenseArchiveAttachment")
|
||||||
|
|
||||||
AttachmentVariants AttachmentVariants[] @relation("attachmentVariantAttachment")
|
AttachmentVariants AttachmentVariants[] @relation("attachmentVariantAttachment")
|
||||||
|
|
||||||
@@index([owner_type, owner_id, created_at])
|
@@index([owner_type, owner_id, created_at])
|
||||||
@@index([sha256])
|
@@index([sha256])
|
||||||
@@map("attachments")
|
@@map("attachments")
|
||||||
|
|
@ -313,7 +330,7 @@ model Attachments {
|
||||||
model AttachmentVariants {
|
model AttachmentVariants {
|
||||||
id Int @id @default(autoincrement())
|
id Int @id @default(autoincrement())
|
||||||
attachment_id Int
|
attachment_id Int
|
||||||
attachment Attachments @relation("attachmentVariantAttachment",fields: [attachment_id], references: [id], onDelete: Cascade)
|
attachment Attachments @relation("attachmentVariantAttachment", fields: [attachment_id], references: [id], onDelete: Cascade)
|
||||||
variant String
|
variant String
|
||||||
path String
|
path String
|
||||||
bytes Int
|
bytes Int
|
||||||
|
|
@ -326,18 +343,16 @@ model AttachmentVariants {
|
||||||
}
|
}
|
||||||
|
|
||||||
model Preferences {
|
model Preferences {
|
||||||
id Int @id @default(autoincrement())
|
id Int @id @default(autoincrement())
|
||||||
user Users @relation("UserPreferences", fields: [user_id], references: [id])
|
user Users @relation("UserPreferences", fields: [user_id], references: [id])
|
||||||
user_id String @unique @db.Uuid
|
user_id String @unique @db.Uuid
|
||||||
|
|
||||||
notifications Int @default(0)
|
notifications Int @default(0)
|
||||||
dark_mode Int @default(0)
|
is_dark_mode Boolean @default(false)
|
||||||
lang_switch Int @default(0)
|
display_language String @default("fr-FR") //'fr-FR' | 'en-CA';
|
||||||
lefty_mode Int @default(0)
|
is_lefty_mode Boolean @default(false)
|
||||||
|
is_employee_list_grid Boolean @default(true)
|
||||||
employee_list_display Int @default(0)
|
is_timesheet_approval_grid Boolean @default(true)
|
||||||
validation_display Int @default(0)
|
|
||||||
timesheet_display Int @default(0)
|
|
||||||
|
|
||||||
@@map("preferences")
|
@@map("preferences")
|
||||||
}
|
}
|
||||||
|
|
@ -398,4 +413,3 @@ enum Weekday {
|
||||||
FRI
|
FRI
|
||||||
SAT
|
SAT
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
270
scripts/done/import-employees-from-csv.ts
Normal file
270
scripts/done/import-employees-from-csv.ts
Normal file
|
|
@ -0,0 +1,270 @@
|
||||||
|
// src/scripts/import-employees-from-csv.ts
|
||||||
|
import { PrismaClient, Roles } from '@prisma/client';
|
||||||
|
import * as fs from 'fs';
|
||||||
|
import * as path from 'path';
|
||||||
|
|
||||||
|
const prisma = new PrismaClient();
|
||||||
|
|
||||||
|
// ⚙️ Chemin vers ton CSV employees
|
||||||
|
const CSV_PATH = path.resolve(__dirname, '../../data/export_new_employee_table.csv');
|
||||||
|
|
||||||
|
// Rôles éligibles pour la table Employees
|
||||||
|
const ELIGIBLE_ROLES: Roles[] = [
|
||||||
|
Roles.EMPLOYEE,
|
||||||
|
Roles.SUPERVISOR,
|
||||||
|
Roles.HR,
|
||||||
|
Roles.ACCOUNTING,
|
||||||
|
Roles.ADMIN,
|
||||||
|
];
|
||||||
|
|
||||||
|
// Type correspondant EXACT aux colonnes de ton CSV
|
||||||
|
type EmployeeCsvRow = {
|
||||||
|
employee_number: string;
|
||||||
|
email: string;
|
||||||
|
job_title: string;
|
||||||
|
company: string; // sera converti en number
|
||||||
|
is_supervisor: string; // "True"/"False" (ou variantes)
|
||||||
|
onboarding: string; // millis
|
||||||
|
offboarding: string; // millis ou "NULL"
|
||||||
|
};
|
||||||
|
|
||||||
|
// Représentation minimale d'un user
|
||||||
|
type UserSummary = {
|
||||||
|
id: string; // UUID
|
||||||
|
email: string;
|
||||||
|
role: Roles;
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============ Helpers CSV ============
|
||||||
|
|
||||||
|
function splitCsvLine(line: string): string[] {
|
||||||
|
const result: string[] = [];
|
||||||
|
let current = '';
|
||||||
|
let inQuotes = false;
|
||||||
|
|
||||||
|
for (let i = 0; i < line.length; i++) {
|
||||||
|
const char = line[i];
|
||||||
|
|
||||||
|
if (char === '"') {
|
||||||
|
// guillemet échappé ""
|
||||||
|
if (inQuotes && line[i + 1] === '"') {
|
||||||
|
current += '"';
|
||||||
|
i++;
|
||||||
|
} else {
|
||||||
|
inQuotes = !inQuotes;
|
||||||
|
}
|
||||||
|
} else if (char === ',' && !inQuotes) {
|
||||||
|
result.push(current);
|
||||||
|
current = '';
|
||||||
|
} else {
|
||||||
|
current += char;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result.push(current);
|
||||||
|
return result.map((v) => v.trim());
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============ Helpers de parsing ============
|
||||||
|
|
||||||
|
function parseBoolean(value: string): boolean {
|
||||||
|
const v = value.trim().toLowerCase();
|
||||||
|
return v === 'true' || v === '1' || v === 'yes' || v === 'y' || v === 'oui';
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseIntSafe(value: string, fieldName: string): number | null {
|
||||||
|
const trimmed = value.trim();
|
||||||
|
if (!trimmed || trimmed.toUpperCase() === 'NULL') return null;
|
||||||
|
|
||||||
|
const n = Number.parseInt(trimmed, 10);
|
||||||
|
if (Number.isNaN(n)) {
|
||||||
|
console.warn(`⚠️ Impossible de parser "${value}" en entier pour le champ ${fieldName}`);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
function millisToDate(value: string): Date | null {
|
||||||
|
const trimmed = value.trim().toUpperCase();
|
||||||
|
if (!trimmed || trimmed === 'NULL') return null;
|
||||||
|
|
||||||
|
const ms = Number(trimmed);
|
||||||
|
if (!Number.isFinite(ms)) {
|
||||||
|
console.warn(`⚠️ Impossible de parser "${value}" en millis pour une Date`);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const d = new Date(ms);
|
||||||
|
// On normalise au jour (minuit UTC)
|
||||||
|
const normalized = new Date(Date.UTC(
|
||||||
|
d.getUTCFullYear(),
|
||||||
|
d.getUTCMonth(),
|
||||||
|
d.getUTCDate(),
|
||||||
|
));
|
||||||
|
|
||||||
|
return normalized;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============ MAIN ============
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
// 1. Lecture du CSV
|
||||||
|
const fileContent = fs.readFileSync(CSV_PATH, 'utf-8');
|
||||||
|
|
||||||
|
const lines = fileContent
|
||||||
|
.split(/\r?\n/)
|
||||||
|
.map((l) => l.trim())
|
||||||
|
.filter((l) => l.length > 0);
|
||||||
|
|
||||||
|
if (lines.length <= 1) {
|
||||||
|
console.error('CSV vide ou seulement un header');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const header = splitCsvLine(lines[0]); // ["employee_number","email",...]
|
||||||
|
const dataLines = lines.slice(1);
|
||||||
|
|
||||||
|
const csvRows: EmployeeCsvRow[] = dataLines.map((line) => {
|
||||||
|
const values = splitCsvLine(line);
|
||||||
|
const row: any = {};
|
||||||
|
|
||||||
|
header.forEach((col, idx) => {
|
||||||
|
row[col] = values[idx] ?? '';
|
||||||
|
});
|
||||||
|
|
||||||
|
return row as EmployeeCsvRow;
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(`➡️ ${csvRows.length} lignes trouvées dans le CSV employees`);
|
||||||
|
|
||||||
|
// 2. Récupérer tous les emails du CSV
|
||||||
|
const emails = Array.from(
|
||||||
|
new Set(
|
||||||
|
csvRows
|
||||||
|
.map((r) => r.email.trim())
|
||||||
|
.filter((e) => e.length > 0),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log(`➡️ ${emails.length} emails uniques trouvés dans le CSV`);
|
||||||
|
|
||||||
|
// 3. Charger les users correspondants avec les bons rôles
|
||||||
|
const users = (await prisma.users.findMany({
|
||||||
|
where: {
|
||||||
|
email: { in: emails },
|
||||||
|
role: { in: ELIGIBLE_ROLES },
|
||||||
|
},
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
email: true,
|
||||||
|
role: true,
|
||||||
|
},
|
||||||
|
})) as UserSummary[];
|
||||||
|
|
||||||
|
console.log(`➡️ ${users.length} users éligibles trouvés dans la DB`);
|
||||||
|
|
||||||
|
// Map email → user
|
||||||
|
const userByEmail = new Map<string, UserSummary>();
|
||||||
|
for (const user of users) {
|
||||||
|
const key = user.email.trim().toLowerCase();
|
||||||
|
userByEmail.set(key, user);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Construire les données pour employees.createMany
|
||||||
|
const employeesToCreate: {
|
||||||
|
user_id: string;
|
||||||
|
external_payroll_id: number;
|
||||||
|
company_code: number;
|
||||||
|
first_work_day: Date;
|
||||||
|
last_work_day: Date | null;
|
||||||
|
job_title: string | null;
|
||||||
|
is_supervisor: boolean;
|
||||||
|
supervisor_id?: number | null;
|
||||||
|
}[] = [];
|
||||||
|
|
||||||
|
const rowsWithoutUser: EmployeeCsvRow[] = [];
|
||||||
|
const rowsWithInvalidNumbers: EmployeeCsvRow[] = [];
|
||||||
|
|
||||||
|
for (const row of csvRows) {
|
||||||
|
const emailKey = row.email.trim().toLowerCase();
|
||||||
|
const user = userByEmail.get(emailKey);
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
rowsWithoutUser.push(row);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const external_payroll_id = parseIntSafe(row.employee_number, 'external_payroll_id');
|
||||||
|
const company_code = parseIntSafe(String(row.company), 'company_code');
|
||||||
|
|
||||||
|
if (external_payroll_id === null || company_code === null) {
|
||||||
|
rowsWithInvalidNumbers.push(row);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const first_work_day = millisToDate(row.onboarding);
|
||||||
|
const last_work_day = millisToDate(row.offboarding);
|
||||||
|
const is_supervisor = parseBoolean(row.is_supervisor);
|
||||||
|
const job_title = row.job_title?.trim() || null;
|
||||||
|
|
||||||
|
if (!first_work_day) {
|
||||||
|
console.warn(
|
||||||
|
`⚠️ Date d'onboarding invalide pour ${row.email} (employee_number=${row.employee_number})`,
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
employeesToCreate.push({
|
||||||
|
user_id: user.id,
|
||||||
|
external_payroll_id,
|
||||||
|
company_code,
|
||||||
|
first_work_day,
|
||||||
|
last_work_day,
|
||||||
|
job_title,
|
||||||
|
is_supervisor,
|
||||||
|
supervisor_id: null, // on pourra gérer ça plus tard si tu as les infos
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`➡️ ${employeesToCreate.length} entrées Employees prêtes à être insérées`);
|
||||||
|
|
||||||
|
if (rowsWithoutUser.length > 0) {
|
||||||
|
console.warn(`⚠️ ${rowsWithoutUser.length} lignes CSV sans user correspondant (email / rôle) :`);
|
||||||
|
for (const row of rowsWithoutUser) {
|
||||||
|
console.warn(
|
||||||
|
` - email=${row.email}, employee_number=${row.employee_number}, company=${row.company}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rowsWithInvalidNumbers.length > 0) {
|
||||||
|
console.warn(`⚠️ ${rowsWithInvalidNumbers.length} lignes CSV avec ids/compagnies invalides :`);
|
||||||
|
for (const row of rowsWithInvalidNumbers) {
|
||||||
|
console.warn(
|
||||||
|
` - email=${row.email}, employee_number="${row.employee_number}", company="${row.company}"`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (employeesToCreate.length === 0) {
|
||||||
|
console.warn('⚠️ Aucun Employees à créer, arrêt.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. Insert en batch
|
||||||
|
const result = await prisma.employees.createMany({
|
||||||
|
data: employeesToCreate,
|
||||||
|
skipDuplicates: true, // évite les erreurs si tu relances le script
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(`✅ ${result.count} employees insérés dans la DB`);
|
||||||
|
}
|
||||||
|
|
||||||
|
main()
|
||||||
|
.catch((err) => {
|
||||||
|
console.error('❌ Erreur pendant l’import CSV → Employees', err);
|
||||||
|
process.exit(1);
|
||||||
|
})
|
||||||
|
.finally(async () => {
|
||||||
|
await prisma.$disconnect();
|
||||||
|
});
|
||||||
106
scripts/done/import-users-from-csv.ts
Normal file
106
scripts/done/import-users-from-csv.ts
Normal file
|
|
@ -0,0 +1,106 @@
|
||||||
|
// src/scripts/import-users-from-csv.ts
|
||||||
|
import { PrismaClient } from '@prisma/client';
|
||||||
|
import * as fs from 'fs';
|
||||||
|
import * as path from 'path';
|
||||||
|
|
||||||
|
const prisma = new PrismaClient();
|
||||||
|
|
||||||
|
// ⚙️ Chemin vers ton CSV (à adapter selon où tu le mets)
|
||||||
|
const CSV_PATH = path.resolve(__dirname, '../../data/export_employee_table.csv');
|
||||||
|
|
||||||
|
// Type aligné sur les colonnes du CSV
|
||||||
|
type CsvUserRow = {
|
||||||
|
email: string;
|
||||||
|
first_name: string;
|
||||||
|
last_name: string;
|
||||||
|
phone_number: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Petit parseur de ligne CSV sans dépendance
|
||||||
|
function splitCsvLine(line: string): string[] {
|
||||||
|
const result: string[] = [];
|
||||||
|
let current = '';
|
||||||
|
let inQuotes = false;
|
||||||
|
|
||||||
|
for (let i = 0; i < line.length; i++) {
|
||||||
|
const char = line[i];
|
||||||
|
|
||||||
|
if (char === '"') {
|
||||||
|
// guillemet échappé ""
|
||||||
|
if (inQuotes && line[i + 1] === '"') {
|
||||||
|
current += '"';
|
||||||
|
i++; // on saute le deuxième
|
||||||
|
} else {
|
||||||
|
inQuotes = !inQuotes;
|
||||||
|
}
|
||||||
|
} else if (char === ',' && !inQuotes) {
|
||||||
|
result.push(current);
|
||||||
|
current = '';
|
||||||
|
} else {
|
||||||
|
current += char;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result.push(current);
|
||||||
|
return result.map((v) => v.trim());
|
||||||
|
}
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
// 1. Lecture du fichier CSV
|
||||||
|
const fileContent = fs.readFileSync(CSV_PATH, 'utf-8');
|
||||||
|
|
||||||
|
const lines = fileContent
|
||||||
|
.split(/\r?\n/)
|
||||||
|
.map((l) => l.trim())
|
||||||
|
.filter((l) => l.length > 0);
|
||||||
|
|
||||||
|
if (lines.length <= 1) {
|
||||||
|
console.error('CSV vide ou seulement un header');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Header (noms de colonnes) -> ["email", "first_name", "last_name", "phone_number"]
|
||||||
|
const header = splitCsvLine(lines[0]);
|
||||||
|
const dataLines = lines.slice(1);
|
||||||
|
|
||||||
|
// 3. Conversion de chaque ligne en objet { email, first_name, last_name, phone_number }
|
||||||
|
const records: CsvUserRow[] = dataLines.map((line) => {
|
||||||
|
const values = splitCsvLine(line);
|
||||||
|
const row: any = {};
|
||||||
|
|
||||||
|
header.forEach((col, idx) => {
|
||||||
|
row[col] = values[idx] ?? '';
|
||||||
|
});
|
||||||
|
|
||||||
|
return row as CsvUserRow;
|
||||||
|
});
|
||||||
|
|
||||||
|
// 4. Mapping vers le format attendu par Prisma (model Users)
|
||||||
|
const data = records.map((row) => ({
|
||||||
|
email: row.email.trim(),
|
||||||
|
first_name: row.first_name.trim(),
|
||||||
|
last_name: row.last_name.trim(),
|
||||||
|
phone_number: row.phone_number.trim(),
|
||||||
|
// residence: null, // si tu veux la forcer à null
|
||||||
|
// role: 'GUEST', // sinon Prisma va appliquer la valeur par défaut
|
||||||
|
}));
|
||||||
|
|
||||||
|
console.log(`➡️ ${data.length} lignes trouvées dans le CSV`);
|
||||||
|
console.log('Exemple importé :', data[0]);
|
||||||
|
|
||||||
|
|
||||||
|
const result = await prisma.users.createMany({
|
||||||
|
data,
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(`✅ ${result.count} utilisateurs insérés dans la DB`);
|
||||||
|
}
|
||||||
|
|
||||||
|
main()
|
||||||
|
.catch((err) => {
|
||||||
|
console.error('❌ Erreur pendant l’import CSV → DB', err);
|
||||||
|
process.exit(1);
|
||||||
|
})
|
||||||
|
.finally(async () => {
|
||||||
|
await prisma.$disconnect();
|
||||||
|
});
|
||||||
67
scripts/done/init-preferences.ts
Normal file
67
scripts/done/init-preferences.ts
Normal file
|
|
@ -0,0 +1,67 @@
|
||||||
|
// src/scripts/init-preferences.ts
|
||||||
|
import { PrismaClient } from '@prisma/client';
|
||||||
|
|
||||||
|
const prisma = new PrismaClient();
|
||||||
|
|
||||||
|
type UserSummary = {
|
||||||
|
id: string; // UUID
|
||||||
|
email: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
console.log('➡️ Initialisation des préférences utilisateurs…');
|
||||||
|
|
||||||
|
// 1. Récupérer tous les users
|
||||||
|
const users = (await prisma.users.findMany({
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
email: true,
|
||||||
|
},
|
||||||
|
})) as UserSummary[];
|
||||||
|
|
||||||
|
console.log(`➡️ ${users.length} users trouvés dans la DB`);
|
||||||
|
|
||||||
|
// 2. Récupérer toutes les préférences existantes
|
||||||
|
const existingPrefs = await prisma.preferences.findMany({
|
||||||
|
select: {
|
||||||
|
user_id: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const userIdsWithPrefs = new Set(existingPrefs.map((p) => p.user_id));
|
||||||
|
|
||||||
|
console.log(`➡️ ${existingPrefs.length} users ont déjà des préférences`);
|
||||||
|
|
||||||
|
// 3. Filtrer les users qui n'ont pas encore de preferences
|
||||||
|
const usersWithoutPrefs = users.filter((u) => !userIdsWithPrefs.has(u.id));
|
||||||
|
|
||||||
|
console.log(`➡️ ${usersWithoutPrefs.length} users n'ont pas encore de préférences`);
|
||||||
|
|
||||||
|
if (usersWithoutPrefs.length === 0) {
|
||||||
|
console.log('✅ Rien à faire, toutes les préférences sont déjà créées.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Préparer les entrées pour createMany
|
||||||
|
const prefsToCreate = usersWithoutPrefs.map((u) => ({
|
||||||
|
user_id: u.id,
|
||||||
|
// tous les autres champs prendront leurs valeurs par défaut (0)
|
||||||
|
}));
|
||||||
|
|
||||||
|
// 5. Insertion en batch
|
||||||
|
const result = await prisma.preferences.createMany({
|
||||||
|
data: prefsToCreate,
|
||||||
|
skipDuplicates: true, // sécurité si jamais le script est relancé
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(`✅ ${result.count} préférences créées dans la DB`);
|
||||||
|
}
|
||||||
|
|
||||||
|
main()
|
||||||
|
.catch((err) => {
|
||||||
|
console.error('❌ Erreur pendant l’initialisation des préférences', err);
|
||||||
|
process.exit(1);
|
||||||
|
})
|
||||||
|
.finally(async () => {
|
||||||
|
await prisma.$disconnect();
|
||||||
|
});
|
||||||
165
scripts/migrate-expenses.ts
Normal file
165
scripts/migrate-expenses.ts
Normal file
|
|
@ -0,0 +1,165 @@
|
||||||
|
import { PrismaClient as Prisma } from "@prisma/client";
|
||||||
|
import { PrismaClient as PrismaLegacy } from "@prisma/client-legacy"
|
||||||
|
import { toDateFromString, toHHmmFromDate, toStringFromDate } from "src/common/utils/date-utils";
|
||||||
|
|
||||||
|
const prisma_legacy = new PrismaLegacy({});
|
||||||
|
const prisma = new Prisma({});
|
||||||
|
|
||||||
|
type NewEmployee = {
|
||||||
|
id: number;
|
||||||
|
company_code: number;
|
||||||
|
external_payroll_id: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
type OldExpense = {
|
||||||
|
time_sheet_id: string | null;
|
||||||
|
date: string | null;
|
||||||
|
code: string | null;
|
||||||
|
description: string | null;
|
||||||
|
value: number | null;
|
||||||
|
status: boolean | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const extractOldExpenses = async () => {
|
||||||
|
for (let id = 1; id <= 61; id++) {
|
||||||
|
|
||||||
|
console.log(`Start of Expense migration ***************************************************************`);
|
||||||
|
|
||||||
|
const new_employee = await findOneNewEmployee(id);
|
||||||
|
console.log(`Employee ${id} found in new DB`);
|
||||||
|
|
||||||
|
const new_timesheets = await findManyNewTimesheets(new_employee.id);
|
||||||
|
console.log(`New Timesheets found for employee ${id}`);
|
||||||
|
|
||||||
|
const old_employee_id = await findOneOldEmployee(new_employee);
|
||||||
|
console.log(`Employee ${new_employee.id} found in old DB`);
|
||||||
|
|
||||||
|
const old_timesheets = await findManyOldTimesheets(old_employee_id);
|
||||||
|
console.log(`Timesheets for employee ${old_employee_id}/${new_employee.id} found in old DB`);
|
||||||
|
|
||||||
|
console.log('Start of Expense creation*****************************************************************');
|
||||||
|
for (const old_timesheet of old_timesheets) {
|
||||||
|
if (!old_timesheet.start_date) continue;
|
||||||
|
const new_timesheet = new_timesheets.find((ts) => ts.start_date.getTime() === old_timesheet.start_date!.getTime());
|
||||||
|
if (!new_timesheet) {
|
||||||
|
console.warn(`No new timesheet matching legacy timesheet ${old_timesheet.id}`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const old_expenses = await prisma_legacy.expenses.findMany({
|
||||||
|
where: { time_sheet_id: old_timesheet.id },
|
||||||
|
select: {
|
||||||
|
time_sheet_id: true,
|
||||||
|
date: true,
|
||||||
|
code: true,
|
||||||
|
description: true,
|
||||||
|
value: true,
|
||||||
|
status: true,
|
||||||
|
|
||||||
|
},
|
||||||
|
});
|
||||||
|
await createManyNewExpenses(new_timesheet.id, old_expenses);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
await prisma_legacy.$disconnect();
|
||||||
|
await prisma.$disconnect();
|
||||||
|
}
|
||||||
|
|
||||||
|
const findOneNewEmployee = async (id: number): Promise<NewEmployee> => {
|
||||||
|
const new_employee = await prisma.employees.findUnique({
|
||||||
|
where: { id: id },
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
company_code: true,
|
||||||
|
external_payroll_id: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (!new_employee) throw new Error(`New Employee with id ${id} not found`)
|
||||||
|
return new_employee;
|
||||||
|
}
|
||||||
|
|
||||||
|
const findOneOldEmployee = async (new_employee: NewEmployee): Promise<string> => {
|
||||||
|
const old_employee = await prisma_legacy.employees.findFirst({
|
||||||
|
where: {
|
||||||
|
company: new_employee.company_code,
|
||||||
|
employee_number: new_employee.external_payroll_id.toString(),
|
||||||
|
},
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (!old_employee) throw new Error(`Old Employee not found`);
|
||||||
|
return old_employee.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
const findManyOldTimesheets = async (old_employee_id: string) => {
|
||||||
|
const old_timesheets = await prisma_legacy.time_sheets.findMany({
|
||||||
|
where: { employee_id: old_employee_id },
|
||||||
|
select: { id: true, start_date: true, status: true }
|
||||||
|
});
|
||||||
|
return old_timesheets;
|
||||||
|
}
|
||||||
|
|
||||||
|
const findManyNewTimesheets = async (employee_id: number) => {
|
||||||
|
const timesheets = await prisma.timesheets.findMany({
|
||||||
|
where: { employee_id: employee_id },
|
||||||
|
select: { id: true, start_date: true }
|
||||||
|
})
|
||||||
|
return timesheets;
|
||||||
|
}
|
||||||
|
|
||||||
|
const createManyNewExpenses = async (timesheet_id: number, old_expenses: OldExpense[]) => {
|
||||||
|
for (const old_expense of old_expenses) {
|
||||||
|
let mileage: number = 0;
|
||||||
|
let amount: number = old_expense.value ?? 0;
|
||||||
|
if (old_expense.code === 'G503') {
|
||||||
|
mileage = old_expense.value!;
|
||||||
|
amount = mileage * 0.72;
|
||||||
|
}
|
||||||
|
if (mileage < 0 || amount < 0) {
|
||||||
|
console.warn(`expense of value less than '0' found`)
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (old_expense.date == null) {
|
||||||
|
console.warn(`Expense date invalid ${old_expense.date}`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const date = toDateFromString(old_expense.date);
|
||||||
|
|
||||||
|
if (old_expense.status == null) {
|
||||||
|
console.warn(`status null for legacy expense ${old_expense}`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (old_expense.code == null) {
|
||||||
|
console.warn(`Code null for legacy expense ${old_expense.code}`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const bank_code_id = await findBankCodeIdUsingOldCode(old_expense.code);
|
||||||
|
|
||||||
|
await prisma.expenses.create({
|
||||||
|
// where: { unique_ts_id_date_amount_mileage: { timesheet_id: timesheet_id, date, amount, mileage } },
|
||||||
|
// update: {
|
||||||
|
// is_approved: old_expense.status,
|
||||||
|
// },
|
||||||
|
data: {
|
||||||
|
date: date,
|
||||||
|
comment: old_expense.description ?? '',
|
||||||
|
timesheet_id: timesheet_id,
|
||||||
|
bank_code_id: bank_code_id,
|
||||||
|
amount: amount,
|
||||||
|
mileage: mileage,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const findBankCodeIdUsingOldCode = async (code: string): Promise<number> => {
|
||||||
|
const bank_code = await prisma.bankCodes.findFirst({
|
||||||
|
where: { bank_code: code },
|
||||||
|
select: { id: true },
|
||||||
|
});
|
||||||
|
if (!bank_code) throw new Error(`Bank_code_id not found for Code ${code}`)
|
||||||
|
return bank_code.id;
|
||||||
|
}
|
||||||
210
scripts/migrate-shifts.ts
Normal file
210
scripts/migrate-shifts.ts
Normal file
|
|
@ -0,0 +1,210 @@
|
||||||
|
import { PrismaClient as PrismaNew } from "@prisma/client";
|
||||||
|
import { PrismaClient as PrismaLegacy } from "@prisma/client-legacy"
|
||||||
|
|
||||||
|
const prisma_legacy = new PrismaLegacy({});
|
||||||
|
const prisma = new PrismaNew({});
|
||||||
|
|
||||||
|
type NewEmployee = {
|
||||||
|
id: number;
|
||||||
|
company_code: number;
|
||||||
|
external_payroll_id: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
type OldShifts = {
|
||||||
|
time_sheet_id: string | null;
|
||||||
|
code: string | null;
|
||||||
|
type: string | null;
|
||||||
|
date: Date | null;
|
||||||
|
start_time: bigint | null;
|
||||||
|
end_time: bigint | null;
|
||||||
|
comment: string | null;
|
||||||
|
status: boolean | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const extractOldShifts = async () => {
|
||||||
|
// for (let id = 1; id <= 61; id++) {
|
||||||
|
console.log(`Start of shift migration ***************************************************************`);
|
||||||
|
const new_employee = await findOneNewEmployee(50);
|
||||||
|
console.log(`Employee ${50} found in new DB`);
|
||||||
|
|
||||||
|
const new_timesheets = await findManyNewTimesheets(new_employee.id);
|
||||||
|
console.log(`New Timesheets found for employee ${50}`);
|
||||||
|
for (const ts of new_timesheets) {
|
||||||
|
console.log(`start_date = ${ts.start_date} timesheet_id = ${ts.id}`)
|
||||||
|
|
||||||
|
}
|
||||||
|
console.log('***************************************************************');
|
||||||
|
const old_employee_id = await findOneOldEmployee(new_employee);
|
||||||
|
console.log(`Employee ${new_employee.id} found in old DB`);
|
||||||
|
|
||||||
|
const old_timesheets = await findManyOldTimesheets(old_employee_id);
|
||||||
|
console.log(`Timesheets for employee ${old_employee_id}/${new_employee.id} found in old DB`);
|
||||||
|
|
||||||
|
for (const old_timesheet of old_timesheets) {
|
||||||
|
if (!old_timesheet.start_date) continue;
|
||||||
|
const new_timesheet = new_timesheets.find((ts) => ts.start_date.getTime() === old_timesheet.start_date!.getTime());
|
||||||
|
if (!new_timesheet) {
|
||||||
|
console.warn(`No new timesheet ${new_timesheet} matching legacy timesheet ${old_timesheet.id}`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const old_shifts = await prisma_legacy.shifts.findMany({
|
||||||
|
where: { time_sheet_id: old_timesheet.id },
|
||||||
|
select: {
|
||||||
|
time_sheet_id: true,
|
||||||
|
code: true,
|
||||||
|
type: true,
|
||||||
|
date: true,
|
||||||
|
start_time: true,
|
||||||
|
end_time: true,
|
||||||
|
comment: true,
|
||||||
|
status: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
await createManyNewShifts(new_timesheet.id, old_shifts);
|
||||||
|
}
|
||||||
|
// }
|
||||||
|
await prisma_legacy.$disconnect();
|
||||||
|
await prisma.$disconnect();
|
||||||
|
}
|
||||||
|
|
||||||
|
const findOneNewEmployee = async (id: number): Promise<NewEmployee> => {
|
||||||
|
const new_employee = await prisma.employees.findUnique({
|
||||||
|
where: { id: id },
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
company_code: true,
|
||||||
|
external_payroll_id: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (!new_employee) throw new Error(`New Employee with id ${id} not found`)
|
||||||
|
return new_employee;
|
||||||
|
}
|
||||||
|
|
||||||
|
const findOneOldEmployee = async (new_employee: NewEmployee): Promise<string> => {
|
||||||
|
const old_employee = await prisma_legacy.employees.findFirst({
|
||||||
|
where: {
|
||||||
|
company: new_employee.company_code,
|
||||||
|
employee_number: new_employee.external_payroll_id.toString(),
|
||||||
|
},
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (!old_employee) throw new Error(`Old Employee not found`);
|
||||||
|
return old_employee.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
const findManyOldTimesheets = async (old_employee_id: string) => {
|
||||||
|
const old_timesheets = await prisma_legacy.time_sheets.findMany({
|
||||||
|
where: { employee_id: old_employee_id },
|
||||||
|
select: { id: true, start_date: true, status: true }
|
||||||
|
});
|
||||||
|
return old_timesheets;
|
||||||
|
}
|
||||||
|
|
||||||
|
const findManyNewTimesheets = async (employee_id: number) => {
|
||||||
|
const timesheets = await prisma.timesheets.findMany({
|
||||||
|
where: { employee_id: employee_id },
|
||||||
|
select: { id: true, start_date: true }
|
||||||
|
})
|
||||||
|
return timesheets;
|
||||||
|
}
|
||||||
|
|
||||||
|
const createManyNewShifts = async (timesheet_id: number, old_shifts: OldShifts[]) => {
|
||||||
|
for (const old_shift of old_shifts) {
|
||||||
|
let is_remote = true;
|
||||||
|
|
||||||
|
const start = toHHmmfromLegacyTimestamp(old_shift.start_time);
|
||||||
|
if (old_shift.start_time == null || !start) {
|
||||||
|
console.warn(`Shift start invalid ${old_shift.start_time}`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const end = toHHmmfromLegacyTimestamp(old_shift.end_time);
|
||||||
|
if (old_shift.end_time == null || !end) {
|
||||||
|
console.warn(`Shift end invalid ${old_shift.end_time}`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (old_shift.date == null) {
|
||||||
|
console.warn(`Shift date invalid ${old_shift.date}`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (old_shift.status == null) {
|
||||||
|
console.warn(`status null for legacy shift ${old_shift}`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (old_shift.type == null) {
|
||||||
|
console.warn(`type null for legacy shift ${old_shift.type}`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (old_shift.type === 'office') {
|
||||||
|
is_remote = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (old_shift.code == null) {
|
||||||
|
console.warn(`Code null for legacy shift ${old_shift.code}`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const bank_code_id = await findBankCodeIdUsingOldCode(old_shift.code);
|
||||||
|
try {
|
||||||
|
await prisma.shifts.create({
|
||||||
|
// where: { unique_ts_id_date_start_time: {
|
||||||
|
// timesheet_id,
|
||||||
|
// date: old_shift.date,
|
||||||
|
// start_time: toDateFromHHmm(start) }},
|
||||||
|
// update: {
|
||||||
|
// start_time: toDateFromHHmm(start),
|
||||||
|
// end_time: toDateFromHHmm(end),
|
||||||
|
// comment: old_shift.comment,
|
||||||
|
// is_approved: old_shift.status,
|
||||||
|
// is_remote: is_remote,
|
||||||
|
// bank_code_id: bank_code_id,
|
||||||
|
// },
|
||||||
|
data: {
|
||||||
|
date: old_shift.date,
|
||||||
|
start_time: toDateFromHHmm(start),
|
||||||
|
end_time: toDateFromHHmm(end),
|
||||||
|
comment: old_shift.comment,
|
||||||
|
is_approved: old_shift.status,
|
||||||
|
is_remote: is_remote,
|
||||||
|
timesheet_id: timesheet_id,
|
||||||
|
bank_code_id: bank_code_id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.log('An error occured during shifts creation');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const toHHmmfromLegacyTimestamp = (value: bigint | null): string | null => {
|
||||||
|
if (value == null) return null;
|
||||||
|
const date = new Date(Number(value));
|
||||||
|
const hh = String(date.getHours()).padStart(2, '0');
|
||||||
|
const mm = String(date.getMinutes()).padStart(2, '0');
|
||||||
|
return `${hh}:${mm}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const toDateFromHHmm = (hhmm: string): Date => {
|
||||||
|
const [hh, mm] = hhmm.split(':');
|
||||||
|
const hours = Number(hh);
|
||||||
|
const minutes = Number(mm);
|
||||||
|
return new Date(Date.UTC(1970, 0, 1, hours, minutes, 0, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
const findBankCodeIdUsingOldCode = async (code: string): Promise<number> => {
|
||||||
|
if (code === 'G700') {
|
||||||
|
code = 'G104';
|
||||||
|
} else if (code === 'G140') {
|
||||||
|
code = 'G56'
|
||||||
|
}
|
||||||
|
const bank_code = await prisma.bankCodes.findFirst({
|
||||||
|
where: { bank_code: code },
|
||||||
|
select: { id: true, bank_code: true },
|
||||||
|
});
|
||||||
|
if (!bank_code) throw new Error(`Bank_code_id not found for Code ${code}`)
|
||||||
|
return bank_code.id;
|
||||||
|
}
|
||||||
118
scripts/migrate-timesheets.ts
Normal file
118
scripts/migrate-timesheets.ts
Normal file
|
|
@ -0,0 +1,118 @@
|
||||||
|
import { PrismaClient as Prisma } from "@prisma/client";
|
||||||
|
import { PrismaClient as PrismaLegacy } from "@prisma/client-legacy"
|
||||||
|
import { toStringFromDate } from "src/common/utils/date-utils";
|
||||||
|
|
||||||
|
|
||||||
|
type NewEmployee = {
|
||||||
|
id: number;
|
||||||
|
company_code: number;
|
||||||
|
external_payroll_id: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
type OldTimesheets = {
|
||||||
|
id: string;
|
||||||
|
start_date: Date | null;
|
||||||
|
status: boolean | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const prisma_legacy = new PrismaLegacy({});
|
||||||
|
const prisma_new = new Prisma({});
|
||||||
|
|
||||||
|
export const extractOldTimesheets = async () => {
|
||||||
|
for (let id = 1; id <= 61; id++) {
|
||||||
|
const new_employee = await findOneNewEmployee(id);
|
||||||
|
console.log(`Employee ${id} found in new DB ${new_employee.external_payroll_id}`);
|
||||||
|
|
||||||
|
const old_employee_id = await findOneOldEmployee(new_employee);
|
||||||
|
console.log(`Employee ${new_employee.id} found in old DB`);
|
||||||
|
|
||||||
|
const old_timesheets = await findManyOldTimesheets(old_employee_id);
|
||||||
|
console.log(` ${old_timesheets.length} Timesheets for employee ${old_employee_id}/${new_employee.id} found in old DB`);
|
||||||
|
|
||||||
|
await createManyNewTimesheets(old_timesheets, new_employee);
|
||||||
|
console.log(`${old_timesheets.length} New Timesheets created in new DB for employee ${new_employee.id}`);
|
||||||
|
}
|
||||||
|
await prisma_legacy.$disconnect();
|
||||||
|
await prisma_new.$disconnect();
|
||||||
|
}
|
||||||
|
|
||||||
|
const findOneNewEmployee = async (id: number): Promise<NewEmployee> => {
|
||||||
|
const new_employee = await prisma_new.employees.findUnique({
|
||||||
|
where: { id: id },
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
company_code: true,
|
||||||
|
external_payroll_id: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (!new_employee) throw new Error(`New Employee with id ${id} not found`)
|
||||||
|
return new_employee;
|
||||||
|
}
|
||||||
|
|
||||||
|
const findOneOldEmployee = async (new_employee: NewEmployee): Promise<string> => {
|
||||||
|
const employee_number = new_employee.external_payroll_id.toString()
|
||||||
|
const old_employee = await prisma_legacy.employees.findFirst({
|
||||||
|
where: {
|
||||||
|
company: new_employee.company_code,
|
||||||
|
employee_number: employee_number,
|
||||||
|
},
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (!old_employee) throw new Error(`Old Employee not found`);
|
||||||
|
return old_employee.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
const findManyOldTimesheets = async (old_employee_id: string) => {
|
||||||
|
const old_timesheets = await prisma_legacy.time_sheets.findMany({
|
||||||
|
where: { employee_id: old_employee_id },
|
||||||
|
select: { id: true, start_date: true, status: true }
|
||||||
|
});
|
||||||
|
if (!old_timesheets) throw new Error(`old Timesheets not found for employee_id ${old_employee_id}`)
|
||||||
|
return old_timesheets;
|
||||||
|
}
|
||||||
|
|
||||||
|
const createManyNewTimesheets = async (old_timesheets: OldTimesheets[], new_employee: NewEmployee) => {
|
||||||
|
for (const timesheet of old_timesheets) {
|
||||||
|
if (timesheet.start_date == null) {
|
||||||
|
console.warn(`start_date invalid for legacy timesheet ${timesheet.id}`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (timesheet.status == null) {
|
||||||
|
console.warn(`status null for legacy timesheet ${timesheet.id}`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const new_timesheet = await prisma_new.timesheets.upsert({
|
||||||
|
where: { employee_id_start_date: { employee_id: new_employee.id, start_date: timesheet.start_date } },
|
||||||
|
update: {
|
||||||
|
is_approved: timesheet.status,
|
||||||
|
},
|
||||||
|
create: {
|
||||||
|
employee_id: new_employee.id,
|
||||||
|
start_date: timesheet.start_date,
|
||||||
|
is_approved: timesheet.status,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (!new_timesheet) throw new Error(
|
||||||
|
`Timesheet with start_date: ${toStringFromDate(timesheet.start_date!)} for employee ${new_employee.id} not created`
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error('An error occured during timesheets creation');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extractOldTimesheets()
|
||||||
|
.then(() => {
|
||||||
|
console.log("Migration completed");
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error("Migration failed:", error);
|
||||||
|
})
|
||||||
|
.finally(async () => {
|
||||||
|
await prisma_legacy.$disconnect();
|
||||||
|
await prisma_new.$disconnect();
|
||||||
|
});
|
||||||
21
scripts/migration.service.ts
Normal file
21
scripts/migration.service.ts
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
// import { extractOldTimesheets } from "scripts/migrate-timesheets";
|
||||||
|
// import { extractOldExpenses } from "scripts/migrate-expenses";
|
||||||
|
// import { extractOldShifts } from "scripts/migrate-shifts";
|
||||||
|
import { Injectable } from "@nestjs/common";
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class MigrationService {
|
||||||
|
constructor() {}
|
||||||
|
|
||||||
|
async migrateTimesheets() {
|
||||||
|
// extractOldTimesheets();
|
||||||
|
};
|
||||||
|
|
||||||
|
async migrateShifts() {
|
||||||
|
// extractOldShifts();
|
||||||
|
}
|
||||||
|
|
||||||
|
async migrateExpenses() {
|
||||||
|
// extractOldExpenses();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -12,6 +12,7 @@ import { ValidationError } from 'class-validator';
|
||||||
import { TimeAndAttendanceModule } from 'src/time-and-attendance/time-and-attendance.module';
|
import { TimeAndAttendanceModule } from 'src/time-and-attendance/time-and-attendance.module';
|
||||||
import { AuthenticationModule } from 'src/identity-and-account/authentication/auth.module';
|
import { AuthenticationModule } from 'src/identity-and-account/authentication/auth.module';
|
||||||
import { IdentityAndAccountModule } from 'src/identity-and-account/identity-and-account.module';
|
import { IdentityAndAccountModule } from 'src/identity-and-account/identity-and-account.module';
|
||||||
|
import { PrismaLegacyModule } from 'src/prisma-legacy/prisma.module';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
|
|
@ -20,6 +21,7 @@ import { IdentityAndAccountModule } from 'src/identity-and-account/identity-and-
|
||||||
ScheduleModule.forRoot(), //cronjobs
|
ScheduleModule.forRoot(), //cronjobs
|
||||||
NotificationsModule,
|
NotificationsModule,
|
||||||
PrismaModule,
|
PrismaModule,
|
||||||
|
PrismaLegacyModule,
|
||||||
TimeAndAttendanceModule,
|
TimeAndAttendanceModule,
|
||||||
IdentityAndAccountModule,
|
IdentityAndAccountModule,
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -76,7 +76,7 @@ export const toStringFromDate = (date: Date) =>
|
||||||
|
|
||||||
|
|
||||||
//converts HHmm format to string
|
//converts HHmm format to string
|
||||||
export const toHHmmFromString = (hhmm: string): Date => {
|
export const toDateFromHHmm = (hhmm: string): Date => {
|
||||||
const [hh, mm] = hhmm.split(':').map(Number);
|
const [hh, mm] = hhmm.split(':').map(Number);
|
||||||
const date = new Date('1970-01-01T00:00:00.000Z');
|
const date = new Date('1970-01-01T00:00:00.000Z');
|
||||||
date.setUTCHours(hh, mm, 0, 0);
|
date.setUTCHours(hh, mm, 0, 0);
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,10 @@ import { EmployeesService } from "src/identity-and-account/employees/services/em
|
||||||
import { PreferencesController } from "src/identity-and-account/preferences/controllers/preferences.controller";
|
import { PreferencesController } from "src/identity-and-account/preferences/controllers/preferences.controller";
|
||||||
import { PreferencesModule } from "src/identity-and-account/preferences/preferences.module";
|
import { PreferencesModule } from "src/identity-and-account/preferences/preferences.module";
|
||||||
import { PreferencesService } from "src/identity-and-account/preferences/services/preferences.service";
|
import { PreferencesService } from "src/identity-and-account/preferences/services/preferences.service";
|
||||||
|
import { ModuleAccessModule } from "src/identity-and-account/user-module-access/module-access.module";
|
||||||
|
import { ModuleAccessController } from "src/identity-and-account/user-module-access/controllers/module-access.controller";
|
||||||
|
import { AccessGetService } from "src/identity-and-account/user-module-access/services/module-access-get.service";
|
||||||
|
import { AccessUpdateService } from "src/identity-and-account/user-module-access/services/module-access-update.service";
|
||||||
import { UsersService } from "src/identity-and-account/users-management/services/users.service";
|
import { UsersService } from "src/identity-and-account/users-management/services/users.service";
|
||||||
import { UsersModule } from "src/identity-and-account/users-management/users.module";
|
import { UsersModule } from "src/identity-and-account/users-management/users.module";
|
||||||
|
|
||||||
|
|
@ -15,10 +19,12 @@ import { UsersModule } from "src/identity-and-account/users-management/users.mod
|
||||||
UsersModule,
|
UsersModule,
|
||||||
EmployeesModule,
|
EmployeesModule,
|
||||||
PreferencesModule,
|
PreferencesModule,
|
||||||
|
ModuleAccessModule,
|
||||||
],
|
],
|
||||||
controllers: [
|
controllers: [
|
||||||
EmployeesController,
|
EmployeesController,
|
||||||
PreferencesController,
|
PreferencesController,
|
||||||
|
ModuleAccessController,
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
EmployeesArchivalService,
|
EmployeesArchivalService,
|
||||||
|
|
@ -26,6 +32,8 @@ import { UsersModule } from "src/identity-and-account/users-management/users.mod
|
||||||
PreferencesService,
|
PreferencesService,
|
||||||
UsersService,
|
UsersService,
|
||||||
EmailToIdResolver,
|
EmailToIdResolver,
|
||||||
|
AccessUpdateService,
|
||||||
|
AccessGetService,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class IdentityAndAccountModule { };
|
export class IdentityAndAccountModule { };
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { Body, Controller, Patch, Req } from "@nestjs/common";
|
import { Body, Controller, Get, Patch, Query, Req } from "@nestjs/common";
|
||||||
import { PreferencesService } from "../services/preferences.service";
|
import { PreferencesService } from "../services/preferences.service";
|
||||||
import { PreferencesDto } from "../dtos/preferences.dto";
|
import { PreferencesDto } from "../dtos/preferences.dto";
|
||||||
import { Result } from "src/common/errors/result-error.factory";
|
import { Result } from "src/common/errors/result-error.factory";
|
||||||
|
|
@ -7,7 +7,7 @@ import { Result } from "src/common/errors/result-error.factory";
|
||||||
export class PreferencesController {
|
export class PreferencesController {
|
||||||
constructor(private readonly service: PreferencesService){}
|
constructor(private readonly service: PreferencesService){}
|
||||||
|
|
||||||
@Patch('update_preferences')
|
@Patch('update')
|
||||||
async updatePreferences(
|
async updatePreferences(
|
||||||
@Req()req,
|
@Req()req,
|
||||||
@Body() payload: PreferencesDto
|
@Body() payload: PreferencesDto
|
||||||
|
|
@ -16,4 +16,10 @@ export class PreferencesController {
|
||||||
return this.service.updatePreferences(email, payload);
|
return this.service.updatePreferences(email, payload);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Get()
|
||||||
|
async findPreferences(@Req() req, @Query() employee_email?:string) {
|
||||||
|
const email = req.user?.email;
|
||||||
|
return this.service.findPreferences(email, employee_email);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -2,10 +2,9 @@ import { IsInt } from "class-validator";
|
||||||
|
|
||||||
export class PreferencesDto {
|
export class PreferencesDto {
|
||||||
notifications: number;
|
notifications: number;
|
||||||
dark_mode: number;
|
is_dark_mode: boolean | null;
|
||||||
lang_switch: number;
|
display_language: string | 'fr-FR' | 'en-CA';
|
||||||
lefty_mode: number;
|
is_lefty_mode: boolean;
|
||||||
employee_list_display: number;
|
is_employee_list_grid: boolean;
|
||||||
validation_display: number;
|
is_timesheet_approval_grid: boolean;
|
||||||
timesheet_display: number;
|
|
||||||
}
|
}
|
||||||
|
|
@ -10,9 +10,37 @@ export class PreferencesService {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly prisma: PrismaService,
|
private readonly prisma: PrismaService,
|
||||||
private readonly emailResolver: EmailToIdResolver,
|
private readonly emailResolver: EmailToIdResolver,
|
||||||
|
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
|
async findPreferences(email: string, employee_email?: string): Promise<Result<PreferencesDto, string>> {
|
||||||
|
const account_email = employee_email ?? email;
|
||||||
|
const user_id = await this.emailResolver.resolveUserIdWithEmail(account_email);
|
||||||
|
if (!user_id.success) return { success: false, error: 'EMPLOYEE_NOT_FOUND' };
|
||||||
|
|
||||||
|
const user_preferences = await this.prisma.preferences.findUnique({
|
||||||
|
where: { user_id: user_id.data },
|
||||||
|
select: {
|
||||||
|
notifications: true,
|
||||||
|
is_dark_mode: true,
|
||||||
|
display_language: true,
|
||||||
|
is_lefty_mode: true,
|
||||||
|
is_employee_list_grid: true,
|
||||||
|
is_timesheet_approval_grid: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (!user_preferences) return { success: false, error: 'PREFERENCES_NOT_FOUND' };
|
||||||
|
|
||||||
|
const preferences: PreferencesDto = {
|
||||||
|
is_dark_mode: user_preferences.is_dark_mode,
|
||||||
|
display_language: user_preferences.display_language,
|
||||||
|
is_lefty_mode: user_preferences.is_lefty_mode,
|
||||||
|
notifications: user_preferences.notifications,
|
||||||
|
is_employee_list_grid: user_preferences.is_employee_list_grid,
|
||||||
|
is_timesheet_approval_grid: user_preferences.is_timesheet_approval_grid,
|
||||||
|
};
|
||||||
|
return { success: true, data: preferences };
|
||||||
|
}
|
||||||
|
|
||||||
async updatePreferences(email: string, dto: PreferencesDto): Promise<Result<Preferences, string>> {
|
async updatePreferences(email: string, dto: PreferencesDto): Promise<Result<Preferences, string>> {
|
||||||
const user_id = await this.emailResolver.resolveUserIdWithEmail(email);
|
const user_id = await this.emailResolver.resolveUserIdWithEmail(email);
|
||||||
if (!user_id.success) return { success: false, error: user_id.error }
|
if (!user_id.success) return { success: false, error: user_id.error }
|
||||||
|
|
@ -22,12 +50,11 @@ export class PreferencesService {
|
||||||
where: { user_id: user_id.data },
|
where: { user_id: user_id.data },
|
||||||
data: {
|
data: {
|
||||||
notifications: dto.notifications,
|
notifications: dto.notifications,
|
||||||
dark_mode: dto.dark_mode,
|
is_dark_mode: dto.is_dark_mode ?? undefined,
|
||||||
lang_switch: dto.lang_switch,
|
display_language: dto.display_language,
|
||||||
lefty_mode: dto.lefty_mode,
|
is_lefty_mode: dto.is_lefty_mode,
|
||||||
employee_list_display: dto.employee_list_display,
|
is_employee_list_grid: dto.is_employee_list_grid,
|
||||||
validation_display: dto.validation_display,
|
is_timesheet_approval_grid: dto.is_timesheet_approval_grid,
|
||||||
timesheet_display: dto.timesheet_display,
|
|
||||||
},
|
},
|
||||||
include: { user: true },
|
include: { user: true },
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
import { Body, Controller, Get, Patch, Query, Req } from "@nestjs/common";
|
||||||
|
import { ModuleAccess } from "src/identity-and-account/user-module-access/dtos/module-acces.dto";
|
||||||
|
import { AccessGetService } from "src/identity-and-account/user-module-access/services/module-access-get.service";
|
||||||
|
import { AccessUpdateService } from "src/identity-and-account/user-module-access/services/module-access-update.service";
|
||||||
|
|
||||||
|
@Controller('module_access')
|
||||||
|
export class ModuleAccessController {
|
||||||
|
constructor(
|
||||||
|
private readonly getService: AccessGetService,
|
||||||
|
private readonly updateService: AccessUpdateService,
|
||||||
|
) { }
|
||||||
|
|
||||||
|
@Get()
|
||||||
|
async findAccess(
|
||||||
|
@Req() req,
|
||||||
|
@Query('employee_email') employee_email?: string
|
||||||
|
) {
|
||||||
|
const email = req.user?.email;
|
||||||
|
await this.getService.findModuleAccess(email, employee_email);
|
||||||
|
};
|
||||||
|
|
||||||
|
@Patch('update')
|
||||||
|
async updateAccess(
|
||||||
|
@Req() req,
|
||||||
|
@Body() dto: ModuleAccess,
|
||||||
|
@Query('employee_email') employee_email?: string
|
||||||
|
) {
|
||||||
|
const email = req.user?.email;
|
||||||
|
await this.updateService.updateModuleAccess(email, dto, employee_email);
|
||||||
|
};
|
||||||
|
|
||||||
|
@Patch('revoke')
|
||||||
|
async revokeModuleAccess(
|
||||||
|
@Req() req,
|
||||||
|
@Query('employee_email') employee_email?: string
|
||||||
|
) {
|
||||||
|
const email = req.user?.email;
|
||||||
|
await this.updateService.revokeModuleAccess(email, employee_email);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
import { IsBoolean } from "class-validator";
|
||||||
|
|
||||||
|
export class ModuleAccess {
|
||||||
|
@IsBoolean() timesheets!: boolean;
|
||||||
|
@IsBoolean() timesheets_approval!: boolean;
|
||||||
|
@IsBoolean() employee_list!: boolean;
|
||||||
|
@IsBoolean() employee_management!: boolean;
|
||||||
|
@IsBoolean() personnal_profile!: boolean;
|
||||||
|
@IsBoolean() blocked!: boolean;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
import { Module } from "@nestjs/common";
|
||||||
|
import { AccessUpdateService } from "src/identity-and-account/user-module-access/services/module-access-update.service";
|
||||||
|
import { ModuleAccessController } from "src/identity-and-account/user-module-access/controllers/module-access.controller";
|
||||||
|
import { AccessGetService } from "src/identity-and-account/user-module-access/services/module-access-get.service";
|
||||||
|
import { EmailToIdResolver } from "src/common/mappers/email-id.mapper";
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
controllers: [ModuleAccessController],
|
||||||
|
providers: [AccessUpdateService, AccessGetService, EmailToIdResolver],
|
||||||
|
exports: [],
|
||||||
|
})
|
||||||
|
export class ModuleAccessModule { }
|
||||||
|
|
@ -0,0 +1,42 @@
|
||||||
|
import { Injectable } from "@nestjs/common";
|
||||||
|
import { Result } from "src/common/errors/result-error.factory";
|
||||||
|
import { EmailToIdResolver } from "src/common/mappers/email-id.mapper";
|
||||||
|
import { ModuleAccess } from "src/identity-and-account/user-module-access/dtos/module-acces.dto";
|
||||||
|
import { PrismaService } from "src/prisma/prisma.service";
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class AccessGetService {
|
||||||
|
constructor(
|
||||||
|
private readonly prisma: PrismaService,
|
||||||
|
private readonly emailResolver: EmailToIdResolver,
|
||||||
|
) { }
|
||||||
|
|
||||||
|
async findModuleAccess(email: string, employee_email?: string): Promise<Result<ModuleAccess, string>> {
|
||||||
|
const account_email = employee_email ?? email;
|
||||||
|
const user_id = await this.emailResolver.resolveUserIdWithEmail(account_email);
|
||||||
|
if (!user_id.success) return { success: false, error: 'EMPLOYEE_NOT_FOUND' };
|
||||||
|
|
||||||
|
const access = await this.prisma.userModuleAccess.findUnique({
|
||||||
|
where: { user_id: user_id.data },
|
||||||
|
select: {
|
||||||
|
timesheets: true,
|
||||||
|
timesheets_approval: true,
|
||||||
|
employee_list: true,
|
||||||
|
employee_management: true,
|
||||||
|
personnal_profile: true,
|
||||||
|
blocked: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (!access) return { success: false, error: 'MODULE_ACCESS_NOT_FOUND' };
|
||||||
|
|
||||||
|
const granted_access: ModuleAccess = {
|
||||||
|
timesheets: access.timesheets,
|
||||||
|
timesheets_approval: access.timesheets_approval,
|
||||||
|
employee_list: access.employee_list,
|
||||||
|
employee_management: access.employee_management,
|
||||||
|
personnal_profile: access.personnal_profile,
|
||||||
|
blocked: access.blocked,
|
||||||
|
};
|
||||||
|
return { success: true, data: granted_access }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,70 @@
|
||||||
|
import { Injectable } from "@nestjs/common";
|
||||||
|
import { Result } from "src/common/errors/result-error.factory";
|
||||||
|
import { EmailToIdResolver } from "src/common/mappers/email-id.mapper";
|
||||||
|
import { ModuleAccess } from "src/identity-and-account/user-module-access/dtos/module-acces.dto";
|
||||||
|
import { PrismaService } from "src/prisma/prisma.service";
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class AccessUpdateService {
|
||||||
|
constructor(
|
||||||
|
private readonly prisma: PrismaService,
|
||||||
|
private readonly emailResolver: EmailToIdResolver,
|
||||||
|
) { }
|
||||||
|
|
||||||
|
async updateModuleAccess(email: string, dto: ModuleAccess, employee_email?: string): Promise<Result<boolean, string>> {
|
||||||
|
const account_email = employee_email ?? email;
|
||||||
|
const user_id = await this.emailResolver.resolveUserIdWithEmail(account_email);
|
||||||
|
if (!user_id.success) return { success: false, error: 'EMPLOYEE_NOT_FOUND' };
|
||||||
|
|
||||||
|
const orignal_access = await this.prisma.userModuleAccess.findUnique({
|
||||||
|
where: { user_id: user_id.data },
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
timesheets: true,
|
||||||
|
timesheets_approval: true,
|
||||||
|
employee_list: true,
|
||||||
|
employee_management: true,
|
||||||
|
personnal_profile: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (!orignal_access) return { success: false, error: 'MODULE_ACCESS_NOT_FOUND' };
|
||||||
|
|
||||||
|
await this.prisma.userModuleAccess.update({
|
||||||
|
where: { id: orignal_access.id },
|
||||||
|
data: {
|
||||||
|
timesheets: dto.timesheets,
|
||||||
|
timesheets_approval: dto.timesheets_approval,
|
||||||
|
employee_list: dto.employee_list,
|
||||||
|
employee_management: dto.employee_management,
|
||||||
|
personnal_profile: dto.personnal_profile,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return { success: true, data: true };
|
||||||
|
}
|
||||||
|
|
||||||
|
async revokeModuleAccess(email: string, employee_email?: string): Promise<Result<boolean, string>> {
|
||||||
|
const account_email = employee_email ?? email;
|
||||||
|
const user_id = await this.emailResolver.resolveUserIdWithEmail(account_email);
|
||||||
|
if (!user_id.success) return { success: false, error: 'EMPLOYEE_NOT_FOUND' };
|
||||||
|
|
||||||
|
const access = await this.prisma.userModuleAccess.findUnique({
|
||||||
|
where: { user_id: user_id.data },
|
||||||
|
select: { id: true },
|
||||||
|
});
|
||||||
|
if (!access) return { success: false, error: 'MODULE_ACCESS_NOT_FOUND' };
|
||||||
|
|
||||||
|
await this.prisma.userModuleAccess.update({
|
||||||
|
where: { id: access.id },
|
||||||
|
data: {
|
||||||
|
timesheets: false,
|
||||||
|
timesheets_approval: false,
|
||||||
|
employee_list: false,
|
||||||
|
employee_management: false,
|
||||||
|
personnal_profile: false,
|
||||||
|
blocked: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return { success: true, data: true };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -18,6 +18,9 @@ import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
|
||||||
import { writeFileSync } from 'fs';
|
import { writeFileSync } from 'fs';
|
||||||
import * as session from 'express-session';
|
import * as session from 'express-session';
|
||||||
import * as passport from 'passport';
|
import * as passport from 'passport';
|
||||||
|
import { extractOldShifts } from 'scripts/migrate-shifts';
|
||||||
|
import { extractOldTimesheets } from 'scripts/migrate-timesheets';
|
||||||
|
import { extractOldExpenses } from 'scripts/migrate-expenses';
|
||||||
|
|
||||||
const SESSION_TOKEN_DURATION_MINUTES = 180
|
const SESSION_TOKEN_DURATION_MINUTES = 180
|
||||||
|
|
||||||
|
|
@ -89,5 +92,11 @@ async function bootstrap() {
|
||||||
|
|
||||||
await ensureAttachmentsTmpDir();
|
await ensureAttachmentsTmpDir();
|
||||||
await app.listen(process.env.PORT ?? 3000);
|
await app.listen(process.env.PORT ?? 3000);
|
||||||
|
|
||||||
|
|
||||||
|
// migration function calls
|
||||||
|
// await extractOldTimesheets();
|
||||||
|
// await extractOldShifts();
|
||||||
|
// await extractOldExpenses();
|
||||||
}
|
}
|
||||||
bootstrap();
|
bootstrap();
|
||||||
|
|
|
||||||
9
src/prisma-legacy/prisma.module.ts
Normal file
9
src/prisma-legacy/prisma.module.ts
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
import { Global, Module } from '@nestjs/common';
|
||||||
|
import { PrismaLegacyService } from './prisma.service';
|
||||||
|
|
||||||
|
@Global()
|
||||||
|
@Module({
|
||||||
|
providers: [PrismaLegacyService],
|
||||||
|
exports: [PrismaLegacyService],
|
||||||
|
})
|
||||||
|
export class PrismaLegacyModule {}
|
||||||
18
src/prisma-legacy/prisma.service.ts
Normal file
18
src/prisma-legacy/prisma.service.ts
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
import { Injectable, OnModuleInit, OnModuleDestroy } from '@nestjs/common';
|
||||||
|
import { PrismaClient as PrismaLegacyClient } from '@prisma/client';
|
||||||
|
|
||||||
|
//Gestion des connections à la DB
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class PrismaLegacyService
|
||||||
|
extends PrismaLegacyClient
|
||||||
|
implements OnModuleInit, OnModuleDestroy
|
||||||
|
{
|
||||||
|
async onModuleInit() {
|
||||||
|
await this.$connect();
|
||||||
|
}
|
||||||
|
|
||||||
|
async onModuleDestroy() {
|
||||||
|
await this.$disconnect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,64 +1,66 @@
|
||||||
// import { Controller, Param, Query, Body, Get, Post, ParseIntPipe, Delete, Patch, Req } from "@nestjs/common";
|
import { Controller, Param, Query, Body, Get, Post, ParseIntPipe, Delete, Patch, Req } from "@nestjs/common";
|
||||||
// import { RolesAllowed } from "src/common/decorators/roles.decorators";
|
import { RolesAllowed } from "src/common/decorators/roles.decorators";
|
||||||
// import { GLOBAL_CONTROLLER_ROLES, MANAGER_ROLES } from "src/common/shared/role-groupes";
|
import { GLOBAL_CONTROLLER_ROLES, MANAGER_ROLES } from "src/common/shared/role-groupes";
|
||||||
// import { SchedulePresetsDto } from "src/time-and-attendance/schedule-presets/dtos/create-schedule-presets.dto";
|
import { SchedulePresetsDto } from "src/time-and-attendance/schedule-presets/dtos/create-schedule-presets.dto";
|
||||||
// import { SchedulePresetsUpdateDto } from "src/time-and-attendance/schedule-presets/dtos/update-schedule-presets.dto";
|
import { SchedulePresetsApplyService } from "src/time-and-attendance/schedule-presets/services/schedule-presets-apply.service";
|
||||||
// import { SchedulePresetsApplyService } from "src/time-and-attendance/schedule-presets/services/schedule-presets-apply.service";
|
import { SchedulePresetsGetService } from "src/time-and-attendance/schedule-presets/services/schedule-presets-get.service";
|
||||||
// import { SchedulePresetsGetService } from "src/time-and-attendance/schedule-presets/services/schedule-presets-get.service";
|
import { SchedulePresetsCreateService } from "src/time-and-attendance/schedule-presets/services/schedule-presets-create.service";
|
||||||
// import { SchedulePresetsUpsertService } from "src/time-and-attendance/schedule-presets/services/schedule-presets-upsert.service";
|
import { SchedulePresetUpdateDeleteService } from "src/time-and-attendance/schedule-presets/services/schedule-presets-update-delete.service";
|
||||||
|
|
||||||
// @Controller('schedule-presets')
|
@Controller('schedule-presets')
|
||||||
// @RolesAllowed(...GLOBAL_CONTROLLER_ROLES)
|
@RolesAllowed(...GLOBAL_CONTROLLER_ROLES)
|
||||||
// export class SchedulePresetsController {
|
export class SchedulePresetsController {
|
||||||
// constructor(
|
constructor(
|
||||||
// private readonly upsertService: SchedulePresetsUpsertService,
|
private readonly createService: SchedulePresetsCreateService,
|
||||||
// private readonly getService: SchedulePresetsGetService,
|
private readonly getService: SchedulePresetsGetService,
|
||||||
// private readonly applyPresetsService: SchedulePresetsApplyService,
|
private readonly applyPresetsService: SchedulePresetsApplyService,
|
||||||
// ) { }
|
private readonly updateDeleteService: SchedulePresetUpdateDeleteService,
|
||||||
|
) { }
|
||||||
|
|
||||||
// //used to create a schedule preset
|
// used to create a schedule preset
|
||||||
// @Post('create')
|
@Post('create')
|
||||||
// @RolesAllowed(...MANAGER_ROLES)
|
@RolesAllowed(...MANAGER_ROLES)
|
||||||
// async createPreset(@Req() req, @Body() dto: SchedulePresetsDto) {
|
async createPreset(@Req() req, @Body() dto: SchedulePresetsDto) {
|
||||||
// const email = req.user?.email;
|
const email = req.user?.email;
|
||||||
// return await this.upsertService.createPreset(email, dto);
|
return await this.createService.createPreset(email, dto);
|
||||||
// }
|
}
|
||||||
|
|
||||||
// // //used to update an already existing schedule preset
|
//used to update an already existing schedule preset
|
||||||
// // @Patch('update/:preset_id')
|
@Patch('update/:preset_id')
|
||||||
// // @RolesAllowed(...MANAGER_ROLES)
|
@RolesAllowed(...MANAGER_ROLES)
|
||||||
// // async updatePreset(
|
async updatePreset(
|
||||||
// // @Param('preset_id', ParseIntPipe) preset_id: number,
|
@Param('preset_id', ParseIntPipe) preset_id: number,
|
||||||
// // @Body() dto: SchedulePresetsUpdateDto
|
@Body() dto: SchedulePresetsDto,
|
||||||
// // ) {
|
@Req() req,
|
||||||
// // return await this.upsertService.updatePreset(preset_id, dto);
|
) {
|
||||||
// // }
|
const email = req.user?.email;
|
||||||
|
return await this.updateDeleteService.updatePreset(preset_id, dto, email);
|
||||||
|
}
|
||||||
|
|
||||||
// //used to delete a schedule preset
|
//used to delete a schedule preset
|
||||||
// @Delete('delete/:preset_id')
|
@Delete('delete/:preset_id')
|
||||||
// @RolesAllowed(...MANAGER_ROLES)
|
@RolesAllowed(...MANAGER_ROLES)
|
||||||
// async deletePreset(
|
async deletePreset(@Param('preset_id', ParseIntPipe) preset_id: number, @Req() req) {
|
||||||
// @Param('preset_id', ParseIntPipe) preset_id: number) {
|
const email = req.user?.email;
|
||||||
// return await this.upsertService.deletePreset(preset_id);
|
return await this.updateDeleteService.deletePreset(preset_id, email);
|
||||||
// }
|
}
|
||||||
|
|
||||||
|
//used to show the list of available schedule presets
|
||||||
|
@Get('find-list')
|
||||||
|
@RolesAllowed(...MANAGER_ROLES)
|
||||||
|
async findListById(@Req() req) {
|
||||||
|
const email = req.user?.email;
|
||||||
|
return this.getService.getSchedulePresets(email);
|
||||||
|
}
|
||||||
|
|
||||||
// //used to show the list of available schedule presets
|
//used to apply a preset to a timesheet
|
||||||
// @Get('find-list')
|
@Post('apply-presets')
|
||||||
// @RolesAllowed(...MANAGER_ROLES)
|
async applyPresets(
|
||||||
// async findListById(@Req() req) {
|
@Body('preset') preset_id: number,
|
||||||
// const email = req.user?.email;
|
@Body('start') start_date: string,
|
||||||
// return this.getService.getSchedulePresets(email);
|
@Req() req
|
||||||
// }
|
) {
|
||||||
|
const email = req.user?.email;
|
||||||
// //used to apply a preset to a timesheet
|
return this.applyPresetsService.applyToTimesheet(email, preset_id, start_date);
|
||||||
// @Post('apply-presets')
|
}
|
||||||
// async applyPresets(
|
}
|
||||||
// @Req() req,
|
|
||||||
// @Body('preset') preset_id: number,
|
|
||||||
// @Body('start') start_date: string
|
|
||||||
// ) {
|
|
||||||
// const email = req.user?.email;
|
|
||||||
// return this.applyPresetsService.applyToTimesheet(email, preset_id, start_date);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
@ -3,28 +3,11 @@ import { HH_MM_REGEX } from "src/common/utils/constants.utils";
|
||||||
import { Weekday } from "@prisma/client";
|
import { Weekday } from "@prisma/client";
|
||||||
|
|
||||||
export class SchedulePresetShiftsDto {
|
export class SchedulePresetShiftsDto {
|
||||||
@IsEnum(Weekday)
|
@IsInt() preset_id!: number;
|
||||||
week_day!: Weekday;
|
@IsEnum(Weekday) week_day!: Weekday;
|
||||||
|
@IsInt() @Min(1) sort_order!: number;
|
||||||
@IsInt()
|
@IsString() type!: string;
|
||||||
preset_id!: number;
|
@IsString() @Matches(HH_MM_REGEX) start_time!: string;
|
||||||
|
@IsString() @Matches(HH_MM_REGEX) end_time!: string;
|
||||||
@IsInt()
|
@IsOptional() @IsBoolean() is_remote?: boolean;
|
||||||
@Min(1)
|
|
||||||
sort_order!: number;
|
|
||||||
|
|
||||||
@IsString()
|
|
||||||
type!: string;
|
|
||||||
|
|
||||||
@IsString()
|
|
||||||
@Matches(HH_MM_REGEX)
|
|
||||||
start_time!: string;
|
|
||||||
|
|
||||||
@IsString()
|
|
||||||
@Matches(HH_MM_REGEX)
|
|
||||||
end_time!: string;
|
|
||||||
|
|
||||||
@IsOptional()
|
|
||||||
@IsBoolean()
|
|
||||||
is_remote?: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
@ -2,18 +2,8 @@ import { ArrayMinSize, IsArray, IsBoolean, IsInt, IsOptional, IsString } from "c
|
||||||
import { SchedulePresetShiftsDto } from "src/time-and-attendance/schedule-presets/dtos/create-schedule-preset-shifts.dto";
|
import { SchedulePresetShiftsDto } from "src/time-and-attendance/schedule-presets/dtos/create-schedule-preset-shifts.dto";
|
||||||
|
|
||||||
export class SchedulePresetsDto {
|
export class SchedulePresetsDto {
|
||||||
|
@IsInt() id!: number;
|
||||||
@IsInt()
|
@IsString() name!: string;
|
||||||
id!: number;
|
@IsBoolean() @IsOptional() is_default: boolean;
|
||||||
|
@IsArray() @ArrayMinSize(1) preset_shifts: SchedulePresetShiftsDto[];
|
||||||
@IsString()
|
|
||||||
name!: string;
|
|
||||||
|
|
||||||
@IsBoolean()
|
|
||||||
@IsOptional()
|
|
||||||
is_default: boolean;
|
|
||||||
|
|
||||||
@IsArray()
|
|
||||||
@ArrayMinSize(1)
|
|
||||||
preset_shifts: SchedulePresetShiftsDto[];
|
|
||||||
}
|
}
|
||||||
|
|
@ -1,21 +1,25 @@
|
||||||
|
|
||||||
import { Module } from "@nestjs/common";
|
import { Module } from "@nestjs/common";
|
||||||
// import { SchedulePresetsController } from "src/time-and-attendance/time-tracker/schedule-presets/controller/schedule-presets.controller";
|
import { SchedulePresetsController } from "src/time-and-attendance/schedule-presets/controller/schedule-presets.controller";
|
||||||
// import { SchedulePresetsApplyService } from "src/time-and-attendance/time-tracker/schedule-presets/services/schedule-presets-apply.service";
|
import { SchedulePresetsApplyService } from "src/time-and-attendance/schedule-presets/services/schedule-presets-apply.service";
|
||||||
// import { SchedulePresetsGetService } from "src/time-and-attendance/time-tracker/schedule-presets/services/schedule-presets-get.service";
|
import { SchedulePresetsCreateService } from "src/time-and-attendance/schedule-presets/services/schedule-presets-create.service";
|
||||||
// import { SchedulePresetsUpsertService } from "src/time-and-attendance/time-tracker/schedule-presets/services/schedule-presets-upsert.service";
|
import { SchedulePresetsGetService } from "src/time-and-attendance/schedule-presets/services/schedule-presets-get.service";
|
||||||
|
import { SchedulePresetUpdateDeleteService } from "src/time-and-attendance/schedule-presets/services/schedule-presets-update-delete.service";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
controllers: [/*SchedulePresetsController*/],
|
controllers: [SchedulePresetsController],
|
||||||
// providers: [
|
providers: [
|
||||||
// SchedulePresetsUpsertService,
|
SchedulePresetsCreateService,
|
||||||
// SchedulePresetsGetService,
|
SchedulePresetUpdateDeleteService,
|
||||||
// SchedulePresetsApplyService,
|
SchedulePresetsGetService,
|
||||||
// ],
|
SchedulePresetsApplyService,
|
||||||
exports:[
|
|
||||||
// SchedulePresetsUpsertService,
|
|
||||||
// SchedulePresetsGetService,
|
|
||||||
// SchedulePresetsApplyService,
|
|
||||||
],
|
],
|
||||||
}) export class SchedulePresetsModule {}
|
exports: [
|
||||||
|
SchedulePresetsCreateService,
|
||||||
|
SchedulePresetUpdateDeleteService,
|
||||||
|
SchedulePresetsGetService,
|
||||||
|
SchedulePresetsApplyService,
|
||||||
|
],
|
||||||
|
}) export class SchedulePresetsModule { }
|
||||||
|
|
@ -9,7 +9,10 @@ import { Result } from "src/common/errors/result-error.factory";
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class SchedulePresetsApplyService {
|
export class SchedulePresetsApplyService {
|
||||||
constructor(private readonly prisma: PrismaService, private readonly emailResolver: EmailToIdResolver) { }
|
constructor(
|
||||||
|
private readonly prisma: PrismaService,
|
||||||
|
private readonly emailResolver: EmailToIdResolver
|
||||||
|
) { }
|
||||||
|
|
||||||
async applyToTimesheet(email: string, id: number, start_date_iso: string): Promise<Result<ApplyResult, string>> {
|
async applyToTimesheet(email: string, id: number, start_date_iso: string): Promise<Result<ApplyResult, string>> {
|
||||||
if (!DATE_ISO_FORMAT.test(start_date_iso)) return { success: false, error: 'start_date must be of format :YYYY-MM-DD' };
|
if (!DATE_ISO_FORMAT.test(start_date_iso)) return { success: false, error: 'start_date must be of format :YYYY-MM-DD' };
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,125 @@
|
||||||
|
import { Injectable } from "@nestjs/common";
|
||||||
|
import { Weekday } from "@prisma/client";
|
||||||
|
import { PrismaService } from "src/prisma/prisma.service";
|
||||||
|
import { BankCodesResolver } from "src/common/mappers/bank-type-id.mapper";
|
||||||
|
import { EmailToIdResolver } from "src/common/mappers/email-id.mapper";
|
||||||
|
import { Result } from "src/common/errors/result-error.factory";
|
||||||
|
import { overlaps, toDateFromHHmm } from "src/common/utils/date-utils";
|
||||||
|
import { SchedulePresetsDto } from "src/time-and-attendance/schedule-presets/dtos/create-schedule-presets.dto";
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class SchedulePresetsCreateService {
|
||||||
|
constructor(
|
||||||
|
private readonly prisma: PrismaService,
|
||||||
|
private readonly typeResolver: BankCodesResolver,
|
||||||
|
private readonly emailResolver: EmailToIdResolver,
|
||||||
|
) { }
|
||||||
|
//_________________________________________________________________
|
||||||
|
// CREATE
|
||||||
|
//_________________________________________________________________
|
||||||
|
async createPreset(email: string, dto: SchedulePresetsDto): Promise<Result<boolean, string>> {
|
||||||
|
try {
|
||||||
|
|
||||||
|
//validate email and fetch employee_id
|
||||||
|
const employee_id = await this.emailResolver.findIdByEmail(email);
|
||||||
|
if (!employee_id.success) return { success: false, error: employee_id.error };
|
||||||
|
|
||||||
|
//validate new unique name
|
||||||
|
const existing = await this.prisma.schedulePresets.findFirst({
|
||||||
|
where: { name: dto.name, employee_id: employee_id.data },
|
||||||
|
select: { name: true },
|
||||||
|
});
|
||||||
|
if (!existing) return { success: false, error: 'INVALID_SCHEDULE_PRESET' };
|
||||||
|
|
||||||
|
const normalized_shifts = dto.preset_shifts.map((shift) => ({
|
||||||
|
...shift,
|
||||||
|
start: toDateFromHHmm(shift.start_time),
|
||||||
|
end: toDateFromHHmm(shift.end_time),
|
||||||
|
}));
|
||||||
|
|
||||||
|
for (const preset_shifts of normalized_shifts) {
|
||||||
|
for (const other_shifts of normalized_shifts) {
|
||||||
|
//skip if same object or id week_day is not the same
|
||||||
|
if (preset_shifts === other_shifts) continue;
|
||||||
|
if (preset_shifts.week_day !== other_shifts.week_day) continue;
|
||||||
|
//check overlaping possibilities
|
||||||
|
const has_overlap = overlaps(
|
||||||
|
{ start: preset_shifts.start, end: preset_shifts.end },
|
||||||
|
{ start: other_shifts.start, end: other_shifts.end },
|
||||||
|
)
|
||||||
|
if (has_overlap) return { success: false, error: 'SCHEDULE_PRESET_OVERLAP' };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//validate bank_code_id/type and map them
|
||||||
|
const bank_code_results = await Promise.all(dto.preset_shifts.map((shift) =>
|
||||||
|
this.typeResolver.findBankCodeIDByType(shift.type),
|
||||||
|
));
|
||||||
|
for (const result of bank_code_results) {
|
||||||
|
if (!result.success) return { success: false, error: 'INVALID_SCHEDULE_PRESET' }
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.prisma.$transaction(async (tx) => {
|
||||||
|
//check if employee chose this preset has a default preset and ensure all others are false
|
||||||
|
if (dto.is_default) {
|
||||||
|
await tx.schedulePresets.updateMany({
|
||||||
|
where: { employee_id: employee_id.data, is_default: true },
|
||||||
|
data: { is_default: false },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
await tx.schedulePresets.create({
|
||||||
|
data: {
|
||||||
|
employee_id: employee_id.data,
|
||||||
|
name: dto.name,
|
||||||
|
is_default: dto.is_default ?? false,
|
||||||
|
shifts: {
|
||||||
|
create: dto.preset_shifts.map((shift, index) => {
|
||||||
|
//validated bank_codes sent as a Result Array to access its data
|
||||||
|
const result = bank_code_results[index] as { success: true, data: number };
|
||||||
|
return {
|
||||||
|
week_day: shift.week_day,
|
||||||
|
sort_order: shift.sort_order,
|
||||||
|
start_time: toDateFromHHmm(shift.start_time),
|
||||||
|
end_time: toDateFromHHmm(shift.end_time),
|
||||||
|
is_remote: shift.is_remote ?? false,
|
||||||
|
bank_code: {
|
||||||
|
//connect uses the FK links to set the bank_code_id
|
||||||
|
connect: { id: result.data },
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return { success: true, data: true }
|
||||||
|
} catch (error) {
|
||||||
|
return { success: false, error: 'INVALID_SCHEDULE_PRESET'}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// //PRIVATE HELPERS
|
||||||
|
|
||||||
|
//resolve bank_code_id using type and convert hours to TIME and valid shifts end/start
|
||||||
|
// private async normalizePresetShifts(preset_shift: SchedulePresetShiftsDto, schedul_preset: SchedulePresetsDto): Promise<Result<Normalized, string>> {
|
||||||
|
|
||||||
|
// const bank_code = await this.typeResolver.findIdAndModifierByType(preset_shift.type);
|
||||||
|
// if (!bank_code.success) return { success: false, error: 'INVALID_SCHEDULE_PRESET_SHIFT' };
|
||||||
|
|
||||||
|
// const start = await toDateFromHHmm(preset_shift.start_time);
|
||||||
|
// const end = await toDateFromHHmm(preset_shift.end_time);
|
||||||
|
|
||||||
|
// //TODO: add a way to fetch
|
||||||
|
|
||||||
|
|
||||||
|
// const normalized_preset_shift:Normalized = {
|
||||||
|
// date: ,
|
||||||
|
// start_time : start,
|
||||||
|
// end_time: end,
|
||||||
|
// bank_code_id: bank_code.data.id,
|
||||||
|
// }
|
||||||
|
// return { success: true data: normalized_preset_shift }
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
@ -6,32 +6,33 @@ import { Result } from "src/common/errors/result-error.factory";
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class SchedulePresetsGetService {
|
export class SchedulePresetsGetService {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly prisma: PrismaService,
|
private readonly prisma: PrismaService,
|
||||||
private readonly emailResolver: EmailToIdResolver,
|
private readonly emailResolver: EmailToIdResolver,
|
||||||
){}
|
) { }
|
||||||
|
|
||||||
async getSchedulePresets(email: string): Promise<Result<PresetResponse[], string>> {
|
async getSchedulePresets(email: string): Promise<Result<PresetResponse[], string>> {
|
||||||
try {
|
try {
|
||||||
const employee_id = await this.emailResolver.findIdByEmail(email);
|
const employee_id = await this.emailResolver.findIdByEmail(email);
|
||||||
if(!employee_id.success) return { success: false, error: employee_id.error }
|
if (!employee_id.success) return { success: false, error: employee_id.error };
|
||||||
|
|
||||||
const presets = await this.prisma.schedulePresets.findMany({
|
const presets = await this.prisma.schedulePresets.findMany({
|
||||||
where: { employee_id: employee_id.data },
|
where: { employee_id: employee_id.data },
|
||||||
orderBy: [{is_default: 'desc' }, { name: 'asc' }],
|
orderBy: [{ is_default: 'desc' }, { name: 'asc' }],
|
||||||
include: {
|
include: {
|
||||||
shifts: {
|
shifts: {
|
||||||
orderBy: [{week_day:'asc'}, { sort_order: 'asc'}],
|
orderBy: [{ week_day: 'asc' }, { sort_order: 'asc' }],
|
||||||
include: { bank_code: { select: { type: true } } },
|
include: { bank_code: { select: { type: true } } },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
const hhmm = (date: Date) => date.toISOString().slice(11,16);
|
const hhmm = (date: Date) => date.toISOString().slice(11, 16);
|
||||||
|
|
||||||
const response: PresetResponse[] = presets.map((preset) => ({
|
const response: PresetResponse[] = presets.map((preset) => ({
|
||||||
id: preset.id,
|
id: preset.id,
|
||||||
name: preset.name,
|
name: preset.name,
|
||||||
is_default: preset.is_default,
|
is_default: preset.is_default,
|
||||||
shifts: preset.shifts.map<ShiftResponse>((shift)=> ({
|
shifts: preset.shifts.map<ShiftResponse>((shift) => ({
|
||||||
week_day: shift.week_day,
|
week_day: shift.week_day,
|
||||||
sort_order: shift.sort_order,
|
sort_order: shift.sort_order,
|
||||||
start_time: hhmm(shift.start_time),
|
start_time: hhmm(shift.start_time),
|
||||||
|
|
@ -40,10 +41,10 @@ export class SchedulePresetsGetService {
|
||||||
type: shift.bank_code?.type,
|
type: shift.bank_code?.type,
|
||||||
})),
|
})),
|
||||||
}));
|
}));
|
||||||
return { success: true, data:response};
|
return { success: true, data: response };
|
||||||
} catch ( error) {
|
} catch (error) {
|
||||||
return { success: false, error: `Schedule presets for employee with email ${email} not found`};
|
return { success: false, error: `SCHEDULE_PRESET_NOT_FOUND` };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,125 @@
|
||||||
|
import { Injectable } from "@nestjs/common";
|
||||||
|
import { Result } from "src/common/errors/result-error.factory";
|
||||||
|
import { BankCodesResolver } from "src/common/mappers/bank-type-id.mapper";
|
||||||
|
import { EmailToIdResolver } from "src/common/mappers/email-id.mapper";
|
||||||
|
import { overlaps, toDateFromHHmm } from "src/common/utils/date-utils";
|
||||||
|
import { PrismaService } from "src/prisma/prisma.service";
|
||||||
|
import { SchedulePresetsDto } from "src/time-and-attendance/schedule-presets/dtos/create-schedule-presets.dto";
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class SchedulePresetUpdateDeleteService {
|
||||||
|
constructor(
|
||||||
|
private readonly prisma: PrismaService,
|
||||||
|
private readonly typeResolver: BankCodesResolver,
|
||||||
|
private readonly emailResolver: EmailToIdResolver,
|
||||||
|
) { }
|
||||||
|
|
||||||
|
//_________________________________________________________________
|
||||||
|
// UPDATE
|
||||||
|
//_________________________________________________________________
|
||||||
|
async updatePreset(preset_id: number, dto: SchedulePresetsDto, email: string): Promise<Result<boolean, string>> {
|
||||||
|
|
||||||
|
const employee_id = await this.emailResolver.findIdByEmail(email);
|
||||||
|
if (!employee_id.success) return { success: false, error: 'EMPLOYEE_NOT_FOUND' }
|
||||||
|
//look for existing schedule_preset and return OG's data
|
||||||
|
const existing = await this.prisma.schedulePresets.findFirst({
|
||||||
|
where: { id: preset_id, name: dto.name, employee_id: employee_id.data },
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
is_default: true,
|
||||||
|
employee_id: true,
|
||||||
|
shifts: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (!existing) return { success: false, error: `SCHEDULE_PRESET_NOT_FOUND` };
|
||||||
|
//normalized shifts start and end time to make an overlap check with other shifts
|
||||||
|
const normalized_shifts = dto.preset_shifts.map((shift) => ({
|
||||||
|
...shift,
|
||||||
|
start: toDateFromHHmm(shift.start_time),
|
||||||
|
end: toDateFromHHmm(shift.end_time),
|
||||||
|
}));
|
||||||
|
|
||||||
|
for (const preset_shifts of normalized_shifts) {
|
||||||
|
for (const other_shifts of normalized_shifts) {
|
||||||
|
//skip if same object or id week_day is not the same
|
||||||
|
if (preset_shifts === other_shifts) continue;
|
||||||
|
if (preset_shifts.week_day !== other_shifts.week_day) continue;
|
||||||
|
//check overlaping possibilities
|
||||||
|
const has_overlap = overlaps(
|
||||||
|
{ start: preset_shifts.start, end: preset_shifts.end },
|
||||||
|
{ start: other_shifts.start, end: other_shifts.end },
|
||||||
|
)
|
||||||
|
if (has_overlap) return { success: false, error: 'SCHEDULE_PRESET_OVERLAP' };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//validate bank_code_id/type and map them
|
||||||
|
const bank_code_results = await Promise.all(dto.preset_shifts.map((shift) =>
|
||||||
|
this.typeResolver.findBankCodeIDByType(shift.type),
|
||||||
|
));
|
||||||
|
for (const result of bank_code_results) {
|
||||||
|
if (!result.success) return { success: false, error: 'INVALID_SCHEDULE_PRESET' }
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.prisma.$transaction(async (tx) => {
|
||||||
|
//check if employee chose this preset has a default preset and ensure all others are false
|
||||||
|
if (dto.is_default) {
|
||||||
|
await tx.schedulePresets.updateMany({
|
||||||
|
where: {
|
||||||
|
employee_id: existing.employee_id,
|
||||||
|
is_default: true,
|
||||||
|
NOT: { id: existing.id },
|
||||||
|
},
|
||||||
|
data: { is_default: false },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
//deletes old preset shifts to make place to new ones
|
||||||
|
await tx.schedulePresetShifts.deleteMany({ where: { preset_id: existing.id } });
|
||||||
|
|
||||||
|
await tx.schedulePresets.update({
|
||||||
|
where: { id: existing.id },
|
||||||
|
data: {
|
||||||
|
name: dto.name,
|
||||||
|
is_default: dto.is_default ?? false,
|
||||||
|
shifts: {
|
||||||
|
create: dto.preset_shifts.map((shift, index) => {
|
||||||
|
//validated bank_codes sent as a Result Array to access its data
|
||||||
|
const result = bank_code_results[index] as { success: true, data: number };
|
||||||
|
return {
|
||||||
|
week_day: shift.week_day,
|
||||||
|
sort_order: shift.sort_order,
|
||||||
|
start_time: toDateFromHHmm(shift.start_time),
|
||||||
|
end_time: toDateFromHHmm(shift.end_time),
|
||||||
|
is_remote: shift.is_remote ?? false,
|
||||||
|
bank_code: {
|
||||||
|
//connect uses the FK links to set the bank_code_id
|
||||||
|
connect: { id: result.data },
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return { success: true, data: true };
|
||||||
|
}
|
||||||
|
|
||||||
|
//_________________________________________________________________
|
||||||
|
// DELETE
|
||||||
|
//_________________________________________________________________
|
||||||
|
async deletePreset(preset_id: number, email: string): Promise<Result<boolean, string>> {
|
||||||
|
const employee_id = await this.emailResolver.findIdByEmail(email);
|
||||||
|
if (!employee_id.success) return { success: false, error: 'EMPLOYEE_NOT_FOUND' }
|
||||||
|
|
||||||
|
const preset = await this.prisma.schedulePresets.findFirst({
|
||||||
|
where: { id: preset_id, employee_id: employee_id.data },
|
||||||
|
select: { id: true },
|
||||||
|
});
|
||||||
|
if (!preset) return { success: false, error: `SCHEDULE_PRESET_NOT_FOUND` };
|
||||||
|
|
||||||
|
await this.prisma.$transaction(async (tx) => {
|
||||||
|
await tx.schedulePresetShifts.deleteMany({ where: { preset_id: preset_id } });
|
||||||
|
await tx.schedulePresets.delete({ where: { id: preset_id } });
|
||||||
|
});
|
||||||
|
return { success: true, data: true };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,227 +0,0 @@
|
||||||
// import { Injectable, BadRequestException, NotFoundException, ConflictException } from "@nestjs/common";
|
|
||||||
// import { Prisma, Weekday } from "@prisma/client";
|
|
||||||
// import { PrismaService } from "src/prisma/prisma.service";
|
|
||||||
// import { BankCodesResolver } from "src/common/mappers/bank-type-id.mapper";
|
|
||||||
// import { EmailToIdResolver } from "src/common/mappers/email-id.mapper";
|
|
||||||
// import { Result } from "src/common/errors/result-error.factory";
|
|
||||||
// import { toHHmmFromDate, toDateFromString } from "src/common/utils/date-utils";
|
|
||||||
// import { SchedulePresetsDto } from "src/time-and-attendance/schedule-presets/dtos/create-schedule-presets.dto";
|
|
||||||
|
|
||||||
// @Injectable()
|
|
||||||
// export class SchedulePresetsUpsertService {
|
|
||||||
// constructor(
|
|
||||||
// private readonly prisma: PrismaService,
|
|
||||||
// private readonly typeResolver: BankCodesResolver,
|
|
||||||
// private readonly emailResolver: EmailToIdResolver,
|
|
||||||
// ) { }
|
|
||||||
// //_________________________________________________________________
|
|
||||||
// // CREATE
|
|
||||||
// //_________________________________________________________________
|
|
||||||
// async createPreset(email: string, dto: SchedulePresetsDto): Promise<Result<SchedulePresetsDto, string>> {
|
|
||||||
// try {
|
|
||||||
// const shifts_data = await this.normalizePresetShifts(dto);
|
|
||||||
// if (!shifts_data.success) return { success: false, error: `Employee with email: ${email} or dto not found` };
|
|
||||||
|
|
||||||
// const employee_id = await this.emailResolver.findIdByEmail(email);
|
|
||||||
// if (!employee_id.success) return { success: false, error: employee_id.error };
|
|
||||||
|
|
||||||
// const created = await this.prisma.$transaction(async (tx) => {
|
|
||||||
// if (dto.is_default) {
|
|
||||||
// await tx.schedulePresets.updateMany({
|
|
||||||
// where: { is_default: true, employee_id: employee_id.data },
|
|
||||||
// data: { is_default: false },
|
|
||||||
// });
|
|
||||||
// await tx.schedulePresets.create({
|
|
||||||
// data: {
|
|
||||||
// id: dto.id,
|
|
||||||
// employee_id: employee_id.data,
|
|
||||||
// name: dto.name,
|
|
||||||
// is_default: !!dto.is_default,
|
|
||||||
// shifts: { create: shifts_data.data },
|
|
||||||
// },
|
|
||||||
// });
|
|
||||||
// return { success: true, data: created }
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
// return { success: true, data: created }
|
|
||||||
// } catch (error) {
|
|
||||||
// return { success: false, error: ' An error occured during create. Invalid Schedule data' };
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// //_________________________________________________________________
|
|
||||||
// // UPDATE
|
|
||||||
// //_________________________________________________________________
|
|
||||||
// async updatePreset(preset_id: number, dto: SchedulePresetsDto): Promise<Result<SchedulePresetsDto, string>> {
|
|
||||||
// try {
|
|
||||||
// const existing = await this.prisma.schedulePresets.findFirst({
|
|
||||||
// where: { id: preset_id },
|
|
||||||
// select: {
|
|
||||||
// id: true,
|
|
||||||
// is_default: true,
|
|
||||||
// employee_id: true,
|
|
||||||
// },
|
|
||||||
// });
|
|
||||||
// if (!existing) return { success: false, error: `Preset "${dto.name}" not found` };
|
|
||||||
|
|
||||||
// const shifts_data = await this.normalizePresetShifts(dto);
|
|
||||||
// if (!shifts_data.success) return { success: false, error: 'An error occured during normalization' }
|
|
||||||
|
|
||||||
// await this.prisma.$transaction(async (tx) => {
|
|
||||||
// if (typeof dto.is_default === 'boolean') {
|
|
||||||
// if (dto.is_default) {
|
|
||||||
// await tx.schedulePresets.updateMany({
|
|
||||||
// where: {
|
|
||||||
// employee_id: existing.employee_id,
|
|
||||||
// is_default: true,
|
|
||||||
// NOT: { id: existing.id },
|
|
||||||
// },
|
|
||||||
// data: { is_default: false },
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
// await tx.schedulePresets.update({
|
|
||||||
// where: { id: existing.id },
|
|
||||||
// data: {
|
|
||||||
// is_default: dto.is_default,
|
|
||||||
// name: dto.name,
|
|
||||||
// },
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
// if (shifts_data.data.length <= 0) return { success: false, error: 'Preset shifts to update not found' };
|
|
||||||
|
|
||||||
// await tx.schedulePresetShifts.deleteMany({ where: { preset_id: existing.id } });
|
|
||||||
|
|
||||||
// try {
|
|
||||||
// const create_many_data: Result<Prisma.SchedulePresetShiftsCreateManyInput[], string> =
|
|
||||||
// shifts_data.data.map((shift) => {
|
|
||||||
// if (!shift.bank_code || !('connect' in shift.bank_code) || typeof shift.bank_code.connect?.id !== 'number') {
|
|
||||||
// return { success: false, error: `Bank code is required for updates( ${shift.week_day}, ${shift.sort_order})`}
|
|
||||||
// }
|
|
||||||
// const bank_code_id = shift.bank_code.connect.id;
|
|
||||||
// return {
|
|
||||||
// preset_id: existing.id,
|
|
||||||
// week_day: shift.week_day,
|
|
||||||
// sort_order: shift.sort_order,
|
|
||||||
// start_time: shift.start_time,
|
|
||||||
// end_time: shift.end_time,
|
|
||||||
// is_remote: shift.is_remote ?? false,
|
|
||||||
// bank_code_id: bank_code_id,
|
|
||||||
// };
|
|
||||||
// });
|
|
||||||
// if(!create_many_data.success) return { success: false, error: 'Invalid data'}
|
|
||||||
// await tx.schedulePresetShifts.createMany({ data: create_many_data.data });
|
|
||||||
|
|
||||||
// return { success: true, data: create_many_data }
|
|
||||||
// } catch (error) {
|
|
||||||
// return { success: false, error: 'An error occured. Invalid data detected. ' };
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
|
|
||||||
// const saved = await this.prisma.schedulePresets.findUnique({
|
|
||||||
// where: { id: existing.id },
|
|
||||||
// include: {
|
|
||||||
// shifts: {
|
|
||||||
// orderBy: [{ week_day: 'asc' }, { sort_order: 'asc' }],
|
|
||||||
// include: { bank_code: { select: { type: true } } },
|
|
||||||
// }
|
|
||||||
// },
|
|
||||||
// });
|
|
||||||
// if (!saved) return { success: false, error: `Preset with id: ${existing.id} not found` };
|
|
||||||
|
|
||||||
// const response_dto: SchedulePresetsDto = {
|
|
||||||
// id: saved.id,
|
|
||||||
// name: saved.name,
|
|
||||||
// is_default: saved.is_default,
|
|
||||||
// preset_shifts: saved.shifts.map((shift) => ({
|
|
||||||
// preset_id: shift.preset_id,
|
|
||||||
// week_day: shift.week_day,
|
|
||||||
// sort_order: shift.sort_order,
|
|
||||||
// type: shift.bank_code.type,
|
|
||||||
// start_time: toHHmmFromDate(shift.start_time),
|
|
||||||
// end_time: toHHmmFromDate(shift.end_time),
|
|
||||||
// is_remote: shift.is_remote,
|
|
||||||
// })),
|
|
||||||
// };
|
|
||||||
|
|
||||||
// return { success: true, data: response_dto };
|
|
||||||
// } catch (error) {
|
|
||||||
// return { success: false, error: 'An error occured during update. Invalid data' }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// //_________________________________________________________________
|
|
||||||
// // DELETE
|
|
||||||
// //_________________________________________________________________
|
|
||||||
// async deletePreset(preset_id: number): Promise<Result<number, string>> {
|
|
||||||
// try {
|
|
||||||
// await this.prisma.$transaction(async (tx) => {
|
|
||||||
// const preset = await tx.schedulePresets.findFirst({
|
|
||||||
// where: { id: preset_id },
|
|
||||||
// select: { id: true },
|
|
||||||
// });
|
|
||||||
// if (!preset) return { success: false, error: `Preset with id ${preset_id} not found` };
|
|
||||||
// await tx.schedulePresets.delete({ where: { id: preset_id } });
|
|
||||||
|
|
||||||
// return { success: true };
|
|
||||||
// });
|
|
||||||
// return { success: true, data: preset_id };
|
|
||||||
|
|
||||||
// } catch (error) {
|
|
||||||
// return { success: false, error: `Preset schedule with id ${preset_id} not found` };
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// //PRIVATE HELPERS
|
|
||||||
|
|
||||||
// //resolve bank_code_id using type and convert hours to TIME and valid shifts end/start
|
|
||||||
// private async normalizePresetShifts(
|
|
||||||
// dto: SchedulePresetsDto
|
|
||||||
// ): Promise<Result<Prisma.SchedulePresetShiftsCreateWithoutPresetInput[], string>> {
|
|
||||||
// if (!dto.preset_shifts?.length) return { success: false, error: `Empty or preset shifts not found` }
|
|
||||||
|
|
||||||
// const types = Array.from(new Set(dto.preset_shifts.map((shift) => shift.type)));
|
|
||||||
// const bank_code_set = new Map<string, number>();
|
|
||||||
|
|
||||||
// for (const type of types) {
|
|
||||||
// const bank_code = await this.typeResolver.findIdAndModifierByType(type);
|
|
||||||
// if (!bank_code.success) return { success: false, error: 'Bank_code not found' }
|
|
||||||
// bank_code_set.set(type, bank_code.data.id);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// const pair_set = new Set<string>();
|
|
||||||
// for (const shift of dto.preset_shifts) {
|
|
||||||
// const key = `${shift.week_day}:${shift.sort_order}`;
|
|
||||||
// if (pair_set.has(key)) {
|
|
||||||
// return { success: false, error: `Duplicate shift for day/order (${shift.week_day}, ${shift.sort_order})` }
|
|
||||||
// }
|
|
||||||
// pair_set.add(key);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// const items = await dto.preset_shifts.map((shift) => {
|
|
||||||
// try {
|
|
||||||
// const bank_code_id = bank_code_set.get(shift.type);
|
|
||||||
// if (!bank_code_id) return { success: false, error: `Bank code not found for type ${shift.type}` }
|
|
||||||
// if (!shift.start_time || !shift.end_time) {
|
|
||||||
// return { success: false, error: `start_time and end_time are required for (${shift.week_day}, ${shift.sort_order})` }
|
|
||||||
// }
|
|
||||||
// const start = toDateFromString(shift.start_time);
|
|
||||||
// const end = toDateFromString(shift.end_time);
|
|
||||||
// if (end.getTime() <= start.getTime()) {
|
|
||||||
// return { success: false, error: `end_time must be > start_time ( day: ${shift.week_day}, order: ${shift.sort_order})` }
|
|
||||||
// }
|
|
||||||
// return {
|
|
||||||
// sort_order: shift.sort_order,
|
|
||||||
// start_time: start,
|
|
||||||
// end_time: end,
|
|
||||||
// is_remote: !!shift.is_remote,
|
|
||||||
// week_day: shift.week_day as Weekday,
|
|
||||||
// bank_code: { connect: { id: bank_code_id } },
|
|
||||||
// }
|
|
||||||
|
|
||||||
// } catch (error) {
|
|
||||||
// return { success: false, error: '' }
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
// return { success: true, data: items};
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
@ -5,7 +5,7 @@ import { BankCodesResolver } from "src/common/mappers/bank-type-id.mapper";
|
||||||
import { EmailToIdResolver } from "src/common/mappers/email-id.mapper";
|
import { EmailToIdResolver } from "src/common/mappers/email-id.mapper";
|
||||||
import { PrismaService } from "src/prisma/prisma.service";
|
import { PrismaService } from "src/prisma/prisma.service";
|
||||||
import { Result } from "src/common/errors/result-error.factory";
|
import { Result } from "src/common/errors/result-error.factory";
|
||||||
import { toStringFromHHmm, toStringFromDate, toDateFromString, overlaps, toHHmmFromString } from "src/common/utils/date-utils";
|
import { toStringFromHHmm, toStringFromDate, toDateFromString, overlaps, toDateFromHHmm } from "src/common/utils/date-utils";
|
||||||
import { ShiftDto } from "src/time-and-attendance/shifts/dtos/shift-create.dto";
|
import { ShiftDto } from "src/time-and-attendance/shifts/dtos/shift-create.dto";
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
|
|
@ -135,8 +135,8 @@ export class ShiftsCreateService {
|
||||||
|
|
||||||
//TODO: validate date and time to ensure "banana" is not accepted using an if statement and a REGEX
|
//TODO: validate date and time to ensure "banana" is not accepted using an if statement and a REGEX
|
||||||
const date = toDateFromString(dto.date);
|
const date = toDateFromString(dto.date);
|
||||||
const start_time = toHHmmFromString(dto.start_time);
|
const start_time = toDateFromHHmm(dto.start_time);
|
||||||
const end_time = toHHmmFromString(dto.end_time);
|
const end_time = toDateFromHHmm(dto.end_time);
|
||||||
|
|
||||||
return { success: true, data: { date, start_time, end_time, bank_code_id: bank_code_id.data } };
|
return { success: true, data: { date, start_time, end_time, bank_code_id: bank_code_id.data } };
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ import { Injectable } from "@nestjs/common";
|
||||||
import { Normalized } from "src/time-and-attendance/utils/type.utils";
|
import { Normalized } from "src/time-and-attendance/utils/type.utils";
|
||||||
import { Result } from "src/common/errors/result-error.factory";
|
import { Result } from "src/common/errors/result-error.factory";
|
||||||
import { EmployeeTimesheetResolver } from "src/common/mappers/timesheet.mapper";
|
import { EmployeeTimesheetResolver } from "src/common/mappers/timesheet.mapper";
|
||||||
import { toDateFromString, toStringFromHHmm, toStringFromDate, toHHmmFromString, overlaps } from "src/common/utils/date-utils";
|
import { toDateFromString, toStringFromHHmm, toStringFromDate, toDateFromHHmm, overlaps } from "src/common/utils/date-utils";
|
||||||
import { ShiftDto } from "src/time-and-attendance/shifts/dtos/shift-create.dto";
|
import { ShiftDto } from "src/time-and-attendance/shifts/dtos/shift-create.dto";
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
|
|
@ -145,8 +145,8 @@ export class ShiftsUpdateDeleteService {
|
||||||
success: true,
|
success: true,
|
||||||
data: {
|
data: {
|
||||||
date: toDateFromString(dto.date),
|
date: toDateFromString(dto.date),
|
||||||
start_time: toHHmmFromString(dto.start_time),
|
start_time: toDateFromHHmm(dto.start_time),
|
||||||
end_time: toHHmmFromString(dto.end_time),
|
end_time: toDateFromHHmm(dto.end_time),
|
||||||
bank_code_id: bank_code_id.data
|
bank_code_id: bank_code_id.data
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -160,8 +160,8 @@ export class ShiftsUpdateDeleteService {
|
||||||
|
|
||||||
if (shift_a.date !== shift_b.date || shift_a.id === shift_b.id) continue;
|
if (shift_a.date !== shift_b.date || shift_a.id === shift_b.id) continue;
|
||||||
const has_overlap = overlaps(
|
const has_overlap = overlaps(
|
||||||
{ start: toHHmmFromString(shift_a.start_time), end: toHHmmFromString(shift_a.end_time) },
|
{ start: toDateFromHHmm(shift_a.start_time), end: toDateFromHHmm(shift_a.end_time) },
|
||||||
{ start: toHHmmFromString(shift_b.start_time), end: toHHmmFromString(shift_b.end_time) },
|
{ start: toDateFromHHmm(shift_b.start_time), end: toDateFromHHmm(shift_b.end_time) },
|
||||||
);
|
);
|
||||||
if (has_overlap) return { success: false, error: `SHIFT_OVERLAP` };
|
if (has_overlap) return { success: false, error: `SHIFT_OVERLAP` };
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -124,7 +124,7 @@
|
||||||
// // }
|
// // }
|
||||||
|
|
||||||
// // async findAll(filters: SearchExpensesDto): Promise<Expenses[]> {
|
// // async findAll(filters: SearchExpensesDto): Promise<Expenses[]> {
|
||||||
// // const where = buildPrismaWhere(filters);
|
// const where = buildPrismaWhere(filters);
|
||||||
// // const expenses = await this.prisma.expenses.findMany({ where })
|
// // const expenses = await this.prisma.expenses.findMany({ where })
|
||||||
// // return expenses;
|
// // return expenses;
|
||||||
// // }
|
// // }
|
||||||
|
|
|
||||||
|
|
@ -1,125 +1,125 @@
|
||||||
import * as request from 'supertest';
|
// import * as request from 'supertest';
|
||||||
import { INestApplication } from '@nestjs/common';
|
// import { INestApplication } from '@nestjs/common';
|
||||||
import { createApp } from './utils/testing-app';
|
// import { createApp } from './utils/testing-app';
|
||||||
import { PrismaService } from 'src/prisma/prisma.service';
|
// import { PrismaService } from 'src/prisma/prisma.service';
|
||||||
|
|
||||||
type CustomerPayload = {
|
// type CustomerPayload = {
|
||||||
user_id?: string;
|
// user_id?: string;
|
||||||
first_name: string;
|
// first_name: string;
|
||||||
last_name: string;
|
// last_name: string;
|
||||||
email?: string;
|
// email?: string;
|
||||||
phone_number: number;
|
// phone_number: number;
|
||||||
residence?: string;
|
// residence?: string;
|
||||||
invoice_id: number;
|
// invoice_id: number;
|
||||||
};
|
// };
|
||||||
|
|
||||||
const BASE = '/customers';
|
// const BASE = '/customers';
|
||||||
|
|
||||||
const uniqueEmail = () =>
|
// const uniqueEmail = () =>
|
||||||
`customer+${Date.now()}_${Math.random().toString(36).slice(2,8)}@test.local`;
|
// `customer+${Date.now()}_${Math.random().toString(36).slice(2,8)}@test.local`;
|
||||||
const uniquePhone = () =>
|
// const uniquePhone = () =>
|
||||||
Math.floor(100_000_000 + Math.random() * 900_000_000);
|
// Math.floor(100_000_000 + Math.random() * 900_000_000);
|
||||||
|
|
||||||
function makeCustomerPayload(overrides: Partial<CustomerPayload> = {}): CustomerPayload {
|
// function makeCustomerPayload(overrides: Partial<CustomerPayload> = {}): CustomerPayload {
|
||||||
return {
|
// return {
|
||||||
first_name: 'Gandalf',
|
// first_name: 'Gandalf',
|
||||||
last_name: 'TheGray',
|
// last_name: 'TheGray',
|
||||||
email: uniqueEmail(),
|
// email: uniqueEmail(),
|
||||||
phone_number: uniquePhone(),
|
// phone_number: uniquePhone(),
|
||||||
residence: '1 Ringbearer’s Way, Mount Doom, ME',
|
// residence: '1 Ringbearer’s Way, Mount Doom, ME',
|
||||||
invoice_id: Math.floor(1_000_000 + Math.random() * 9_000_000),
|
// invoice_id: Math.floor(1_000_000 + Math.random() * 9_000_000),
|
||||||
...overrides,
|
// ...overrides,
|
||||||
};
|
// };
|
||||||
}
|
// }
|
||||||
|
|
||||||
describe('Customers (e2e) — autonome', () => {
|
// describe('Customers (e2e) — autonome', () => {
|
||||||
let app: INestApplication;
|
// let app: INestApplication;
|
||||||
let prisma: PrismaService;
|
// let prisma: PrismaService;
|
||||||
let createdId: number | null = null;
|
// let createdId: number | null = null;
|
||||||
|
|
||||||
beforeAll(async () => {
|
// beforeAll(async () => {
|
||||||
app = await createApp();
|
// app = await createApp();
|
||||||
prisma = app.get(PrismaService);
|
// prisma = app.get(PrismaService);
|
||||||
});
|
// });
|
||||||
|
|
||||||
afterAll(async () => {
|
// afterAll(async () => {
|
||||||
if (createdId) {
|
// if (createdId) {
|
||||||
try { await prisma.customers.delete({ where: { id: createdId } }); } catch {}
|
// try { await prisma.customers.delete({ where: { id: createdId } }); } catch {}
|
||||||
}
|
// }
|
||||||
await app.close();
|
// await app.close();
|
||||||
await prisma.$disconnect();
|
// await prisma.$disconnect();
|
||||||
});
|
// });
|
||||||
|
|
||||||
it(`GET ${BASE} → 200 (array)`, async () => {
|
// it(`GET ${BASE} → 200 (array)`, async () => {
|
||||||
const res = await request(app.getHttpServer()).get(BASE);
|
// const res = await request(app.getHttpServer()).get(BASE);
|
||||||
expect(res.status).toBe(200);
|
// expect(res.status).toBe(200);
|
||||||
expect(Array.isArray(res.body)).toBe(true);
|
// expect(Array.isArray(res.body)).toBe(true);
|
||||||
});
|
// });
|
||||||
|
|
||||||
it(`POST ${BASE} (valid) → 201 puis GET /:id → 200`, async () => {
|
// it(`POST ${BASE} (valid) → 201 puis GET /:id → 200`, async () => {
|
||||||
const payload = makeCustomerPayload();
|
// const payload = makeCustomerPayload();
|
||||||
|
|
||||||
const createRes = await request(app.getHttpServer()).post(BASE).send(payload);
|
// const createRes = await request(app.getHttpServer()).post(BASE).send(payload);
|
||||||
if (createRes.status !== 201) {
|
// if (createRes.status !== 201) {
|
||||||
|
|
||||||
console.log('Create error:', createRes.body || createRes.text);
|
// console.log('Create error:', createRes.body || createRes.text);
|
||||||
}
|
// }
|
||||||
expect(createRes.status).toBe(201);
|
// expect(createRes.status).toBe(201);
|
||||||
|
|
||||||
expect(createRes.body).toEqual(
|
// expect(createRes.body).toEqual(
|
||||||
expect.objectContaining({
|
// expect.objectContaining({
|
||||||
id: expect.any(Number),
|
// id: expect.any(Number),
|
||||||
user_id: expect.any(String),
|
// user_id: expect.any(String),
|
||||||
invoice_id: payload.invoice_id,
|
// invoice_id: payload.invoice_id,
|
||||||
})
|
// })
|
||||||
);
|
// );
|
||||||
expect(createRes.body.user_id).toMatch(/^[0-9a-fA-F-]{36}$/);
|
// expect(createRes.body.user_id).toMatch(/^[0-9a-fA-F-]{36}$/);
|
||||||
|
|
||||||
createdId = createRes.body.id;
|
// createdId = createRes.body.id;
|
||||||
|
|
||||||
const getRes = await request(app.getHttpServer()).get(`${BASE}/${createdId}`);
|
// const getRes = await request(app.getHttpServer()).get(`${BASE}/${createdId}`);
|
||||||
expect(getRes.status).toBe(200);
|
// expect(getRes.status).toBe(200);
|
||||||
expect(getRes.body).toEqual(expect.objectContaining({ id: createdId }));
|
// expect(getRes.body).toEqual(expect.objectContaining({ id: createdId }));
|
||||||
});
|
// });
|
||||||
|
|
||||||
it(`PATCH ${BASE}/:id → 200 (first_name mis à jour)`, async () => {
|
// it(`PATCH ${BASE}/:id → 200 (first_name mis à jour)`, async () => {
|
||||||
if (!createdId) {
|
// if (!createdId) {
|
||||||
const create = await request(app.getHttpServer()).post(BASE).send(makeCustomerPayload());
|
// const create = await request(app.getHttpServer()).post(BASE).send(makeCustomerPayload());
|
||||||
expect(create.status).toBe(201);
|
// expect(create.status).toBe(201);
|
||||||
createdId = create.body.id;
|
// createdId = create.body.id;
|
||||||
}
|
// }
|
||||||
|
|
||||||
const patchRes = await request(app.getHttpServer())
|
// const patchRes = await request(app.getHttpServer())
|
||||||
.patch(`${BASE}/${createdId}`)
|
// .patch(`${BASE}/${createdId}`)
|
||||||
.send({ first_name: 'Mithrandir' });
|
// .send({ first_name: 'Mithrandir' });
|
||||||
expect([200, 204]).toContain(patchRes.status);
|
// expect([200, 204]).toContain(patchRes.status);
|
||||||
|
|
||||||
const getRes = await request(app.getHttpServer()).get(`${BASE}/${createdId}`);
|
// const getRes = await request(app.getHttpServer()).get(`${BASE}/${createdId}`);
|
||||||
expect(getRes.status).toBe(200);
|
// expect(getRes.status).toBe(200);
|
||||||
expect(getRes.body.first_name ?? 'Mithrandir').toBe('Mithrandir');
|
// expect(getRes.body.first_name ?? 'Mithrandir').toBe('Mithrandir');
|
||||||
});
|
// });
|
||||||
|
|
||||||
it(`GET ${BASE}/:id (not found) → 404/400`, async () => {
|
// it(`GET ${BASE}/:id (not found) → 404/400`, async () => {
|
||||||
const res = await request(app.getHttpServer()).get(`${BASE}/999999`);
|
// const res = await request(app.getHttpServer()).get(`${BASE}/999999`);
|
||||||
expect([404, 400]).toContain(res.status);
|
// expect([404, 400]).toContain(res.status);
|
||||||
});
|
// });
|
||||||
|
|
||||||
it(`POST ${BASE} (invalid payload) → 400`, async () => {
|
// it(`POST ${BASE} (invalid payload) → 400`, async () => {
|
||||||
const res = await request(app.getHttpServer()).post(BASE).send({});
|
// const res = await request(app.getHttpServer()).post(BASE).send({});
|
||||||
expect(res.status).toBeGreaterThanOrEqual(400);
|
// expect(res.status).toBeGreaterThanOrEqual(400);
|
||||||
expect(res.status).toBeLessThan(500);
|
// expect(res.status).toBeLessThan(500);
|
||||||
});
|
// });
|
||||||
|
|
||||||
it(`DELETE ${BASE}/:id → 200/204`, async () => {
|
// it(`DELETE ${BASE}/:id → 200/204`, async () => {
|
||||||
let id = createdId;
|
// let id = createdId;
|
||||||
if (!id) {
|
// if (!id) {
|
||||||
const create = await request(app.getHttpServer()).post(BASE).send(makeCustomerPayload());
|
// const create = await request(app.getHttpServer()).post(BASE).send(makeCustomerPayload());
|
||||||
expect(create.status).toBe(201);
|
// expect(create.status).toBe(201);
|
||||||
id = create.body.id;
|
// id = create.body.id;
|
||||||
}
|
// }
|
||||||
|
|
||||||
const del = await request(app.getHttpServer()).delete(`${BASE}/${id}`);
|
// const del = await request(app.getHttpServer()).delete(`${BASE}/${id}`);
|
||||||
expect([200, 204]).toContain(del.status);
|
// expect([200, 204]).toContain(del.status);
|
||||||
if (createdId === id) createdId = null;
|
// if (createdId === id) createdId = null;
|
||||||
});
|
// });
|
||||||
});
|
// });
|
||||||
|
|
|
||||||
|
|
@ -1,143 +1,143 @@
|
||||||
// test/pay-periods-approval.e2e-spec.ts
|
// // test/pay-periods-approval.e2e-spec.ts
|
||||||
const supertest = require('supertest');
|
// const supertest = require('supertest');
|
||||||
import { INestApplication } from '@nestjs/common';
|
// import { INestApplication } from '@nestjs/common';
|
||||||
import { PrismaService } from 'src/prisma/prisma.service';
|
// import { PrismaService } from 'src/prisma/prisma.service';
|
||||||
import { createApp } from './utils/testing-app';
|
// import { createApp } from './utils/testing-app';
|
||||||
import { makeEmployee } from './factories/employee.factory';
|
// import { makeEmployee } from './factories/employee.factory';
|
||||||
import { makeTimesheet } from './factories/timesheet.factory';
|
// import { makeTimesheet } from './factories/timesheet.factory';
|
||||||
|
|
||||||
describe('PayPeriods approval (e2e)', () => {
|
// describe('PayPeriods approval (e2e)', () => {
|
||||||
const BASE = '/pay-periods';
|
// const BASE = '/pay-periods';
|
||||||
let app: INestApplication;
|
// let app: INestApplication;
|
||||||
let prisma: PrismaService;
|
// let prisma: PrismaService;
|
||||||
|
|
||||||
let periodYear: number;
|
// let periodYear: number;
|
||||||
let periodNumber: number;
|
// let periodNumber: number;
|
||||||
|
|
||||||
let employeeId: number;
|
// let employeeId: number;
|
||||||
let timesheetId: number;
|
// let timesheetId: number;
|
||||||
let shiftId: number;
|
// let shiftId: number;
|
||||||
let expenseId: number;
|
// let expenseId: number;
|
||||||
|
|
||||||
const isoDay = (d: Date) =>
|
// const isoDay = (d: Date) =>
|
||||||
new Date(Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate())).toISOString();
|
// new Date(Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate())).toISOString();
|
||||||
const isoTime = (h: number, m = 0) =>
|
// const isoTime = (h: number, m = 0) =>
|
||||||
new Date(Date.UTC(1970, 0, 1, h, m, 0)).toISOString();
|
// new Date(Date.UTC(1970, 0, 1, h, m, 0)).toISOString();
|
||||||
|
|
||||||
beforeAll(async () => {
|
// beforeAll(async () => {
|
||||||
app = await createApp();
|
// app = await createApp();
|
||||||
prisma = app.get(PrismaService);
|
// prisma = app.get(PrismaService);
|
||||||
|
|
||||||
// 1) Récupère un pay period existant
|
// // 1) Récupère un pay period existant
|
||||||
const period = await prisma.payPeriods.findFirst({ orderBy: { period_number: 'asc' } });
|
// const period = await prisma.payPeriods.findFirst({ orderBy: { period_number: 'asc' } });
|
||||||
if (!period) throw new Error('Aucun pay period en DB (seed requis).');
|
// if (!period) throw new Error('Aucun pay period en DB (seed requis).');
|
||||||
|
|
||||||
periodYear = period.year;
|
// periodYear = period.year;
|
||||||
periodNumber = period.period_number;
|
// periodNumber = period.period_number;
|
||||||
|
|
||||||
// 2) Crée un employé + timesheet (non approuvé)
|
// // 2) Crée un employé + timesheet (non approuvé)
|
||||||
const empRes = await supertest(app.getHttpServer())
|
// const empRes = await supertest(app.getHttpServer())
|
||||||
.post('/employees')
|
// .post('/employees')
|
||||||
.send(makeEmployee());
|
// .send(makeEmployee());
|
||||||
if (empRes.status !== 201) {
|
// if (empRes.status !== 201) {
|
||||||
// eslint-disable-next-line no-console
|
// // eslint-disable-next-line no-console
|
||||||
console.warn('Create employee error:', empRes.body || empRes.text);
|
// console.warn('Create employee error:', empRes.body || empRes.text);
|
||||||
throw new Error('Impossible de créer un employé pour le test pay-periods.');
|
// throw new Error('Impossible de créer un employé pour le test pay-periods.');
|
||||||
}
|
// }
|
||||||
employeeId = empRes.body.id;
|
// employeeId = empRes.body.id;
|
||||||
|
|
||||||
const tsRes = await supertest(app.getHttpServer())
|
// const tsRes = await supertest(app.getHttpServer())
|
||||||
.post('/timesheets')
|
// .post('/timesheets')
|
||||||
.send(makeTimesheet(employeeId, { is_approved: false }));
|
// .send(makeTimesheet(employeeId, { is_approved: false }));
|
||||||
if (tsRes.status !== 201) {
|
// if (tsRes.status !== 201) {
|
||||||
// eslint-disable-next-line no-console
|
// // eslint-disable-next-line no-console
|
||||||
console.warn('Create timesheet error:', tsRes.body || tsRes.text);
|
// console.warn('Create timesheet error:', tsRes.body || tsRes.text);
|
||||||
throw new Error('Impossible de créer un timesheet pour le test pay-periods.');
|
// throw new Error('Impossible de créer un timesheet pour le test pay-periods.');
|
||||||
}
|
// }
|
||||||
timesheetId = tsRes.body.id;
|
// timesheetId = tsRes.body.id;
|
||||||
|
|
||||||
// 3) Bank codes
|
// // 3) Bank codes
|
||||||
const bcShift = await prisma.bankCodes.findFirst({
|
// const bcShift = await prisma.bankCodes.findFirst({
|
||||||
where: { categorie: 'SHIFT' },
|
// where: { categorie: 'SHIFT' },
|
||||||
select: { id: true },
|
// select: { id: true },
|
||||||
});
|
// });
|
||||||
if (!bcShift) throw new Error('Aucun bank code SHIFT trouvé.');
|
// if (!bcShift) throw new Error('Aucun bank code SHIFT trouvé.');
|
||||||
const bcExpense = await prisma.bankCodes.findFirst({
|
// const bcExpense = await prisma.bankCodes.findFirst({
|
||||||
where: { categorie: 'EXPENSE' },
|
// where: { categorie: 'EXPENSE' },
|
||||||
select: { id: true },
|
// select: { id: true },
|
||||||
});
|
// });
|
||||||
if (!bcExpense) throw new Error('Aucun bank code EXPENSE trouvé.');
|
// if (!bcExpense) throw new Error('Aucun bank code EXPENSE trouvé.');
|
||||||
|
|
||||||
// 4) Crée 1 shift + 1 expense DANS la période choisie
|
// // 4) Crée 1 shift + 1 expense DANS la période choisie
|
||||||
const dateISO = isoDay(period.start_date);
|
// const dateISO = isoDay(period.start_date);
|
||||||
|
|
||||||
const shiftRes = await supertest(app.getHttpServer())
|
// const shiftRes = await supertest(app.getHttpServer())
|
||||||
.post('/shifts')
|
// .post('/shifts')
|
||||||
.send({
|
// .send({
|
||||||
timesheet_id: timesheetId,
|
// timesheet_id: timesheetId,
|
||||||
bank_code_id: bcShift.id,
|
// bank_code_id: bcShift.id,
|
||||||
date: dateISO,
|
// date: dateISO,
|
||||||
start_time: isoTime(9),
|
// start_time: isoTime(9),
|
||||||
end_time: isoTime(17),
|
// end_time: isoTime(17),
|
||||||
description: 'PP approval shift',
|
// description: 'PP approval shift',
|
||||||
});
|
// });
|
||||||
if (shiftRes.status !== 201) {
|
// if (shiftRes.status !== 201) {
|
||||||
// eslint-disable-next-line no-console
|
// // eslint-disable-next-line no-console
|
||||||
console.warn('Create shift error:', shiftRes.body || shiftRes.text);
|
// console.warn('Create shift error:', shiftRes.body || shiftRes.text);
|
||||||
throw new Error('Création shift échouée.');
|
// throw new Error('Création shift échouée.');
|
||||||
}
|
// }
|
||||||
shiftId = shiftRes.body.id;
|
// shiftId = shiftRes.body.id;
|
||||||
|
|
||||||
const expenseRes = await supertest(app.getHttpServer())
|
// const expenseRes = await supertest(app.getHttpServer())
|
||||||
.post('/Expenses') // <- respecte ta casse de route
|
// .post('/Expenses') // <- respecte ta casse de route
|
||||||
.send({
|
// .send({
|
||||||
timesheet_id: timesheetId,
|
// timesheet_id: timesheetId,
|
||||||
bank_code_id: bcExpense.id,
|
// bank_code_id: bcExpense.id,
|
||||||
date: dateISO,
|
// date: dateISO,
|
||||||
amount: 42,
|
// amount: 42,
|
||||||
description: 'PP approval expense',
|
// description: 'PP approval expense',
|
||||||
is_approved: false,
|
// is_approved: false,
|
||||||
});
|
// });
|
||||||
if (expenseRes.status !== 201) {
|
// if (expenseRes.status !== 201) {
|
||||||
// eslint-disable-next-line no-console
|
// // eslint-disable-next-line no-console
|
||||||
console.warn('Create expense error:', expenseRes.body || expenseRes.text);
|
// console.warn('Create expense error:', expenseRes.body || expenseRes.text);
|
||||||
throw new Error('Création expense échouée.');
|
// throw new Error('Création expense échouée.');
|
||||||
}
|
// }
|
||||||
expenseId = expenseRes.body.id;
|
// expenseId = expenseRes.body.id;
|
||||||
});
|
// });
|
||||||
|
|
||||||
afterAll(async () => {
|
// afterAll(async () => {
|
||||||
await app.close();
|
// await app.close();
|
||||||
await prisma.$disconnect();
|
// await prisma.$disconnect();
|
||||||
});
|
// });
|
||||||
|
|
||||||
it(`PATCH ${BASE}/:year/:periodNumber/approval → 200 (cascade approval)`, async () => {
|
// it(`PATCH ${BASE}/:year/:periodNumber/approval → 200 (cascade approval)`, async () => {
|
||||||
const res = await supertest(app.getHttpServer())
|
// const res = await supertest(app.getHttpServer())
|
||||||
.patch(`${BASE}/${periodYear}/${periodNumber}/approval`)
|
// .patch(`${BASE}/${periodYear}/${periodNumber}/approval`)
|
||||||
.send(); // aucun body requis par ton contrôleur
|
// .send(); // aucun body requis par ton contrôleur
|
||||||
expect([200, 204]).toContain(res.status);
|
// expect([200, 204]).toContain(res.status);
|
||||||
if (res.body?.message) {
|
// if (res.body?.message) {
|
||||||
expect(String(res.body.message)).toContain(`${periodYear}-${periodNumber}`);
|
// expect(String(res.body.message)).toContain(`${periodYear}-${periodNumber}`);
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Vérifie cascade:
|
// // Vérifie cascade:
|
||||||
const tsCheck = await supertest(app.getHttpServer()).get(`/timesheets/${timesheetId}`);
|
// const tsCheck = await supertest(app.getHttpServer()).get(`/timesheets/${timesheetId}`);
|
||||||
expect(tsCheck.status).toBe(200);
|
// expect(tsCheck.status).toBe(200);
|
||||||
expect(tsCheck.body?.is_approved).toBe(true);
|
// expect(tsCheck.body?.is_approved).toBe(true);
|
||||||
|
|
||||||
const shiftCheck = await supertest(app.getHttpServer()).get(`/shifts/${shiftId}`);
|
// const shiftCheck = await supertest(app.getHttpServer()).get(`/shifts/${shiftId}`);
|
||||||
expect(shiftCheck.status).toBe(200);
|
// expect(shiftCheck.status).toBe(200);
|
||||||
expect(shiftCheck.body?.is_approved).toBe(true);
|
// expect(shiftCheck.body?.is_approved).toBe(true);
|
||||||
|
|
||||||
const expCheck = await supertest(app.getHttpServer()).get(`/Expenses/${expenseId}`);
|
// const expCheck = await supertest(app.getHttpServer()).get(`/Expenses/${expenseId}`);
|
||||||
expect(expCheck.status).toBe(200);
|
// expect(expCheck.status).toBe(200);
|
||||||
expect(expCheck.body?.is_approved).toBe(true);
|
// expect(expCheck.body?.is_approved).toBe(true);
|
||||||
});
|
// });
|
||||||
|
|
||||||
it(`PATCH ${BASE}/2099/999/approval → 404 (period not found)`, async () => {
|
// it(`PATCH ${BASE}/2099/999/approval → 404 (period not found)`, async () => {
|
||||||
const bad = await supertest(app.getHttpServer())
|
// const bad = await supertest(app.getHttpServer())
|
||||||
.patch(`${BASE}/2099/999/approval`)
|
// .patch(`${BASE}/2099/999/approval`)
|
||||||
.send();
|
// .send();
|
||||||
expect(bad.status).toBe(404);
|
// expect(bad.status).toBe(404);
|
||||||
});
|
// });
|
||||||
});
|
// });
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user