feat(project-ratecard): implement project rate card management with CRUD operations and integrate into frontend

This commit is contained in:
shancds
2025-05-21 17:16:35 +05:30
parent db1108a48d
commit c3bec74897
11 changed files with 671 additions and 144 deletions

View File

@@ -0,0 +1,121 @@
import db from "../config/db";
import { IWorkLenzRequest } from "../interfaces/worklenz-request";
import { IWorkLenzResponse } from "../interfaces/worklenz-response";
import { ServerResponse } from "../models/server-response";
import HandleExceptions from "../decorators/handle-exceptions";
import WorklenzControllerBase from "./worklenz-controller-base";
export default class ProjectRateCardController extends WorklenzControllerBase {
// Insert multiple roles for a project
@HandleExceptions()
public static async insertMany(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise<IWorkLenzResponse> {
const { project_id, roles } = req.body;
if (!Array.isArray(roles) || !project_id) {
return res.status(400).send(new ServerResponse(false, null, "Invalid input"));
}
const values = roles.map((role: any) => [
project_id,
role.job_title_id,
role.rate
]);
const q = `
INSERT INTO finance_project_rate_card_roles (project_id, job_title_id, rate)
VALUES ${values.map((_, i) => `($${i * 3 + 1}, $${i * 3 + 2}, $${i * 3 + 3})`).join(",")}
ON CONFLICT (project_id, job_title_id) DO UPDATE SET rate = EXCLUDED.rate
RETURNING *;
`;
const flatValues = values.flat();
const result = await db.query(q, flatValues);
return res.status(200).send(new ServerResponse(true, result.rows));
}
// Get all roles for a project
@HandleExceptions()
public static async getFromProjectId(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise<IWorkLenzResponse> {
const { project_id } = req.params;
const q = `
SELECT fprr.*, jt.name as jobtitle
FROM finance_project_rate_card_roles fprr
LEFT JOIN job_titles jt ON fprr.job_title_id = jt.id
WHERE fprr.project_id = $1
ORDER BY jt.name;
`;
const result = await db.query(q, [project_id]);
return res.status(200).send(new ServerResponse(true, result.rows));
}
// Get a single role by id
@HandleExceptions()
public static async getFromId(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise<IWorkLenzResponse> {
const { id } = req.params;
const q = `
SELECT fprr.*, jt.name as jobtitle
FROM finance_project_rate_card_roles fprr
LEFT JOIN job_titles jt ON fprr.job_title_id = jt.id
WHERE fprr.id = $1;
`;
const result = await db.query(q, [id]);
return res.status(200).send(new ServerResponse(true, result.rows[0]));
}
// Update a single role by id
@HandleExceptions()
public static async updateFromId(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise<IWorkLenzResponse> {
const { id } = req.params;
const { job_title_id, rate } = req.body;
const q = `
UPDATE finance_project_rate_card_roles
SET job_title_id = $1, rate = $2, updated_at = NOW()
WHERE id = $3
RETURNING *;
`;
const result = await db.query(q, [job_title_id, rate, id]);
return res.status(200).send(new ServerResponse(true, result.rows[0]));
}
// Update all roles for a project (delete then insert)
@HandleExceptions()
public static async updateFromProjectId(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise<IWorkLenzResponse> {
const { project_id, roles } = req.body;
if (!Array.isArray(roles) || !project_id) {
return res.status(400).send(new ServerResponse(false, null, "Invalid input"));
}
// Delete existing
await db.query(`DELETE FROM finance_project_rate_card_roles WHERE project_id = $1`, [project_id]);
// Insert new
if (roles.length === 0) {
return res.status(200).send(new ServerResponse(true, []));
}
const values = roles.map((role: any) => [
project_id,
role.job_title_id,
role.rate
]);
const q = `
INSERT INTO finance_project_rate_card_roles (project_id, job_title_id, rate)
VALUES ${values.map((_, i) => `($${i * 3 + 1}, $${i * 3 + 2}, $${i * 3 + 3})`).join(",")}
RETURNING *;
`;
const flatValues = values.flat();
const result = await db.query(q, flatValues);
return res.status(200).send(new ServerResponse(true, result.rows));
}
// Delete a single role by id
@HandleExceptions()
public static async deleteFromId(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise<IWorkLenzResponse> {
const { id } = req.params;
const q = `DELETE FROM finance_project_rate_card_roles WHERE id = $1 RETURNING *;`;
const result = await db.query(q, [id]);
return res.status(200).send(new ServerResponse(true, result.rows[0]));
}
// Delete all roles for a project
@HandleExceptions()
public static async deleteFromProjectId(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise<IWorkLenzResponse> {
const { project_id } = req.params;
const q = `DELETE FROM finance_project_rate_card_roles WHERE project_id = $1 RETURNING *;`;
const result = await db.query(q, [project_id]);
return res.status(200).send(new ServerResponse(true, result.rows));
}
}

View File

@@ -59,6 +59,7 @@ import taskRecurringApiRouter from "./task-recurring-api-router";
import customColumnsApiRouter from "./custom-columns-api-router";
import ratecardApiRouter from "./ratecard-api-router";
import projectRatecardApiRouter from "./project-ratecard-api-router";
const api = express.Router();
@@ -67,6 +68,7 @@ api.use("/team-members", teamMembersApiRouter);
api.use("/job-titles", jobTitlesApiRouter);
api.use("/clients", clientsApiRouter);
api.use("/rate-cards", ratecardApiRouter);
api.use("/project-rate-cards", projectRatecardApiRouter);
api.use("/teams", teamsApiRouter);
api.use("/tasks", tasksApiRouter);
api.use("/settings", settingsApiRouter);

View File

@@ -0,0 +1,55 @@
import express from "express";
import ProjectRateCardController from "../../controllers/project-ratecard-controller";
import idParamValidator from "../../middlewares/validators/id-param-validator";
import safeControllerFunction from "../../shared/safe-controller-function";
import projectManagerValidator from "../../middlewares/validators/project-manager-validator";
const projectRatecardApiRouter = express.Router();
// Insert multiple roles for a project
projectRatecardApiRouter.post(
"/",
projectManagerValidator,
safeControllerFunction(ProjectRateCardController.insertMany)
);
// Get all roles for a project
projectRatecardApiRouter.get(
"/project/:project_id",
safeControllerFunction(ProjectRateCardController.getFromProjectId)
);
// Get a single role by id
projectRatecardApiRouter.get(
"/:id",
idParamValidator,
safeControllerFunction(ProjectRateCardController.getFromId)
);
// Update a single role by id
projectRatecardApiRouter.put(
"/:id",
idParamValidator,
safeControllerFunction(ProjectRateCardController.updateFromId)
);
// Update all roles for a project (delete then insert)
projectRatecardApiRouter.put(
"/project/:project_id",
safeControllerFunction(ProjectRateCardController.updateFromProjectId)
);
// Delete a single role by id
projectRatecardApiRouter.delete(
"/:id",
idParamValidator,
safeControllerFunction(ProjectRateCardController.deleteFromId)
);
// Delete all roles for a project
projectRatecardApiRouter.delete(
"/project/:project_id",
safeControllerFunction(ProjectRateCardController.deleteFromProjectId)
);
export default projectRatecardApiRouter;