fix(expense): Fix issue where expenses were not properly calculating amount
Also add better permission handling when creating shifts or expenses for other users through timesheet-approval module
This commit is contained in:
parent
6df3aa944d
commit
91fe8843b3
|
|
@ -23,11 +23,24 @@ export class ExpenseCreateService {
|
||||||
//_________________________________________________________________
|
//_________________________________________________________________
|
||||||
async createExpense(
|
async createExpense(
|
||||||
dto: ExpenseDto,
|
dto: ExpenseDto,
|
||||||
email: string,
|
requesterEmail: string,
|
||||||
employee_email?: string
|
targetEmail?: string
|
||||||
): Promise<Result<ExpenseDto, string>> {
|
): Promise<Result<ExpenseDto, string>> {
|
||||||
try {
|
try {
|
||||||
const accountEmail = employee_email ?? email;
|
// check if requester has access to timesheet_approval
|
||||||
|
if (!!targetEmail && requesterEmail !== targetEmail) {
|
||||||
|
const requester = await this.prisma.users.findFirst({
|
||||||
|
where: { email: requesterEmail, },
|
||||||
|
select: { user_module_access: true, }
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!requester) return { success: false, error: 'INVALID_USER' }
|
||||||
|
if (!requester.user_module_access?.timesheets_approval)
|
||||||
|
return { success: false, error: 'ACCESS_DENIED' }
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
const accountEmail = targetEmail ?? requesterEmail;
|
||||||
|
|
||||||
//fetch employee_id using req.user.email
|
//fetch employee_id using req.user.email
|
||||||
const employee_id = await this.emailResolver.findIdByEmail(accountEmail);
|
const employee_id = await this.emailResolver.findIdByEmail(accountEmail);
|
||||||
|
|
@ -77,6 +90,7 @@ export class ExpenseCreateService {
|
||||||
employee_email: accountEmail,
|
employee_email: accountEmail,
|
||||||
event_type: 'expense',
|
event_type: 'expense',
|
||||||
action: 'create',
|
action: 'create',
|
||||||
|
date: created.date,
|
||||||
});
|
});
|
||||||
|
|
||||||
return { success: true, data: created };
|
return { success: true, data: created };
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import { Injectable } from "@nestjs/common";
|
||||||
import { PrismaPostgresService } from "prisma/postgres/prisma-postgres.service";
|
import { PrismaPostgresService } from "prisma/postgres/prisma-postgres.service";
|
||||||
import { Result } from "src/common/errors/result-error.factory";
|
import { Result } from "src/common/errors/result-error.factory";
|
||||||
import { EmailToIdResolver } from "src/common/mappers/email-id.mapper";
|
import { EmailToIdResolver } from "src/common/mappers/email-id.mapper";
|
||||||
|
import { toStringFromDate } from "src/common/utils/date-utils";
|
||||||
import { PayPeriodEventService } from "src/time-and-attendance/pay-period/services/pay-period-event.service";
|
import { PayPeriodEventService } from "src/time-and-attendance/pay-period/services/pay-period-event.service";
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
|
|
@ -25,6 +26,7 @@ export class ExpenseDeleteService {
|
||||||
const expense = await this.prisma.expenses.findUnique({
|
const expense = await this.prisma.expenses.findUnique({
|
||||||
where: { id: expense_id },
|
where: { id: expense_id },
|
||||||
select: {
|
select: {
|
||||||
|
date: true,
|
||||||
timesheet: {
|
timesheet: {
|
||||||
select: {
|
select: {
|
||||||
employee_id: true,
|
employee_id: true,
|
||||||
|
|
@ -52,6 +54,7 @@ export class ExpenseDeleteService {
|
||||||
employee_email: email,
|
employee_email: email,
|
||||||
event_type: 'expense',
|
event_type: 'expense',
|
||||||
action: 'delete',
|
action: 'delete',
|
||||||
|
date: toStringFromDate(expense.date)
|
||||||
});
|
});
|
||||||
|
|
||||||
return { success: true, data: expense_id };
|
return { success: true, data: expense_id };
|
||||||
|
|
|
||||||
|
|
@ -20,38 +20,51 @@ export class ExpenseUpdateService {
|
||||||
|
|
||||||
async updateExpense(
|
async updateExpense(
|
||||||
dto: ExpenseDto,
|
dto: ExpenseDto,
|
||||||
email: string,
|
requesterEmail: string,
|
||||||
employee_email?: string
|
targetEmail?: string
|
||||||
): Promise<Result<ExpenseDto, string>> {
|
): Promise<Result<ExpenseDto, string>> {
|
||||||
try {
|
try {
|
||||||
const account_email = employee_email ?? email;
|
// check if requester has access to timesheet_approval
|
||||||
//fetch employee_id using req.user.email
|
if (!!targetEmail && requesterEmail !== targetEmail) {
|
||||||
const employee_id = await this.emailResolver.findIdByEmail(account_email);
|
const requester = await this.prisma.users.findFirst({
|
||||||
|
where: { email: requesterEmail, },
|
||||||
|
select: { user_module_access: true, }
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!requester) return { success: false, error: 'INVALID_USER' }
|
||||||
|
if (!requester.user_module_access?.timesheets_approval)
|
||||||
|
return { success: false, error: 'ACCESS_DENIED' }
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
const accountEmail = targetEmail ?? requesterEmail;
|
||||||
|
|
||||||
|
const employee_id = await this.emailResolver.findIdByEmail(accountEmail);
|
||||||
if (!employee_id.success) return { success: false, error: employee_id.error };
|
if (!employee_id.success) return { success: false, error: employee_id.error };
|
||||||
//normalize string , date format and parse numbers
|
|
||||||
const normed_expense = await normalizeAndParseExpenseDto(dto);
|
const normed_expense = await normalizeAndParseExpenseDto(dto);
|
||||||
if (!normed_expense.success) return { success: false, error: normed_expense.error }
|
if (!normed_expense.success) return { success: false, error: normed_expense.error }
|
||||||
|
|
||||||
const type = await this.typeResolver.findBankCodeIDByType(dto.type);
|
const type = await this.typeResolver.findBankCodeIDByType(dto.type);
|
||||||
if (!type.success) return { success: false, error: 'INVALID_EXPENSE_TYPE' }
|
if (!type.success) return { success: false, error: 'INVALID_EXPENSE_TYPE' }
|
||||||
//added timesheet_id modification check according to the new date
|
|
||||||
const new_timesheet_start_date = weekStartSunday(toDateFromString(dto.date));
|
const new_timesheet_start_date = weekStartSunday(toDateFromString(dto.date));
|
||||||
|
|
||||||
const timesheet = await this.prisma.timesheets.findFirst({
|
const timesheet = await this.prisma.timesheets.findFirst({
|
||||||
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_NOT_FOUND` }
|
if (!timesheet) return { success: false, error: `TIMESHEET_NOT_FOUND` }
|
||||||
|
|
||||||
//checks for modifications
|
|
||||||
const data = {
|
const data = {
|
||||||
...normed_expense.data,
|
...normed_expense.data,
|
||||||
bank_code_id: type.data,
|
bank_code_id: type.data,
|
||||||
is_approved: dto.is_approved,
|
is_approved: dto.is_approved,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!data) return { success: false, error: `INVALID_EXPENSE` }
|
if (!data) return { success: false, error: `INVALID_EXPENSE` }
|
||||||
|
|
||||||
//push updates and get updated datas
|
|
||||||
const expense = await this.prisma.expenses.update({
|
const expense = await this.prisma.expenses.update({
|
||||||
where: { id: dto.id, timesheet_id: timesheet.id },
|
where: { id: dto.id, timesheet_id: timesheet.id },
|
||||||
data,
|
data,
|
||||||
|
|
@ -59,7 +72,6 @@ export class ExpenseUpdateService {
|
||||||
});
|
});
|
||||||
if (!expense) return { success: false, error: 'INVALID_EXPENSE' }
|
if (!expense) return { success: false, error: 'INVALID_EXPENSE' }
|
||||||
|
|
||||||
//build an object to return to the frontend
|
|
||||||
const updated: ExpenseDto = {
|
const updated: ExpenseDto = {
|
||||||
...expense,
|
...expense,
|
||||||
type: expense.bank_code.type,
|
type: expense.bank_code.type,
|
||||||
|
|
@ -70,12 +82,13 @@ export class ExpenseUpdateService {
|
||||||
};
|
};
|
||||||
|
|
||||||
// notify timesheet-approval observers of changes, but only if it came
|
// notify timesheet-approval observers of changes, but only if it came
|
||||||
// from timesheet and not timesheet-approval (no employee_email)
|
// from timesheet and not timesheet-approval (no targetEmail)
|
||||||
if (!employee_email) {
|
if (!targetEmail) {
|
||||||
this.payPeriodEventService.emit({
|
this.payPeriodEventService.emit({
|
||||||
employee_email: email,
|
employee_email: accountEmail,
|
||||||
event_type: 'expense',
|
event_type: 'expense',
|
||||||
action: 'update',
|
action: 'update',
|
||||||
|
date: updated.date,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -79,7 +79,7 @@ export class CsvExportService {
|
||||||
const week = computeWeekNumber(start, shift.date);
|
const week = computeWeekNumber(start, shift.date);
|
||||||
const type_transaction = shift.bank_code.bank_code.charAt(0);
|
const type_transaction = shift.bank_code.bank_code.charAt(0);
|
||||||
const code = Number(shift.bank_code.bank_code.slice(1,));
|
const code = Number(shift.bank_code.bank_code.slice(1,));
|
||||||
const isPTO = PTO_SHIFT_CODES.includes(shift.bank_code.bank_code)
|
const isPTO = PTO_SHIFT_CODES.includes(shift.bank_code.bank_code);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
timesheet_id: shift.timesheet.id,
|
timesheet_id: shift.timesheet.id,
|
||||||
|
|
@ -118,6 +118,8 @@ export class CsvExportService {
|
||||||
const type_transaction = expense.bank_code.bank_code.charAt(0);
|
const type_transaction = expense.bank_code.bank_code.charAt(0);
|
||||||
const code = Number(expense.bank_code.bank_code.slice(1,))
|
const code = Number(expense.bank_code.bank_code.slice(1,))
|
||||||
const week = computeWeekNumber(start, expense.date);
|
const week = computeWeekNumber(start, expense.date);
|
||||||
|
const amount = expense.bank_code.bank_code === 'G503' ? expense.mileage : expense.amount;
|
||||||
|
const adjustedAmount = Number(amount) * expense.bank_code.modifier;
|
||||||
|
|
||||||
rows.push({
|
rows.push({
|
||||||
timesheet_id: expense.timesheet.id,
|
timesheet_id: expense.timesheet.id,
|
||||||
|
|
@ -129,7 +131,7 @@ export class CsvExportService {
|
||||||
code: code,
|
code: code,
|
||||||
quantite_hre: undefined,
|
quantite_hre: undefined,
|
||||||
taux_horaire: undefined,
|
taux_horaire: undefined,
|
||||||
montant: Number(expense.amount),
|
montant: adjustedAmount,
|
||||||
semaine_no: week,
|
semaine_no: week,
|
||||||
division_no: undefined,
|
division_no: undefined,
|
||||||
service_no: undefined,
|
service_no: undefined,
|
||||||
|
|
|
||||||
|
|
@ -2,4 +2,5 @@ export class PayPeriodEvent {
|
||||||
employee_email: string;
|
employee_email: string;
|
||||||
event_type: 'expense' | 'shift' | 'preset';
|
event_type: 'expense' | 'shift' | 'preset';
|
||||||
action: 'create' | 'update' | 'delete';
|
action: 'create' | 'update' | 'delete';
|
||||||
|
date: string;
|
||||||
}
|
}
|
||||||
|
|
@ -87,6 +87,7 @@ export class SchedulePresetsApplyService {
|
||||||
employee_email: user_email,
|
employee_email: user_email,
|
||||||
event_type: 'preset',
|
event_type: 'preset',
|
||||||
action: 'create',
|
action: 'create',
|
||||||
|
date: created_shifts[0].date
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -142,6 +143,7 @@ export class SchedulePresetsApplyService {
|
||||||
employee_email: user_email,
|
employee_email: user_email,
|
||||||
event_type: 'preset',
|
event_type: 'preset',
|
||||||
action: 'create',
|
action: 'create',
|
||||||
|
date
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -65,7 +65,8 @@ export class ShiftsCreateService {
|
||||||
this.payPeriodEventService.emit({
|
this.payPeriodEventService.emit({
|
||||||
employee_email: email,
|
employee_email: email,
|
||||||
event_type: 'shift',
|
event_type: 'shift',
|
||||||
action: 'create'
|
action: 'create',
|
||||||
|
date: shifts[0].date,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import { EmailToIdResolver } from "src/common/mappers/email-id.mapper";
|
||||||
import { PrismaPostgresService } from "prisma/postgres/prisma-postgres.service";
|
import { PrismaPostgresService } from "prisma/postgres/prisma-postgres.service";
|
||||||
import { PaidTimeOffBankHoursService } from "src/time-and-attendance/paid-time-off/paid-time-off.service";
|
import { PaidTimeOffBankHoursService } from "src/time-and-attendance/paid-time-off/paid-time-off.service";
|
||||||
import { PayPeriodEventService } from "src/time-and-attendance/pay-period/services/pay-period-event.service";
|
import { PayPeriodEventService } from "src/time-and-attendance/pay-period/services/pay-period-event.service";
|
||||||
|
import { toStringFromDate } from "src/common/utils/date-utils";
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ShiftsDeleteService {
|
export class ShiftsDeleteService {
|
||||||
|
|
@ -72,7 +73,8 @@ export class ShiftsDeleteService {
|
||||||
this.payPeriodEventService.emit({
|
this.payPeriodEventService.emit({
|
||||||
employee_email: email,
|
employee_email: email,
|
||||||
event_type: 'shift',
|
event_type: 'shift',
|
||||||
action: 'delete'
|
action: 'delete',
|
||||||
|
date: toStringFromDate(shift.date),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -63,7 +63,8 @@ export class ShiftsUpdateService {
|
||||||
this.payPeriodEventService.emit({
|
this.payPeriodEventService.emit({
|
||||||
employee_email: email,
|
employee_email: email,
|
||||||
event_type: 'shift',
|
event_type: 'shift',
|
||||||
action: 'update'
|
action: 'update',
|
||||||
|
date: shifts[0].date
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -106,7 +106,8 @@ export const select_csv_shift_lines = {
|
||||||
export const select_csv_expense_lines = {
|
export const select_csv_expense_lines = {
|
||||||
date: true,
|
date: true,
|
||||||
amount: true,
|
amount: true,
|
||||||
bank_code: { select: { bank_code: true } },
|
mileage: true,
|
||||||
|
bank_code: { select: { bank_code: true, modifier: true } },
|
||||||
timesheet: {
|
timesheet: {
|
||||||
select: {
|
select: {
|
||||||
id: true,
|
id: true,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user