Spaces:
Runtime error
Runtime error
Bansari Akhani commited on
Commit ·
178b3d8
1
Parent(s): 1c7eff7
invoice approval
Browse files- src/controllers/invoice/invoice.controller.ts +61 -58
- src/middlewares/authMiddleware.ts +8 -4
- src/models/invoiceApproval.ts +8 -8
- src/routes/invoice.routes.ts +24 -24
- src/shared/interfaces/invoice.interface.ts +6 -0
- src/shared/interfaces/invoiceApproval.interface.ts +4 -4
- src/shared/interfaces/user.interface.ts +6 -0
src/controllers/invoice/invoice.controller.ts
CHANGED
|
@@ -16,6 +16,8 @@ import { fetchWorkorderById } from "../../shared/services/propertyware.service";
|
|
| 16 |
import { logInvoiceAction } from "../invoiceActivityLogs.controller";
|
| 17 |
import InvoiceApproval from "../../models/invoiceApproval";
|
| 18 |
import Role from "../../models/roles";
|
|
|
|
|
|
|
| 19 |
|
| 20 |
export const createInvoice = async (req: Request, res: Response) => {
|
| 21 |
const files = req.files as Express.Multer.File[];
|
|
@@ -445,9 +447,9 @@ export const updateInvoice = async (req: Request, res: Response): Promise<Respon
|
|
| 445 |
uploaded_by: invoice.uploaded_by,
|
| 446 |
};
|
| 447 |
|
| 448 |
-
|
| 449 |
|
| 450 |
-
|
| 451 |
|
| 452 |
const updatedInvoice = await Invoice.findByPk(id, {
|
| 453 |
include: [{ model: InvoiceDetail }],
|
|
@@ -479,23 +481,18 @@ export const deleteInvoice = async (req: Request, res: Response): Promise<Respon
|
|
| 479 |
}
|
| 480 |
};
|
| 481 |
|
| 482 |
-
// Interface for approval data
|
| 483 |
-
interface ApprovalData {
|
| 484 |
-
invoiceId: number;
|
| 485 |
-
userId: number;
|
| 486 |
-
comment?: string;
|
| 487 |
-
}
|
| 488 |
-
|
| 489 |
// Function to approve invoice
|
| 490 |
-
export const approveInvoice = async (req:
|
| 491 |
-
const {
|
|
|
|
|
|
|
| 492 |
|
| 493 |
try {
|
| 494 |
const invoice = await Invoice.findOne({
|
| 495 |
-
where: { id:
|
| 496 |
include: [{ model: InvoiceDetail }]
|
| 497 |
});
|
| 498 |
-
|
| 499 |
if (!invoice) {
|
| 500 |
res.status(404).json({ error: 'Invoice not found' });
|
| 501 |
return;
|
|
@@ -509,55 +506,61 @@ export const approveInvoice = async (req: Request, res: Response): Promise<void>
|
|
| 509 |
res.status(400).json({ error: 'Invoice cannot be approved without property address' });
|
| 510 |
return;
|
| 511 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 512 |
|
| 513 |
-
|
| 514 |
-
|
| 515 |
-
|
| 516 |
-
|
| 517 |
-
|
| 518 |
-
return;
|
| 519 |
-
}
|
| 520 |
|
| 521 |
-
|
| 522 |
-
|
| 523 |
-
|
| 524 |
-
return;
|
| 525 |
-
}
|
| 526 |
-
const approvalRoleId = role.id;
|
| 527 |
|
| 528 |
-
|
| 529 |
-
|
| 530 |
-
await approveAndCreateRecord(invoice.id as number, userId, approvalRoleId, comment);
|
| 531 |
-
|
| 532 |
-
invoice.status = 'Approved';
|
| 533 |
-
await invoice.save();
|
| 534 |
-
|
| 535 |
-
res.status(200).json({ message: 'Invoice approved' });
|
| 536 |
-
} else {
|
| 537 |
-
res.status(403).json({ error: 'Only Property Manager or Accounting Supervisor can approve this invoice' });
|
| 538 |
-
}
|
| 539 |
-
} else {
|
| 540 |
-
if (role.name === 'PM') {
|
| 541 |
-
await approveAndCreateRecord(invoice.id as number, userId, approvalRoleId, comment);
|
| 542 |
|
| 543 |
-
|
| 544 |
-
|
| 545 |
-
|
| 546 |
-
|
| 547 |
-
|
| 548 |
-
} else if (role.name === 'Accounting Supervisor' && invoice.status === 'PM Approved') {
|
| 549 |
-
await approveAndCreateRecord(invoice.id as number, userId, approvalRoleId, comment);
|
| 550 |
-
|
| 551 |
-
invoice.status = 'Approved';
|
| 552 |
-
await invoice.save();
|
| 553 |
-
|
| 554 |
-
res.status(200).json({ message: 'Invoice approved by Accounting Supervisor' });
|
| 555 |
-
|
| 556 |
} else {
|
| 557 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 558 |
}
|
|
|
|
|
|
|
|
|
|
| 559 |
}
|
| 560 |
} catch (error) {
|
|
|
|
| 561 |
res.status(500).json({ error: error });
|
| 562 |
}
|
| 563 |
};
|
|
@@ -569,11 +572,11 @@ const approveAndCreateRecord = async (
|
|
| 569 |
comment: string
|
| 570 |
): Promise<void> => {
|
| 571 |
await InvoiceApproval.create({
|
| 572 |
-
|
| 573 |
-
|
| 574 |
-
|
| 575 |
comment,
|
| 576 |
-
|
| 577 |
});
|
| 578 |
};
|
| 579 |
|
|
|
|
| 16 |
import { logInvoiceAction } from "../invoiceActivityLogs.controller";
|
| 17 |
import InvoiceApproval from "../../models/invoiceApproval";
|
| 18 |
import Role from "../../models/roles";
|
| 19 |
+
import { AuthenticatedRequest } from "shared/interfaces/user.interface";
|
| 20 |
+
import { ApprovalData } from "shared/interfaces/invoice.interface";
|
| 21 |
|
| 22 |
export const createInvoice = async (req: Request, res: Response) => {
|
| 23 |
const files = req.files as Express.Multer.File[];
|
|
|
|
| 447 |
uploaded_by: invoice.uploaded_by,
|
| 448 |
};
|
| 449 |
|
| 450 |
+
await updateInvoiceData(existingInvoice.id as number, updatedInvoiceData, userId);
|
| 451 |
|
| 452 |
+
await updateInvoiceDetails(existingInvoice.id as number, billSplit, userId);
|
| 453 |
|
| 454 |
const updatedInvoice = await Invoice.findByPk(id, {
|
| 455 |
include: [{ model: InvoiceDetail }],
|
|
|
|
| 481 |
}
|
| 482 |
};
|
| 483 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 484 |
// Function to approve invoice
|
| 485 |
+
export const approveInvoice = async (req: AuthenticatedRequest, res: Response): Promise<void> => {
|
| 486 |
+
const { id } = req.params;
|
| 487 |
+
const user = req.user;
|
| 488 |
+
const { comment } = req.body;
|
| 489 |
|
| 490 |
try {
|
| 491 |
const invoice = await Invoice.findOne({
|
| 492 |
+
where: { id: id },
|
| 493 |
include: [{ model: InvoiceDetail }]
|
| 494 |
});
|
| 495 |
+
|
| 496 |
if (!invoice) {
|
| 497 |
res.status(404).json({ error: 'Invoice not found' });
|
| 498 |
return;
|
|
|
|
| 506 |
res.status(400).json({ error: 'Invoice cannot be approved without property address' });
|
| 507 |
return;
|
| 508 |
}
|
| 509 |
+
let roleData;
|
| 510 |
+
if (user && typeof user !== 'string') {
|
| 511 |
+
|
| 512 |
+
const userData = await User.findByPk(user?.id, {
|
| 513 |
+
include: [{ model: Role, as: 'role' }],
|
| 514 |
+
});
|
| 515 |
+
if (!userData) {
|
| 516 |
+
res.status(400).json({ error: 'Invalid User' });
|
| 517 |
+
return;
|
| 518 |
+
}
|
| 519 |
|
| 520 |
+
roleData = await Role.findByPk(userData.role_id);
|
| 521 |
+
if (!roleData) {
|
| 522 |
+
res.status(400).json({ error: 'Invalid approval role ID' });
|
| 523 |
+
return;
|
| 524 |
+
}
|
|
|
|
|
|
|
| 525 |
|
| 526 |
+
if (invoice.total < 1500) {
|
| 527 |
+
if (roleData.name === 'Property Manager' || roleData.name === 'Accounting Supervisor') {
|
| 528 |
+
await approveAndCreateRecord(invoice.id as number, userData.id, roleData.id, comment);
|
|
|
|
|
|
|
|
|
|
| 529 |
|
| 530 |
+
invoice.status = 'Approved';
|
| 531 |
+
await invoice.save();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 532 |
|
| 533 |
+
res.status(200).json({ message: 'Invoice approved' });
|
| 534 |
+
} else {
|
| 535 |
+
res.status(403).json({ error: 'Only Property Manager or Accounting Supervisor can approve this invoice' });
|
| 536 |
+
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 537 |
} else {
|
| 538 |
+
if (roleData.name === 'Property Manager') {
|
| 539 |
+
await approveAndCreateRecord(invoice.id as number, userData.id, roleData.id, comment);
|
| 540 |
+
|
| 541 |
+
invoice.status = 'PM Approved';
|
| 542 |
+
await invoice.save();
|
| 543 |
+
|
| 544 |
+
res.status(200).json({ message: 'Invoice approved by PM' });
|
| 545 |
+
|
| 546 |
+
} else if (roleData.name === 'Accounting Supervisor' && invoice.status === 'PM Approved') {
|
| 547 |
+
await approveAndCreateRecord(invoice.id as number, userData.id, roleData.id, comment);
|
| 548 |
+
|
| 549 |
+
invoice.status = 'Approved';
|
| 550 |
+
await invoice.save();
|
| 551 |
+
|
| 552 |
+
res.status(200).json({ message: 'Invoice approved by Accounting Supervisor' });
|
| 553 |
+
|
| 554 |
+
} else {
|
| 555 |
+
res.status(403).json({ error: 'Invoice needs to be approved by Property Manager first' });
|
| 556 |
+
}
|
| 557 |
}
|
| 558 |
+
} else {
|
| 559 |
+
res.status(400).json({ error: 'Invalid User' });
|
| 560 |
+
return;
|
| 561 |
}
|
| 562 |
} catch (error) {
|
| 563 |
+
console.log(error);
|
| 564 |
res.status(500).json({ error: error });
|
| 565 |
}
|
| 566 |
};
|
|
|
|
| 572 |
comment: string
|
| 573 |
): Promise<void> => {
|
| 574 |
await InvoiceApproval.create({
|
| 575 |
+
invoice_id: invoiceId,
|
| 576 |
+
approved_by: userId,
|
| 577 |
+
approval_role_id: approvalRoleId,
|
| 578 |
comment,
|
| 579 |
+
created_at: new Date()
|
| 580 |
});
|
| 581 |
};
|
| 582 |
|
src/middlewares/authMiddleware.ts
CHANGED
|
@@ -1,13 +1,17 @@
|
|
| 1 |
-
import {
|
| 2 |
import { verifyToken } from '../utils/jwtUtils';
|
| 3 |
-
|
|
|
|
|
|
|
| 4 |
const token = req.header('authorization')?.split(' ')[1];
|
| 5 |
if (!token) {
|
| 6 |
return res.status(401).json({ error: 'Unauthorized' });
|
| 7 |
}
|
| 8 |
|
| 9 |
try {
|
| 10 |
-
const
|
|
|
|
|
|
|
| 11 |
next();
|
| 12 |
} catch (error) {
|
| 13 |
return res.status(401).json({ error: 'Unauthorized' });
|
|
@@ -15,4 +19,4 @@ const jwtMiddleware = (req: Request, res: Response, next: NextFunction) => {
|
|
| 15 |
|
| 16 |
}
|
| 17 |
|
| 18 |
-
export {jwtMiddleware}
|
|
|
|
| 1 |
+
import { Response, NextFunction } from 'express';
|
| 2 |
import { verifyToken } from '../utils/jwtUtils';
|
| 3 |
+
import { AuthenticatedRequest } from '../shared/interfaces/user.interface';
|
| 4 |
+
|
| 5 |
+
const jwtMiddleware = (req: AuthenticatedRequest, res: Response, next: NextFunction) => {
|
| 6 |
const token = req.header('authorization')?.split(' ')[1];
|
| 7 |
if (!token) {
|
| 8 |
return res.status(401).json({ error: 'Unauthorized' });
|
| 9 |
}
|
| 10 |
|
| 11 |
try {
|
| 12 |
+
const user = verifyToken(token);
|
| 13 |
+
console.log("Loagged in User ", user);
|
| 14 |
+
req.user = user;
|
| 15 |
next();
|
| 16 |
} catch (error) {
|
| 17 |
return res.status(401).json({ error: 'Unauthorized' });
|
|
|
|
| 19 |
|
| 20 |
}
|
| 21 |
|
| 22 |
+
export {jwtMiddleware}
|
src/models/invoiceApproval.ts
CHANGED
|
@@ -6,11 +6,11 @@ import { sequelize } from './index';
|
|
| 6 |
class InvoiceApproval extends Model<InvoiceApprovalAttributes>
|
| 7 |
implements InvoiceApprovalAttributes {
|
| 8 |
public id?: number;
|
| 9 |
-
public
|
| 10 |
-
public
|
| 11 |
-
public
|
| 12 |
public comment?: string;
|
| 13 |
-
public
|
| 14 |
|
| 15 |
}
|
| 16 |
|
|
@@ -21,7 +21,7 @@ InvoiceApproval.init(
|
|
| 21 |
autoIncrement: true,
|
| 22 |
primaryKey: true,
|
| 23 |
},
|
| 24 |
-
|
| 25 |
type: DataTypes.INTEGER,
|
| 26 |
allowNull: false,
|
| 27 |
references: {
|
|
@@ -31,11 +31,11 @@ InvoiceApproval.init(
|
|
| 31 |
onUpdate: 'CASCADE',
|
| 32 |
onDelete: 'CASCADE',
|
| 33 |
},
|
| 34 |
-
|
| 35 |
type: DataTypes.INTEGER,
|
| 36 |
allowNull: false,
|
| 37 |
},
|
| 38 |
-
|
| 39 |
type: DataTypes.INTEGER,
|
| 40 |
allowNull: false,
|
| 41 |
},
|
|
@@ -43,7 +43,7 @@ InvoiceApproval.init(
|
|
| 43 |
type: DataTypes.TEXT,
|
| 44 |
allowNull: true,
|
| 45 |
},
|
| 46 |
-
|
| 47 |
type: DataTypes.DATE,
|
| 48 |
defaultValue: DataTypes.NOW,
|
| 49 |
},
|
|
|
|
| 6 |
class InvoiceApproval extends Model<InvoiceApprovalAttributes>
|
| 7 |
implements InvoiceApprovalAttributes {
|
| 8 |
public id?: number;
|
| 9 |
+
public invoice_id!: number;
|
| 10 |
+
public approved_by!: number;
|
| 11 |
+
public approval_role_id!: number;
|
| 12 |
public comment?: string;
|
| 13 |
+
public created_at?: Date;
|
| 14 |
|
| 15 |
}
|
| 16 |
|
|
|
|
| 21 |
autoIncrement: true,
|
| 22 |
primaryKey: true,
|
| 23 |
},
|
| 24 |
+
invoice_id: {
|
| 25 |
type: DataTypes.INTEGER,
|
| 26 |
allowNull: false,
|
| 27 |
references: {
|
|
|
|
| 31 |
onUpdate: 'CASCADE',
|
| 32 |
onDelete: 'CASCADE',
|
| 33 |
},
|
| 34 |
+
approved_by: {
|
| 35 |
type: DataTypes.INTEGER,
|
| 36 |
allowNull: false,
|
| 37 |
},
|
| 38 |
+
approval_role_id: {
|
| 39 |
type: DataTypes.INTEGER,
|
| 40 |
allowNull: false,
|
| 41 |
},
|
|
|
|
| 43 |
type: DataTypes.TEXT,
|
| 44 |
allowNull: true,
|
| 45 |
},
|
| 46 |
+
created_at: {
|
| 47 |
type: DataTypes.DATE,
|
| 48 |
defaultValue: DataTypes.NOW,
|
| 49 |
},
|
src/routes/invoice.routes.ts
CHANGED
|
@@ -12,30 +12,6 @@ import {
|
|
| 12 |
} from '../controllers/invoice/invoice.controller';
|
| 13 |
|
| 14 |
const invoiceRouter = express.Router();
|
| 15 |
-
|
| 16 |
-
/**
|
| 17 |
-
* @swagger
|
| 18 |
-
* /api/invoices/{id}/approve:
|
| 19 |
-
* post:
|
| 20 |
-
* summary: Approve Invoice
|
| 21 |
-
* tags: [Invoices]
|
| 22 |
-
* parameters:
|
| 23 |
-
* - in: path
|
| 24 |
-
* name: id
|
| 25 |
-
* schema:
|
| 26 |
-
* type: integer
|
| 27 |
-
* required: true
|
| 28 |
-
* description: Invoice ID
|
| 29 |
-
* responses:
|
| 30 |
-
* 200:
|
| 31 |
-
* description: Invoices approved successfully
|
| 32 |
-
* 400:
|
| 33 |
-
* description: Bad Request
|
| 34 |
-
* 500:
|
| 35 |
-
* description: Error approving invoices
|
| 36 |
-
*/
|
| 37 |
-
invoiceRouter.post("/:id/approve", [], approveInvoice);
|
| 38 |
-
|
| 39 |
invoiceRouter.use(jwtMiddleware);
|
| 40 |
|
| 41 |
/**
|
|
@@ -366,4 +342,28 @@ invoiceRouter.put('/:id', updateInvoice);
|
|
| 366 |
*/
|
| 367 |
invoiceRouter.delete('/:id', deleteInvoice);
|
| 368 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 369 |
export default invoiceRouter;
|
|
|
|
| 12 |
} from '../controllers/invoice/invoice.controller';
|
| 13 |
|
| 14 |
const invoiceRouter = express.Router();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 15 |
invoiceRouter.use(jwtMiddleware);
|
| 16 |
|
| 17 |
/**
|
|
|
|
| 342 |
*/
|
| 343 |
invoiceRouter.delete('/:id', deleteInvoice);
|
| 344 |
|
| 345 |
+
|
| 346 |
+
/**
|
| 347 |
+
* @swagger
|
| 348 |
+
* /api/invoices/{id}/approve:
|
| 349 |
+
* post:
|
| 350 |
+
* summary: Approve Invoice
|
| 351 |
+
* tags: [Invoices]
|
| 352 |
+
* parameters:
|
| 353 |
+
* - in: path
|
| 354 |
+
* name: id
|
| 355 |
+
* schema:
|
| 356 |
+
* type: integer
|
| 357 |
+
* required: true
|
| 358 |
+
* description: Invoice ID
|
| 359 |
+
* responses:
|
| 360 |
+
* 200:
|
| 361 |
+
* description: Invoices approved successfully
|
| 362 |
+
* 400:
|
| 363 |
+
* description: Bad Request
|
| 364 |
+
* 500:
|
| 365 |
+
* description: Error approving invoices
|
| 366 |
+
*/
|
| 367 |
+
invoiceRouter.post("/:id/approve", [], approveInvoice);
|
| 368 |
+
|
| 369 |
export default invoiceRouter;
|
src/shared/interfaces/invoice.interface.ts
CHANGED
|
@@ -17,3 +17,9 @@ export interface InvoiceInterface {
|
|
| 17 |
status: string;
|
| 18 |
uploaded_by: number;
|
| 19 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 17 |
status: string;
|
| 18 |
uploaded_by: number;
|
| 19 |
}
|
| 20 |
+
|
| 21 |
+
export interface ApprovalData {
|
| 22 |
+
invoiceId: string;
|
| 23 |
+
userId: number;
|
| 24 |
+
comment?: string;
|
| 25 |
+
}
|
src/shared/interfaces/invoiceApproval.interface.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
| 1 |
export interface InvoiceApprovalAttributes {
|
| 2 |
id?: number;
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
comment?: string;
|
| 7 |
-
|
| 8 |
}
|
|
|
|
| 1 |
export interface InvoiceApprovalAttributes {
|
| 2 |
id?: number;
|
| 3 |
+
invoice_id: number;
|
| 4 |
+
approved_by: number;
|
| 5 |
+
approval_role_id: number;
|
| 6 |
comment?: string;
|
| 7 |
+
created_at?: Date;
|
| 8 |
}
|
src/shared/interfaces/user.interface.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
|
|
|
|
|
| 1 |
export interface UserInterface {
|
| 2 |
id?: number;
|
| 3 |
name: string;
|
|
@@ -6,3 +8,7 @@ export interface UserInterface {
|
|
| 6 |
status: string;
|
| 7 |
password: string;
|
| 8 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { JwtPayload } from 'jsonwebtoken';
|
| 2 |
+
import { Request } from 'express';
|
| 3 |
export interface UserInterface {
|
| 4 |
id?: number;
|
| 5 |
name: string;
|
|
|
|
| 8 |
status: string;
|
| 9 |
password: string;
|
| 10 |
}
|
| 11 |
+
|
| 12 |
+
export interface AuthenticatedRequest extends Request {
|
| 13 |
+
user?: string | JwtPayload;
|
| 14 |
+
}
|