(Feat-Fix): Lots of fixes done, reporting system fixed, stricter types
This commit is contained in:
@@ -1,7 +1,12 @@
|
||||
import { Router } from "@oak/oak";
|
||||
import { db } from "../config/database.ts";
|
||||
import { authenticateToken, authorize, getCurrentUser } from "../middleware/auth.ts";
|
||||
import {
|
||||
authenticateToken,
|
||||
authorize,
|
||||
getCurrentUser,
|
||||
} from "../middleware/auth.ts";
|
||||
import { sanitizeInput } from "../middleware/security.ts";
|
||||
import type { Context } from "@oak/oak";
|
||||
|
||||
const router = new Router();
|
||||
|
||||
@@ -21,14 +26,16 @@ interface StandardRate {
|
||||
}
|
||||
|
||||
// Get all standard rates (default rates for comparison)
|
||||
router.get("/", authenticateToken, async (ctx) => {
|
||||
router.get("/", authenticateToken, async (ctx: Context) => {
|
||||
try {
|
||||
const currentUser = getCurrentUser(ctx);
|
||||
const params = ctx.request.url.searchParams;
|
||||
const departmentId = params.get("departmentId");
|
||||
const subDepartmentId = params.get("subDepartmentId");
|
||||
const activity = params.get("activity");
|
||||
|
||||
const departmentId: string | number | null = params.get("departmentId");
|
||||
const subDepartmentId: string | number | null = params.get(
|
||||
"subDepartmentId",
|
||||
);
|
||||
const activity: string | null = params.get("activity");
|
||||
|
||||
let query = `
|
||||
SELECT sr.*,
|
||||
sd.name as sub_department_name,
|
||||
@@ -44,30 +51,30 @@ router.get("/", authenticateToken, async (ctx) => {
|
||||
WHERE 1=1
|
||||
`;
|
||||
const queryParams: unknown[] = [];
|
||||
|
||||
|
||||
// Supervisors can only see rates for their department
|
||||
if (currentUser.role === "Supervisor") {
|
||||
query += " AND d.id = ?";
|
||||
queryParams.push(currentUser.departmentId);
|
||||
}
|
||||
|
||||
|
||||
if (departmentId) {
|
||||
query += " AND d.id = ?";
|
||||
queryParams.push(departmentId);
|
||||
}
|
||||
|
||||
|
||||
if (subDepartmentId) {
|
||||
query += " AND sr.sub_department_id = ?";
|
||||
queryParams.push(subDepartmentId);
|
||||
}
|
||||
|
||||
|
||||
if (activity) {
|
||||
query += " AND sr.activity = ?";
|
||||
queryParams.push(activity);
|
||||
}
|
||||
|
||||
|
||||
query += " ORDER BY sr.effective_date DESC, sr.created_at DESC";
|
||||
|
||||
|
||||
const rates = await db.query<StandardRate[]>(query, queryParams);
|
||||
ctx.response.body = rates;
|
||||
} catch (error) {
|
||||
@@ -78,15 +85,19 @@ router.get("/", authenticateToken, async (ctx) => {
|
||||
});
|
||||
|
||||
// Get all rates (contractor + standard) for SuperAdmin - all departments, sorted by date
|
||||
router.get("/all-rates", authenticateToken, authorize("SuperAdmin"), async (ctx) => {
|
||||
try {
|
||||
const params = ctx.request.url.searchParams;
|
||||
const departmentId = params.get("departmentId");
|
||||
const startDate = params.get("startDate");
|
||||
const endDate = params.get("endDate");
|
||||
|
||||
// Get contractor rates
|
||||
let contractorQuery = `
|
||||
router.get(
|
||||
"/all-rates",
|
||||
authenticateToken,
|
||||
authorize("SuperAdmin"),
|
||||
async (ctx: Context) => {
|
||||
try {
|
||||
const params = ctx.request.url.searchParams;
|
||||
const departmentId: string | number | null = params.get("departmentId");
|
||||
const startDate: string | null = params.get("startDate");
|
||||
const endDate: string | null = params.get("endDate");
|
||||
|
||||
// Get contractor rates
|
||||
let contractorQuery = `
|
||||
SELECT
|
||||
cr.id,
|
||||
'contractor' as rate_type,
|
||||
@@ -108,25 +119,25 @@ router.get("/all-rates", authenticateToken, authorize("SuperAdmin"), async (ctx)
|
||||
LEFT JOIN departments d ON sd.department_id = d.id
|
||||
WHERE 1=1
|
||||
`;
|
||||
const contractorParams: unknown[] = [];
|
||||
|
||||
if (departmentId) {
|
||||
contractorQuery += " AND d.id = ?";
|
||||
contractorParams.push(departmentId);
|
||||
}
|
||||
|
||||
if (startDate) {
|
||||
contractorQuery += " AND cr.effective_date >= ?";
|
||||
contractorParams.push(startDate);
|
||||
}
|
||||
|
||||
if (endDate) {
|
||||
contractorQuery += " AND cr.effective_date <= ?";
|
||||
contractorParams.push(endDate);
|
||||
}
|
||||
|
||||
// Get standard rates
|
||||
let standardQuery = `
|
||||
const contractorParams: unknown[] = [];
|
||||
|
||||
if (departmentId) {
|
||||
contractorQuery += " AND d.id = ?";
|
||||
contractorParams.push(departmentId);
|
||||
}
|
||||
|
||||
if (startDate) {
|
||||
contractorQuery += " AND cr.effective_date >= ?";
|
||||
contractorParams.push(startDate);
|
||||
}
|
||||
|
||||
if (endDate) {
|
||||
contractorQuery += " AND cr.effective_date <= ?";
|
||||
contractorParams.push(endDate);
|
||||
}
|
||||
|
||||
// Get standard rates
|
||||
let standardQuery = `
|
||||
SELECT
|
||||
sr.id,
|
||||
'standard' as rate_type,
|
||||
@@ -148,66 +159,77 @@ router.get("/all-rates", authenticateToken, authorize("SuperAdmin"), async (ctx)
|
||||
LEFT JOIN users u ON sr.created_by = u.id
|
||||
WHERE 1=1
|
||||
`;
|
||||
const standardParams: unknown[] = [];
|
||||
|
||||
if (departmentId) {
|
||||
standardQuery += " AND d.id = ?";
|
||||
standardParams.push(departmentId);
|
||||
}
|
||||
|
||||
if (startDate) {
|
||||
standardQuery += " AND sr.effective_date >= ?";
|
||||
standardParams.push(startDate);
|
||||
}
|
||||
|
||||
if (endDate) {
|
||||
standardQuery += " AND sr.effective_date <= ?";
|
||||
standardParams.push(endDate);
|
||||
}
|
||||
|
||||
const contractorRates = await db.query<any[]>(contractorQuery, contractorParams);
|
||||
const standardRates = await db.query<any[]>(standardQuery, standardParams);
|
||||
|
||||
// Combine and sort by date
|
||||
const allRates = [...contractorRates, ...standardRates].sort((a, b) => {
|
||||
const dateA = new Date(a.effective_date).getTime();
|
||||
const dateB = new Date(b.effective_date).getTime();
|
||||
return dateB - dateA; // Descending order
|
||||
});
|
||||
|
||||
ctx.response.body = {
|
||||
allRates,
|
||||
summary: {
|
||||
totalContractorRates: contractorRates.length,
|
||||
totalStandardRates: standardRates.length,
|
||||
totalRates: allRates.length,
|
||||
const standardParams: unknown[] = [];
|
||||
|
||||
if (departmentId) {
|
||||
standardQuery += " AND d.id = ?";
|
||||
standardParams.push(departmentId);
|
||||
}
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("Get all rates error:", error);
|
||||
ctx.response.status = 500;
|
||||
ctx.response.body = { error: "Internal server error" };
|
||||
}
|
||||
});
|
||||
|
||||
if (startDate) {
|
||||
standardQuery += " AND sr.effective_date >= ?";
|
||||
standardParams.push(startDate);
|
||||
}
|
||||
|
||||
if (endDate) {
|
||||
standardQuery += " AND sr.effective_date <= ?";
|
||||
standardParams.push(endDate);
|
||||
}
|
||||
|
||||
const contractorRates = await db.query<any[]>(
|
||||
contractorQuery,
|
||||
contractorParams,
|
||||
);
|
||||
const standardRates = await db.query<any[]>(
|
||||
standardQuery,
|
||||
standardParams,
|
||||
);
|
||||
|
||||
// Combine and sort by date
|
||||
const allRates = [...contractorRates, ...standardRates].sort((a, b) => {
|
||||
const dateA = new Date(a.effective_date).getTime();
|
||||
const dateB = new Date(b.effective_date).getTime();
|
||||
return dateB - dateA; // Descending order
|
||||
});
|
||||
|
||||
ctx.response.body = {
|
||||
allRates,
|
||||
summary: {
|
||||
totalContractorRates: contractorRates.length,
|
||||
totalStandardRates: standardRates.length,
|
||||
totalRates: allRates.length,
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("Get all rates error:", error);
|
||||
ctx.response.status = 500;
|
||||
ctx.response.body = { error: "Internal server error" };
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
// Compare contractor rates with standard rates
|
||||
router.get("/compare", authenticateToken, authorize("Supervisor", "SuperAdmin"), async (ctx) => {
|
||||
try {
|
||||
const currentUser = getCurrentUser(ctx);
|
||||
const params = ctx.request.url.searchParams;
|
||||
const contractorId = params.get("contractorId");
|
||||
const subDepartmentId = params.get("subDepartmentId");
|
||||
|
||||
let departmentFilter = "";
|
||||
const queryParams: unknown[] = [];
|
||||
|
||||
if (currentUser.role === "Supervisor") {
|
||||
departmentFilter = " AND d.id = ?";
|
||||
queryParams.push(currentUser.departmentId);
|
||||
}
|
||||
|
||||
// Get standard rates
|
||||
let standardQuery = `
|
||||
router.get(
|
||||
"/compare",
|
||||
authenticateToken,
|
||||
authorize("Supervisor", "SuperAdmin"),
|
||||
async (ctx) => {
|
||||
try {
|
||||
const currentUser = getCurrentUser(ctx);
|
||||
const params = ctx.request.url.searchParams;
|
||||
const contractorId = params.get("contractorId");
|
||||
const subDepartmentId = params.get("subDepartmentId");
|
||||
|
||||
let departmentFilter = "";
|
||||
const queryParams: unknown[] = [];
|
||||
|
||||
if (currentUser.role === "Supervisor") {
|
||||
departmentFilter = " AND d.id = ?";
|
||||
queryParams.push(currentUser.departmentId);
|
||||
}
|
||||
|
||||
// Get standard rates
|
||||
let standardQuery = `
|
||||
SELECT sr.*,
|
||||
sd.name as sub_department_name,
|
||||
d.name as department_name,
|
||||
@@ -219,18 +241,21 @@ router.get("/compare", authenticateToken, authorize("Supervisor", "SuperAdmin"),
|
||||
LEFT JOIN activities a ON a.sub_department_id = sr.sub_department_id AND a.name = sr.activity
|
||||
WHERE 1=1 ${departmentFilter}
|
||||
`;
|
||||
|
||||
if (subDepartmentId) {
|
||||
standardQuery += " AND sr.sub_department_id = ?";
|
||||
queryParams.push(subDepartmentId);
|
||||
}
|
||||
|
||||
standardQuery += " ORDER BY sr.effective_date DESC";
|
||||
|
||||
const standardRates = await db.query<StandardRate[]>(standardQuery, queryParams);
|
||||
|
||||
// Get contractor rates for comparison
|
||||
let contractorQuery = `
|
||||
|
||||
if (subDepartmentId) {
|
||||
standardQuery += " AND sr.sub_department_id = ?";
|
||||
queryParams.push(subDepartmentId);
|
||||
}
|
||||
|
||||
standardQuery += " ORDER BY sr.effective_date DESC";
|
||||
|
||||
const standardRates = await db.query<StandardRate[]>(
|
||||
standardQuery,
|
||||
queryParams,
|
||||
);
|
||||
|
||||
// Get contractor rates for comparison
|
||||
let contractorQuery = `
|
||||
SELECT cr.*,
|
||||
u.name as contractor_name,
|
||||
sd.name as sub_department_name,
|
||||
@@ -244,103 +269,123 @@ router.get("/compare", authenticateToken, authorize("Supervisor", "SuperAdmin"),
|
||||
LEFT JOIN activities a ON a.sub_department_id = cr.sub_department_id AND a.name = cr.activity
|
||||
WHERE 1=1
|
||||
`;
|
||||
const contractorParams: unknown[] = [];
|
||||
|
||||
if (currentUser.role === "Supervisor") {
|
||||
contractorQuery += " AND d.id = ?";
|
||||
contractorParams.push(currentUser.departmentId);
|
||||
}
|
||||
|
||||
if (contractorId) {
|
||||
contractorQuery += " AND cr.contractor_id = ?";
|
||||
contractorParams.push(contractorId);
|
||||
}
|
||||
|
||||
if (subDepartmentId) {
|
||||
contractorQuery += " AND cr.sub_department_id = ?";
|
||||
contractorParams.push(subDepartmentId);
|
||||
}
|
||||
|
||||
contractorQuery += " ORDER BY cr.effective_date DESC";
|
||||
|
||||
const contractorRates = await db.query<any[]>(contractorQuery, contractorParams);
|
||||
|
||||
// Build comparison data
|
||||
const comparisons = contractorRates.map(cr => {
|
||||
// Find matching standard rate
|
||||
const matchingStandard = standardRates.find(sr =>
|
||||
sr.sub_department_id === cr.sub_department_id &&
|
||||
sr.activity === cr.activity
|
||||
const contractorParams: unknown[] = [];
|
||||
|
||||
if (currentUser.role === "Supervisor") {
|
||||
contractorQuery += " AND d.id = ?";
|
||||
contractorParams.push(currentUser.departmentId);
|
||||
}
|
||||
|
||||
if (contractorId) {
|
||||
contractorQuery += " AND cr.contractor_id = ?";
|
||||
contractorParams.push(contractorId);
|
||||
}
|
||||
|
||||
if (subDepartmentId) {
|
||||
contractorQuery += " AND cr.sub_department_id = ?";
|
||||
contractorParams.push(subDepartmentId);
|
||||
}
|
||||
|
||||
contractorQuery += " ORDER BY cr.effective_date DESC";
|
||||
|
||||
const contractorRates = await db.query<any[]>(
|
||||
contractorQuery,
|
||||
contractorParams,
|
||||
);
|
||||
|
||||
const standardRate = matchingStandard?.rate || 0;
|
||||
const contractorRate = cr.rate || 0;
|
||||
const difference = contractorRate - standardRate;
|
||||
const percentageDiff = standardRate > 0 ? ((difference / standardRate) * 100).toFixed(2) : null;
|
||||
|
||||
return {
|
||||
...cr,
|
||||
standard_rate: standardRate,
|
||||
difference,
|
||||
percentage_difference: percentageDiff,
|
||||
is_above_standard: difference > 0,
|
||||
is_below_standard: difference < 0,
|
||||
|
||||
// Build comparison data
|
||||
const comparisons = contractorRates.map((cr) => {
|
||||
// Find matching standard rate
|
||||
const matchingStandard = standardRates.find((sr) =>
|
||||
sr.sub_department_id === cr.sub_department_id &&
|
||||
sr.activity === cr.activity
|
||||
);
|
||||
|
||||
const standardRate = matchingStandard?.rate || 0;
|
||||
const contractorRate = cr.rate || 0;
|
||||
const difference = contractorRate - standardRate;
|
||||
const percentageDiff = standardRate > 0
|
||||
? ((difference / standardRate) * 100).toFixed(2)
|
||||
: null;
|
||||
|
||||
return {
|
||||
...cr,
|
||||
standard_rate: standardRate,
|
||||
difference,
|
||||
percentage_difference: percentageDiff,
|
||||
is_above_standard: difference > 0,
|
||||
is_below_standard: difference < 0,
|
||||
};
|
||||
});
|
||||
|
||||
ctx.response.body = {
|
||||
standardRates,
|
||||
contractorRates,
|
||||
comparisons,
|
||||
};
|
||||
});
|
||||
|
||||
ctx.response.body = {
|
||||
standardRates,
|
||||
contractorRates,
|
||||
comparisons,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("Compare rates error:", error);
|
||||
ctx.response.status = 500;
|
||||
ctx.response.body = { error: "Internal server error" };
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Compare rates error:", error);
|
||||
ctx.response.status = 500;
|
||||
ctx.response.body = { error: "Internal server error" };
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
// Create standard rate (Supervisor or SuperAdmin)
|
||||
router.post("/", authenticateToken, authorize("Supervisor", "SuperAdmin"), async (ctx) => {
|
||||
try {
|
||||
const currentUser = getCurrentUser(ctx);
|
||||
const body = await ctx.request.body.json() as {
|
||||
subDepartmentId?: number;
|
||||
activity?: string;
|
||||
rate: number;
|
||||
effectiveDate: string;
|
||||
};
|
||||
const { subDepartmentId, activity, rate, effectiveDate } = body;
|
||||
|
||||
if (!rate || !effectiveDate) {
|
||||
ctx.response.status = 400;
|
||||
ctx.response.body = { error: "Missing required fields (rate, effectiveDate)" };
|
||||
return;
|
||||
}
|
||||
|
||||
// Verify sub-department belongs to supervisor's department if supervisor
|
||||
if (subDepartmentId && currentUser.role === "Supervisor") {
|
||||
const subDepts = await db.query<any[]>(
|
||||
"SELECT sd.* FROM sub_departments sd JOIN departments d ON sd.department_id = d.id WHERE sd.id = ? AND d.id = ?",
|
||||
[subDepartmentId, currentUser.departmentId]
|
||||
);
|
||||
|
||||
if (subDepts.length === 0) {
|
||||
ctx.response.status = 403;
|
||||
ctx.response.body = { error: "Sub-department not in your department" };
|
||||
router.post(
|
||||
"/",
|
||||
authenticateToken,
|
||||
authorize("Supervisor", "SuperAdmin"),
|
||||
async (ctx) => {
|
||||
try {
|
||||
const currentUser = getCurrentUser(ctx);
|
||||
const body = await ctx.request.body.json() as {
|
||||
subDepartmentId?: number;
|
||||
activity?: string;
|
||||
rate: number;
|
||||
effectiveDate: string;
|
||||
};
|
||||
const { subDepartmentId, activity, rate, effectiveDate } = body;
|
||||
|
||||
if (!rate || !effectiveDate) {
|
||||
ctx.response.status = 400;
|
||||
ctx.response.body = {
|
||||
error: "Missing required fields (rate, effectiveDate)",
|
||||
};
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const sanitizedActivity = activity ? sanitizeInput(activity) : null;
|
||||
|
||||
const result = await db.execute(
|
||||
"INSERT INTO standard_rates (sub_department_id, activity, rate, effective_date, created_by) VALUES (?, ?, ?, ?, ?)",
|
||||
[subDepartmentId || null, sanitizedActivity, rate, effectiveDate, currentUser.id]
|
||||
);
|
||||
|
||||
const newRate = await db.query<StandardRate[]>(
|
||||
`SELECT sr.*,
|
||||
|
||||
// Verify sub-department belongs to supervisor's department if supervisor
|
||||
if (subDepartmentId && currentUser.role === "Supervisor") {
|
||||
const subDepts = await db.query<any[]>(
|
||||
"SELECT sd.* FROM sub_departments sd JOIN departments d ON sd.department_id = d.id WHERE sd.id = ? AND d.id = ?",
|
||||
[subDepartmentId, currentUser.departmentId],
|
||||
);
|
||||
|
||||
if (subDepts.length === 0) {
|
||||
ctx.response.status = 403;
|
||||
ctx.response.body = {
|
||||
error: "Sub-department not in your department",
|
||||
};
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const sanitizedActivity = activity ? sanitizeInput(activity) : null;
|
||||
|
||||
const result = await db.execute(
|
||||
"INSERT INTO standard_rates (sub_department_id, activity, rate, effective_date, created_by) VALUES (?, ?, ?, ?, ?)",
|
||||
[
|
||||
subDepartmentId || null,
|
||||
sanitizedActivity,
|
||||
rate,
|
||||
effectiveDate,
|
||||
currentUser.id,
|
||||
],
|
||||
);
|
||||
|
||||
const newRate = await db.query<StandardRate[]>(
|
||||
`SELECT sr.*,
|
||||
sd.name as sub_department_name,
|
||||
d.name as department_name,
|
||||
u.name as created_by_name,
|
||||
@@ -351,82 +396,96 @@ router.post("/", authenticateToken, authorize("Supervisor", "SuperAdmin"), async
|
||||
LEFT JOIN users u ON sr.created_by = u.id
|
||||
LEFT JOIN activities a ON a.sub_department_id = sr.sub_department_id AND a.name = sr.activity
|
||||
WHERE sr.id = ?`,
|
||||
[result.insertId]
|
||||
);
|
||||
|
||||
ctx.response.status = 201;
|
||||
ctx.response.body = newRate[0];
|
||||
} catch (error) {
|
||||
console.error("Create standard rate error:", error);
|
||||
ctx.response.status = 500;
|
||||
ctx.response.body = { error: "Internal server error" };
|
||||
}
|
||||
});
|
||||
[result.insertId],
|
||||
);
|
||||
|
||||
ctx.response.status = 201;
|
||||
ctx.response.body = newRate[0];
|
||||
} catch (error) {
|
||||
console.error("Create standard rate error:", error);
|
||||
ctx.response.status = 500;
|
||||
ctx.response.body = { error: "Internal server error" };
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
// Update standard rate
|
||||
router.put("/:id", authenticateToken, authorize("Supervisor", "SuperAdmin"), async (ctx) => {
|
||||
try {
|
||||
const currentUser = getCurrentUser(ctx);
|
||||
const rateId = ctx.params.id;
|
||||
const body = await ctx.request.body.json() as { rate?: number; activity?: string; effectiveDate?: string };
|
||||
const { rate, activity, effectiveDate } = body;
|
||||
|
||||
// Verify rate exists and user has access
|
||||
let query = `
|
||||
router.put(
|
||||
"/:id",
|
||||
authenticateToken,
|
||||
authorize("Supervisor", "SuperAdmin"),
|
||||
async (ctx) => {
|
||||
try {
|
||||
const currentUser = getCurrentUser(ctx);
|
||||
const rateId = ctx.params.id;
|
||||
const body = await ctx.request.body.json() as {
|
||||
rate?: number;
|
||||
activity?: string;
|
||||
effectiveDate?: string;
|
||||
};
|
||||
const { rate, activity, effectiveDate } = body;
|
||||
|
||||
// Verify rate exists and user has access
|
||||
let query = `
|
||||
SELECT sr.*, d.id as department_id
|
||||
FROM standard_rates sr
|
||||
LEFT JOIN sub_departments sd ON sr.sub_department_id = sd.id
|
||||
LEFT JOIN departments d ON sd.department_id = d.id
|
||||
WHERE sr.id = ?
|
||||
`;
|
||||
const params: unknown[] = [rateId];
|
||||
|
||||
const existing = await db.query<any[]>(query, params);
|
||||
|
||||
if (existing.length === 0) {
|
||||
ctx.response.status = 404;
|
||||
ctx.response.body = { error: "Standard rate not found" };
|
||||
return;
|
||||
}
|
||||
|
||||
// Supervisors can only update rates in their department
|
||||
if (currentUser.role === "Supervisor" && existing[0].department_id !== currentUser.departmentId) {
|
||||
ctx.response.status = 403;
|
||||
ctx.response.body = { error: "Access denied - rate not in your department" };
|
||||
return;
|
||||
}
|
||||
|
||||
const updates: string[] = [];
|
||||
const updateParams: unknown[] = [];
|
||||
|
||||
if (rate !== undefined) {
|
||||
updates.push("rate = ?");
|
||||
updateParams.push(rate);
|
||||
}
|
||||
if (activity !== undefined) {
|
||||
updates.push("activity = ?");
|
||||
updateParams.push(sanitizeInput(activity));
|
||||
}
|
||||
if (effectiveDate !== undefined) {
|
||||
updates.push("effective_date = ?");
|
||||
updateParams.push(effectiveDate);
|
||||
}
|
||||
|
||||
if (updates.length === 0) {
|
||||
ctx.response.status = 400;
|
||||
ctx.response.body = { error: "No fields to update" };
|
||||
return;
|
||||
}
|
||||
|
||||
updateParams.push(rateId);
|
||||
|
||||
await db.execute(
|
||||
`UPDATE standard_rates SET ${updates.join(", ")} WHERE id = ?`,
|
||||
updateParams
|
||||
);
|
||||
|
||||
const updatedRate = await db.query<StandardRate[]>(
|
||||
`SELECT sr.*,
|
||||
const params: unknown[] = [rateId];
|
||||
|
||||
const existing = await db.query<any[]>(query, params);
|
||||
|
||||
if (existing.length === 0) {
|
||||
ctx.response.status = 404;
|
||||
ctx.response.body = { error: "Standard rate not found" };
|
||||
return;
|
||||
}
|
||||
|
||||
// Supervisors can only update rates in their department
|
||||
if (
|
||||
currentUser.role === "Supervisor" &&
|
||||
existing[0].department_id !== currentUser.departmentId
|
||||
) {
|
||||
ctx.response.status = 403;
|
||||
ctx.response.body = {
|
||||
error: "Access denied - rate not in your department",
|
||||
};
|
||||
return;
|
||||
}
|
||||
|
||||
const updates: string[] = [];
|
||||
const updateParams: unknown[] = [];
|
||||
|
||||
if (rate !== undefined) {
|
||||
updates.push("rate = ?");
|
||||
updateParams.push(rate);
|
||||
}
|
||||
if (activity !== undefined) {
|
||||
updates.push("activity = ?");
|
||||
updateParams.push(sanitizeInput(activity));
|
||||
}
|
||||
if (effectiveDate !== undefined) {
|
||||
updates.push("effective_date = ?");
|
||||
updateParams.push(effectiveDate);
|
||||
}
|
||||
|
||||
if (updates.length === 0) {
|
||||
ctx.response.status = 400;
|
||||
ctx.response.body = { error: "No fields to update" };
|
||||
return;
|
||||
}
|
||||
|
||||
updateParams.push(rateId);
|
||||
|
||||
await db.execute(
|
||||
`UPDATE standard_rates SET ${updates.join(", ")} WHERE id = ?`,
|
||||
updateParams,
|
||||
);
|
||||
|
||||
const updatedRate = await db.query<StandardRate[]>(
|
||||
`SELECT sr.*,
|
||||
sd.name as sub_department_name,
|
||||
d.name as department_name,
|
||||
u.name as created_by_name,
|
||||
@@ -437,53 +496,64 @@ router.put("/:id", authenticateToken, authorize("Supervisor", "SuperAdmin"), asy
|
||||
LEFT JOIN users u ON sr.created_by = u.id
|
||||
LEFT JOIN activities a ON a.sub_department_id = sr.sub_department_id AND a.name = sr.activity
|
||||
WHERE sr.id = ?`,
|
||||
[rateId]
|
||||
);
|
||||
|
||||
ctx.response.body = updatedRate[0];
|
||||
} catch (error) {
|
||||
console.error("Update standard rate error:", error);
|
||||
ctx.response.status = 500;
|
||||
ctx.response.body = { error: "Internal server error" };
|
||||
}
|
||||
});
|
||||
[rateId],
|
||||
);
|
||||
|
||||
ctx.response.body = updatedRate[0];
|
||||
} catch (error) {
|
||||
console.error("Update standard rate error:", error);
|
||||
ctx.response.status = 500;
|
||||
ctx.response.body = { error: "Internal server error" };
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
// Delete standard rate
|
||||
router.delete("/:id", authenticateToken, authorize("Supervisor", "SuperAdmin"), async (ctx) => {
|
||||
try {
|
||||
const currentUser = getCurrentUser(ctx);
|
||||
const rateId = ctx.params.id;
|
||||
|
||||
// Verify rate exists and user has access
|
||||
const existing = await db.query<any[]>(
|
||||
`SELECT sr.*, d.id as department_id
|
||||
router.delete(
|
||||
"/:id",
|
||||
authenticateToken,
|
||||
authorize("Supervisor", "SuperAdmin"),
|
||||
async (ctx: Context) => {
|
||||
try {
|
||||
const currentUser = getCurrentUser(ctx);
|
||||
const rateId = ctx.params.id;
|
||||
|
||||
// Verify rate exists and user has access
|
||||
const existing = await db.query<any[]>(
|
||||
`SELECT sr.*, d.id as department_id
|
||||
FROM standard_rates sr
|
||||
LEFT JOIN sub_departments sd ON sr.sub_department_id = sd.id
|
||||
LEFT JOIN departments d ON sd.department_id = d.id
|
||||
WHERE sr.id = ?`,
|
||||
[rateId]
|
||||
);
|
||||
|
||||
if (existing.length === 0) {
|
||||
ctx.response.status = 404;
|
||||
ctx.response.body = { error: "Standard rate not found" };
|
||||
return;
|
||||
[rateId],
|
||||
);
|
||||
|
||||
if (existing.length === 0) {
|
||||
ctx.response.status = 404;
|
||||
ctx.response.body = { error: "Standard rate not found" };
|
||||
return;
|
||||
}
|
||||
|
||||
// Supervisors can only delete rates in their department
|
||||
if (
|
||||
currentUser.role === "Supervisor" &&
|
||||
existing[0].department_id !== currentUser.departmentId
|
||||
) {
|
||||
ctx.response.status = 403;
|
||||
ctx.response.body = {
|
||||
error: "Access denied - rate not in your department",
|
||||
};
|
||||
return;
|
||||
}
|
||||
|
||||
await db.execute("DELETE FROM standard_rates WHERE id = ?", [rateId]);
|
||||
ctx.response.body = { message: "Standard rate deleted successfully" };
|
||||
} catch (error) {
|
||||
console.error("Delete standard rate error:", error);
|
||||
ctx.response.status = 500;
|
||||
ctx.response.body = { error: "Internal server error" };
|
||||
}
|
||||
|
||||
// Supervisors can only delete rates in their department
|
||||
if (currentUser.role === "Supervisor" && existing[0].department_id !== currentUser.departmentId) {
|
||||
ctx.response.status = 403;
|
||||
ctx.response.body = { error: "Access denied - rate not in your department" };
|
||||
return;
|
||||
}
|
||||
|
||||
await db.execute("DELETE FROM standard_rates WHERE id = ?", [rateId]);
|
||||
ctx.response.body = { message: "Standard rate deleted successfully" };
|
||||
} catch (error) {
|
||||
console.error("Delete standard rate error:", error);
|
||||
ctx.response.status = 500;
|
||||
ctx.response.body = { error: "Internal server error" };
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
export default router;
|
||||
|
||||
Reference in New Issue
Block a user