narinder1231's picture
improve error logging and error messages
d747060
import { Request, Response } from "express";
import { uploadInvoice } from "../../shared/services/invoice.service";
import { parseInvoice } from "../../shared/services/ai.service";
import FormData from "form-data";
import Invoice from "../../models/invoice";
import InvoiceDetail from "../../models/invoicedetail";
import User from "../../models/users";
import { FindOptions, Op } from "sequelize";
import { logger } from '../../utils/logger';
import ErrorLog from "../../models/errorLog";
import { fetchBuildingsById, fetchPortfolioById, fetchUnitsById, fetchBuildingPropertyManager, fetchGLAccountById } from "../../shared/services/propertyware.service";
import { logInvoiceAction } from "../invoiceActivityLogs.controller";
import InvoiceApproval from "../../models/invoiceApproval";
import Role from "../../models/roles";
import { AuthenticatedRequest } from "shared/interfaces/user.interface";
import { isVendorHasDefaultBillsplitAccount } from "../../controllers/propertyware/vendors.controller";
import InvoiceActivityLog from "../../models/invoiceActivityLogs";
import PwWorkOrders from "../../models/pwWorkOrders";
export const createInvoice = async (req: AuthenticatedRequest, res: Response) => {
const files = req.files as Express.Multer.File[];
if (!files?.length) {
return res.status(400).json({ message: "No files uploaded" });
}
try {
const uploadResults = await uploadInvoice(files);
res.status(201).json({ message: "Files uploaded successfully, processing invoices in the background" });
// Process the rest of the operations in the background
const createInvoicePromises = files.map(async (file, index) => {
try {
const formData = new FormData();
formData.append("file", file.buffer, file.originalname);
// Send the file URLs to the AI service API for parsing
const aiServiceResponse = await parseInvoice(formData);
const aiServiceData = aiServiceResponse[0];
console.log("file name ", file.originalname);
console.log("aiServiceData ", aiServiceData);
// validate if parsed workorderID exists in propertyware workorders
let portfolioId: number | null = null;
let buildingId: number | null = null;
let unitId: number | null = null;
if (aiServiceData.workOrderID) {
try {
const PWWorkorderDetails = await PwWorkOrders.findOne({
where: {
number: aiServiceData.workOrderID,
},
});
if (PWWorkorderDetails) {
portfolioId = PWWorkorderDetails.portfolio_id;
buildingId = PWWorkorderDetails.building_id;
unitId = PWWorkorderDetails.unit_id;
aiServiceData.workOrderID = PWWorkorderDetails.pw_id;
} else {
aiServiceData.workOrderID = null;
}
} catch (error) {
logger.error(error);
aiServiceData.workOrderID = null;
}
} else {
aiServiceData.workOrderID = null;
}
const totalAmount = aiServiceData.billSplits.reduce(
(total: number, split: { amount: number }) => total + split.amount,
0
);
const invoiceDate = new Date(aiServiceData.billDate);
const dueDate = new Date(aiServiceData.dueDate);
// check if vendor has default bill split ID defined in Propertyware
let isDefaultBillsplitAccountSet = null;
if (aiServiceData.vendor_id) {
try {
isDefaultBillsplitAccountSet = await isVendorHasDefaultBillsplitAccount(aiServiceData.vendor_id);
} catch (error) {
logger.error(error);
}
}
let invoiceRecord = {
reference_number: aiServiceData.refNo,
invoice_number: aiServiceData.refNo,
vendor_name: aiServiceData.vendor_name,
pw_vendor_id: aiServiceData.vendor_id,
invoice_date: invoiceDate,
due_date: dueDate,
total: totalAmount,
description: '',
status: "Pending",
amount_paid: 0,
term: aiServiceData.terms,
pw_work_order_id: aiServiceData.workOrderID,
filename: file.originalname,
pdf_url: (await uploadResults[index]).url,
uploaded_by: req.baseUrl === "/private" ? 1 : req.user?.id as number
};
const invoice = await Invoice.create(invoiceRecord);
const invoiceDetailsPromises = aiServiceData.billSplits.map(async (details: { portfolio: any; building: any; unitID: any; glAccount: any; amount: any; description: any; }) => {
return {
invoice_id: invoice.id,
pw_portfolio_id: portfolioId ? portfolioId : (details.portfolio ? details.portfolio : null),
pw_building_id: buildingId ? buildingId : (details.building ? details.building : null),
pw_unit_id: unitId ? unitId : (details.unitID ? details.unitID : null),
pw_gl_account_id: isDefaultBillsplitAccountSet ? isDefaultBillsplitAccountSet : (details.glAccount ? details.glAccount : null),
amount: details.amount,
description: details.description,
};
});
const invoiceDetails = await Promise.all(invoiceDetailsPromises);
await InvoiceDetail.bulkCreate(invoiceDetails);
await logInvoiceAction({ invoice_id: invoice.id as number, user_id: invoice.uploaded_by, activity_type: 'create', field_name: 'invoice', old_value: '', new_value: JSON.stringify(invoice) });
} catch (error) {
if (error instanceof Error) {
let errorMessage = error.message;
if ((error as any)?.parent?.sqlMessage) {
errorMessage = (error as any).parent.sqlMessage;
}
logger.error(`Error creating invoice for file ${file.originalname}`);
logger.error(error);
// Add entry to error log
const errorLogData = { error_type: 'Invoice Create Failed', error_details: `Error creating invoice for file ${file.originalname}: ${errorMessage}` };
await ErrorLog.create(errorLogData);
} else {
logger.error(`Unknown error creating invoice for file ${file.originalname}`);
logger.error(error);
const errorLogData = { error_type: 'Invoice Create Failed', error_details: `Unknown error creating invoice for file ${file.originalname}: ${JSON.stringify(error)}` };
await ErrorLog.create(errorLogData);
}
}
});
try {
await Promise.all(createInvoicePromises);
logger.info("Invoices processed successfully in the background");
} catch (error) {
if (error instanceof Error) {
logger.error(`Error processing invoices in the background`);
logger.error(error);
const errorLogData = { error_type: 'Invoice Process Failed', error_details: `Error processing invoices in the background: ${error.message}` };
await ErrorLog.create(errorLogData);
} else {
logger.error('Unknown error processing invoices in the background');
logger.error(error);
const errorLogData = { error_type: 'Invoice Process Failed', error_details: `Unknown error processing invoices in the background: ${JSON.stringify(error)}` };
await ErrorLog.create(errorLogData);
}
}
} catch (error) {
if (error instanceof Error) {
logger.error(`Error uploading invoices: ${error.message}`);
logger.error(error);
const errorLogData = { error_type: 'Invoice Upload Failed', error_details: `Error uploading invoices: ${error.message}` };
await ErrorLog.create(errorLogData);
} else {
logger.error('Unknown error uploading invoices', error);
logger.error(error);
const errorLogData = { error_type: 'Invoice Upload Failed', error_details: `Unknown error uploading invoices: ${JSON.stringify(error)}` };
await ErrorLog.create(errorLogData);
}
}
};
const buildInvoiceWhereClause = (filter: Record<string, any>): any => {
const whereClause: any = {};
if (filter) {
if (filter.date) {
const date = new Date(filter.date);
if (!isNaN(date.getTime())) {
const startOfDay = new Date(date);
startOfDay.setHours(0, 0, 0, 0);
const endOfDay = new Date(date);
endOfDay.setHours(23, 59, 59, 999);
whereClause.created_at = {
[Op.gte]: startOfDay,
[Op.lte]: endOfDay
};
}
}
if (filter.id) {
whereClause.id = { [Op.eq]: filter.id };
}
if (filter.filename) {
whereClause.filename = { [Op.like]: `%${filter.filename}%` };
}
if (filter.id) {
whereClause.id = { [Op.eq]: filter.id };
}
if (filter.vendor_name) {
whereClause.vendor_name = { [Op.like]: `%${filter.vendor_name}%` };
}
if (filter.reference_number) {
whereClause.reference_number = { [Op.like]: `%${filter.reference_number}%` };
}
if (filter.invoice_date) {
const date = new Date(filter.invoice_date);
if (!isNaN(date.getTime())) {
whereClause.invoice_date = { [Op.eq]: date };
}
}
if (filter.uploaded_by) {
whereClause.uploaded_by = { [Op.eq]: filter.uploaded_by };
}
if (filter.due_date) {
const date = new Date(filter.due_date);
if (!isNaN(date.getTime())) {
whereClause.due_date = { [Op.eq]: date };
}
}
if (filter.payment_status) {
whereClause.payment_status = { [Op.eq]: filter.payment_status };
}
if (filter.status && filter.status != '*') {
whereClause.status = { [Op.eq]: filter.status };
}
if (filter.pw_work_order_id) {
whereClause.pw_work_order_id = { [Op.eq]: filter.pw_work_order_id };
}
if (filter.created_before) {
const beforeDate = new Date(filter.created_before);
if (!isNaN(beforeDate.getTime())) {
whereClause.created_at = { ...whereClause.created_at, [Op.lte]: beforeDate };
}
}
if (filter.created_after) {
const afterDate = new Date(filter.created_after);
if (!isNaN(afterDate.getTime())) {
whereClause.created_at = { ...whereClause.created_at, [Op.gte]: afterDate };
}
}
if (filter.total) {
whereClause.total = { [Op.eq]: filter.total };
}
}
return whereClause;
};
// Get All Invoices
export const getAllInvoices = async (req: AuthenticatedRequest, res: Response): Promise<Response> => {
try {
const { sort_by, sort_order, page, limit } = req.query;
const filter = req.query.filter as Record<string, any>;
const roleData = await Role.findByPk(req.user?.role_id);
const allowedSortColumns = [
'id',
'invoice_date',
'total',
'amount_paid',
'due_date',
'pw_work_order_id',
'pw_vendor_id',
'uploaded_by',
'created_at',
'reference_number',
'filename',
'status'
];
const whereClause = buildInvoiceWhereClause(filter);
const currentPage = parseInt(page as string) || 1;
const pageSize = parseInt(limit as string) || 10;
const options: FindOptions = {
where: whereClause,
order: []
};
if (sort_by && allowedSortColumns.includes(sort_by as string)) {
options.order = [[sort_by as string, sort_order === 'desc' ? 'DESC' : 'ASC']];
} else {
options.order = [['id', 'DESC']];
}
let invoices: any = await Invoice.findAll({
...options,
include: [
{
model: User,
as: 'uploadedBy',
attributes: { exclude: ['password'] }
},
{
model: InvoiceDetail,
as: "InvoiceDetails",
},
],
paranoid: false,
});
if (roleData?.name === "Property Manager") {
const propertyManagerEmail = req?.user?.email;
invoices = await Promise.all(
invoices.map(async (invoice: any) => {
const invoiceDetails = invoice.InvoiceDetails || [];
for (const detail of invoiceDetails) {
if (detail.pw_building_id) {
const buildingManagers = await fetchBuildingPropertyManager(detail.pw_building_id);
if (buildingManagers.some((manager: any) => manager.email === propertyManagerEmail)) {
return invoice;
}
}
// TODO: Check with client if there is any possibility of having different Building in same invoice. and if so, will they have same PM assigned ?
// For now assuming it will be same, checking associated PM for first bill split item location only.
break;
}
return null;
})
);
invoices = invoices.filter((invoice: any) => invoice !== null);
} else if (roleData?.name === "Accountant") {
const accountantId = req.user?.id;
let filteredInvoices = invoices.filter((invoice: any) => {
const invoiceDetails = invoice.InvoiceDetails || [];
return !invoiceDetails.some((detail: any) => {
return detail.pw_building_id || detail.pw_unit_id || detail.pw_portfolio_id;
});
});
const updatedInvoices = await InvoiceActivityLog.findAll({
where: {
user_id: accountantId
},
attributes: ['invoice_id']
});
const updatedInvoiceIds = updatedInvoices.map(log => log.invoice_id);
const invoicesUpdatedByAccountant = invoices.filter((invoice: any) =>
updatedInvoiceIds.includes(invoice.id)
);
invoices = [...filteredInvoices, ...invoicesUpdatedByAccountant];
}
const totalInvoices = invoices.length;
let paginatedInvoices;
if (currentPage === -1) {
paginatedInvoices = invoices;
} else {
paginatedInvoices = invoices.slice((currentPage - 1) * pageSize, currentPage * pageSize);
}
const responseData = {
page: currentPage === -1 ? 1 : currentPage,
limit: pageSize,
total: totalInvoices,
data: paginatedInvoices
};
return res.status(200).json(responseData);
} catch (error) {
logger.error("Error fetching invoices:");
logger.error(error);
return res.status(500).json({ error: "Error fetching invoices" });
}
};
export const getInvoiceById = async (req: AuthenticatedRequest, res: Response): Promise<Response> => {
try {
const { id } = req.params;
const invoice = await Invoice.findOne({
where: { id: id }, include: [
{ model: InvoiceDetail }
]
});
if (!invoice) {
return res.status(404).json({ error: "Invoice not found" });
}
const invoiceDetailsPromises = invoice.InvoiceDetails.map(async (invoiceDetails) => {
return {
amount: invoiceDetails.amount,
expenseAccount: invoiceDetails.pw_gl_account_id,
location: { portfolioId: invoiceDetails.pw_portfolio_id, buildingId: invoiceDetails.pw_building_id, unitId: invoiceDetails.pw_unit_id },
description: invoiceDetails.description || ''
};
});
const billSplit = await Promise.all(invoiceDetailsPromises);
let showApprove = true;
if (invoice.status === 'Approved') {
showApprove = false;
} else {
const user = req.user;
const roleData = await Role.findByPk(user?.role_id);
if (roleData) {
if (roleData.name === 'Property Manager') {
if (invoice.status === 'PM Approved') {
showApprove = false;
}
}
if (roleData.name === "Accountant") {
showApprove = false;
}
}
}
return res.status(200).json({ invoice: invoice.dataValues, billSplit: billSplit, showApprove: showApprove });
} catch (error) {
logger.error("Error fetching invoice:");
logger.error(error);
return res.status(500).json({ error: "Error fetching invoice details" });
}
};
const updateInvoiceDetails = async (invoiceId: number, billSplit: any[], userId: number) => {
const existingInvoiceDetails = await InvoiceDetail.findAll({ where: { invoice_id: invoiceId } });
// Delete existing InvoiceDetails
await InvoiceDetail.destroy({ where: { invoice_id: invoiceId } });
// Create new InvoiceDetails
const newInvoiceDetails = billSplit.map((detail: any) => ({
invoice_id: invoiceId,
pw_portfolio_id: detail.location.portfolioId,
pw_building_id: detail.location.buildingId,
pw_unit_id: detail.location.unitId,
pw_gl_account_id: detail.expenseAccount,
amount: detail.amount,
description: detail.description,
}));
const invoiceDetails = await InvoiceDetail.bulkCreate(newInvoiceDetails);
for (let i = 0; i < newInvoiceDetails.length; i++) {
const newDetail: any = newInvoiceDetails[i];
const existingDetail: any = existingInvoiceDetails[i] || {};
if (!existingDetail.id) {
const newPortfolioName = newDetail.pw_portfolio_id
? (await fetchPortfolioById(newDetail.pw_portfolio_id))?.name || ''
: 'null';
const newBuildingName = newDetail.pw_building_id
? (await fetchBuildingsById(newDetail.pw_building_id))?.name || ''
: 'null';
const newUnitName = newDetail.pw_unit_id
? (await fetchUnitsById(newDetail.pw_unit_id))?.name || ''
: 'null';
const newValue = `${newPortfolioName}, ${newBuildingName}, ${newUnitName}`;
await logInvoiceAction({
invoice_id: invoiceId,
user_id: userId,
activity_type: 'create',
field_name: 'bills split',
old_value: "null",
new_value: newValue,
});
} else {
if (existingDetail.pw_portfolio_id !== newDetail.pw_portfolio_id) {
const oldPortfolioName = existingDetail.pw_portfolio_id
? (await fetchPortfolioById(existingDetail.pw_portfolio_id))?.name || 'Unknown Portfolio'
: 'null';
const newPortfolioName = newDetail.pw_portfolio_id
? (await fetchPortfolioById(newDetail.pw_portfolio_id))?.name || ''
: 'null';
if (oldPortfolioName !== newPortfolioName) {
await logInvoiceAction({
invoice_id: invoiceId,
user_id: userId,
activity_type: 'update bill split',
field_name: 'portfolio',
old_value: oldPortfolioName,
new_value: newPortfolioName,
});
}
}
if (existingDetail.pw_building_id !== newDetail.pw_building_id) {
const oldBuildingName = existingDetail.pw_building_id
? (await fetchBuildingsById(existingDetail.pw_building_id))?.name || 'Unknown Building'
: 'null';
const newBuildingName = newDetail.pw_building_id
? (await fetchBuildingsById(newDetail.pw_building_id))?.name || ''
: 'null';
if (oldBuildingName !== newBuildingName) {
await logInvoiceAction({
invoice_id: invoiceId,
user_id: userId,
activity_type: 'update bill split',
field_name: 'building',
old_value: oldBuildingName,
new_value: newBuildingName,
});
}
}
if (existingDetail.pw_unit_id !== newDetail.pw_unit_id) {
const oldUnitName = existingDetail.pw_unit_id
? (await fetchUnitsById(existingDetail.pw_unit_id))?.name || 'Unknown Unit'
: 'null';
const newUnitName = newDetail.pw_unit_id
? (await fetchUnitsById(newDetail.pw_unit_id))?.name || ''
: 'null';
if (oldUnitName !== newUnitName) {
await logInvoiceAction({
invoice_id: invoiceId,
user_id: userId,
activity_type: 'update bill split',
field_name: 'unit',
old_value: oldUnitName,
new_value: newUnitName,
});
}
}
if (existingDetail.pw_gl_account_id !== newDetail.pw_gl_account_id) {
const oldGlAccountName = existingDetail.pw_gl_account_id
? (await fetchGLAccountById(existingDetail.pw_gl_account_id))?.name || 'Unknown GL Account'
: 'null';
const newGlAccountName = newDetail.pw_gl_account_id
? (await fetchGLAccountById(newDetail.pw_gl_account_id))?.name || ''
: 'null';
if (oldGlAccountName !== newGlAccountName) {
await logInvoiceAction({
invoice_id: invoiceId,
user_id: userId,
activity_type: 'update bill split',
field_name: 'gl account',
old_value: oldGlAccountName,
new_value: newGlAccountName,
});
}
}
if (newDetail.amount !== existingDetail.amount) {
await logInvoiceAction({
invoice_id: invoiceId,
user_id: userId,
activity_type: 'update bill split',
field_name: 'amount',
old_value: String(existingDetail.amount || 0),
new_value: String(newDetail.amount || 0),
});
}
if (newDetail.description !== existingDetail.description) {
await logInvoiceAction({
invoice_id: invoiceId,
user_id: userId,
activity_type: 'update bill split',
field_name: 'description',
old_value: existingDetail.description || '',
new_value: newDetail.description || '',
});
}
}
}
return invoiceDetails;
};
export const updateInvoiceData = async (invoiceId: number, updatedInvoiceData: any, userId: number) => {
const invoice = await Invoice.findByPk(invoiceId);
if (!invoice) {
throw new Error('Invoice not found');
}
const originalInvoiceData: Record<string, unknown> = { ...invoice.dataValues };
for (const key in updatedInvoiceData) {
if (originalInvoiceData[key] !== updatedInvoiceData[key]) {
await logInvoiceAction({ invoice_id: invoiceId, user_id: userId, activity_type: 'update', field_name: key, old_value: originalInvoiceData[key] as string, new_value: updatedInvoiceData[key] as string });
}
}
return await invoice.update(updatedInvoiceData);
};
export const updateInvoice = async (req: Request & { user: { id: number } }, res: Response): Promise<Response> => {
const { id } = req.params;
const { invoice, billSplit } = req.body;
const userId = req.user?.id;
if (!userId) {
return res.status(401).json({ error: 'Unauthorized: User ID is missing' });
}
try {
const existingInvoice = await Invoice.findByPk(id);
if (!existingInvoice) {
return res.status(404).json({ error: 'Invoice not found' });
}
// Prepare updated invoice data
const updatedInvoiceData = {
reference_number: invoice.reference_number,
invoice_number: invoice.invoice_number,
vendor_name: invoice.vendor_name,
pw_vendor_id: invoice.pw_vendor_id,
invoice_date: new Date(invoice.invoice_date),
due_date: new Date(invoice.due_date),
total: invoice.total,
description: invoice.description,
status: 'pending', // change status to pending whenever details are updated
amount_paid: invoice.amount_paid,
term: invoice.term,
pw_work_order_id: invoice.pw_work_order_id,
filename: invoice.filename,
pdf_url: invoice.pdf_url,
};
await updateInvoiceData(existingInvoice.id as number, updatedInvoiceData, userId);
await updateInvoiceDetails(existingInvoice.id as number, billSplit, userId);
const updatedInvoice = await Invoice.findByPk(id, {
include: [{ model: InvoiceDetail }],
});
return res.status(200).json({ invoice: updatedInvoice, message: 'Invoice updated successfully.' });
} catch (error) {
logger.error(error);
logger.error('Error updating invoice:', error);
return res.status(500).json({ error: 'Internal server error' });
}
};
// Delete an invoice
export const deleteInvoice = async (req: Request, res: Response): Promise<Response> => {
try {
const { id } = req.params;
const invoice = await Invoice.findByPk(id);
if (!invoice) {
return res.status(404).json({ error: "Invoice not found" });
}
if (invoice.status === "sync success") {
return res
.status(400)
.json({ error: "Invoice has already been synced and cannot be modified" });
}
invoice.status = "archived";
await invoice.save();
await invoice.destroy();
return res.status(204).send();
} catch (error) {
logger.error("Error deleting invoice:");
logger.error(error);
return res.status(500).json({ error: "Internal server error" });
}
};
// Function to approve invoice
export const approveInvoice = async (req: AuthenticatedRequest, res: Response): Promise<void> => {
const { id } = req.params;
const user = req.user;
const { comment } = req.body;
try {
const invoice = await Invoice.findOne({
where: { id: id },
include: [{ model: InvoiceDetail }]
});
if (!invoice) {
res.status(404).json({ error: 'Invoice not found' });
return;
}
const missingPwPortfolioId = invoice.InvoiceDetails.some(
(detail: InvoiceDetail) => !detail.pw_portfolio_id
);
if (missingPwPortfolioId) {
res.status(400).json({ error: 'Invoice cannot be approved without property address' });
return;
}
let roleData;
if (user && typeof user !== 'string') {
const userData = await User.findByPk(user?.id, {
include: [{ model: Role, as: 'role' }],
});
if (!userData) {
res.status(400).json({ error: 'Invalid User' });
return;
}
roleData = await Role.findByPk(userData.role_id);
if (!roleData) {
res.status(400).json({ error: 'Invalid approval role ID' });
return;
}
if (roleData.name === "Admin") {
await approveAndCreateRecord(invoice.id as number, userData.id, roleData.id, comment);
invoice.status = 'Approved';
await invoice.save();
res.status(200).json({ message: 'Invoice approved' });
} else if (invoice.total < 1500) {
if (roleData.name === 'Property Manager' || roleData.name === 'Accounting Supervisor') {
await approveAndCreateRecord(invoice.id as number, userData.id, roleData.id, comment);
invoice.status = 'Approved';
await invoice.save();
res.status(200).json({ message: 'Invoice approved' });
} else {
res.status(403).json({ error: 'Only Property Manager or Accounting Supervisor can approve this invoice' });
}
} else {
if (roleData.name === 'Property Manager') {
await approveAndCreateRecord(invoice.id as number, userData.id, roleData.id, comment);
invoice.status = 'PM Approved';
await invoice.save();
res.status(200).json({ message: 'Invoice approved by PM' });
} else if (roleData.name === 'Accounting Supervisor' && invoice.status === 'PM Approved') {
await approveAndCreateRecord(invoice.id as number, userData.id, roleData.id, comment);
invoice.status = 'Approved';
await invoice.save();
res.status(200).json({ message: 'Invoice approved by Accounting Supervisor' });
} else {
res.status(403).json({ error: 'Invoice needs to be approved by Property Manager first' });
}
}
} else {
res.status(400).json({ error: 'Invalid User' });
return;
}
} catch (error) {
logger.error(error);
res.status(500).json({ error: error });
}
};
export const disapproveInvoice = async (req: AuthenticatedRequest, res: Response): Promise<void> => {
const { id } = req.params;
const user = req.user;
try {
const invoice = await Invoice.findOne({
where: { id: id },
include: [{ model: InvoiceDetail }]
});
if (!invoice) {
res.status(404).json({ error: 'Invoice not found' });
return;
}
if (invoice.status === 'sync success') {
res.status(400).json({ error: 'Invoice has already been synced and cannot be modified' });
return;
}
if (user && typeof user !== 'string') {
const userData = await User.findByPk(user?.id, {
include: [{ model: Role, as: 'role' }],
});
if (!userData) {
res.status(400).json({ error: 'Invalid User' });
return;
}
const roleData = await Role.findByPk(userData.role_id);
if (!roleData) {
res.status(400).json({ error: 'Invalid approval role ID' });
return;
}
if (roleData.name === "Admin" || roleData.name === 'Property Manager') {
invoice.status = 'pending';
await invoice.save();
await logInvoiceAction({
invoice_id: id as unknown as number,
user_id: req.user?.id as number,
activity_type: 'approval',
field_name: 'status',
old_value: 'approve',
new_value: 'pending',
});
res.status(200).json({ message: 'Invoice approval reverted' });
} else {
res.status(403).json({ error: 'Only Admin or Property Manager can revert invoice approval' });
}
} else {
res.status(400).json({ error: 'Invalid User' });
}
} catch (error) {
logger.error(error);
res.status(500).json({ error: 'An error occurred while processing the request' });
}
};
const approveAndCreateRecord = async (
invoiceId: number,
userId: number,
approvalRoleId: number,
comment: string
): Promise<void> => {
await InvoiceApproval.create({
invoice_id: invoiceId,
approved_by: userId,
approval_role_id: approvalRoleId,
comment,
created_at: new Date()
});
};