feat(role-guards): added role-guards group and added role check to controllers
This commit is contained in:
parent
02ebb23d7a
commit
1a0532846f
|
|
@ -17,9 +17,9 @@ export class OwnershipGuard implements CanActivate {
|
||||||
constructor(
|
constructor(
|
||||||
private reflector: Reflector,
|
private reflector: Reflector,
|
||||||
private moduleRef: ModuleRef,
|
private moduleRef: ModuleRef,
|
||||||
) {}
|
) { }
|
||||||
|
|
||||||
async canActivate(context: ExecutionContext): Promise<boolean>{
|
async canActivate(context: ExecutionContext): Promise<boolean> {
|
||||||
const meta = this.reflector.get<OwnershipMeta>(
|
const meta = this.reflector.get<OwnershipMeta>(
|
||||||
OWNER_KEY, context.getHandler(),
|
OWNER_KEY, context.getHandler(),
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ interface RequestWithUser extends Request {
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class RolesGuard implements CanActivate {
|
export class RolesGuard implements CanActivate {
|
||||||
constructor(private reflector: Reflector) {}
|
constructor(private reflector: Reflector) { }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @swagger
|
* @swagger
|
||||||
|
|
@ -37,9 +37,9 @@ export class RolesGuard implements CanActivate {
|
||||||
* or returns `false` if the user is not authenticated.
|
* or returns `false` if the user is not authenticated.
|
||||||
*/
|
*/
|
||||||
canActivate(ctx: ExecutionContext): boolean {
|
canActivate(ctx: ExecutionContext): boolean {
|
||||||
const requiredRoles = this.reflector.get<Roles[]>(
|
const requiredRoles = this.reflector.getAllAndOverride<Roles[]>(
|
||||||
ROLES_KEY,
|
ROLES_KEY,
|
||||||
ctx.getHandler(),
|
[ctx.getHandler(), ctx.getClass()],
|
||||||
);
|
);
|
||||||
//for "deny-by-default" when role is wrong or unavailable
|
//for "deny-by-default" when role is wrong or unavailable
|
||||||
if (!requiredRoles || requiredRoles.length === 0) {
|
if (!requiredRoles || requiredRoles.length === 0) {
|
||||||
|
|
|
||||||
15
src/common/shared/role-groupes.ts
Normal file
15
src/common/shared/role-groupes.ts
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
import { Roles as RoleEnum } from ".prisma/client";
|
||||||
|
|
||||||
|
export const GLOBAL_CONTROLLER_ROLES: readonly RoleEnum[] = [
|
||||||
|
RoleEnum.EMPLOYEE,
|
||||||
|
RoleEnum.ACCOUNTING,
|
||||||
|
RoleEnum.HR,
|
||||||
|
RoleEnum.SUPERVISOR,
|
||||||
|
RoleEnum.ADMIN,
|
||||||
|
];
|
||||||
|
|
||||||
|
export const MANAGER_ROLES: readonly RoleEnum[] = [
|
||||||
|
RoleEnum.HR,
|
||||||
|
RoleEnum.SUPERVISOR,
|
||||||
|
RoleEnum.ADMIN,
|
||||||
|
]
|
||||||
|
|
@ -1,37 +1,35 @@
|
||||||
import { Controller, Get, Patch, Param, Body, NotFoundException } from "@nestjs/common";
|
import { Controller, Get, Patch, Param, Body, NotFoundException } from "@nestjs/common";
|
||||||
import { ApiBearerAuth, ApiOperation, ApiResponse, ApiParam } from "@nestjs/swagger";
|
import { RolesAllowed } from "src/common/decorators/roles.decorators";
|
||||||
|
import { GLOBAL_CONTROLLER_ROLES, MANAGER_ROLES } from "src/common/shared/role-groupes";
|
||||||
import { EmployeeListItemDto } from "src/identity-and-account/employees/dtos/list-employee.dto";
|
import { EmployeeListItemDto } from "src/identity-and-account/employees/dtos/list-employee.dto";
|
||||||
import { EmployeeProfileItemDto } from "src/identity-and-account/employees/dtos/profil-employee.dto";
|
import { EmployeeProfileItemDto } from "src/identity-and-account/employees/dtos/profil-employee.dto";
|
||||||
import { UpdateEmployeeDto } from "src/identity-and-account/employees/dtos/update-employee.dto";
|
import { UpdateEmployeeDto } from "src/identity-and-account/employees/dtos/update-employee.dto";
|
||||||
import { EmployeesArchivalService } from "src/identity-and-account/employees/services/employees-archival.service";
|
import { EmployeesArchivalService } from "src/identity-and-account/employees/services/employees-archival.service";
|
||||||
import { EmployeesService } from "src/identity-and-account/employees/services/employees.service";
|
import { EmployeesService } from "src/identity-and-account/employees/services/employees.service";
|
||||||
|
|
||||||
@ApiBearerAuth('access-token')
|
@RolesAllowed(...GLOBAL_CONTROLLER_ROLES)
|
||||||
// @UseGuards()
|
|
||||||
@Controller('employees')
|
@Controller('employees')
|
||||||
export class EmployeesController {
|
export class EmployeesController {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly employeesService: EmployeesService,
|
private readonly employeesService: EmployeesService,
|
||||||
private readonly archiveService: EmployeesArchivalService,
|
private readonly archiveService: EmployeesArchivalService,
|
||||||
) {}
|
) { }
|
||||||
|
|
||||||
@Get('employee-list')
|
@Get('employee-list')
|
||||||
//@RolesAllowed(RoleEnum.ADMIN, RoleEnum.HR, RoleEnum.SUPERVISOR, RoleEnum.ACCOUNTING)
|
@RolesAllowed(...MANAGER_ROLES)
|
||||||
|
|
||||||
findListEmployees(): Promise<EmployeeListItemDto[]> {
|
findListEmployees(): Promise<EmployeeListItemDto[]> {
|
||||||
return this.employeesService.findListEmployees();
|
return this.employeesService.findListEmployees();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Patch(':email')
|
@Patch(':email')
|
||||||
//@RolesAllowed(RoleEnum.ADMIN, RoleEnum.HR, RoleEnum.SUPERVISOR)
|
@RolesAllowed(...MANAGER_ROLES)
|
||||||
@ApiBearerAuth('access-token')
|
|
||||||
async updateOrArchiveOrRestore(@Param('email') email: string, @Body() dto: UpdateEmployeeDto,) {
|
async updateOrArchiveOrRestore(@Param('email') email: string, @Body() dto: UpdateEmployeeDto,) {
|
||||||
// if last_work_day is set => archive the employee
|
// if last_work_day is set => archive the employee
|
||||||
// else if employee is archived and first_work_day or last_work_day = null => restore
|
// else if employee is archived and first_work_day or last_work_day = null => restore
|
||||||
//otherwise => standard update
|
//otherwise => standard update
|
||||||
const result = await this.archiveService.patchEmployee(email, dto);
|
const result = await this.archiveService.patchEmployee(email, dto);
|
||||||
if(!result) {
|
if (!result) {
|
||||||
throw new NotFoundException(`Employee with email: ${ email } is not found in active or archive.`)
|
throw new NotFoundException(`Employee with email: ${email} is not found in active or archive.`)
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
@ -68,10 +66,6 @@ export class EmployeesController {
|
||||||
// }
|
// }
|
||||||
|
|
||||||
@Get('profile/:email')
|
@Get('profile/:email')
|
||||||
@ApiOperation({summary: 'Find employee profile' })
|
|
||||||
@ApiParam({ name: 'email', type: String, description: 'Identifier of the employee' })
|
|
||||||
@ApiResponse({ status: 200, description: 'Employee profile found', type: EmployeeProfileItemDto })
|
|
||||||
@ApiResponse({ status: 400, description: 'Employee profile not found' })
|
|
||||||
findOneProfile(@Param('email') email: string): Promise<EmployeeProfileItemDto> {
|
findOneProfile(@Param('email') email: string): Promise<EmployeeProfileItemDto> {
|
||||||
return this.employeesService.findOneProfile(email);
|
return this.employeesService.findOneProfile(email);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,13 +4,13 @@ import { PrismaService } from 'src/prisma/prisma.service';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export abstract class AbstractUserService {
|
export abstract class AbstractUserService {
|
||||||
constructor(protected readonly prisma: PrismaService) {}
|
constructor(protected readonly prisma: PrismaService) { }
|
||||||
|
|
||||||
findAll(): Promise<Users[]> {
|
findAll(): Promise<Users[]> {
|
||||||
return this.prisma.users.findMany();
|
return this.prisma.users.findMany();
|
||||||
}
|
}
|
||||||
|
|
||||||
async findOne( id: string ): Promise<Users> {
|
async findOne(id: string): Promise<Users> {
|
||||||
const user = await this.prisma.users.findUnique({ where: { id } });
|
const user = await this.prisma.users.findUnique({ where: { id } });
|
||||||
if (!user) {
|
if (!user) {
|
||||||
throw new NotFoundException(`User #${id} not found`);
|
throw new NotFoundException(`User #${id} not found`);
|
||||||
|
|
@ -18,7 +18,7 @@ export abstract class AbstractUserService {
|
||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
|
|
||||||
async findOneByEmail( email: string ): Promise<Partial<Users>> {
|
async findOneByEmail(email: string): Promise<Partial<Users>> {
|
||||||
const user = await this.prisma.users.findUnique({ where: { email } });
|
const user = await this.prisma.users.findUnique({ where: { email } });
|
||||||
if (!user) {
|
if (!user) {
|
||||||
throw new NotFoundException(`No user with email #${email} exists`);
|
throw new NotFoundException(`No user with email #${email} exists`);
|
||||||
|
|
|
||||||
|
|
@ -6,53 +6,54 @@ import { SchedulePresetsApplyService } from "src/time-and-attendance/time-tracke
|
||||||
import { SchedulePresetsGetService } from "src/time-and-attendance/time-tracker/schedule-presets/services/schedule-presets-get.service";
|
import { SchedulePresetsGetService } from "src/time-and-attendance/time-tracker/schedule-presets/services/schedule-presets-get.service";
|
||||||
import { SchedulePresetsUpsertService } from "src/time-and-attendance/time-tracker/schedule-presets/services/schedule-presets-upsert.service";
|
import { SchedulePresetsUpsertService } from "src/time-and-attendance/time-tracker/schedule-presets/services/schedule-presets-upsert.service";
|
||||||
import { Roles as RoleEnum } from '.prisma/client';
|
import { Roles as RoleEnum } from '.prisma/client';
|
||||||
|
import { GLOBAL_CONTROLLER_ROLES, MANAGER_ROLES } from "src/common/shared/role-groupes";
|
||||||
|
|
||||||
@Controller('schedule-presets')
|
@Controller('schedule-presets')
|
||||||
|
@RolesAllowed(...GLOBAL_CONTROLLER_ROLES)
|
||||||
export class SchedulePresetsController {
|
export class SchedulePresetsController {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly upsertService: SchedulePresetsUpsertService,
|
private readonly upsertService: SchedulePresetsUpsertService,
|
||||||
private readonly getService: SchedulePresetsGetService,
|
private readonly getService: SchedulePresetsGetService,
|
||||||
private readonly applyPresetsService: SchedulePresetsApplyService,
|
private readonly applyPresetsService: SchedulePresetsApplyService,
|
||||||
){}
|
) { }
|
||||||
|
|
||||||
//used to create a schedule preset
|
//used to create a schedule preset
|
||||||
@Post('create')
|
@Post('create')
|
||||||
@RolesAllowed(RoleEnum.ACCOUNTING, RoleEnum.HR, RoleEnum.SUPERVISOR, RoleEnum.ADMIN)
|
@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.upsertService.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(RoleEnum.ACCOUNTING, RoleEnum.HR, RoleEnum.SUPERVISOR, RoleEnum.ADMIN)
|
@RolesAllowed(...MANAGER_ROLES)
|
||||||
async updatePreset( @Param('preset_id', ParseIntPipe) preset_id: number,@Body() dto: SchedulePresetsUpdateDto ) {
|
async updatePreset(@Param('preset_id', ParseIntPipe) preset_id: number, @Body() dto: SchedulePresetsUpdateDto) {
|
||||||
return await this.upsertService.updatePreset(preset_id, dto);
|
return await this.upsertService.updatePreset(preset_id, dto);
|
||||||
}
|
}
|
||||||
|
|
||||||
//used to delete a schedule preset
|
//used to delete a schedule preset
|
||||||
@Delete('delete/:preset_id')
|
@Delete('delete/:preset_id')
|
||||||
@RolesAllowed(RoleEnum.ADMIN)
|
@RolesAllowed(RoleEnum.ADMIN)
|
||||||
async deletePreset( @Param('preset_id') preset_id: number ) {
|
async deletePreset(@Param('preset_id') preset_id: number) {
|
||||||
return await this.upsertService.deletePreset(preset_id);
|
return await this.upsertService.deletePreset(preset_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
//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(RoleEnum.ACCOUNTING, RoleEnum.HR, RoleEnum.SUPERVISOR, RoleEnum.ADMIN)
|
@RolesAllowed(...MANAGER_ROLES)
|
||||||
async findListById( @Req() req) {
|
async findListById(@Req() req) {
|
||||||
const email = req.user?.email;
|
const email = req.user?.email;
|
||||||
return this.getService.getSchedulePresets(email);
|
return this.getService.getSchedulePresets(email);
|
||||||
}
|
}
|
||||||
|
|
||||||
//used to apply a preset to a timesheet
|
//used to apply a preset to a timesheet
|
||||||
@Post('apply-presets')
|
@Post('apply-presets')
|
||||||
@RolesAllowed(RoleEnum.EMPLOYEE, RoleEnum.ACCOUNTING, RoleEnum.HR, RoleEnum.SUPERVISOR, RoleEnum.ADMIN)
|
async applyPresets(@Req() req, @Query('preset') preset_name: string, @Query('start') start_date: string) {
|
||||||
async applyPresets( @Req() req, @Query('preset') preset_name: string, @Query('start') start_date: string ) {
|
|
||||||
const email = req.user?.email;
|
const email = req.user?.email;
|
||||||
if(!preset_name?.trim()) throw new BadRequestException('Query "preset" is required');
|
if (!preset_name?.trim()) throw new BadRequestException('Query "preset" is required');
|
||||||
if(!start_date?.trim()) throw new BadRequestException('Query "start" is required YYYY-MM-DD');
|
if (!start_date?.trim()) throw new BadRequestException('Query "start" is required YYYY-MM-DD');
|
||||||
return this.applyPresetsService.applyToTimesheet(email, preset_name, start_date);
|
return this.applyPresetsService.applyToTimesheet(email, preset_name, start_date);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -3,17 +3,18 @@ import { ShiftDto } from "src/time-and-attendance/time-tracker/shifts/dtos/shift
|
||||||
import { UpdateShiftDto } from "src/time-and-attendance/time-tracker/shifts/dtos/shift-update.dto";
|
import { UpdateShiftDto } from "src/time-and-attendance/time-tracker/shifts/dtos/shift-update.dto";
|
||||||
import { ShiftsUpsertService } from "src/time-and-attendance/time-tracker/shifts/services/shifts-upsert.service";
|
import { ShiftsUpsertService } from "src/time-and-attendance/time-tracker/shifts/services/shifts-upsert.service";
|
||||||
import { CreateShiftResult, UpdateShiftResult } from "src/time-and-attendance/utils/type.utils";
|
import { CreateShiftResult, UpdateShiftResult } from "src/time-and-attendance/utils/type.utils";
|
||||||
import { Roles as RoleEnum } from '.prisma/client';
|
|
||||||
import { RolesAllowed } from "src/common/decorators/roles.decorators";
|
import { RolesAllowed } from "src/common/decorators/roles.decorators";
|
||||||
|
import { GLOBAL_CONTROLLER_ROLES } from "src/common/shared/role-groupes";
|
||||||
|
|
||||||
|
|
||||||
@Controller('shift')
|
@Controller('shift')
|
||||||
|
@RolesAllowed(...GLOBAL_CONTROLLER_ROLES)
|
||||||
export class ShiftController {
|
export class ShiftController {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly upsert_service: ShiftsUpsertService,
|
private readonly upsert_service: ShiftsUpsertService,
|
||||||
){}
|
){}
|
||||||
|
|
||||||
@Post('create')
|
@Post('create')
|
||||||
@RolesAllowed(RoleEnum.EMPLOYEE, RoleEnum.ACCOUNTING, RoleEnum.HR, RoleEnum.SUPERVISOR, RoleEnum.ADMIN)
|
|
||||||
createBatch( @Req() req, @Body()dtos: ShiftDto[]): Promise<CreateShiftResult[]> {
|
createBatch( @Req() req, @Body()dtos: ShiftDto[]): Promise<CreateShiftResult[]> {
|
||||||
const email = req.user?.email;
|
const email = req.user?.email;
|
||||||
const list = Array.isArray(dtos) ? dtos : [];
|
const list = Array.isArray(dtos) ? dtos : [];
|
||||||
|
|
@ -21,10 +22,7 @@ export class ShiftController {
|
||||||
return this.upsert_service.createShifts(email, dtos)
|
return this.upsert_service.createShifts(email, dtos)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
//change Body to receive dtos
|
|
||||||
@Patch('update')
|
@Patch('update')
|
||||||
@RolesAllowed(RoleEnum.EMPLOYEE, RoleEnum.ACCOUNTING, RoleEnum.HR, RoleEnum.SUPERVISOR, RoleEnum.ADMIN)
|
|
||||||
updateBatch( @Body() dtos: UpdateShiftDto[]): Promise<UpdateShiftResult[]>{
|
updateBatch( @Body() dtos: UpdateShiftDto[]): Promise<UpdateShiftResult[]>{
|
||||||
const list = Array.isArray(dtos) ? dtos: [];
|
const list = Array.isArray(dtos) ? dtos: [];
|
||||||
if(list.length === 0) throw new BadRequestException('Body is missing or invalid (update shifts)');
|
if(list.length === 0) throw new BadRequestException('Body is missing or invalid (update shifts)');
|
||||||
|
|
@ -32,7 +30,6 @@ export class ShiftController {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Delete(':shift_id')
|
@Delete(':shift_id')
|
||||||
@RolesAllowed(RoleEnum.EMPLOYEE, RoleEnum.ACCOUNTING, RoleEnum.HR, RoleEnum.SUPERVISOR, RoleEnum.ADMIN)
|
|
||||||
remove(@Param('shift_id') shift_id: number ) {
|
remove(@Param('shift_id') shift_id: number ) {
|
||||||
return this.upsert_service.deleteShift(shift_id);
|
return this.upsert_service.deleteShift(shift_id);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -49,8 +49,8 @@ export class ShiftsUpsertService {
|
||||||
};
|
};
|
||||||
return { index, error };
|
return { index, error };
|
||||||
}
|
}
|
||||||
if(!normed.end_time) throw new BadRequestException('A shift needs an end_time');
|
if (!normed.end_time) throw new BadRequestException('A shift needs an end_time');
|
||||||
if(!normed.start_time) throw new BadRequestException('A shift needs a start_time');
|
if (!normed.start_time) throw new BadRequestException('A shift needs a start_time');
|
||||||
|
|
||||||
const timesheet = await this.prisma.timesheets.findUnique({
|
const timesheet = await this.prisma.timesheets.findUnique({
|
||||||
where: { id: dto.timesheet_id, employee_id },
|
where: { id: dto.timesheet_id, employee_id },
|
||||||
|
|
@ -148,7 +148,7 @@ export class ShiftsUpsertService {
|
||||||
where: { timesheet_id, date: day_date },
|
where: { timesheet_id, date: day_date },
|
||||||
select: { start_time: true, end_time: true, id: true, date: true },
|
select: { start_time: true, end_time: true, id: true, date: true },
|
||||||
});
|
});
|
||||||
existing_map.set( key, rows.map((row) => ({ start_time: row.start_time, end_time: row.end_time, date: row.date })));
|
existing_map.set(key, rows.map((row) => ({ start_time: row.start_time, end_time: row.end_time, date: row.date })));
|
||||||
}
|
}
|
||||||
|
|
||||||
normed_shifts.forEach((x, i) => {
|
normed_shifts.forEach((x, i) => {
|
||||||
|
|
@ -165,7 +165,7 @@ export class ShiftsUpsertService {
|
||||||
existing_map.set(map_key, existing);
|
existing_map.set(map_key, existing);
|
||||||
}
|
}
|
||||||
const hit = existing.find(exist => overlaps({ start: exist.start_time, end: exist.end_time, date: exist.date },
|
const hit = existing.find(exist => overlaps({ start: exist.start_time, end: exist.end_time, date: exist.date },
|
||||||
{ start: normed.start_time, end: normed.end_time, date:normed.date}));
|
{ start: normed.start_time, end: normed.end_time, date: normed.date }));
|
||||||
if (hit) {
|
if (hit) {
|
||||||
results[index] = {
|
results[index] = {
|
||||||
ok: false,
|
ok: false,
|
||||||
|
|
@ -236,11 +236,11 @@ export class ShiftsUpsertService {
|
||||||
// recalculate overtime after update
|
// recalculate overtime after update
|
||||||
// return an updated version to display
|
// return an updated version to display
|
||||||
async updateShifts(dtos: UpdateShiftDto[]): Promise<UpdateShiftResult[]> {
|
async updateShifts(dtos: UpdateShiftDto[]): Promise<UpdateShiftResult[]> {
|
||||||
if (!Array.isArray(dtos) || dtos.length === 0) return [];
|
if (!Array.isArray(dtos) || dtos.length === 0) throw new BadRequestException({ error_code: 'SHIFT_MISSING' });
|
||||||
|
|
||||||
const updates: UpdateShiftPayload[] = await Promise.all(dtos.map((item) => {
|
const updates: UpdateShiftPayload[] = await Promise.all(dtos.map((item) => {
|
||||||
const { id, ...rest } = item;
|
const { id, ...rest } = item;
|
||||||
if (!Number.isInteger(id)) throw new ConflictException({ error_code: 'INVALID_SHIFT'});
|
if (!id) throw new BadRequestException({ error_code: 'SHIFT_INVALID' });
|
||||||
|
|
||||||
const changes: UpdateShiftChanges = {};
|
const changes: UpdateShiftChanges = {};
|
||||||
if (rest.date !== undefined) changes.date = rest.date;
|
if (rest.date !== undefined) changes.date = rest.date;
|
||||||
|
|
@ -265,13 +265,15 @@ export class ShiftsUpsertService {
|
||||||
const existing = regroup_id.get(update.id);
|
const existing = regroup_id.get(update.id);
|
||||||
if (!existing) {
|
if (!existing) {
|
||||||
return updates.map(exist => exist.id === update.id
|
return updates.map(exist => exist.id === update.id
|
||||||
? ({ ok: false, id: update.id, error: new NotFoundException(`Shift with id: ${update.id} not found`) } as UpdateShiftResult)
|
? ({ ok: false, id: update.id, error: new NotFoundException({ error_code: 'SHIFT_MISSING' }) } as UpdateShiftResult)
|
||||||
: ({ ok: false, id: exist.id, error: new BadRequestException('Batch aborted due to missing shift') }));
|
: ({ ok: false, id: exist.id, error: new BadRequestException({ error_code: 'SHIFT_INVALID' }) })
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if (existing.is_approved) {
|
if (existing.is_approved) {
|
||||||
return updates.map(exist => exist.id === update.id
|
return updates.map(exist => exist.id === update.id
|
||||||
? ({ ok: false, id: update.id, error: new BadRequestException('Approved shift cannot be updated') } as UpdateShiftResult)
|
? ({ ok: false, id: update.id, error: new BadRequestException({ error_code: 'SHIFT_INVALID' }) } as UpdateShiftResult)
|
||||||
: ({ ok: false, id: exist.id, error: new BadRequestException('Batch aborted due to approved shift in update set') }));
|
: ({ ok: false, id: exist.id, error: new BadRequestException({ error_code: 'SHIFT_INVALID' }) })
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -307,12 +309,14 @@ export class ShiftsUpsertService {
|
||||||
where: { timesheet_id: group.timesheet_id, date: day_date },
|
where: { timesheet_id: group.timesheet_id, date: day_date },
|
||||||
select: { id: true, start_time: true, end_time: true, date: true },
|
select: { id: true, start_time: true, end_time: true, date: true },
|
||||||
});
|
});
|
||||||
groups.set(key(group.timesheet_id, day_date), { existing: existing.map(row => ({
|
groups.set(key(group.timesheet_id, day_date), {
|
||||||
|
existing: existing.map(row => ({
|
||||||
id: row.id,
|
id: row.id,
|
||||||
start: row.start_time,
|
start: row.start_time,
|
||||||
end: row.end_time,
|
end: row.end_time,
|
||||||
date: row.date,
|
date: row.date,
|
||||||
})), incoming: planned_updates });
|
})), incoming: planned_updates
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const planned of planned_updates) {
|
for (const planned of planned_updates) {
|
||||||
|
|
@ -327,7 +331,7 @@ export class ShiftsUpsertService {
|
||||||
return updates.map(exist =>
|
return updates.map(exist =>
|
||||||
exist.id === planned.exist_shift.id
|
exist.id === planned.exist_shift.id
|
||||||
? ({
|
? ({
|
||||||
ok: false, id: exist.id, error:{
|
ok: false, id: exist.id, error: {
|
||||||
error_code: 'SHIFT_OVERLAP',
|
error_code: 'SHIFT_OVERLAP',
|
||||||
conflicts: {
|
conflicts: {
|
||||||
start_time: toStringFromHHmm(conflict.start),
|
start_time: toStringFromHHmm(conflict.start),
|
||||||
|
|
@ -336,7 +340,7 @@ export class ShiftsUpsertService {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
} as UpdateShiftResult)
|
} as UpdateShiftResult)
|
||||||
: ({ ok: false, id: exist.id, error: new BadRequestException('Batch aborted due to overlap in another update') })
|
: ({ ok: false, id: exist.id, error: new BadRequestException({ error_code: 'SHIFT_OVERLAP' }) })
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -426,7 +430,7 @@ export class ShiftsUpsertService {
|
||||||
where: { id: shift_id },
|
where: { id: shift_id },
|
||||||
select: { id: true, date: true, timesheet_id: true },
|
select: { id: true, date: true, timesheet_id: true },
|
||||||
});
|
});
|
||||||
if (!shift) throw new ConflictException({ error_code: 'INVALID_SHIFT'});
|
if (!shift) throw new ConflictException({ error_code: 'SHIFT_INVALID' });
|
||||||
|
|
||||||
await tx.shifts.delete({ where: { id: shift_id } });
|
await tx.shifts.delete({ where: { id: shift_id } });
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,31 +1,34 @@
|
||||||
import { Body, Controller, Get, ParseBoolPipe, ParseIntPipe, Patch, Query, Req, UnauthorizedException} from "@nestjs/common";
|
import { Body, Controller, Get, ParseBoolPipe, ParseIntPipe, Patch, Query, Req, UnauthorizedException } from "@nestjs/common";
|
||||||
import { RolesAllowed } from "src/common/decorators/roles.decorators";
|
import { RolesAllowed } from "src/common/decorators/roles.decorators";
|
||||||
import { GetTimesheetsOverviewService } from "src/time-and-attendance/time-tracker/timesheets/services/timesheet-get-overview.service";
|
import { GetTimesheetsOverviewService } from "src/time-and-attendance/time-tracker/timesheets/services/timesheet-get-overview.service";
|
||||||
import { Roles as RoleEnum } from '.prisma/client';
|
|
||||||
import { TimesheetApprovalService } from "src/time-and-attendance/time-tracker/timesheets/services/timesheet-approval.service";
|
import { TimesheetApprovalService } from "src/time-and-attendance/time-tracker/timesheets/services/timesheet-approval.service";
|
||||||
|
import { GLOBAL_CONTROLLER_ROLES, MANAGER_ROLES } from "src/common/shared/role-groupes";
|
||||||
|
|
||||||
|
|
||||||
@Controller('timesheets')
|
@Controller('timesheets')
|
||||||
|
@RolesAllowed(...GLOBAL_CONTROLLER_ROLES)
|
||||||
export class TimesheetController {
|
export class TimesheetController {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly timesheetOverview: GetTimesheetsOverviewService,
|
private readonly timesheetOverview: GetTimesheetsOverviewService,
|
||||||
private readonly approvalService: TimesheetApprovalService,
|
private readonly approvalService: TimesheetApprovalService,
|
||||||
){}
|
) { }
|
||||||
|
|
||||||
@Get()
|
@Get()
|
||||||
@RolesAllowed(RoleEnum.SUPERVISOR, RoleEnum.HR, RoleEnum.ACCOUNTING, RoleEnum.ADMIN)
|
getTimesheetByPayPeriod(
|
||||||
async getTimesheetByIds(
|
@Req() req,
|
||||||
@Req() req, @Query('year', ParseIntPipe) year:number, @Query('period_number', ParseIntPipe) period_number: number) {
|
@Query('year', ParseIntPipe) year: number,
|
||||||
|
@Query('period_number', ParseIntPipe) period_number: number
|
||||||
|
) {
|
||||||
const email = req.user?.email;
|
const email = req.user?.email;
|
||||||
if(!email) throw new UnauthorizedException('Unauthorized User');
|
if (!email) throw new UnauthorizedException('Unauthorized User');
|
||||||
return this.timesheetOverview.getTimesheetsForEmployeeByPeriod(email, year, period_number);
|
return this.timesheetOverview.getTimesheetsForEmployeeByPeriod(email, year, period_number);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Patch('timesheet-approval')
|
@Patch('timesheet-approval')
|
||||||
@RolesAllowed(RoleEnum.SUPERVISOR, RoleEnum.HR, RoleEnum.ACCOUNTING, RoleEnum.ADMIN)
|
@RolesAllowed(...MANAGER_ROLES)
|
||||||
async approveTimesheet(
|
approveTimesheet(
|
||||||
@Body('timesheet_id', ParseIntPipe) timesheet_id: number,
|
@Body('timesheet_id', ParseIntPipe) timesheet_id: number,
|
||||||
@Body('is_approved' , ParseBoolPipe) is_approved: boolean,
|
@Body('is_approved', ParseBoolPipe) is_approved: boolean,
|
||||||
) {
|
) {
|
||||||
return this.approvalService.approveTimesheetById(timesheet_id, is_approved);
|
return this.approvalService.approveTimesheetById(timesheet_id, is_approved);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user