(Feat-Fix): Lots of fixes done, reporting system fixed, stricter types
This commit is contained in:
@@ -1,21 +1,35 @@
|
||||
import { Router } from "@oak/oak";
|
||||
import { Router, type RouterContext, type State } 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 { WorkAllocation, CreateWorkAllocationRequest, ContractorRate } from "../types/index.ts";
|
||||
import type {
|
||||
ContractorRate,
|
||||
CreateWorkAllocationRequest,
|
||||
JWTPayload,
|
||||
WorkAllocation,
|
||||
} from "../types/index.ts";
|
||||
|
||||
const router = new Router();
|
||||
|
||||
// Get all work allocations
|
||||
router.get("/", authenticateToken, async (ctx) => {
|
||||
try {
|
||||
const currentUser = getCurrentUser(ctx);
|
||||
const params = ctx.request.url.searchParams;
|
||||
const employeeId = params.get("employeeId");
|
||||
const status = params.get("status");
|
||||
const departmentId = params.get("departmentId");
|
||||
|
||||
let query = `
|
||||
router.get(
|
||||
"/",
|
||||
authenticateToken,
|
||||
async (
|
||||
ctx: RouterContext<"/", Record<string | number, string | undefined>, State>,
|
||||
) => {
|
||||
try {
|
||||
const currentUser: JWTPayload = getCurrentUser(ctx);
|
||||
const params: URLSearchParams = ctx.request.url.searchParams;
|
||||
const employeeId: string | null = params.get("employeeId");
|
||||
const status: string | null = params.get("status");
|
||||
const departmentId: string | null = params.get("departmentId");
|
||||
|
||||
let query: string = `
|
||||
SELECT wa.*,
|
||||
e.name as employee_name, e.username as employee_username,
|
||||
s.name as supervisor_name,
|
||||
@@ -30,52 +44,53 @@ router.get("/", authenticateToken, async (ctx) => {
|
||||
LEFT JOIN departments d ON e.department_id = d.id
|
||||
WHERE 1=1
|
||||
`;
|
||||
const queryParams: unknown[] = [];
|
||||
|
||||
// Role-based filtering
|
||||
if (currentUser.role === "Supervisor") {
|
||||
query += " AND wa.supervisor_id = ?";
|
||||
queryParams.push(currentUser.id);
|
||||
} else if (currentUser.role === "Employee") {
|
||||
query += " AND wa.employee_id = ?";
|
||||
queryParams.push(currentUser.id);
|
||||
} else if (currentUser.role === "Contractor") {
|
||||
query += " AND wa.contractor_id = ?";
|
||||
queryParams.push(currentUser.id);
|
||||
const queryParams: unknown[] = [];
|
||||
|
||||
// Role-based filtering
|
||||
if (currentUser.role === "Supervisor") {
|
||||
query += " AND wa.supervisor_id = ?";
|
||||
queryParams.push(currentUser.id);
|
||||
} else if (currentUser.role === "Employee") {
|
||||
query += " AND wa.employee_id = ?";
|
||||
queryParams.push(currentUser.id);
|
||||
} else if (currentUser.role === "Contractor") {
|
||||
query += " AND wa.contractor_id = ?";
|
||||
queryParams.push(currentUser.id);
|
||||
}
|
||||
|
||||
if (employeeId) {
|
||||
query += " AND wa.employee_id = ?";
|
||||
queryParams.push(employeeId);
|
||||
}
|
||||
|
||||
if (status) {
|
||||
query += " AND wa.status = ?";
|
||||
queryParams.push(status);
|
||||
}
|
||||
|
||||
if (departmentId) {
|
||||
query += " AND e.department_id = ?";
|
||||
queryParams.push(departmentId);
|
||||
}
|
||||
|
||||
query += " ORDER BY wa.assigned_date DESC, wa.created_at DESC";
|
||||
|
||||
const allocations = await db.query<WorkAllocation[]>(query, queryParams);
|
||||
ctx.response.body = allocations;
|
||||
} catch (error) {
|
||||
console.error("Get work allocations error:", error);
|
||||
ctx.response.status = 500;
|
||||
ctx.response.body = { error: "Internal server error" };
|
||||
}
|
||||
|
||||
if (employeeId) {
|
||||
query += " AND wa.employee_id = ?";
|
||||
queryParams.push(employeeId);
|
||||
}
|
||||
|
||||
if (status) {
|
||||
query += " AND wa.status = ?";
|
||||
queryParams.push(status);
|
||||
}
|
||||
|
||||
if (departmentId) {
|
||||
query += " AND e.department_id = ?";
|
||||
queryParams.push(departmentId);
|
||||
}
|
||||
|
||||
query += " ORDER BY wa.assigned_date DESC, wa.created_at DESC";
|
||||
|
||||
const allocations = await db.query<WorkAllocation[]>(query, queryParams);
|
||||
ctx.response.body = allocations;
|
||||
} catch (error) {
|
||||
console.error("Get work allocations error:", error);
|
||||
ctx.response.status = 500;
|
||||
ctx.response.body = { error: "Internal server error" };
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
// Get work allocation by ID
|
||||
router.get("/:id", authenticateToken, async (ctx) => {
|
||||
router.get("/:id", authenticateToken, async (ctx: RouterContext<"/:id">) => {
|
||||
try {
|
||||
const allocationId = ctx.params.id;
|
||||
|
||||
const allocations = await db.query<WorkAllocation[]>(
|
||||
const allocationId: string | undefined = ctx.params.id;
|
||||
|
||||
const allocations: WorkAllocation[] = await db.query<WorkAllocation[]>(
|
||||
`SELECT wa.*,
|
||||
e.name as employee_name, e.username as employee_username,
|
||||
s.name as supervisor_name,
|
||||
@@ -89,15 +104,15 @@ router.get("/:id", authenticateToken, async (ctx) => {
|
||||
LEFT JOIN sub_departments sd ON wa.sub_department_id = sd.id
|
||||
LEFT JOIN departments d ON e.department_id = d.id
|
||||
WHERE wa.id = ?`,
|
||||
[allocationId]
|
||||
[allocationId],
|
||||
);
|
||||
|
||||
|
||||
if (allocations.length === 0) {
|
||||
ctx.response.status = 404;
|
||||
ctx.response.body = { error: "Work allocation not found" };
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
ctx.response.body = allocations[0];
|
||||
} catch (error) {
|
||||
console.error("Get work allocation error:", error);
|
||||
@@ -107,57 +122,92 @@ router.get("/:id", authenticateToken, async (ctx) => {
|
||||
});
|
||||
|
||||
// Create work allocation (Supervisor or SuperAdmin)
|
||||
router.post("/", authenticateToken, authorize("Supervisor", "SuperAdmin"), async (ctx) => {
|
||||
try {
|
||||
const currentUser = getCurrentUser(ctx);
|
||||
const body = await ctx.request.body.json() as CreateWorkAllocationRequest;
|
||||
const { employeeId, contractorId, subDepartmentId, activity, description, assignedDate, rate, units, totalAmount, departmentId } = body;
|
||||
|
||||
if (!employeeId || !contractorId || !assignedDate) {
|
||||
ctx.response.status = 400;
|
||||
ctx.response.body = { error: "Missing required fields" };
|
||||
return;
|
||||
}
|
||||
|
||||
// Verify employee exists
|
||||
let employeeQuery = "SELECT * FROM users WHERE id = ?";
|
||||
const employeeParams: unknown[] = [employeeId];
|
||||
|
||||
if (currentUser.role === "Supervisor") {
|
||||
employeeQuery += " AND department_id = ?";
|
||||
employeeParams.push(currentUser.departmentId);
|
||||
}
|
||||
|
||||
const employees = await db.query<{ id: number }[]>(employeeQuery, employeeParams);
|
||||
|
||||
if (employees.length === 0) {
|
||||
ctx.response.status = 403;
|
||||
ctx.response.body = { error: "Employee not found or not in your department" };
|
||||
return;
|
||||
}
|
||||
|
||||
// Use provided rate or get contractor's current rate
|
||||
let finalRate = rate;
|
||||
if (!finalRate) {
|
||||
const rates = await db.query<ContractorRate[]>(
|
||||
"SELECT rate FROM contractor_rates WHERE contractor_id = ? ORDER BY effective_date DESC LIMIT 1",
|
||||
[contractorId]
|
||||
router.post(
|
||||
"/",
|
||||
authenticateToken,
|
||||
authorize("Supervisor", "SuperAdmin"),
|
||||
async (
|
||||
ctx: RouterContext<"/", Record<string | number, string | undefined>, State>,
|
||||
) => {
|
||||
try {
|
||||
const currentUser = getCurrentUser(ctx);
|
||||
const body = await ctx.request.body.json() as CreateWorkAllocationRequest;
|
||||
const {
|
||||
employeeId,
|
||||
contractorId,
|
||||
subDepartmentId,
|
||||
activity,
|
||||
description,
|
||||
assignedDate,
|
||||
rate,
|
||||
units,
|
||||
totalAmount,
|
||||
departmentId,
|
||||
} = body;
|
||||
|
||||
if (!employeeId || !contractorId || !assignedDate) {
|
||||
ctx.response.status = 400;
|
||||
ctx.response.body = { error: "Missing required fields" };
|
||||
return;
|
||||
}
|
||||
|
||||
// Verify employee exists
|
||||
let employeeQuery = "SELECT * FROM users WHERE id = ?";
|
||||
const employeeParams: unknown[] = [employeeId];
|
||||
|
||||
if (currentUser.role === "Supervisor") {
|
||||
employeeQuery += " AND department_id = ?";
|
||||
employeeParams.push(currentUser.departmentId);
|
||||
}
|
||||
|
||||
const employees = await db.query<{ id: number }[]>(
|
||||
employeeQuery,
|
||||
employeeParams,
|
||||
);
|
||||
finalRate = rates.length > 0 ? rates[0].rate : null;
|
||||
}
|
||||
|
||||
const sanitizedActivity = activity ? sanitizeInput(activity) : null;
|
||||
const sanitizedDescription = description ? sanitizeInput(description) : null;
|
||||
|
||||
const result = await db.execute(
|
||||
`INSERT INTO work_allocations
|
||||
|
||||
if (employees.length === 0) {
|
||||
ctx.response.status = 403;
|
||||
ctx.response.body = {
|
||||
error: "Employee not found or not in your department",
|
||||
};
|
||||
return;
|
||||
}
|
||||
|
||||
// Use provided rate or get contractor's current rate
|
||||
let finalRate = rate;
|
||||
if (!finalRate) {
|
||||
const rates = await db.query<ContractorRate[]>(
|
||||
"SELECT rate FROM contractor_rates WHERE contractor_id = ? ORDER BY effective_date DESC LIMIT 1",
|
||||
[contractorId],
|
||||
);
|
||||
finalRate = rates.length > 0 ? rates[0].rate : null;
|
||||
}
|
||||
|
||||
const sanitizedActivity = activity ? sanitizeInput(activity) : null;
|
||||
const sanitizedDescription = description
|
||||
? sanitizeInput(description)
|
||||
: null;
|
||||
|
||||
const result = await db.execute(
|
||||
`INSERT INTO work_allocations
|
||||
(employee_id, supervisor_id, contractor_id, sub_department_id, activity, description, assigned_date, rate, units, total_amount)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||
[employeeId, currentUser.id, contractorId, subDepartmentId || null, sanitizedActivity, sanitizedDescription, assignedDate, finalRate, units || null, totalAmount || null]
|
||||
);
|
||||
|
||||
const newAllocation = await db.query<WorkAllocation[]>(
|
||||
`SELECT wa.*,
|
||||
[
|
||||
employeeId,
|
||||
currentUser.id,
|
||||
contractorId,
|
||||
subDepartmentId || null,
|
||||
sanitizedActivity,
|
||||
sanitizedDescription,
|
||||
assignedDate,
|
||||
finalRate,
|
||||
units || null,
|
||||
totalAmount || null,
|
||||
],
|
||||
);
|
||||
|
||||
const newAllocation = await db.query<WorkAllocation[]>(
|
||||
`SELECT wa.*,
|
||||
e.name as employee_name, e.username as employee_username,
|
||||
s.name as supervisor_name,
|
||||
c.name as contractor_name,
|
||||
@@ -170,56 +220,66 @@ router.post("/", authenticateToken, authorize("Supervisor", "SuperAdmin"), async
|
||||
LEFT JOIN sub_departments sd ON wa.sub_department_id = sd.id
|
||||
LEFT JOIN departments d ON e.department_id = d.id
|
||||
WHERE wa.id = ?`,
|
||||
[result.insertId]
|
||||
);
|
||||
|
||||
ctx.response.status = 201;
|
||||
ctx.response.body = newAllocation[0];
|
||||
} catch (error) {
|
||||
console.error("Create work allocation error:", error);
|
||||
ctx.response.status = 500;
|
||||
ctx.response.body = { error: "Internal server error" };
|
||||
}
|
||||
});
|
||||
[result.insertId],
|
||||
);
|
||||
|
||||
ctx.response.status = 201;
|
||||
ctx.response.body = newAllocation[0];
|
||||
} catch (error) {
|
||||
console.error("Create work allocation error:", error);
|
||||
ctx.response.status = 500;
|
||||
ctx.response.body = { error: "Internal server error" };
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
// Update work allocation status (Supervisor or SuperAdmin)
|
||||
router.put("/:id/status", authenticateToken, authorize("Supervisor", "SuperAdmin"), async (ctx) => {
|
||||
try {
|
||||
const currentUser = getCurrentUser(ctx);
|
||||
const allocationId = ctx.params.id;
|
||||
const body = await ctx.request.body.json() as { status: string; completionDate?: string };
|
||||
const { status, completionDate } = body;
|
||||
|
||||
if (!status) {
|
||||
ctx.response.status = 400;
|
||||
ctx.response.body = { error: "Status required" };
|
||||
return;
|
||||
}
|
||||
|
||||
// Verify allocation exists and user has access
|
||||
let query = "SELECT * FROM work_allocations WHERE id = ?";
|
||||
const params: unknown[] = [allocationId];
|
||||
|
||||
if (currentUser.role === "Supervisor") {
|
||||
query += " AND supervisor_id = ?";
|
||||
params.push(currentUser.id);
|
||||
}
|
||||
|
||||
const allocations = await db.query<WorkAllocation[]>(query, params);
|
||||
|
||||
if (allocations.length === 0) {
|
||||
ctx.response.status = 403;
|
||||
ctx.response.body = { error: "Work allocation not found or access denied" };
|
||||
return;
|
||||
}
|
||||
|
||||
await db.execute(
|
||||
"UPDATE work_allocations SET status = ?, completion_date = ? WHERE id = ?",
|
||||
[status, completionDate || null, allocationId]
|
||||
);
|
||||
|
||||
const updatedAllocation = await db.query<WorkAllocation[]>(
|
||||
`SELECT wa.*,
|
||||
router.put(
|
||||
"/:id/status",
|
||||
authenticateToken,
|
||||
authorize("Supervisor", "SuperAdmin"),
|
||||
async (ctx) => {
|
||||
try {
|
||||
const currentUser = getCurrentUser(ctx);
|
||||
const allocationId = ctx.params.id;
|
||||
const body = await ctx.request.body.json() as {
|
||||
status: string;
|
||||
completionDate?: string;
|
||||
};
|
||||
const { status, completionDate } = body;
|
||||
|
||||
if (!status) {
|
||||
ctx.response.status = 400;
|
||||
ctx.response.body = { error: "Status required" };
|
||||
return;
|
||||
}
|
||||
|
||||
// Verify allocation exists and user has access
|
||||
let query = "SELECT * FROM work_allocations WHERE id = ?";
|
||||
const params: unknown[] = [allocationId];
|
||||
|
||||
if (currentUser.role === "Supervisor") {
|
||||
query += " AND supervisor_id = ?";
|
||||
params.push(currentUser.id);
|
||||
}
|
||||
|
||||
const allocations = await db.query<WorkAllocation[]>(query, params);
|
||||
|
||||
if (allocations.length === 0) {
|
||||
ctx.response.status = 403;
|
||||
ctx.response.body = {
|
||||
error: "Work allocation not found or access denied",
|
||||
};
|
||||
return;
|
||||
}
|
||||
|
||||
await db.execute(
|
||||
"UPDATE work_allocations SET status = ?, completion_date = ? WHERE id = ?",
|
||||
[status, completionDate || null, allocationId],
|
||||
);
|
||||
|
||||
const updatedAllocation = await db.query<WorkAllocation[]>(
|
||||
`SELECT wa.*,
|
||||
e.name as employee_name, e.username as employee_username,
|
||||
s.name as supervisor_name,
|
||||
c.name as contractor_name,
|
||||
@@ -232,47 +292,57 @@ router.put("/:id/status", authenticateToken, authorize("Supervisor", "SuperAdmin
|
||||
LEFT JOIN sub_departments sd ON wa.sub_department_id = sd.id
|
||||
LEFT JOIN departments d ON e.department_id = d.id
|
||||
WHERE wa.id = ?`,
|
||||
[allocationId]
|
||||
);
|
||||
|
||||
ctx.response.body = updatedAllocation[0];
|
||||
} catch (error) {
|
||||
console.error("Update work allocation error:", error);
|
||||
ctx.response.status = 500;
|
||||
ctx.response.body = { error: "Internal server error" };
|
||||
}
|
||||
});
|
||||
[allocationId],
|
||||
);
|
||||
|
||||
ctx.response.body = updatedAllocation[0];
|
||||
} catch (error) {
|
||||
console.error("Update work allocation error:", error);
|
||||
ctx.response.status = 500;
|
||||
ctx.response.body = { error: "Internal server error" };
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
// Delete work allocation (Supervisor or SuperAdmin)
|
||||
router.delete("/:id", authenticateToken, authorize("Supervisor", "SuperAdmin"), async (ctx) => {
|
||||
try {
|
||||
const currentUser = getCurrentUser(ctx);
|
||||
const allocationId = ctx.params.id;
|
||||
|
||||
// Verify allocation exists and user has access
|
||||
let query = "SELECT * FROM work_allocations WHERE id = ?";
|
||||
const params: unknown[] = [allocationId];
|
||||
|
||||
if (currentUser.role === "Supervisor") {
|
||||
query += " AND supervisor_id = ?";
|
||||
params.push(currentUser.id);
|
||||
router.delete(
|
||||
"/:id",
|
||||
authenticateToken,
|
||||
authorize("Supervisor", "SuperAdmin"),
|
||||
async (ctx) => {
|
||||
try {
|
||||
const currentUser = getCurrentUser(ctx);
|
||||
const allocationId = ctx.params.id;
|
||||
|
||||
// Verify allocation exists and user has access
|
||||
let query = "SELECT * FROM work_allocations WHERE id = ?";
|
||||
const params: unknown[] = [allocationId];
|
||||
|
||||
if (currentUser.role === "Supervisor") {
|
||||
query += " AND supervisor_id = ?";
|
||||
params.push(currentUser.id);
|
||||
}
|
||||
|
||||
const allocations = await db.query<WorkAllocation[]>(query, params);
|
||||
|
||||
if (allocations.length === 0) {
|
||||
ctx.response.status = 403;
|
||||
ctx.response.body = {
|
||||
error: "Work allocation not found or access denied",
|
||||
};
|
||||
return;
|
||||
}
|
||||
|
||||
await db.execute("DELETE FROM work_allocations WHERE id = ?", [
|
||||
allocationId,
|
||||
]);
|
||||
ctx.response.body = { message: "Work allocation deleted successfully" };
|
||||
} catch (error) {
|
||||
console.error("Delete work allocation error:", error);
|
||||
ctx.response.status = 500;
|
||||
ctx.response.body = { error: "Internal server error" };
|
||||
}
|
||||
|
||||
const allocations = await db.query<WorkAllocation[]>(query, params);
|
||||
|
||||
if (allocations.length === 0) {
|
||||
ctx.response.status = 403;
|
||||
ctx.response.body = { error: "Work allocation not found or access denied" };
|
||||
return;
|
||||
}
|
||||
|
||||
await db.execute("DELETE FROM work_allocations WHERE id = ?", [allocationId]);
|
||||
ctx.response.body = { message: "Work allocation deleted successfully" };
|
||||
} catch (error) {
|
||||
console.error("Delete work allocation error:", error);
|
||||
ctx.response.status = 500;
|
||||
ctx.response.body = { error: "Internal server error" };
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
export default router;
|
||||
|
||||
Reference in New Issue
Block a user