Merge branch 'main' of git.targo.ca:Targo/targo_backend
This commit is contained in:
commit
6d1ac6c634
|
|
@ -215,114 +215,6 @@
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"/schedule-presets/create": {
|
|
||||||
"post": {
|
|
||||||
"operationId": "SchedulePresetsController_createPreset",
|
|
||||||
"parameters": [],
|
|
||||||
"requestBody": {
|
|
||||||
"required": true,
|
|
||||||
"content": {
|
|
||||||
"application/json": {
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/components/schemas/SchedulePresetsDto"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"responses": {
|
|
||||||
"201": {
|
|
||||||
"description": ""
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"tags": [
|
|
||||||
"SchedulePresets"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/schedule-presets/update/{preset_id}": {
|
|
||||||
"patch": {
|
|
||||||
"operationId": "SchedulePresetsController_updatePreset",
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"name": "preset_id",
|
|
||||||
"required": true,
|
|
||||||
"in": "path",
|
|
||||||
"schema": {
|
|
||||||
"type": "number"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"requestBody": {
|
|
||||||
"required": true,
|
|
||||||
"content": {
|
|
||||||
"application/json": {
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/components/schemas/SchedulePresetsUpdateDto"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": ""
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"tags": [
|
|
||||||
"SchedulePresets"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/schedule-presets/delete/{preset_id}": {
|
|
||||||
"delete": {
|
|
||||||
"operationId": "SchedulePresetsController_deletePreset",
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"name": "preset_id",
|
|
||||||
"required": true,
|
|
||||||
"in": "path",
|
|
||||||
"schema": {
|
|
||||||
"type": "number"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": ""
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"tags": [
|
|
||||||
"SchedulePresets"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/schedule-presets/find-list": {
|
|
||||||
"get": {
|
|
||||||
"operationId": "SchedulePresetsController_findListById",
|
|
||||||
"parameters": [],
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": ""
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"tags": [
|
|
||||||
"SchedulePresets"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/schedule-presets/apply-presets": {
|
|
||||||
"post": {
|
|
||||||
"operationId": "SchedulePresetsController_applyPresets",
|
|
||||||
"parameters": [],
|
|
||||||
"responses": {
|
|
||||||
"201": {
|
|
||||||
"description": ""
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"tags": [
|
|
||||||
"SchedulePresets"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/expense/create": {
|
"/expense/create": {
|
||||||
"post": {
|
"post": {
|
||||||
"operationId": "ExpenseController_create",
|
"operationId": "ExpenseController_create",
|
||||||
|
|
@ -727,14 +619,6 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"schemas": {
|
"schemas": {
|
||||||
"SchedulePresetsDto": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {}
|
|
||||||
},
|
|
||||||
"SchedulePresetsUpdateDto": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {}
|
|
||||||
},
|
|
||||||
"ExpenseDto": {
|
"ExpenseDto": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {}
|
"properties": {}
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ export class BankCodesResolver {
|
||||||
where: { type },
|
where: { type },
|
||||||
select: { id: true, modifier: true },
|
select: { id: true, modifier: true },
|
||||||
});
|
});
|
||||||
if (!bank) return { success: false, error: `Unknown bank code type: ${type}` };
|
if (!bank) return { success: false, error: `INVALID_TYPE` };
|
||||||
|
|
||||||
return { success: true, data: { id: bank.id, modifier: bank.modifier } };
|
return { success: true, data: { id: bank.id, modifier: bank.modifier } };
|
||||||
};
|
};
|
||||||
|
|
@ -30,7 +30,7 @@ export class BankCodesResolver {
|
||||||
where: { type },
|
where: { type },
|
||||||
select: { id: true },
|
select: { id: true },
|
||||||
});
|
});
|
||||||
if (!bank_code) return { success: false, error: `Unkown bank type: ${type}` };
|
if (!bank_code) return { success: false, error: `INVALID_TYPE` };
|
||||||
|
|
||||||
return { success: true, data: bank_code.id };
|
return { success: true, data: bank_code.id };
|
||||||
}
|
}
|
||||||
|
|
@ -42,7 +42,7 @@ export class BankCodesResolver {
|
||||||
where: { id: bank_code_id },
|
where: { id: bank_code_id },
|
||||||
select: { type: true },
|
select: { type: true },
|
||||||
});
|
});
|
||||||
if (!bank_code) return { success: false, error: `Type with id : ${bank_code_id} not found` }
|
if (!bank_code) return { success: false, error: `INVALID_TYPE` }
|
||||||
|
|
||||||
return { success: true, data: bank_code.type };
|
return { success: true, data: bank_code.type };
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ export class EmailToIdResolver {
|
||||||
where: { user: { email } },
|
where: { user: { email } },
|
||||||
select: { id: true },
|
select: { id: true },
|
||||||
});
|
});
|
||||||
if (!employee) return { success: false, error: `Employee with email:${email} not found` };
|
if (!employee) return { success: false, error: `EMPLOYEE_NOT_FOUND` };
|
||||||
return { data: employee.id, success: true };
|
return { data: employee.id, success: true };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -30,7 +30,7 @@ export class EmailToIdResolver {
|
||||||
where: { email },
|
where: { email },
|
||||||
select: { id: true },
|
select: { id: true },
|
||||||
});
|
});
|
||||||
if (!user) return { success: false, error: `User with email:${email} not found` };
|
if (!user) return { success: false, error: `EMPLOYEE_NOT_FOUND` };
|
||||||
return { success: true, data: user.id };
|
return { success: true, data: user.id };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -15,7 +15,7 @@ export class FullNameResolver {
|
||||||
where: { id: employee_id },
|
where: { id: employee_id },
|
||||||
select: { user: { select: {first_name: true, last_name: true} } },
|
select: { user: { select: {first_name: true, last_name: true} } },
|
||||||
});
|
});
|
||||||
if(!employee) return { success: false, error: `Unknown user with id ${employee_id}`}
|
if(!employee) return { success: false, error: `INVALID_EMPLOYEE`}
|
||||||
|
|
||||||
const full_name = ( employee.user.first_name + " " + employee.user.last_name ) || " ";
|
const full_name = ( employee.user.first_name + " " + employee.user.last_name ) || " ";
|
||||||
return {success: true, data: full_name };
|
return {success: true, data: full_name };
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,7 @@ export class ShiftIdResolver {
|
||||||
},
|
},
|
||||||
select: { id: true },
|
select: { id: true },
|
||||||
});
|
});
|
||||||
if (!shift) return { success: false, error: `shift not found` }
|
if (!shift) return { success: false, error: `SHIFT_NOT_FOUND` }
|
||||||
|
|
||||||
return { success: true, data: shift.id };
|
return { success: true, data: shift.id };
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ export class EmployeeTimesheetResolver {
|
||||||
where: { employee_id : employee_id.data, start_date: start_date },
|
where: { employee_id : employee_id.data, start_date: start_date },
|
||||||
select: { id: true },
|
select: { id: true },
|
||||||
});
|
});
|
||||||
if(!timesheet) throw new NotFoundException(`timesheet not found`);
|
if(!timesheet) throw new NotFoundException(`TIMESHEET_NOT_FOUND`);
|
||||||
return { success: true, data: {id: timesheet.id} };
|
return { success: true, data: {id: timesheet.id} };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -4,7 +4,6 @@ import { ExpenseDto } from "src/time-and-attendance/expenses/dtos/expense-create
|
||||||
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";
|
import { GLOBAL_CONTROLLER_ROLES } from "src/common/shared/role-groupes";
|
||||||
import { Result } from "src/common/errors/result-error.factory";
|
import { Result } from "src/common/errors/result-error.factory";
|
||||||
import { GetExpenseDto } from "src/time-and-attendance/expenses/dtos/expense-get.dto";
|
|
||||||
|
|
||||||
@Controller('expense')
|
@Controller('expense')
|
||||||
@RolesAllowed(...GLOBAL_CONTROLLER_ROLES)
|
@RolesAllowed(...GLOBAL_CONTROLLER_ROLES)
|
||||||
|
|
|
||||||
|
|
@ -1,15 +0,0 @@
|
||||||
// import { BankCodeEntity } from "src/modules/bank-codes/dtos/bank-code-entity";
|
|
||||||
|
|
||||||
export class ExpenseEntity {
|
|
||||||
id: number;
|
|
||||||
timesheet_id: number;
|
|
||||||
bank_code_id: number;
|
|
||||||
attachment?:number | null;
|
|
||||||
date: Date;
|
|
||||||
amount?: number | null;
|
|
||||||
mileage?:number | null;
|
|
||||||
comment: string;
|
|
||||||
supervisor_comment?:string | null;
|
|
||||||
is_approved: boolean;
|
|
||||||
// bank_code?: BankCodeEntity;
|
|
||||||
}
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
export class GetExpenseDto {
|
|
||||||
id: number;
|
|
||||||
timesheet_id: number;
|
|
||||||
bank_code_id: number;
|
|
||||||
attachment?: number;
|
|
||||||
date: string;
|
|
||||||
comment: string;
|
|
||||||
mileage?: number;
|
|
||||||
amount?: number;
|
|
||||||
supervisor_comment?: string;
|
|
||||||
is_approved: boolean;
|
|
||||||
}
|
|
||||||
|
|
@ -38,7 +38,7 @@ export class ExpenseUpsertService {
|
||||||
where: { start_date, employee_id: employee_id.data },
|
where: { start_date, employee_id: employee_id.data },
|
||||||
select: { id: true, employee_id: true },
|
select: { id: true, employee_id: true },
|
||||||
});
|
});
|
||||||
if (!timesheet) return { success: false, error: `Timesheet with id : ${dto.timesheet_id} not found` };
|
if (!timesheet) return { success: false, error: `TIMESHEET_NOT_FOUND` };
|
||||||
|
|
||||||
//create a new expense
|
//create a new expense
|
||||||
const expense = await this.prisma.expenses.create({
|
const expense = await this.prisma.expenses.create({
|
||||||
|
|
@ -50,7 +50,7 @@ export class ExpenseUpsertService {
|
||||||
//return the newly created expense with id
|
//return the newly created expense with id
|
||||||
select: expense_select,
|
select: expense_select,
|
||||||
});
|
});
|
||||||
if (!expense) return { success: false, error: `An error occured during creation. Expense is invalid` };
|
if (!expense) return { success: false, error: `INVALID_EXPENSE` };
|
||||||
|
|
||||||
//build an object to return to the frontend to display
|
//build an object to return to the frontend to display
|
||||||
const created: ExpenseDto = {
|
const created: ExpenseDto = {
|
||||||
|
|
@ -65,7 +65,7 @@ export class ExpenseUpsertService {
|
||||||
return { success: true, data: created };
|
return { success: true, data: created };
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return { success: false, error: `An error occured during creation. Expense not created : ` + error };
|
return { success: false, error: 'INVALID_EXPENSE' };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -88,15 +88,14 @@ export class ExpenseUpsertService {
|
||||||
where: { start_date: new_timesheet_start_date, employee_id: employee_id.data },
|
where: { start_date: new_timesheet_start_date, employee_id: employee_id.data },
|
||||||
select: timesheet_select,
|
select: timesheet_select,
|
||||||
});
|
});
|
||||||
if (!timesheet) return { success: false, error: `Timesheet ${dto.timesheet_id} not found` }
|
if (!timesheet) return { success: false, error: `TIMESHEET_NOT_FOUND` }
|
||||||
|
|
||||||
|
|
||||||
//checks for modifications
|
//checks for modifications
|
||||||
const data: Prisma.ExpensesUpdateInput = {
|
const data: Prisma.ExpensesUpdateInput = {
|
||||||
...normed_expense.data,
|
...normed_expense.data,
|
||||||
is_approved: dto.is_approved,
|
is_approved: dto.is_approved,
|
||||||
};
|
};
|
||||||
if (!data) return { success: false, error: `An error occured during normalization. Expense with id: ${dto.id} is invalid` }
|
if (!data) return { success: false, error: `INVALID_EXPENSE` }
|
||||||
|
|
||||||
//push updates and get updated datas
|
//push updates and get updated datas
|
||||||
const expense = await this.prisma.expenses.update({
|
const expense = await this.prisma.expenses.update({
|
||||||
|
|
@ -104,7 +103,7 @@ export class ExpenseUpsertService {
|
||||||
data,
|
data,
|
||||||
select: expense_select,
|
select: expense_select,
|
||||||
});
|
});
|
||||||
if (!expense) return { success: false, error: `An error occured during update. Expense with id: ${dto.id} was not updated` }
|
if (!expense) return { success: false, error: 'INVALID_EXPENSE' }
|
||||||
|
|
||||||
//build an object to return to the frontend
|
//build an object to return to the frontend
|
||||||
const updated: ExpenseDto = {
|
const updated: ExpenseDto = {
|
||||||
|
|
@ -118,7 +117,7 @@ export class ExpenseUpsertService {
|
||||||
};
|
};
|
||||||
return { success: true, data: updated };
|
return { success: true, data: updated };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return { success: false, error: (`Expense with id: ${dto.id} generated an error:` + error) };
|
return { success: false, error: 'EXPENSE_NOT_FOUND' };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//_________________________________________________________________
|
//_________________________________________________________________
|
||||||
|
|
@ -131,20 +130,14 @@ export class ExpenseUpsertService {
|
||||||
where: { id: expense_id },
|
where: { id: expense_id },
|
||||||
select: { id: true },
|
select: { id: true },
|
||||||
});
|
});
|
||||||
if (!expense) return {
|
if (!expense) return { success: false, error: `EXPENSE_NOT_FOUND` };
|
||||||
success: false,
|
|
||||||
error: `An error occured during removal. Expense with id :${expense_id} was not found `
|
|
||||||
};
|
|
||||||
|
|
||||||
await tx.expenses.delete({ where: { id: expense.id } });
|
await tx.expenses.delete({ where: { id: expense.id } });
|
||||||
return { success: true, data: expense.id };
|
return { success: true, data: expense.id };
|
||||||
});
|
});
|
||||||
return { success: true, data: expense_id };
|
return { success: true, data: expense_id };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return {
|
return { success: false, error: `EXPENSE_NOT_FOUND` };
|
||||||
success: false,
|
|
||||||
error: `An error occured during removal. Expense with id :${expense_id} generated an error: ` + error
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -163,7 +156,7 @@ export class ExpenseUpsertService {
|
||||||
|
|
||||||
const date = toDateFromString(dto.date);
|
const date = toDateFromString(dto.date);
|
||||||
const type = await this.typeResolver.findBankCodeIDByType(dto.type);
|
const type = await this.typeResolver.findBankCodeIDByType(dto.type);
|
||||||
if (!type.success) return { success: false, error: 'Bank-type not found' }
|
if (!type.success) return { success: false, error: 'INVALID_EXPENSE_TYPE' }
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
|
|
|
||||||
|
|
@ -2,13 +2,14 @@ import { Body, Controller, Get, Param, ParseBoolPipe, ParseIntPipe, Patch, Query
|
||||||
import { PayPeriodOverviewDto } from "../dtos/overview-pay-period.dto";
|
import { PayPeriodOverviewDto } from "../dtos/overview-pay-period.dto";
|
||||||
import { PayPeriodsQueryService } from "../services/pay-periods-query.service";
|
import { PayPeriodsQueryService } from "../services/pay-periods-query.service";
|
||||||
import { RolesAllowed } from "src/common/decorators/roles.decorators";
|
import { RolesAllowed } from "src/common/decorators/roles.decorators";
|
||||||
import { Roles as RoleEnum } from '.prisma/client';
|
|
||||||
import { PayPeriodsCommandService } from "../services/pay-periods-command.service";
|
import { PayPeriodsCommandService } from "../services/pay-periods-command.service";
|
||||||
import { PayPeriodBundleDto } from "../dtos/bundle-pay-period.dto";
|
import { PayPeriodBundleDto } from "../dtos/bundle-pay-period.dto";
|
||||||
import { BulkCrewApprovalDto } from "../dtos/bulk-crew-approval.dto";
|
import { BulkCrewApprovalDto } from "../dtos/bulk-crew-approval.dto";
|
||||||
|
import { GLOBAL_CONTROLLER_ROLES, MANAGER_ROLES } from "src/common/shared/role-groupes";
|
||||||
|
import { Result } from "src/common/errors/result-error.factory";
|
||||||
|
|
||||||
@Controller('pay-periods')
|
@Controller('pay-periods')
|
||||||
|
@RolesAllowed(...GLOBAL_CONTROLLER_ROLES)
|
||||||
export class PayPeriodsController {
|
export class PayPeriodsController {
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
|
|
@ -17,12 +18,14 @@ export class PayPeriodsController {
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
@Get('current-and-all')
|
@Get('current-and-all')
|
||||||
async getCurrentAndAll(@Query('date') date?: string): Promise<PayPeriodBundleDto> {
|
async getCurrentAndAll(@Query('date') date?: string): Promise<Result<PayPeriodBundleDto, string>> {
|
||||||
const [current, periods] = await Promise.all([
|
const current = await this.queryService.findCurrent(date);
|
||||||
this.queryService.findCurrent(date),
|
if (!current.success) return { success: false, error: 'INVALID_PAY_PERIOD' };
|
||||||
this.queryService.findAll(),
|
|
||||||
]);
|
const periods = await this.queryService.findAll();
|
||||||
return { current, periods };
|
if (!periods.success) return { success: false, error: 'INVALID_PAY_PERIOD' };
|
||||||
|
|
||||||
|
return { success: true, data: { current: current.data, periods: periods.data } };
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get("date/:date")
|
@Get("date/:date")
|
||||||
|
|
@ -39,31 +42,30 @@ export class PayPeriodsController {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Patch("crew/pay-period-approval")
|
@Patch("crew/pay-period-approval")
|
||||||
@RolesAllowed(RoleEnum.ACCOUNTING, RoleEnum.ADMIN, RoleEnum.HR, RoleEnum.SUPERVISOR)
|
@RolesAllowed(...MANAGER_ROLES)
|
||||||
async bulkApproval(@Req() req, @Body() dto: BulkCrewApprovalDto) {
|
async bulkApproval(@Req() req, @Body() dto: BulkCrewApprovalDto) {
|
||||||
const email = req.user?.email;
|
const email = req.user?.email;
|
||||||
if(!email) throw new UnauthorizedException(`Session infos not found`);
|
if (!email) throw new UnauthorizedException(`Session infos not found`);
|
||||||
return this.commandService.bulkApproveCrew(email, dto);
|
return this.commandService.bulkApproveCrew(email, dto);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get('crew/:year/:periodNumber')
|
@Get('crew/:year/:periodNumber')
|
||||||
@RolesAllowed(RoleEnum.SUPERVISOR, RoleEnum.ADMIN)
|
@RolesAllowed(...MANAGER_ROLES)
|
||||||
async getCrewOverview(@Req() req,
|
async getCrewOverview(@Req() req,
|
||||||
@Param('year', ParseIntPipe) year: number,
|
@Param('year', ParseIntPipe) year: number,
|
||||||
@Param('periodNumber', ParseIntPipe) period_no: number,
|
@Param('periodNumber', ParseIntPipe) period_no: number,
|
||||||
@Query('includeSubtree', new ParseBoolPipe({ optional: true })) include_subtree = false,
|
@Query('includeSubtree', new ParseBoolPipe({ optional: true })) include_subtree = false,
|
||||||
): Promise<PayPeriodOverviewDto> {
|
): Promise<Result<PayPeriodOverviewDto, string>> {
|
||||||
const email = req.user?.email;
|
const email = req.user?.email;
|
||||||
if(!email) throw new UnauthorizedException(`Session infos not found`);
|
if (!email) throw new UnauthorizedException(`Session infos not found`);
|
||||||
return this.queryService.getCrewOverview(year, period_no, email, include_subtree);
|
return this.queryService.getCrewOverview(year, period_no, email, include_subtree);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get('overview/:year/:periodNumber')
|
@Get('overview/:year/:periodNumber')
|
||||||
@RolesAllowed(RoleEnum.ACCOUNTING, RoleEnum.ADMIN, RoleEnum.HR, RoleEnum.SUPERVISOR)
|
|
||||||
async getOverviewByYear(
|
async getOverviewByYear(
|
||||||
@Param('year', ParseIntPipe) year: number,
|
@Param('year', ParseIntPipe) year: number,
|
||||||
@Param('periodNumber', ParseIntPipe) period_no: number,
|
@Param('periodNumber', ParseIntPipe) period_no: number,
|
||||||
): Promise<PayPeriodOverviewDto> {
|
): Promise<Result<PayPeriodOverviewDto, string>> {
|
||||||
return this.queryService.getOverviewByYearPeriod(year, period_no);
|
return this.queryService.getOverviewByYearPeriod(year, period_no);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ import { PayPeriodsQueryService } from "./services/pay-periods-query.service";
|
||||||
import { PayPeriodsController } from "./controllers/pay-periods.controller";
|
import { PayPeriodsController } from "./controllers/pay-periods.controller";
|
||||||
import { Module } from "@nestjs/common";
|
import { Module } from "@nestjs/common";
|
||||||
import { PayPeriodsCommandService } from "src/time-and-attendance/pay-period/services/pay-periods-command.service";
|
import { PayPeriodsCommandService } from "src/time-and-attendance/pay-period/services/pay-periods-command.service";
|
||||||
import { TimesheetsModule } from "src/time-and-attendance/time-tracker/timesheets/timesheets.module";
|
import { TimesheetsModule } from "src/time-and-attendance/timesheets/timesheets.module";
|
||||||
import { EmailToIdResolver } from "src/common/mappers/email-id.mapper";
|
import { EmailToIdResolver } from "src/common/mappers/email-id.mapper";
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
|
|
|
||||||
|
|
@ -1,44 +1,47 @@
|
||||||
import { BadRequestException, ForbiddenException, Injectable, NotFoundException } from "@nestjs/common";
|
import { Injectable, NotFoundException } from "@nestjs/common";
|
||||||
import { PrismaService } from "src/prisma/prisma.service";
|
import { PrismaService } from "src/prisma/prisma.service";
|
||||||
import { BulkCrewApprovalDto } from "../dtos/bulk-crew-approval.dto";
|
import { BulkCrewApprovalDto } from "../dtos/bulk-crew-approval.dto";
|
||||||
import { PayPeriodsQueryService } from "./pay-periods-query.service";
|
import { PayPeriodsQueryService } from "./pay-periods-query.service";
|
||||||
import { TimesheetApprovalService } from "src/time-and-attendance/time-tracker/timesheets/services/timesheet-approval.service";
|
import { TimesheetApprovalService } from "src/time-and-attendance/timesheets/services/timesheet-approval.service";
|
||||||
|
import { Result } from "src/common/errors/result-error.factory";
|
||||||
|
|
||||||
|
|
||||||
|
//change promise to return result pattern
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class PayPeriodsCommandService {
|
export class PayPeriodsCommandService {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly prisma: PrismaService,
|
private readonly prisma: PrismaService,
|
||||||
private readonly timesheetsApproval: TimesheetApprovalService,
|
private readonly timesheetsApproval: TimesheetApprovalService,
|
||||||
private readonly query: PayPeriodsQueryService,
|
private readonly query: PayPeriodsQueryService,
|
||||||
) {}
|
) { }
|
||||||
|
|
||||||
//function to approve pay-periods according to selected crew members
|
//function to approve pay-periods according to selected crew members
|
||||||
async bulkApproveCrew(email: string, dto:BulkCrewApprovalDto): Promise<{updated: number}> {
|
async bulkApproveCrew(email: string, dto: BulkCrewApprovalDto): Promise<Result<{ updated: number }, string>> {
|
||||||
const { include_subtree, items } = dto;
|
const { include_subtree, items } = dto;
|
||||||
if(!items?.length) throw new BadRequestException('no items to process');
|
if (!items?.length) return { success: false, error: 'EMPLOYEE_NOT_FOUND' };
|
||||||
|
|
||||||
//fetch and validate supervisor status
|
//fetch and validate supervisor status
|
||||||
const supervisor = await this.query.getSupervisor(email);
|
const supervisor = await this.query.getSupervisor(email);
|
||||||
if(!supervisor) throw new NotFoundException('No employee record linked to current user');
|
if (!supervisor) return { success: false, error: 'EMPLOYEE_NOT_FOUND' };
|
||||||
if(!supervisor.is_supervisor) throw new ForbiddenException('Employee is not a supervisor');
|
if (!supervisor.is_supervisor) return { success: false, error: 'INVALID_EMPLOYEE' };
|
||||||
|
|
||||||
//fetches emails of crew members linked to supervisor
|
//fetches emails of crew members linked to supervisor
|
||||||
const crew_emails = await this.query.resolveCrewEmails(supervisor.id, include_subtree);
|
const crew_emails = await this.query.resolveCrewEmails(supervisor.id, include_subtree);
|
||||||
|
if (!crew_emails.success) return { success: false, error: 'INVALID_EMAIL' };
|
||||||
|
|
||||||
|
for (const item of items) {
|
||||||
for(const item of items) {
|
if (!crew_emails.data.has(item.employee_email)) {
|
||||||
if(!crew_emails.has(item.employee_email)) {
|
return { success: false, error: 'INVALID_EMPLOYEE' }
|
||||||
throw new ForbiddenException(`Employee ${item.employee_email} not in supervisor crew`);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const period_cache = new Map<string, {period_start: Date, period_end: Date}>();
|
const period_cache = new Map<string, { period_start: Date, period_end: Date }>();
|
||||||
const getPeriod = async (year:number, period_no: number) => {
|
const getPeriod = async (year: number, period_no: number) => {
|
||||||
const key = `${year}-${period_no}`;
|
const key = `${year}-${period_no}`;
|
||||||
if(period_cache.has(key)) return period_cache.get(key)!;
|
if (period_cache.has(key)) return period_cache.get(key)!;
|
||||||
|
|
||||||
const period = await this.query.getPeriodWindow(year,period_no);
|
const period = await this.query.getPeriodWindow(year, period_no);
|
||||||
if(!period) throw new NotFoundException(`Pay period ${year}-${period_no} not found`);
|
if (!period) throw new NotFoundException(`Pay period ${year}-${period_no} not found`);
|
||||||
period_cache.set(key, period);
|
period_cache.set(key, period);
|
||||||
return period;
|
return period;
|
||||||
};
|
};
|
||||||
|
|
@ -46,27 +49,27 @@ export class PayPeriodsCommandService {
|
||||||
let updated = 0;
|
let updated = 0;
|
||||||
|
|
||||||
await this.prisma.$transaction(async (transaction) => {
|
await this.prisma.$transaction(async (transaction) => {
|
||||||
for(const item of items) {
|
for (const item of items) {
|
||||||
const { period_start, period_end } = await getPeriod(item.pay_year, item.period_no);
|
const { period_start, period_end } = await getPeriod(item.pay_year, item.period_no);
|
||||||
|
|
||||||
const timesheets = await transaction.timesheets.findMany({
|
const timesheets = await transaction.timesheets.findMany({
|
||||||
where: {
|
where: {
|
||||||
employee: { user: { email: item.employee_email } },
|
employee: { user: { email: item.employee_email } },
|
||||||
OR: [
|
OR: [
|
||||||
{shift : { some: { date: { gte: period_start, lte: period_end } } } },
|
{ shift: { some: { date: { gte: period_start, lte: period_end } } } },
|
||||||
{expense: { some: { date: { gte: period_start, lte: period_end } } } },
|
{ expense: { some: { date: { gte: period_start, lte: period_end } } } },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
select: { id: true },
|
select: { id: true },
|
||||||
});
|
});
|
||||||
|
|
||||||
for(const { id } of timesheets) {
|
for (const { id } of timesheets) {
|
||||||
await this.timesheetsApproval.cascadeApprovalWithtx(transaction, id, item.approve);
|
await this.timesheetsApproval.cascadeApprovalWithtx(transaction, id, item.approve);
|
||||||
updated++;
|
updated++;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return {updated};
|
return { success: true, data: { updated } };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,23 +1,24 @@
|
||||||
import { ForbiddenException, Injectable, NotFoundException } from "@nestjs/common";
|
import { Injectable } from "@nestjs/common";
|
||||||
import { PrismaService } from "src/prisma/prisma.service";
|
import { PrismaService } from "src/prisma/prisma.service";
|
||||||
import { computeHours, computePeriod, listPayYear, payYearOfDate } from "src/common/utils/date-utils";
|
import { computeHours, computePeriod, listPayYear, payYearOfDate } from "src/common/utils/date-utils";
|
||||||
import { PayPeriodOverviewDto } from "../dtos/overview-pay-period.dto";
|
import { PayPeriodOverviewDto } from "../dtos/overview-pay-period.dto";
|
||||||
import { EmployeePeriodOverviewDto } from "../dtos/overview-employee-period.dto";
|
import { EmployeePeriodOverviewDto } from "../dtos/overview-employee-period.dto";
|
||||||
import { PayPeriodDto } from "../dtos/pay-period.dto";
|
import { PayPeriodDto } from "../dtos/pay-period.dto";
|
||||||
import { mapPayPeriodToDto } from "../mappers/pay-periods.mapper";
|
import { mapPayPeriodToDto } from "../mappers/pay-periods.mapper";
|
||||||
import { Prisma } from "@prisma/client";
|
import { Result } from "src/common/errors/result-error.factory";
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class PayPeriodsQueryService {
|
export class PayPeriodsQueryService {
|
||||||
constructor(private readonly prisma: PrismaService) { }
|
constructor(private readonly prisma: PrismaService) { }
|
||||||
|
|
||||||
async getOverview(pay_period_no: number): Promise<PayPeriodOverviewDto> {
|
async getOverview(pay_period_no: number): Promise<Result<PayPeriodOverviewDto, string>> {
|
||||||
const period = await this.prisma.payPeriods.findFirst({
|
const period = await this.prisma.payPeriods.findFirst({
|
||||||
where: { pay_period_no },
|
where: { pay_period_no },
|
||||||
orderBy: { pay_year: "desc" },
|
orderBy: { pay_year: "desc" },
|
||||||
});
|
});
|
||||||
if (!period) throw new NotFoundException(`Period #${pay_period_no} not found`);
|
if (!period) return { success: false, error: `PAY_PERIOD_NOT_FOUND` };
|
||||||
|
|
||||||
return this.buildOverview({
|
const overview = await this.buildOverview({
|
||||||
period_start: period.period_start,
|
period_start: period.period_start,
|
||||||
period_end: period.period_end,
|
period_end: period.period_end,
|
||||||
payday: period.payday,
|
payday: period.payday,
|
||||||
|
|
@ -25,23 +26,28 @@ export class PayPeriodsQueryService {
|
||||||
pay_year: period.pay_year,
|
pay_year: period.pay_year,
|
||||||
label: period.label,
|
label: period.label,
|
||||||
});
|
});
|
||||||
|
if (!overview.success) return { success: false, error: 'INVALID_PAY_PERIOD' }
|
||||||
|
|
||||||
|
return { success: true, data: overview.data }
|
||||||
}
|
}
|
||||||
|
|
||||||
async getOverviewByYearPeriod(pay_year: number, period_no: number): Promise<PayPeriodOverviewDto> {
|
async getOverviewByYearPeriod(pay_year: number, period_no: number): Promise<Result<PayPeriodOverviewDto, string>> {
|
||||||
const period = computePeriod(pay_year, period_no);
|
const period = computePeriod(pay_year, period_no);
|
||||||
return this.buildOverview({
|
const overview = await this.buildOverview({
|
||||||
period_start: period.period_start,
|
period_start: period.period_start,
|
||||||
period_end: period.period_end,
|
period_end: period.period_end,
|
||||||
period_no: period.period_no,
|
period_no: period.period_no,
|
||||||
pay_year: period.pay_year,
|
pay_year: period.pay_year,
|
||||||
payday: period.payday,
|
payday: period.payday,
|
||||||
label: period.label,
|
label: period.label,
|
||||||
} as any);
|
});
|
||||||
|
if (!overview.success) return { success: false, error: 'INVALID_PAY_PERIOD' }
|
||||||
|
return { success: true, data: overview.data }
|
||||||
}
|
}
|
||||||
|
|
||||||
//find crew member associated with supervisor
|
//find crew member associated with supervisor
|
||||||
private async resolveCrew(supervisor_id: number, include_subtree: boolean):
|
private async resolveCrew(supervisor_id: number, include_subtree: boolean):
|
||||||
Promise<Array<{ id: number; first_name: string; last_name: string; email: string }>> {
|
Promise<Result<Array<{ id: number; first_name: string; last_name: string; email: string }>, string>> {
|
||||||
const result: Array<{ id: number; first_name: string; last_name: string; email: string; }> = [];
|
const result: Array<{ id: number; first_name: string; last_name: string; email: string; }> = [];
|
||||||
|
|
||||||
let frontier = await this.prisma.employees.findMany({
|
let frontier = await this.prisma.employees.findMany({
|
||||||
|
|
@ -52,7 +58,7 @@ export class PayPeriodsQueryService {
|
||||||
id: emp.id, first_name: emp.user.first_name, last_name: emp.user.last_name, email: emp.user.email
|
id: emp.id, first_name: emp.user.first_name, last_name: emp.user.last_name, email: emp.user.email
|
||||||
})));
|
})));
|
||||||
|
|
||||||
if (!include_subtree) return result;
|
if (!include_subtree) return { success: true, data: result };
|
||||||
|
|
||||||
while (frontier.length) {
|
while (frontier.length) {
|
||||||
const parent_ids = frontier.map(emp => emp.id);
|
const parent_ids = frontier.map(emp => emp.id);
|
||||||
|
|
@ -66,20 +72,21 @@ export class PayPeriodsQueryService {
|
||||||
})));
|
})));
|
||||||
frontier = next;
|
frontier = next;
|
||||||
}
|
}
|
||||||
return result;
|
return { success: true, data: result };
|
||||||
}
|
}
|
||||||
|
|
||||||
//fetchs crew emails
|
//fetchs crew emails
|
||||||
async resolveCrewEmails(supervisor_id: number, include_subtree: boolean): Promise<Set<string>> {
|
async resolveCrewEmails(supervisor_id: number, include_subtree: boolean): Promise<Result<Set<string>, string>> {
|
||||||
const crew = await this.resolveCrew(supervisor_id, include_subtree);
|
const crew = await this.resolveCrew(supervisor_id, include_subtree);
|
||||||
return new Set(crew.map(crew_member => crew_member.email).filter(Boolean));
|
if (!crew.success) return { success: false, error: crew.error }
|
||||||
|
return { success: true, data: new Set(crew.data.map(crew_member => crew_member.email).filter(Boolean)) }
|
||||||
}
|
}
|
||||||
|
|
||||||
async getCrewOverview(pay_year: number, period_no: number, email: string, include_subtree: boolean):
|
async getCrewOverview(pay_year: number, period_no: number, email: string, include_subtree: boolean):
|
||||||
Promise<PayPeriodOverviewDto> {
|
Promise<Result<PayPeriodOverviewDto, string>> {
|
||||||
// 1) Search for the period
|
// 1) Search for the period
|
||||||
const period = await this.prisma.payPeriods.findFirst({ where: { pay_year, pay_period_no: period_no } });
|
const period = await this.prisma.payPeriods.findFirst({ where: { pay_year, pay_period_no: period_no } });
|
||||||
if (!period) throw new NotFoundException(`Pay period ${pay_year}-${period_no} not found`);
|
if (!period) return { success: false, error: 'PAY_PERIOD_NOT_FOUND' }
|
||||||
|
|
||||||
// 2) fetch supervisor
|
// 2) fetch supervisor
|
||||||
const supervisor = await this.prisma.employees.findFirst({
|
const supervisor = await this.prisma.employees.findFirst({
|
||||||
|
|
@ -90,15 +97,16 @@ export class PayPeriodsQueryService {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!supervisor) throw new NotFoundException('No employee record linked to current user');
|
if (!supervisor) return { success: false, error: 'EMPLOYEE_NOT_FOUND' }
|
||||||
if (!supervisor.is_supervisor) throw new ForbiddenException('Employee is not a supervisor');
|
if (!supervisor.is_supervisor) return { success: false, error: 'INVALID_EMPLOYEE' }
|
||||||
|
|
||||||
// 3)fetchs crew members
|
// 3)fetchs crew members
|
||||||
const crew = await this.resolveCrew(supervisor.id, include_subtree); // [{ id, first_name, last_name }]
|
const crew = await this.resolveCrew(supervisor.id, include_subtree); // [{ id, first_name, last_name }]
|
||||||
const crew_ids = crew.map(c => c.id);
|
if (!crew.success) return { success: false, error: crew.error }
|
||||||
|
const crew_ids = crew.data.map(c => c.id);
|
||||||
// seed names map for employee without data
|
// seed names map for employee without data
|
||||||
const seed_names = new Map<number, { name: string; email: string }>(
|
const seed_names = new Map<number, { name: string; email: string }>(
|
||||||
crew.map(crew => [
|
crew.data.map(crew => [
|
||||||
crew.id,
|
crew.id,
|
||||||
{
|
{
|
||||||
name: `${crew.first_name} ${crew.last_name}`.trim(),
|
name: `${crew.first_name} ${crew.last_name}`.trim(),
|
||||||
|
|
@ -107,17 +115,18 @@ export class PayPeriodsQueryService {
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
const overview = await this.buildOverview({
|
||||||
// 4) overview build
|
|
||||||
return this.buildOverview({
|
|
||||||
period_no: period.pay_period_no,
|
period_no: period.pay_period_no,
|
||||||
period_start: period.period_start,
|
period_start: period.period_start,
|
||||||
period_end: period.period_end,
|
period_end: period.period_end,
|
||||||
payday: period.payday,
|
payday: period.payday,
|
||||||
pay_year: period.pay_year,
|
pay_year: period.pay_year,
|
||||||
label: period.label,
|
label: period.label,
|
||||||
//add is_approved
|
}, { filtered_employee_ids: crew_ids, seed_names })
|
||||||
}, { filtered_employee_ids: crew_ids, seed_names });
|
if (!overview.success) return { success: false, error: 'INVALID_PAY_PERIOD' }
|
||||||
|
|
||||||
|
// 4) overview build
|
||||||
|
return { success: true, data: overview.data }
|
||||||
}
|
}
|
||||||
|
|
||||||
private async buildOverview(
|
private async buildOverview(
|
||||||
|
|
@ -126,7 +135,7 @@ export class PayPeriodsQueryService {
|
||||||
period_no: number; pay_year: number; label: string;
|
period_no: number; pay_year: number; label: string;
|
||||||
}, //add is_approved
|
}, //add is_approved
|
||||||
options?: { filtered_employee_ids?: number[]; seed_names?: Map<number, { name: string, email: string }> }
|
options?: { filtered_employee_ids?: number[]; seed_names?: Map<number, { name: string, email: string }> }
|
||||||
): Promise<PayPeriodOverviewDto> {
|
): Promise<Result<PayPeriodOverviewDto, string>> {
|
||||||
const toDateString = (d: Date) => d.toISOString().slice(0, 10);
|
const toDateString = (d: Date) => d.toISOString().slice(0, 10);
|
||||||
const toMoney = (v: any) => (typeof v === "object" && "toNumber" in v ? v.toNumber() : (v as number));
|
const toMoney = (v: any) => (typeof v === "object" && "toNumber" in v ? v.toNumber() : (v as number));
|
||||||
|
|
||||||
|
|
@ -344,6 +353,8 @@ export class PayPeriodsQueryService {
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
success: true,
|
||||||
|
data: {
|
||||||
pay_period_no: period.period_no,
|
pay_period_no: period.period_no,
|
||||||
pay_year: period.pay_year,
|
pay_year: period.pay_year,
|
||||||
payday: toDateString(payd),
|
payday: toDateString(payd),
|
||||||
|
|
@ -351,6 +362,7 @@ export class PayPeriodsQueryService {
|
||||||
period_end: toDateString(end),
|
period_end: toDateString(end),
|
||||||
label: period.label,
|
label: period.label,
|
||||||
employees_overview,
|
employees_overview,
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -361,42 +373,48 @@ export class PayPeriodsQueryService {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async findAll(): Promise<PayPeriodDto[]> {
|
async findAll(): Promise<Result<PayPeriodDto[], string>> {
|
||||||
const currentPayYear = payYearOfDate(new Date());
|
const currentPayYear = payYearOfDate(new Date());
|
||||||
return listPayYear(currentPayYear).map(period => ({
|
return {
|
||||||
|
success: true,
|
||||||
|
data: listPayYear(currentPayYear).map(period => ({
|
||||||
pay_period_no: period.period_no,
|
pay_period_no: period.period_no,
|
||||||
pay_year: period.pay_year,
|
pay_year: period.pay_year,
|
||||||
payday: period.payday,
|
payday: period.payday,
|
||||||
period_start: period.period_start,
|
period_start: period.period_start,
|
||||||
period_end: period.period_end,
|
period_end: period.period_end,
|
||||||
label: period.label,
|
label: period.label,
|
||||||
//add is_approved
|
}))
|
||||||
}));
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async findOne(period_no: number): Promise<PayPeriodDto> {
|
async findOne(period_no: number): Promise<Result<PayPeriodDto, string>> {
|
||||||
const row = await this.prisma.payPeriods.findFirst({
|
const row = await this.prisma.payPeriods.findFirst({
|
||||||
where: { pay_period_no: period_no },
|
where: { pay_period_no: period_no },
|
||||||
orderBy: { pay_year: "desc" },
|
orderBy: { pay_year: "desc" },
|
||||||
});
|
});
|
||||||
if (!row) throw new NotFoundException(`Pay period #${period_no} not found`);
|
if (!row) return { success: false, error: `PAY_PERIOD_NOT_FOUND` }
|
||||||
return mapPayPeriodToDto(row);
|
return { success: true, data: mapPayPeriodToDto(row) };
|
||||||
}
|
}
|
||||||
|
|
||||||
async findCurrent(date?: string): Promise<PayPeriodDto> {
|
async findCurrent(date?: string): Promise<Result<PayPeriodDto, string>> {
|
||||||
const iso_day = date ?? new Date().toISOString().slice(0, 10);
|
const iso_day = date ?? new Date().toISOString().slice(0, 10);
|
||||||
return this.findByDate(iso_day);
|
const pay_period = await this.findByDate(iso_day);
|
||||||
|
if (!pay_period.success) return { success: false, error: 'INVALID_PAY_PERIOD' }
|
||||||
|
return { success: true, data: pay_period.data }
|
||||||
}
|
}
|
||||||
|
|
||||||
async findOneByYearPeriod(pay_year: number, period_no: number): Promise<PayPeriodDto> {
|
async findOneByYearPeriod(pay_year: number, period_no: number): Promise<Result<PayPeriodDto, string>> {
|
||||||
const row = await this.prisma.payPeriods.findFirst({
|
const row = await this.prisma.payPeriods.findFirst({
|
||||||
where: { pay_year, pay_period_no: period_no },
|
where: { pay_year, pay_period_no: period_no },
|
||||||
});
|
});
|
||||||
if (row) return mapPayPeriodToDto(row);
|
if (row) return { success: true, data: mapPayPeriodToDto(row) };
|
||||||
|
|
||||||
// fallback for outside of view periods
|
// fallback for outside of view periods
|
||||||
const period = computePeriod(pay_year, period_no);
|
const period = computePeriod(pay_year, period_no);
|
||||||
return {
|
return {
|
||||||
|
success: true,
|
||||||
|
data: {
|
||||||
pay_period_no: period.period_no,
|
pay_period_no: period.period_no,
|
||||||
pay_year: period.pay_year,
|
pay_year: period.pay_year,
|
||||||
period_start: period.period_start,
|
period_start: period.period_start,
|
||||||
|
|
@ -405,22 +423,25 @@ export class PayPeriodsQueryService {
|
||||||
label: period.label
|
label: period.label
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//function to cherry pick a Date to find a period
|
//function to cherry pick a Date to find a period
|
||||||
async findByDate(date: string): Promise<PayPeriodDto> {
|
async findByDate(date: string): Promise<Result<PayPeriodDto, string>> {
|
||||||
const dt = new Date(date);
|
const dt = new Date(date);
|
||||||
const row = await this.prisma.payPeriods.findFirst({
|
const row = await this.prisma.payPeriods.findFirst({
|
||||||
where: { period_start: { lte: dt }, period_end: { gte: dt } },
|
where: { period_start: { lte: dt }, period_end: { gte: dt } },
|
||||||
});
|
});
|
||||||
if (row) return mapPayPeriodToDto(row);
|
if (row) return { success: true, data: mapPayPeriodToDto(row) };
|
||||||
|
|
||||||
//fallback for outwside view periods
|
//fallback for outwside view periods
|
||||||
const pay_year = payYearOfDate(date);
|
const pay_year = payYearOfDate(date);
|
||||||
const periods = listPayYear(pay_year);
|
const periods = listPayYear(pay_year);
|
||||||
const hit = periods.find(period => date >= period.period_start && date <= period.period_end);
|
const hit = periods.find(period => date >= period.period_start && date <= period.period_end);
|
||||||
if (!hit) throw new NotFoundException(`No period found for ${date}`);
|
if (!hit) return { success: false, error: `PAY_PERIOD_NOT_FOUND` }
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
success: true,
|
||||||
|
data: {
|
||||||
pay_period_no: hit.period_no,
|
pay_period_no: hit.period_no,
|
||||||
pay_year: hit.pay_year,
|
pay_year: hit.pay_year,
|
||||||
period_start: hit.period_start,
|
period_start: hit.period_start,
|
||||||
|
|
@ -429,6 +450,7 @@ export class PayPeriodsQueryService {
|
||||||
label: hit.label
|
label: hit.label
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async getPeriodWindow(pay_year: number, period_no: number) {
|
async getPeriodWindow(pay_year: number, period_no: number) {
|
||||||
return this.prisma.payPeriods.findFirst({
|
return this.prisma.payPeriods.findFirst({
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,64 @@
|
||||||
|
// import { Controller, Param, Query, Body, Get, Post, ParseIntPipe, Delete, Patch, Req } from "@nestjs/common";
|
||||||
|
// import { RolesAllowed } from "src/common/decorators/roles.decorators";
|
||||||
|
// 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 { 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 { 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";
|
||||||
|
|
||||||
|
// @Controller('schedule-presets')
|
||||||
|
// @RolesAllowed(...GLOBAL_CONTROLLER_ROLES)
|
||||||
|
// export class SchedulePresetsController {
|
||||||
|
// constructor(
|
||||||
|
// private readonly upsertService: SchedulePresetsUpsertService,
|
||||||
|
// private readonly getService: SchedulePresetsGetService,
|
||||||
|
// private readonly applyPresetsService: SchedulePresetsApplyService,
|
||||||
|
// ) { }
|
||||||
|
|
||||||
|
// //used to create a schedule preset
|
||||||
|
// @Post('create')
|
||||||
|
// @RolesAllowed(...MANAGER_ROLES)
|
||||||
|
// async createPreset(@Req() req, @Body() dto: SchedulePresetsDto) {
|
||||||
|
// const email = req.user?.email;
|
||||||
|
// return await this.upsertService.createPreset(email, dto);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // //used to update an already existing schedule preset
|
||||||
|
// // @Patch('update/:preset_id')
|
||||||
|
// // @RolesAllowed(...MANAGER_ROLES)
|
||||||
|
// // async updatePreset(
|
||||||
|
// // @Param('preset_id', ParseIntPipe) preset_id: number,
|
||||||
|
// // @Body() dto: SchedulePresetsUpdateDto
|
||||||
|
// // ) {
|
||||||
|
// // return await this.upsertService.updatePreset(preset_id, dto);
|
||||||
|
// // }
|
||||||
|
|
||||||
|
// //used to delete a schedule preset
|
||||||
|
// @Delete('delete/:preset_id')
|
||||||
|
// @RolesAllowed(...MANAGER_ROLES)
|
||||||
|
// async deletePreset(
|
||||||
|
// @Param('preset_id', ParseIntPipe) preset_id: number) {
|
||||||
|
// return await this.upsertService.deletePreset(preset_id);
|
||||||
|
// }
|
||||||
|
|
||||||
|
|
||||||
|
// //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 apply a preset to a timesheet
|
||||||
|
// @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);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { ArrayMinSize, IsArray, IsBoolean, IsInt, IsOptional, IsString } from "class-validator";
|
import { ArrayMinSize, IsArray, IsBoolean, IsInt, IsOptional, IsString } from "class-validator";
|
||||||
import { SchedulePresetShiftsDto } from "src/time-and-attendance/time-tracker/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 {
|
||||||
|
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
|
||||||
|
import { Module } from "@nestjs/common";
|
||||||
|
// import { SchedulePresetsController } from "src/time-and-attendance/time-tracker/schedule-presets/controller/schedule-presets.controller";
|
||||||
|
// import { SchedulePresetsApplyService } from "src/time-and-attendance/time-tracker/schedule-presets/services/schedule-presets-apply.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";
|
||||||
|
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
controllers: [/*SchedulePresetsController*/],
|
||||||
|
// providers: [
|
||||||
|
// SchedulePresetsUpsertService,
|
||||||
|
// SchedulePresetsGetService,
|
||||||
|
// SchedulePresetsApplyService,
|
||||||
|
// ],
|
||||||
|
exports:[
|
||||||
|
// SchedulePresetsUpsertService,
|
||||||
|
// SchedulePresetsGetService,
|
||||||
|
// SchedulePresetsApplyService,
|
||||||
|
],
|
||||||
|
}) export class SchedulePresetsModule {}
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { Injectable, BadRequestException, NotFoundException, ConflictException } from "@nestjs/common";
|
import { Injectable } from "@nestjs/common";
|
||||||
import { Weekday, Prisma } from "@prisma/client";
|
import { Weekday, Prisma } from "@prisma/client";
|
||||||
import { DATE_ISO_FORMAT, WEEKDAY } from "src/common/utils/constants.utils";
|
import { DATE_ISO_FORMAT, WEEKDAY } from "src/common/utils/constants.utils";
|
||||||
import { PrismaService } from "src/prisma/prisma.service";
|
import { PrismaService } from "src/prisma/prisma.service";
|
||||||
|
|
@ -0,0 +1,227 @@
|
||||||
|
// 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};
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
import { Body, Controller, Delete, Param, Patch, Post, Req } from "@nestjs/common";
|
import { Body, Controller, Delete, Param, Patch, Post, Req } from "@nestjs/common";
|
||||||
import { ShiftDto } from "src/time-and-attendance/time-tracker/shifts/dtos/shift-create.dto";
|
|
||||||
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";
|
import { GLOBAL_CONTROLLER_ROLES } from "src/common/shared/role-groupes";
|
||||||
import { Result } from "src/common/errors/result-error.factory";
|
import { Result } from "src/common/errors/result-error.factory";
|
||||||
import { ShiftsCreateService } from "src/time-and-attendance/time-tracker/shifts/services/shifts-create.service";
|
import { ShiftDto } from "src/time-and-attendance/shifts/dtos/shift-create.dto";
|
||||||
import { ShiftsUpdateDeleteService } from "src/time-and-attendance/time-tracker/shifts/services/shifts-update-delete.service";
|
import { ShiftsCreateService } from "src/time-and-attendance/shifts/services/shifts-create.service";
|
||||||
|
import { ShiftsUpdateDeleteService } from "src/time-and-attendance/shifts/services/shifts-update-delete.service";
|
||||||
|
|
||||||
|
|
||||||
@Controller('shift')
|
@Controller('shift')
|
||||||
|
|
@ -4,9 +4,9 @@ import { timesheet_select } from "src/time-and-attendance/utils/selects.utils";
|
||||||
import { BankCodesResolver } from "src/common/mappers/bank-type-id.mapper";
|
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 { ShiftDto } from "src/time-and-attendance/time-tracker/shifts/dtos/shift-create.dto";
|
|
||||||
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, toHHmmFromString } from "src/common/utils/date-utils";
|
||||||
|
import { ShiftDto } from "src/time-and-attendance/shifts/dtos/shift-create.dto";
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ShiftsCreateService {
|
export class ShiftsCreateService {
|
||||||
|
|
@ -63,26 +63,14 @@ export class ShiftsCreateService {
|
||||||
try {
|
try {
|
||||||
//transform string format to date and HHmm
|
//transform string format to date and HHmm
|
||||||
const normed_shift = await this.normalizeShiftDto(dto);
|
const normed_shift = await this.normalizeShiftDto(dto);
|
||||||
if(!normed_shift.success) return { success: false, error: 'An error occured during normalization' }
|
if (!normed_shift.success) return { success: false, error: normed_shift.error };
|
||||||
if (normed_shift.data.end_time <= normed_shift.data.start_time) return {
|
if (normed_shift.data.end_time <= normed_shift.data.start_time) return { success: false, error: `INVALID_SHIFT_TIME` };
|
||||||
success: false,
|
|
||||||
error: `INVALID_SHIFT - `
|
|
||||||
+ `start_time: ${toStringFromHHmm(normed_shift.data.start_time)},`
|
|
||||||
+ `end_time: ${toStringFromHHmm(normed_shift.data.end_time)},`
|
|
||||||
+ `date: ${toStringFromDate(normed_shift.data.date)}.`
|
|
||||||
}
|
|
||||||
//fetch the right timesheet
|
//fetch the right timesheet
|
||||||
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 },
|
||||||
select: timesheet_select,
|
select: timesheet_select,
|
||||||
});
|
});
|
||||||
if (!timesheet) return {
|
if (!timesheet) return { success: false, error: `INVALID_TIMESHEET` };
|
||||||
success: false,
|
|
||||||
error: `INVALID_TIMESHEET -`
|
|
||||||
+ `start_time: ${toStringFromHHmm(normed_shift.data.start_time)},`
|
|
||||||
+ `end_time: ${toStringFromHHmm(normed_shift.data.end_time)},`
|
|
||||||
+ `date: ${toStringFromDate(normed_shift.data.date)}.`
|
|
||||||
}
|
|
||||||
//finds bank_code_id using the type
|
//finds bank_code_id using the type
|
||||||
const bank_code_id = await this.typeResolver.findBankCodeIDByType(dto.type);
|
const bank_code_id = await this.typeResolver.findBankCodeIDByType(dto.type);
|
||||||
if (!bank_code_id.success) return { success: false, error: bank_code_id.error };
|
if (!bank_code_id.success) return { success: false, error: bank_code_id.error };
|
||||||
|
|
@ -102,13 +90,7 @@ export class ShiftsCreateService {
|
||||||
{ start: existing_start, end: existing_end, date: existing_date },
|
{ start: existing_start, end: existing_end, date: existing_date },
|
||||||
);
|
);
|
||||||
if (has_overlap) {
|
if (has_overlap) {
|
||||||
return {
|
return { success: false, error: `SHIFT_OVERLAP` };
|
||||||
success: false,
|
|
||||||
error: `SHIFT_OVERLAP`
|
|
||||||
+ `new shift: ${toStringFromHHmm(normed_shift.data.start_time)}–${toStringFromHHmm(normed_shift.data.end_time)} `
|
|
||||||
+ `existing shift: ${toStringFromHHmm(existing.start_time)}–${toStringFromHHmm(existing.end_time)} `
|
|
||||||
+ `date: ${toStringFromDate(normed_shift.data.date)})`,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -139,7 +121,7 @@ export class ShiftsCreateService {
|
||||||
}
|
}
|
||||||
return { success: true, data: shift };
|
return { success: true, data: shift };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return { success: false, error: `An error occured during creation, invalid data` };
|
return { success: false, error: `INVALID_SHIFT` };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -149,12 +131,13 @@ export class ShiftsCreateService {
|
||||||
//converts all string hours and date to Date and HHmm formats
|
//converts all string hours and date to Date and HHmm formats
|
||||||
private normalizeShiftDto = async (dto: ShiftDto): Promise<Result<Normalized, string>> => {
|
private normalizeShiftDto = async (dto: ShiftDto): Promise<Result<Normalized, string>> => {
|
||||||
const bank_code_id = await this.typeResolver.findBankCodeIDByType(dto.type);
|
const bank_code_id = await this.typeResolver.findBankCodeIDByType(dto.type);
|
||||||
if(!bank_code_id.success) return { success: false, error: 'Bank_code not found'}
|
if (!bank_code_id.success) return { success: false, error: 'INVALID_SHIFT' }
|
||||||
|
|
||||||
|
//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 = toHHmmFromString(dto.start_time);
|
||||||
const end_time = toHHmmFromString(dto.end_time);
|
const end_time = toHHmmFromString(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 } };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -3,10 +3,10 @@ import { PrismaService } from "src/prisma/prisma.service";
|
||||||
import { shift_select } from "src/time-and-attendance/utils/selects.utils";
|
import { shift_select } from "src/time-and-attendance/utils/selects.utils";
|
||||||
import { Injectable } from "@nestjs/common";
|
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 { ShiftDto } from "src/time-and-attendance/time-tracker/shifts/dtos/shift-create.dto";
|
|
||||||
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, toHHmmFromString, overlaps } from "src/common/utils/date-utils";
|
||||||
|
import { ShiftDto } from "src/time-and-attendance/shifts/dtos/shift-create.dto";
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ShiftsUpdateDeleteService {
|
export class ShiftsUpdateDeleteService {
|
||||||
|
|
@ -67,18 +67,12 @@ export class ShiftsUpdateDeleteService {
|
||||||
where: { id: dto.id, timesheet_id: timesheet.data.id },
|
where: { id: dto.id, timesheet_id: timesheet.data.id },
|
||||||
select: shift_select,
|
select: shift_select,
|
||||||
});
|
});
|
||||||
if (!original) return { success: false, error: `Shift with id: ${dto.id} not found` };
|
if (!original) return { success: false, error: `SHIFT_NOT_FOUND` };
|
||||||
|
|
||||||
//transform string format to date and HHmm
|
//transform string format to date and HHmm
|
||||||
const normed_shift = await this.normalizeShiftDto(dto);
|
const normed_shift = await this.normalizeShiftDto(dto);
|
||||||
if (!normed_shift.success) return { success: false, error: 'An error occured during normalization' }
|
if (!normed_shift.success) return { success: false, error: normed_shift.error }
|
||||||
if (normed_shift.data.end_time <= normed_shift.data.start_time) return {
|
if (normed_shift.data.end_time <= normed_shift.data.start_time) return { success: false, error: `INVALID_SHIFT` };
|
||||||
success: false,
|
|
||||||
error: `INVALID_SHIFT - `
|
|
||||||
+ `start_time: ${toStringFromHHmm(normed_shift.data.start_time)},`
|
|
||||||
+ `end_time: ${toStringFromHHmm(normed_shift.data.end_time)},`
|
|
||||||
+ `date: ${toStringFromDate(normed_shift.data.date)}.`
|
|
||||||
};
|
|
||||||
|
|
||||||
//finds bank_code_id using the type
|
//finds bank_code_id using the type
|
||||||
const bank_code = await this.typeResolver.findBankCodeIDByType(dto.type);
|
const bank_code = await this.typeResolver.findBankCodeIDByType(dto.type);
|
||||||
|
|
@ -98,7 +92,7 @@ export class ShiftsUpdateDeleteService {
|
||||||
},
|
},
|
||||||
select: shift_select,
|
select: shift_select,
|
||||||
});
|
});
|
||||||
if (!updated) return { success: false, error: ' An error occured during update, Invalid Datas' };
|
if (!updated) return { success: false, error: 'INVALID_SHIFT' };
|
||||||
|
|
||||||
// builds an object to return for display in the frontend
|
// builds an object to return for display in the frontend
|
||||||
const shift: ShiftDto = {
|
const shift: ShiftDto = {
|
||||||
|
|
@ -115,7 +109,7 @@ export class ShiftsUpdateDeleteService {
|
||||||
return { success: true, data: shift };
|
return { success: true, data: shift };
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return { success: false, error: `An error occured during update. Invalid Data` };
|
return { success: false, error: `INVALID_SHIFT` };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -131,13 +125,13 @@ export class ShiftsUpdateDeleteService {
|
||||||
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) return { success: false, error: `shift with id ${shift_id} not found ` };
|
if (!shift) return { success: false, error: `SHIFT_NOT_FOUND` };
|
||||||
|
|
||||||
await tx.shifts.delete({ where: { id: shift_id } });
|
await tx.shifts.delete({ where: { id: shift_id } });
|
||||||
return { success: true, data: shift.id };
|
return { success: true, data: shift.id };
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return { success: false, error: `INVALID_SHIFT, shift with id ${shift_id} not found` }
|
return { success: false, error: `SHIFT_NOT_FOUND` }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -146,7 +140,7 @@ export class ShiftsUpdateDeleteService {
|
||||||
//_________________________________________________________________
|
//_________________________________________________________________
|
||||||
private normalizeShiftDto = async (dto: ShiftDto): Promise<Result<Normalized, string>> => {
|
private normalizeShiftDto = async (dto: ShiftDto): Promise<Result<Normalized, string>> => {
|
||||||
const bank_code_id = await this.typeResolver.findBankCodeIDByType(dto.type);
|
const bank_code_id = await this.typeResolver.findBankCodeIDByType(dto.type);
|
||||||
if (!bank_code_id.success) return { success: false, error: 'Bank_code not found' }
|
if (!bank_code_id.success) return { success: false, error: 'INVALID_SHIFT' }
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
data: {
|
data: {
|
||||||
|
|
@ -169,15 +163,7 @@ export class ShiftsUpdateDeleteService {
|
||||||
{ start: toHHmmFromString(shift_a.start_time), end: toHHmmFromString(shift_a.end_time) },
|
{ start: toHHmmFromString(shift_a.start_time), end: toHHmmFromString(shift_a.end_time) },
|
||||||
{ start: toHHmmFromString(shift_b.start_time), end: toHHmmFromString(shift_b.end_time) },
|
{ start: toHHmmFromString(shift_b.start_time), end: toHHmmFromString(shift_b.end_time) },
|
||||||
);
|
);
|
||||||
if (has_overlap) {
|
if (has_overlap) return { success: false, error: `SHIFT_OVERLAP` };
|
||||||
return {
|
|
||||||
success: false,
|
|
||||||
error: `SHIFT_OVERLAP`
|
|
||||||
+ `new shift: ${shift_a.start_time}–${shift_a.end_time} `
|
|
||||||
+ `existing shift: ${shift_b.start_time}–${shift_b.end_time} `
|
|
||||||
+ `date: ${shift_a.date})`,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return { success: true, data: undefined }
|
return { success: true, data: undefined }
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
|
|
||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
import { ShiftController } from 'src/time-and-attendance/time-tracker/shifts/controllers/shift.controller';
|
import { ShiftController } from 'src/time-and-attendance/shifts/controllers/shift.controller';
|
||||||
import { ShiftsCreateService } from 'src/time-and-attendance/time-tracker/shifts/services/shifts-create.service';
|
import { ShiftsCreateService } from 'src/time-and-attendance/shifts/services/shifts-create.service';
|
||||||
import { ShiftsUpdateDeleteService } from 'src/time-and-attendance/time-tracker/shifts/services/shifts-update-delete.service';
|
import { ShiftsUpdateDeleteService } from 'src/time-and-attendance/shifts/services/shifts-update-delete.service';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
controllers: [ShiftController],
|
controllers: [ShiftController],
|
||||||
|
|
@ -4,18 +4,11 @@ import { BusinessLogicsModule } from "src/time-and-attendance/domains/business-l
|
||||||
import { ExpenseController } from "src/time-and-attendance/expenses/controllers/expense.controller";
|
import { ExpenseController } from "src/time-and-attendance/expenses/controllers/expense.controller";
|
||||||
import { ExpenseUpsertService } from "src/time-and-attendance/expenses/services/expense-upsert.service";
|
import { ExpenseUpsertService } from "src/time-and-attendance/expenses/services/expense-upsert.service";
|
||||||
import { PayperiodsModule } from "src/time-and-attendance/pay-period/pay-periods.module";
|
import { PayperiodsModule } from "src/time-and-attendance/pay-period/pay-periods.module";
|
||||||
import { SchedulePresetsController } from "src/time-and-attendance/time-tracker/schedule-presets/controller/schedule-presets.controller";
|
|
||||||
import { SchedulePresetsApplyService } from "src/time-and-attendance/time-tracker/schedule-presets/services/schedule-presets-apply.service";
|
import { TimesheetController } from "src/time-and-attendance/timesheets/controllers/timesheet.controller";
|
||||||
import { SchedulePresetsGetService } from "src/time-and-attendance/time-tracker/schedule-presets/services/schedule-presets-get.service";
|
import { TimesheetApprovalService } from "src/time-and-attendance/timesheets/services/timesheet-approval.service";
|
||||||
import { SchedulePresetsUpsertService } from "src/time-and-attendance/time-tracker/schedule-presets/services/schedule-presets-upsert.service";
|
import { GetTimesheetsOverviewService } from "src/time-and-attendance/timesheets/services/timesheet-get-overview.service";
|
||||||
import { ShiftController } from "src/time-and-attendance/time-tracker/shifts/controllers/shift.controller";
|
import { TimesheetsModule } from "src/time-and-attendance/timesheets/timesheets.module";
|
||||||
import { ShiftsCreateService } from "src/time-and-attendance/time-tracker/shifts/services/shifts-create.service";
|
|
||||||
import { ShiftsGetService } from "src/time-and-attendance/time-tracker/shifts/services/shifts-get.service";
|
|
||||||
import { ShiftsUpdateDeleteService } from "src/time-and-attendance/time-tracker/shifts/services/shifts-update-delete.service";
|
|
||||||
import { TimesheetController } from "src/time-and-attendance/time-tracker/timesheets/controllers/timesheet.controller";
|
|
||||||
import { TimesheetApprovalService } from "src/time-and-attendance/time-tracker/timesheets/services/timesheet-approval.service";
|
|
||||||
import { GetTimesheetsOverviewService } from "src/time-and-attendance/time-tracker/timesheets/services/timesheet-get-overview.service";
|
|
||||||
import { TimesheetsModule } from "src/time-and-attendance/time-tracker/timesheets/timesheets.module";
|
|
||||||
import { BankCodesResolver } from "src/common/mappers/bank-type-id.mapper";
|
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 { EmployeeTimesheetResolver } from "src/common/mappers/timesheet.mapper";
|
import { EmployeeTimesheetResolver } from "src/common/mappers/timesheet.mapper";
|
||||||
|
|
@ -26,6 +19,12 @@ import { PayPeriodsCommandService } from "src/time-and-attendance/pay-period/ser
|
||||||
import { CsvExportModule } from "src/modules/exports/csv-exports.module";
|
import { CsvExportModule } from "src/modules/exports/csv-exports.module";
|
||||||
import { CsvExportService } from "src/modules/exports/services/csv-exports.service";
|
import { CsvExportService } from "src/modules/exports/services/csv-exports.service";
|
||||||
import { CsvExportController } from "src/modules/exports/controllers/csv-exports.controller";
|
import { CsvExportController } from "src/modules/exports/controllers/csv-exports.controller";
|
||||||
|
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 { ShiftController } from "src/time-and-attendance/shifts/controllers/shift.controller";
|
||||||
|
import { ShiftsCreateService } from "src/time-and-attendance/shifts/services/shifts-create.service";
|
||||||
|
import { ShiftsGetService } from "src/time-and-attendance/shifts/services/shifts-get.service";
|
||||||
|
import { ShiftsUpdateDeleteService } from "src/time-and-attendance/shifts/services/shifts-update-delete.service";
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
|
|
@ -39,7 +38,7 @@ import { CsvExportController } from "src/modules/exports/controllers/csv-exports
|
||||||
controllers: [
|
controllers: [
|
||||||
TimesheetController,
|
TimesheetController,
|
||||||
ShiftController,
|
ShiftController,
|
||||||
SchedulePresetsController,
|
// SchedulePresetsController,
|
||||||
ExpenseController,
|
ExpenseController,
|
||||||
PayPeriodsController,
|
PayPeriodsController,
|
||||||
CsvExportController,
|
CsvExportController,
|
||||||
|
|
@ -51,7 +50,7 @@ import { CsvExportController } from "src/modules/exports/controllers/csv-exports
|
||||||
ShiftsCreateService,
|
ShiftsCreateService,
|
||||||
ShiftsUpdateDeleteService,
|
ShiftsUpdateDeleteService,
|
||||||
ExpenseUpsertService,
|
ExpenseUpsertService,
|
||||||
SchedulePresetsUpsertService,
|
// SchedulePresetsUpsertService,
|
||||||
SchedulePresetsGetService,
|
SchedulePresetsGetService,
|
||||||
SchedulePresetsApplyService,
|
SchedulePresetsApplyService,
|
||||||
EmailToIdResolver,
|
EmailToIdResolver,
|
||||||
|
|
|
||||||
|
|
@ -1,64 +0,0 @@
|
||||||
import { Controller, Param, Query, Body, Get, Post, ParseIntPipe, Delete, Patch, Req } from "@nestjs/common";
|
|
||||||
import { RolesAllowed } from "src/common/decorators/roles.decorators";
|
|
||||||
import { SchedulePresetsDto } from "src/time-and-attendance/time-tracker/schedule-presets/dtos/create-schedule-presets.dto";
|
|
||||||
import { SchedulePresetsUpdateDto } from "src/time-and-attendance/time-tracker/schedule-presets/dtos/update-schedule-presets.dto";
|
|
||||||
import { SchedulePresetsApplyService } from "src/time-and-attendance/time-tracker/schedule-presets/services/schedule-presets-apply.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 { GLOBAL_CONTROLLER_ROLES, MANAGER_ROLES } from "src/common/shared/role-groupes";
|
|
||||||
|
|
||||||
@Controller('schedule-presets')
|
|
||||||
@RolesAllowed(...GLOBAL_CONTROLLER_ROLES)
|
|
||||||
export class SchedulePresetsController {
|
|
||||||
constructor(
|
|
||||||
private readonly upsertService: SchedulePresetsUpsertService,
|
|
||||||
private readonly getService: SchedulePresetsGetService,
|
|
||||||
private readonly applyPresetsService: SchedulePresetsApplyService,
|
|
||||||
) { }
|
|
||||||
|
|
||||||
//used to create a schedule preset
|
|
||||||
@Post('create')
|
|
||||||
@RolesAllowed(...MANAGER_ROLES)
|
|
||||||
async createPreset(@Req() req, @Body() dto: SchedulePresetsDto) {
|
|
||||||
const email = req.user?.email;
|
|
||||||
return await this.upsertService.createPreset(email, dto);
|
|
||||||
}
|
|
||||||
|
|
||||||
//used to update an already existing schedule preset
|
|
||||||
@Patch('update/:preset_id')
|
|
||||||
@RolesAllowed(...MANAGER_ROLES)
|
|
||||||
async updatePreset(
|
|
||||||
@Param('preset_id', ParseIntPipe) preset_id: number,
|
|
||||||
@Body() dto: SchedulePresetsUpdateDto
|
|
||||||
) {
|
|
||||||
return await this.upsertService.updatePreset(preset_id, dto);
|
|
||||||
}
|
|
||||||
|
|
||||||
//used to delete a schedule preset
|
|
||||||
@Delete('delete/:preset_id')
|
|
||||||
@RolesAllowed(...MANAGER_ROLES)
|
|
||||||
async deletePreset(
|
|
||||||
@Param('preset_id', ParseIntPipe) preset_id: number) {
|
|
||||||
return await this.upsertService.deletePreset(preset_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
//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 apply a preset to a timesheet
|
|
||||||
@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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
import { SchedulePresetsDto } from "src/time-and-attendance/time-tracker/schedule-presets/dtos/create-schedule-presets.dto";
|
|
||||||
|
|
||||||
|
|
||||||
export class SchedulePresetsUpdateDto extends SchedulePresetsDto{}
|
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
|
|
||||||
import { Module } from "@nestjs/common";
|
|
||||||
import { SchedulePresetsController } from "src/time-and-attendance/time-tracker/schedule-presets/controller/schedule-presets.controller";
|
|
||||||
import { SchedulePresetsApplyService } from "src/time-and-attendance/time-tracker/schedule-presets/services/schedule-presets-apply.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";
|
|
||||||
|
|
||||||
|
|
||||||
@Module({
|
|
||||||
controllers: [SchedulePresetsController],
|
|
||||||
providers: [
|
|
||||||
SchedulePresetsUpsertService,
|
|
||||||
SchedulePresetsGetService,
|
|
||||||
SchedulePresetsApplyService,
|
|
||||||
],
|
|
||||||
exports:[
|
|
||||||
SchedulePresetsUpsertService,
|
|
||||||
SchedulePresetsGetService,
|
|
||||||
SchedulePresetsApplyService,
|
|
||||||
],
|
|
||||||
}) export class SchedulePresetsModule {}
|
|
||||||
|
|
@ -1,223 +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 { SchedulePresetsDto } from "src/time-and-attendance/time-tracker/schedule-presets/dtos/create-schedule-presets.dto";
|
|
||||||
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";
|
|
||||||
|
|
||||||
@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) throw new NotFoundException(`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)) {
|
|
||||||
throw new ConflictException(`Duplicate shift for day/order (${shift.week_day}, ${shift.sort_order})`);
|
|
||||||
}
|
|
||||||
pair_set.add(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
const items: Prisma.SchedulePresetShiftsCreateWithoutPresetInput[] = dto.preset_shifts.map((shift) => {
|
|
||||||
const bank_code_id = bank_code_set.get(shift.type);
|
|
||||||
if (!bank_code_id) throw new NotFoundException(`Bank code not found for type ${shift.type}`);
|
|
||||||
if (!shift.start_time || !shift.end_time) {
|
|
||||||
throw new BadRequestException(`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()) {
|
|
||||||
throw new ConflictException(`end_time must be > start_time ( day: ${shift.week_day}, order: ${shift.sort_order})`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
week_day: shift.week_day as Weekday,
|
|
||||||
sort_order: shift.sort_order,
|
|
||||||
bank_code: { connect: { id: bank_code_id } },
|
|
||||||
start_time: start,
|
|
||||||
end_time: end,
|
|
||||||
is_remote: !!shift.is_remote,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
return { success: true, data: items };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
export class GetShiftDto {
|
|
||||||
shift_id: number;
|
|
||||||
timesheet_id: number;
|
|
||||||
type: string;
|
|
||||||
date: string;
|
|
||||||
start_time: string;
|
|
||||||
end_time: string;
|
|
||||||
is_remote: boolean;
|
|
||||||
is_approved: boolean;
|
|
||||||
comment?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
@ -1,67 +0,0 @@
|
||||||
export class TimesheetEntity {
|
|
||||||
id: number;
|
|
||||||
employee_id: number;
|
|
||||||
start_date: Date;
|
|
||||||
is_approved: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class Timesheets {
|
|
||||||
employee_fullname: string;
|
|
||||||
timesheets: Timesheet[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export class Timesheet {
|
|
||||||
timesheet_id: number;
|
|
||||||
is_approved: boolean;
|
|
||||||
days: TimesheetDay[];
|
|
||||||
weekly_hours: TotalHours;
|
|
||||||
weekly_expenses: TotalExpenses;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class TimesheetDay {
|
|
||||||
date: string;
|
|
||||||
shifts: Shift[];
|
|
||||||
expenses: Expense[];
|
|
||||||
daily_hours: TotalHours;
|
|
||||||
daily_expenses: TotalExpenses;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class TotalHours {
|
|
||||||
regular: number;
|
|
||||||
evening: number;
|
|
||||||
emergency: number;
|
|
||||||
overtime: number;
|
|
||||||
vacation: number;
|
|
||||||
holiday: number;
|
|
||||||
sick: number;
|
|
||||||
}
|
|
||||||
export class TotalExpenses {
|
|
||||||
expenses: number;
|
|
||||||
per_diem: number;
|
|
||||||
on_call: number;
|
|
||||||
mileage: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class Shift {
|
|
||||||
timesheet_id: number;
|
|
||||||
date: string;
|
|
||||||
start_time: string;
|
|
||||||
end_time: string;
|
|
||||||
type: string;
|
|
||||||
is_remote: boolean;
|
|
||||||
is_approved: boolean;
|
|
||||||
shift_id?: number | null;
|
|
||||||
comment?: string | null;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class Expense {
|
|
||||||
date: string;
|
|
||||||
is_approved: boolean;
|
|
||||||
type: string;
|
|
||||||
comment: string;
|
|
||||||
amount?: number;
|
|
||||||
mileage?: number;
|
|
||||||
attachment?: string;
|
|
||||||
id?: number | null;
|
|
||||||
supervisor_comment?: string | null;
|
|
||||||
}
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { Body, Controller, Get, Param, ParseBoolPipe, ParseIntPipe, Patch, Query, Req, UnauthorizedException } from "@nestjs/common";
|
import { Body, Controller, Get, Param, 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/timesheets/services/timesheet-get-overview.service";
|
||||||
import { TimesheetApprovalService } from "src/time-and-attendance/time-tracker/timesheets/services/timesheet-approval.service";
|
import { TimesheetApprovalService } from "src/time-and-attendance/timesheets/services/timesheet-approval.service";
|
||||||
import { GLOBAL_CONTROLLER_ROLES, MANAGER_ROLES } from "src/common/shared/role-groupes";
|
import { GLOBAL_CONTROLLER_ROLES, MANAGER_ROLES } from "src/common/shared/role-groupes";
|
||||||
|
|
||||||
|
|
||||||
70
src/time-and-attendance/timesheets/dtos/timesheet.dto.ts
Normal file
70
src/time-and-attendance/timesheets/dtos/timesheet.dto.ts
Normal file
|
|
@ -0,0 +1,70 @@
|
||||||
|
import { Type } from "class-transformer";
|
||||||
|
import { IsBoolean, IsDate, IsInt, IsOptional, IsString } from "class-validator";
|
||||||
|
|
||||||
|
export class TimesheetEntity {
|
||||||
|
@IsInt() id: number;
|
||||||
|
@IsInt() employee_id: number;
|
||||||
|
@IsDate() start_date: Date;
|
||||||
|
@IsBoolean() is_approved: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Timesheets {
|
||||||
|
@IsString() employee_fullname: string;
|
||||||
|
@Type(() => Timesheet) timesheets: Timesheet[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Timesheet {
|
||||||
|
@IsInt() timesheet_id: number;
|
||||||
|
@IsBoolean() is_approved: boolean;
|
||||||
|
@Type(() => TimesheetDay) days: TimesheetDay[];
|
||||||
|
@Type(() => TotalHours) weekly_hours: TotalHours[];
|
||||||
|
@Type(() => TotalExpenses) weekly_expenses: TotalExpenses[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export class TimesheetDay {
|
||||||
|
@IsString() date: string;
|
||||||
|
@Type(() => Shift) shifts: Shift[];
|
||||||
|
@Type(() => Expense) expenses: Expense[];
|
||||||
|
@Type(() => TotalHours) daily_hours: TotalHours[];
|
||||||
|
@Type(() => TotalExpenses) daily_expenses: TotalExpenses[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export class TotalHours {
|
||||||
|
@Type(() => Number) regular: number;
|
||||||
|
@Type(() => Number) evening: number;
|
||||||
|
@Type(() => Number) emergency: number;
|
||||||
|
@Type(() => Number) overtime: number;
|
||||||
|
@Type(() => Number) vacation: number;
|
||||||
|
@Type(() => Number) holiday: number;
|
||||||
|
@Type(() => Number) sick: number;
|
||||||
|
}
|
||||||
|
export class TotalExpenses {
|
||||||
|
@Type(() => Number) expenses: number;
|
||||||
|
@Type(() => Number) per_diem: number;
|
||||||
|
@Type(() => Number) on_call: number;
|
||||||
|
@Type(() => Number) mileage: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Shift {
|
||||||
|
@IsInt() timesheet_id: number;
|
||||||
|
@IsString() date: string;
|
||||||
|
@IsString() start_time: string;
|
||||||
|
@IsString() end_time: string;
|
||||||
|
@IsString() type: string;
|
||||||
|
@IsBoolean() is_remote: boolean;
|
||||||
|
@IsBoolean() is_approved: boolean;
|
||||||
|
@IsInt() @IsOptional() shift_id?: number | null;
|
||||||
|
@IsString() @IsOptional() comment?: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Expense {
|
||||||
|
@IsString() date: string;
|
||||||
|
@IsBoolean() is_approved: boolean;
|
||||||
|
@IsString() type: string;
|
||||||
|
@IsString() comment: string;
|
||||||
|
@Type(() => Number) @IsOptional() amount?: number;
|
||||||
|
@Type(() => Number) @IsOptional() mileage?: number;
|
||||||
|
@IsString() @IsOptional() attachment?: string;
|
||||||
|
@IsOptional() @IsInt() id?: number | null;
|
||||||
|
@IsString() @IsOptional() supervisor_comment?: string | null;
|
||||||
|
}
|
||||||
|
|
@ -3,7 +3,7 @@ import { NUMBER_OF_TIMESHEETS_TO_RETURN } from "src/common/utils/constants.utils
|
||||||
import { Injectable } from "@nestjs/common";
|
import { Injectable } from "@nestjs/common";
|
||||||
import { PrismaService } from "src/prisma/prisma.service";
|
import { PrismaService } from "src/prisma/prisma.service";
|
||||||
import { EmailToIdResolver } from "src/common/mappers/email-id.mapper";
|
import { EmailToIdResolver } from "src/common/mappers/email-id.mapper";
|
||||||
import { Timesheet, Timesheets } from "src/time-and-attendance/time-tracker/timesheets/dtos/timesheet.dto";
|
import { Timesheet, Timesheets } from "src/time-and-attendance/timesheets/dtos/timesheet.dto";
|
||||||
import { Result } from "src/common/errors/result-error.factory";
|
import { Result } from "src/common/errors/result-error.factory";
|
||||||
import { Prisma } from "@prisma/client";
|
import { Prisma } from "@prisma/client";
|
||||||
import { toDateFromString, sevenDaysFrom, toStringFromDate, toHHmmFromDate } from "src/common/utils/date-utils";
|
import { toDateFromString, sevenDaysFrom, toStringFromDate, toHHmmFromDate } from "src/common/utils/date-utils";
|
||||||
|
|
@ -41,11 +41,11 @@ export class GetTimesheetsOverviewService {
|
||||||
|
|
||||||
//find period using year and period_no
|
//find period using year and period_no
|
||||||
const period = await this.prisma.payPeriods.findFirst({ where: { pay_year, pay_period_no } });
|
const period = await this.prisma.payPeriods.findFirst({ where: { pay_year, pay_period_no } });
|
||||||
if (!period) return { success: false, error: `Pay period ${pay_year}-${pay_period_no} not found` };
|
if (!period) return { success: false, error: `PAY_PERIOD_NOT_FOUND` };
|
||||||
|
|
||||||
//fetch the employee_id using the email
|
//fetch the employee_id using the email
|
||||||
const employee_id = await this.emailResolver.findIdByEmail(account_email);
|
const employee_id = await this.emailResolver.findIdByEmail(account_email);
|
||||||
if (!employee_id.success) return { success: false, error: `employee with email: ${account_email} not found` + employee_id.error }
|
if (!employee_id.success) return { success: false, error: employee_id.error }
|
||||||
|
|
||||||
//loads the timesheets related to the fetched pay-period
|
//loads the timesheets related to the fetched pay-period
|
||||||
let rows = await this.loadTimesheets(employee_id.data, period.period_start, period.period_end);
|
let rows = await this.loadTimesheets(employee_id.data, period.period_start, period.period_end);
|
||||||
|
|
@ -73,7 +73,7 @@ export class GetTimesheetsOverviewService {
|
||||||
where: { id: employee_id.data },
|
where: { id: employee_id.data },
|
||||||
include: { user: true },
|
include: { user: true },
|
||||||
});
|
});
|
||||||
if (!employee) return { success: false, error: `Employee #${employee_id} not found` }
|
if (!employee) return { success: false, error: `EMPLOYEE_NOT_FOUND` }
|
||||||
|
|
||||||
//builds employee full name
|
//builds employee full name
|
||||||
const user = employee.user;
|
const user = employee.user;
|
||||||
|
|
@ -82,11 +82,11 @@ export class GetTimesheetsOverviewService {
|
||||||
|
|
||||||
//maps all timesheet's infos
|
//maps all timesheet's infos
|
||||||
const timesheets = await Promise.all(rows.map((timesheet) => this.mapOneTimesheet(timesheet)));
|
const timesheets = await Promise.all(rows.map((timesheet) => this.mapOneTimesheet(timesheet)));
|
||||||
if (!timesheets) return { success: false, error: 'an error occured during the mapping of a timesheet' }
|
if (!timesheets) return { success: false, error: 'INVALID_TIMESHEET' }
|
||||||
|
|
||||||
return { success: true, data: { employee_fullname, timesheets } };
|
return { success: true, data: { employee_fullname, timesheets } };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return { success: false, error: 'timesheet failed to load: ' + pay_year + pay_period_no }
|
return { success: false, error: 'TIMESHEET_NOT_FOUND' }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
|
|
||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
import { TimesheetController } from 'src/time-and-attendance/time-tracker/timesheets/controllers/timesheet.controller';
|
import { TimesheetController } from 'src/time-and-attendance/timesheets/controllers/timesheet.controller';
|
||||||
import { TimesheetApprovalService } from 'src/time-and-attendance/time-tracker/timesheets/services/timesheet-approval.service';
|
import { TimesheetApprovalService } from 'src/time-and-attendance/timesheets/services/timesheet-approval.service';
|
||||||
import { GetTimesheetsOverviewService } from 'src/time-and-attendance/time-tracker/timesheets/services/timesheet-get-overview.service';
|
import { GetTimesheetsOverviewService } from 'src/time-and-attendance/timesheets/services/timesheet-get-overview.service';
|
||||||
import { EmailToIdResolver } from 'src/common/mappers/email-id.mapper';
|
import { EmailToIdResolver } from 'src/common/mappers/email-id.mapper';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
Loading…
Reference in New Issue
Block a user