216 lines
6.6 KiB
TypeScript
216 lines
6.6 KiB
TypeScript
import { Router } from "@oak/oak";
|
|
import { db } from "../config/database.ts";
|
|
import {
|
|
authenticateToken,
|
|
authorize,
|
|
getCurrentUser,
|
|
} from "../middleware/auth.ts";
|
|
import type { JWTPayload, WorkAllocation } from "../types/index.ts";
|
|
|
|
const router = new Router();
|
|
|
|
// Get completed work allocations for reporting (with optional filters)
|
|
router.get(
|
|
"/completed-allocations",
|
|
authenticateToken,
|
|
authorize("Supervisor", "SuperAdmin"),
|
|
async (ctx) => {
|
|
try {
|
|
const currentUser: JWTPayload = getCurrentUser(ctx);
|
|
const params: URLSearchParams = ctx.request.url.searchParams;
|
|
const startDate: string | null = params.get("startDate");
|
|
const endDate: string | null = params.get("endDate");
|
|
const departmentId: string | null = params.get("departmentId");
|
|
const contractorId: string | null = params.get("contractorId");
|
|
const employeeId: string | null = params.get("employeeId");
|
|
|
|
let query = `
|
|
SELECT wa.*,
|
|
e.name as employee_name, e.username as employee_username,
|
|
e.phone_number as employee_phone,
|
|
s.name as supervisor_name,
|
|
c.name as contractor_name,
|
|
sd.name as sub_department_name,
|
|
d.name as department_name,
|
|
d.id as department_id
|
|
FROM work_allocations wa
|
|
JOIN users e ON wa.employee_id = e.id
|
|
JOIN users s ON wa.supervisor_id = s.id
|
|
JOIN users c ON wa.contractor_id = c.id
|
|
LEFT JOIN sub_departments sd ON wa.sub_department_id = sd.id
|
|
LEFT JOIN departments d ON e.department_id = d.id
|
|
WHERE wa.status = 'Completed'
|
|
`;
|
|
const queryParams: unknown[] = [];
|
|
|
|
// Role-based filtering - Supervisors can only see their department
|
|
if (currentUser.role === "Supervisor") {
|
|
query += " AND e.department_id = ?";
|
|
queryParams.push(currentUser.departmentId);
|
|
}
|
|
|
|
// Date range filter
|
|
if (startDate) {
|
|
query += " AND wa.completion_date >= ?";
|
|
queryParams.push(startDate);
|
|
}
|
|
|
|
if (endDate) {
|
|
query += " AND wa.completion_date <= ?";
|
|
queryParams.push(endDate);
|
|
}
|
|
|
|
// Department filter (for SuperAdmin)
|
|
if (departmentId && currentUser.role === "SuperAdmin") {
|
|
query += " AND e.department_id = ?";
|
|
queryParams.push(departmentId);
|
|
}
|
|
|
|
// Contractor filter
|
|
if (contractorId) {
|
|
query += " AND wa.contractor_id = ?";
|
|
queryParams.push(contractorId);
|
|
}
|
|
|
|
// Employee filter
|
|
if (employeeId) {
|
|
query += " AND wa.employee_id = ?";
|
|
queryParams.push(employeeId);
|
|
}
|
|
|
|
query += " ORDER BY wa.completion_date DESC, wa.created_at DESC";
|
|
|
|
const allocations = await db.query<WorkAllocation[]>(query, queryParams);
|
|
|
|
// Calculate summary stats
|
|
const totalAllocations = allocations.length;
|
|
const totalAmount = allocations.reduce(
|
|
(sum, a) =>
|
|
sum +
|
|
(parseFloat(String(a.total_amount)) || parseFloat(String(a.rate)) ||
|
|
0),
|
|
0,
|
|
);
|
|
const totalUnits = allocations.reduce(
|
|
(sum, a) => sum + (parseFloat(String(a.units)) || 0),
|
|
0,
|
|
);
|
|
|
|
ctx.response.body = {
|
|
allocations,
|
|
summary: {
|
|
totalAllocations,
|
|
totalAmount: totalAmount.toFixed(2),
|
|
totalUnits: totalUnits.toFixed(2),
|
|
},
|
|
};
|
|
} catch (error) {
|
|
console.error("Get completed allocations report error:", error);
|
|
ctx.response.status = 500;
|
|
ctx.response.body = { error: "Internal server error" };
|
|
}
|
|
},
|
|
);
|
|
|
|
// Get summary statistics for completed work
|
|
router.get(
|
|
"/summary",
|
|
authenticateToken,
|
|
authorize("Supervisor", "SuperAdmin"),
|
|
async (ctx) => {
|
|
try {
|
|
const currentUser: JWTPayload = getCurrentUser(ctx);
|
|
const params: URLSearchParams = ctx.request.url.searchParams;
|
|
const startDate: string | null = params.get("startDate");
|
|
const endDate: string | null = params.get("endDate");
|
|
|
|
let departmentFilter = "";
|
|
const queryParams: unknown[] = [];
|
|
|
|
if (currentUser.role === "Supervisor") {
|
|
departmentFilter = " AND e.department_id = ?";
|
|
queryParams.push(currentUser.departmentId);
|
|
}
|
|
|
|
let dateFilter = "";
|
|
if (startDate) {
|
|
dateFilter += " AND wa.completion_date >= ?";
|
|
queryParams.push(startDate);
|
|
}
|
|
if (endDate) {
|
|
dateFilter += " AND wa.completion_date <= ?";
|
|
queryParams.push(endDate);
|
|
}
|
|
|
|
// Get summary by contractor
|
|
const byContractor = await db.query<any[]>(
|
|
`
|
|
SELECT
|
|
c.id as contractor_id,
|
|
c.name as contractor_name,
|
|
COUNT(*) as total_allocations,
|
|
SUM(COALESCE(wa.total_amount, wa.rate, 0)) as total_amount,
|
|
SUM(COALESCE(wa.units, 0)) as total_units
|
|
FROM work_allocations wa
|
|
JOIN users e ON wa.employee_id = e.id
|
|
JOIN users c ON wa.contractor_id = c.id
|
|
WHERE wa.status = 'Completed' ${departmentFilter} ${dateFilter}
|
|
GROUP BY c.id, c.name
|
|
ORDER BY total_amount DESC
|
|
`,
|
|
queryParams,
|
|
);
|
|
|
|
// Get summary by sub-department
|
|
const bySubDepartment = await db.query<any[]>(
|
|
`
|
|
SELECT
|
|
sd.id as sub_department_id,
|
|
sd.name as sub_department_name,
|
|
d.name as department_name,
|
|
COUNT(*) as total_allocations,
|
|
SUM(COALESCE(wa.total_amount, wa.rate, 0)) as total_amount,
|
|
SUM(COALESCE(wa.units, 0)) as total_units
|
|
FROM work_allocations wa
|
|
JOIN users e ON wa.employee_id = e.id
|
|
LEFT JOIN sub_departments sd ON wa.sub_department_id = sd.id
|
|
LEFT JOIN departments d ON sd.department_id = d.id
|
|
WHERE wa.status = 'Completed' ${departmentFilter} ${dateFilter}
|
|
GROUP BY sd.id, sd.name, d.name
|
|
ORDER BY total_amount DESC
|
|
`,
|
|
queryParams,
|
|
);
|
|
|
|
// Get summary by activity type
|
|
const byActivity = await db.query<any[]>(
|
|
`
|
|
SELECT
|
|
COALESCE(wa.activity, 'Standard') as activity,
|
|
COUNT(*) as total_allocations,
|
|
SUM(COALESCE(wa.total_amount, wa.rate, 0)) as total_amount,
|
|
SUM(COALESCE(wa.units, 0)) as total_units
|
|
FROM work_allocations wa
|
|
JOIN users e ON wa.employee_id = e.id
|
|
WHERE wa.status = 'Completed' ${departmentFilter} ${dateFilter}
|
|
GROUP BY wa.activity
|
|
ORDER BY total_amount DESC
|
|
`,
|
|
queryParams,
|
|
);
|
|
|
|
ctx.response.body = {
|
|
byContractor,
|
|
bySubDepartment,
|
|
byActivity,
|
|
};
|
|
} catch (error) {
|
|
console.error("Get report summary error:", error);
|
|
ctx.response.status = 500;
|
|
ctx.response.body = { error: "Internal server error" };
|
|
}
|
|
},
|
|
);
|
|
|
|
export default router;
|