Files
EmployeeManagementSystem/backend-deno/routes/standard-rates.ts

490 lines
16 KiB
TypeScript

import { Router } from "@oak/oak";
import { db } from "../config/database.ts";
import { authenticateToken, authorize, getCurrentUser } from "../middleware/auth.ts";
import { sanitizeInput } from "../middleware/security.ts";
const router = new Router();
// Standard Rate interface
interface StandardRate {
id: number;
sub_department_id: number | null;
activity: string | null;
rate: number;
effective_date: Date;
created_by: number;
created_at: Date;
sub_department_name?: string;
department_name?: string;
department_id?: number;
created_by_name?: string;
}
// Get all standard rates (default rates for comparison)
router.get("/", authenticateToken, async (ctx) => {
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");
let query = `
SELECT sr.*,
sd.name as sub_department_name,
d.name as department_name,
d.id as department_id,
u.name as created_by_name,
a.unit_of_measurement
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
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 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) {
console.error("Get standard rates error:", error);
ctx.response.status = 500;
ctx.response.body = { error: "Internal server error" };
}
});
// 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 = `
SELECT
cr.id,
'contractor' as rate_type,
cr.contractor_id,
u.name as contractor_name,
cr.sub_department_id,
sd.name as sub_department_name,
d.id as department_id,
d.name as department_name,
cr.activity,
cr.rate,
cr.effective_date,
cr.created_at,
NULL as created_by,
NULL as created_by_name
FROM contractor_rates cr
JOIN users u ON cr.contractor_id = u.id
LEFT JOIN sub_departments sd ON cr.sub_department_id = sd.id
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 = `
SELECT
sr.id,
'standard' as rate_type,
NULL as contractor_id,
NULL as contractor_name,
sr.sub_department_id,
sd.name as sub_department_name,
d.id as department_id,
d.name as department_name,
sr.activity,
sr.rate,
sr.effective_date,
sr.created_at,
sr.created_by,
u.name as created_by_name
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
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,
}
};
} 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 = `
SELECT sr.*,
sd.name as sub_department_name,
d.name as department_name,
d.id as department_id,
a.unit_of_measurement
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
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 = `
SELECT cr.*,
u.name as contractor_name,
sd.name as sub_department_name,
d.name as department_name,
d.id as department_id,
a.unit_of_measurement
FROM contractor_rates cr
JOIN users u ON cr.contractor_id = u.id
LEFT JOIN sub_departments sd ON cr.sub_department_id = sd.id
LEFT JOIN departments d ON sd.department_id = d.id
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 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,
};
} 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" };
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,
a.unit_of_measurement
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
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" };
}
});
// 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 = `
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.*,
sd.name as sub_department_name,
d.name as department_name,
u.name as created_by_name,
a.unit_of_measurement
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
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" };
}
});
// 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
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;
}
// 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;