feat(user_module_access): created user_module_access model and module. implemented update, revoke and get methods.
This commit is contained in:
parent
c5c96cce22
commit
26ea84cf1a
|
|
@ -23,20 +23,36 @@ model Users {
|
||||||
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
|
||||||
|
|
@ -180,13 +195,14 @@ model Shifts {
|
||||||
comment String?
|
comment String?
|
||||||
|
|
||||||
archive ShiftsArchive[] @relation("ShiftsToArchive")
|
archive ShiftsArchive[] @relation("ShiftsToArchive")
|
||||||
|
|
||||||
@@unique([timesheet_id, date, start_time], name: "unique_ts_id_date_start_time")
|
@@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")
|
@@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,18 @@ 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)
|
dark_mode Int @default(0)
|
||||||
lang_switch Int @default(0)
|
lang_switch Int @default(0)
|
||||||
lefty_mode Int @default(0)
|
lefty_mode Int @default(0)
|
||||||
|
|
||||||
employee_list_display Int @default(0)
|
employee_list_display Int @default(0)
|
||||||
validation_display Int @default(0)
|
validation_display Int @default(0)
|
||||||
timesheet_display Int @default(0)
|
timesheet_display Int @default(0)
|
||||||
|
|
||||||
@@map("preferences")
|
@@map("preferences")
|
||||||
}
|
}
|
||||||
|
|
@ -398,4 +415,3 @@ enum Weekday {
|
||||||
FRI
|
FRI
|
||||||
SAT
|
SAT
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
11
src/identity-and-account/user-module-access/access.module.ts
Normal file
11
src/identity-and-account/user-module-access/access.module.ts
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
import { Module } from "@nestjs/common";
|
||||||
|
import { AccessUpdateService } from "src/identity-and-account/user-module-access/services/access-update.service";
|
||||||
|
import { AccessController } from "src/identity-and-account/user-module-access/controllers/access.controller";
|
||||||
|
import { AccessGetService } from "src/identity-and-account/user-module-access/services/access-get.service";
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
controllers: [AccessController],
|
||||||
|
providers: [AccessUpdateService, AccessGetService],
|
||||||
|
exports: [],
|
||||||
|
})
|
||||||
|
export class AccessModule { }
|
||||||
|
|
@ -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/acces.dto";
|
||||||
|
import { AccessGetService } from "src/identity-and-account/user-module-access/services/access-get.service";
|
||||||
|
import { AccessUpdateService } from "src/identity-and-account/user-module-access/services/access-update.service";
|
||||||
|
|
||||||
|
@Controller()
|
||||||
|
export class AccessController {
|
||||||
|
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()
|
||||||
|
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()
|
||||||
|
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,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/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/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 };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,18 +2,19 @@ import { Controller, Param, Query, Body, Get, Post, ParseIntPipe, Delete, Patch,
|
||||||
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 { SchedulePresetsUpsertService } from "src/time-and-attendance/schedule-presets/services/schedule-presets-upsert.service";
|
import { SchedulePresetsCreateService } from "src/time-and-attendance/schedule-presets/services/schedule-presets-create.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
|
||||||
|
|
@ -21,34 +22,33 @@ export class SchedulePresetsController {
|
||||||
@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
|
//used to show the list of available schedule presets
|
||||||
@Get('find-list')
|
@Get('find-list')
|
||||||
@RolesAllowed(...MANAGER_ROLES)
|
@RolesAllowed(...MANAGER_ROLES)
|
||||||
async findListById(
|
async findListById(@Req() req) {
|
||||||
@Req() req
|
|
||||||
) {
|
|
||||||
const email = req.user?.email;
|
const email = req.user?.email;
|
||||||
return this.getService.getSchedulePresets(email);
|
return this.getService.getSchedulePresets(email);
|
||||||
}
|
}
|
||||||
|
|
@ -56,9 +56,9 @@ export class SchedulePresetsController {
|
||||||
//used to apply a preset to a timesheet
|
//used to apply a preset to a timesheet
|
||||||
@Post('apply-presets')
|
@Post('apply-presets')
|
||||||
async applyPresets(
|
async applyPresets(
|
||||||
@Req() req,
|
|
||||||
@Body('preset') preset_id: number,
|
@Body('preset') preset_id: number,
|
||||||
@Body('start') start_date: string
|
@Body('start') start_date: string,
|
||||||
|
@Req() req
|
||||||
) {
|
) {
|
||||||
const email = req.user?.email;
|
const email = req.user?.email;
|
||||||
return this.applyPresetsService.applyToTimesheet(email, preset_id, start_date);
|
return this.applyPresetsService.applyToTimesheet(email, preset_id, start_date);
|
||||||
|
|
|
||||||
|
|
@ -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 { }
|
||||||
|
|
@ -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,240 +0,0 @@
|
||||||
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 SchedulePresetsUpsertService {
|
|
||||||
constructor(
|
|
||||||
private readonly prisma: PrismaService,
|
|
||||||
private readonly typeResolver: BankCodesResolver,
|
|
||||||
private readonly emailResolver: EmailToIdResolver,
|
|
||||||
) { }
|
|
||||||
//_________________________________________________________________
|
|
||||||
// CREATE
|
|
||||||
//_________________________________________________________________
|
|
||||||
async createPreset(email: string, dto: SchedulePresetsDto): Promise<Result<boolean, string>> {
|
|
||||||
//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 }
|
|
||||||
}
|
|
||||||
|
|
||||||
// //_________________________________________________________________
|
|
||||||
// // 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(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 }
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
Loading…
Reference in New Issue
Block a user