import { Router, type RouterContext, type State } from "@oak/oak"; import { db } from "../config/database.ts"; import { authenticateToken, authorize, getCurrentUser, } from "../middleware/auth.ts"; import type { CreateSwapRequest, EmployeeSwap, User } from "../types/index.ts"; const router = new Router(); // Get all employee swaps (SuperAdmin only) router.get( "/", authenticateToken, authorize("SuperAdmin"), async ( ctx: RouterContext<"/", Record, State>, ) => { try { const params: URLSearchParams = ctx.request.url.searchParams; const status: string | null = params.get("status"); const employeeId: string | null = params.get("employeeId"); const startDate: string | null = params.get("startDate"); const endDate: string | null = params.get("endDate"); let query = ` SELECT es.*, e.name as employee_name, od.name as original_department_name, td.name as target_department_name, oc.name as original_contractor_name, tc.name as target_contractor_name, sb.name as swapped_by_name FROM employee_swaps es JOIN users e ON es.employee_id = e.id JOIN departments od ON es.original_department_id = od.id JOIN departments td ON es.target_department_id = td.id LEFT JOIN users oc ON es.original_contractor_id = oc.id LEFT JOIN users tc ON es.target_contractor_id = tc.id JOIN users sb ON es.swapped_by = sb.id WHERE 1=1 `; const queryParams: unknown[] = []; if (status) { query += " AND es.status = ?"; queryParams.push(status); } if (employeeId) { query += " AND es.employee_id = ?"; queryParams.push(employeeId); } if (startDate) { query += " AND es.swap_date >= ?"; queryParams.push(startDate); } if (endDate) { query += " AND es.swap_date <= ?"; queryParams.push(endDate); } query += " ORDER BY es.created_at DESC"; const swaps = await db.query(query, queryParams); ctx.response.body = swaps; } catch (error) { console.error("Get employee swaps error:", error); ctx.response.status = 500; ctx.response.body = { error: "Internal server error" }; } }, ); // Get swap by ID router.get( "/:id", authenticateToken, authorize("SuperAdmin"), async ( ctx: RouterContext< "/:id", { id: string } & Record, State >, ) => { try { const swapId = ctx.params.id; const swaps = await db.query( `SELECT es.*, e.name as employee_name, od.name as original_department_name, td.name as target_department_name, oc.name as original_contractor_name, tc.name as target_contractor_name, sb.name as swapped_by_name FROM employee_swaps es JOIN users e ON es.employee_id = e.id JOIN departments od ON es.original_department_id = od.id JOIN departments td ON es.target_department_id = td.id LEFT JOIN users oc ON es.original_contractor_id = oc.id LEFT JOIN users tc ON es.target_contractor_id = tc.id JOIN users sb ON es.swapped_by = sb.id WHERE es.id = ?`, [swapId], ); if (swaps.length === 0) { ctx.response.status = 404; ctx.response.body = { error: "Swap record not found" }; return; } ctx.response.body = swaps[0]; } catch (error) { console.error("Get swap error:", error); ctx.response.status = 500; ctx.response.body = { error: "Internal server error" }; } }, ); // Create new employee swap (SuperAdmin only) router.post("/", authenticateToken, authorize("SuperAdmin"), async (ctx) => { try { const currentUser = getCurrentUser(ctx); const body = await ctx.request.body.json() as CreateSwapRequest; const { employeeId, targetDepartmentId, targetContractorId, swapReason, reasonDetails, workCompletionPercentage, swapDate, } = body; // Validate required fields if (!employeeId || !targetDepartmentId || !swapReason || !swapDate) { ctx.response.status = 400; ctx.response.body = { error: "Employee ID, target department, swap reason, and swap date are required", }; return; } // Validate swap reason const validReasons = ["LeftWork", "Sick", "FinishedEarly", "Other"]; if (!validReasons.includes(swapReason)) { ctx.response.status = 400; ctx.response.body = { error: "Invalid swap reason" }; return; } // Get employee's current department and contractor const employees = await db.query( "SELECT * FROM users WHERE id = ? AND role = 'Employee'", [employeeId], ); if (employees.length === 0) { ctx.response.status = 404; ctx.response.body = { error: "Employee not found" }; return; } const employee = employees[0]; if (!employee.department_id) { ctx.response.status = 400; ctx.response.body = { error: "Employee has no current department" }; return; } // Check if there's already an active swap for this employee const activeSwaps = await db.query( "SELECT * FROM employee_swaps WHERE employee_id = ? AND status = 'Active'", [employeeId], ); if (activeSwaps.length > 0) { ctx.response.status = 400; ctx.response.body = { error: "Employee already has an active swap. Complete or cancel it first.", }; return; } // Use transaction to ensure both operations succeed or fail together const newSwap = await db.transaction(async (connection) => { // Create the swap record const [insertResult] = await connection.execute( `INSERT INTO employee_swaps (employee_id, original_department_id, target_department_id, original_contractor_id, target_contractor_id, swap_reason, reason_details, work_completion_percentage, swap_date, swapped_by, status) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'Active')`, [ employeeId, employee.department_id, targetDepartmentId, employee.contractor_id || null, targetContractorId || null, swapReason, reasonDetails || null, workCompletionPercentage || 0, swapDate, currentUser.id, ], ); const swapInsertId = (insertResult as { insertId: number }).insertId; if (!swapInsertId) { throw new Error("Failed to create swap record"); } // Update the employee's department and contractor const [updateResult] = await connection.execute( "UPDATE users SET department_id = ?, contractor_id = ? WHERE id = ?", [targetDepartmentId, targetContractorId || null, employeeId], ); const affectedRows = (updateResult as { affectedRows: number }).affectedRows; if (affectedRows === 0) { throw new Error("Failed to update employee department"); } // Fetch the created swap const [swapRows] = await connection.query( `SELECT es.*, e.name as employee_name, od.name as original_department_name, td.name as target_department_name, oc.name as original_contractor_name, tc.name as target_contractor_name, sb.name as swapped_by_name FROM employee_swaps es JOIN users e ON es.employee_id = e.id JOIN departments od ON es.original_department_id = od.id JOIN departments td ON es.target_department_id = td.id LEFT JOIN users oc ON es.original_contractor_id = oc.id LEFT JOIN users tc ON es.target_contractor_id = tc.id JOIN users sb ON es.swapped_by = sb.id WHERE es.id = ?`, [swapInsertId], ); return (swapRows as EmployeeSwap[])[0]; }); ctx.response.status = 201; ctx.response.body = newSwap; } catch (error) { console.error("Create swap error:", error); ctx.response.status = 500; ctx.response.body = { error: "Internal server error" }; } }); // Complete a swap (return employee to original department) router.put( "/:id/complete", authenticateToken, authorize("SuperAdmin"), async (ctx) => { try { const swapId = ctx.params.id; // Get the swap record const swaps = await db.query( "SELECT * FROM employee_swaps WHERE id = ? AND status = 'Active'", [swapId], ); if (swaps.length === 0) { ctx.response.status = 404; ctx.response.body = { error: "Active swap not found" }; return; } const swap = swaps[0]; // Use transaction to ensure both operations succeed or fail together const updatedSwap = await db.transaction(async (connection) => { // Return employee to original department and contractor await connection.execute( "UPDATE users SET department_id = ?, contractor_id = ? WHERE id = ?", [ swap.original_department_id, swap.original_contractor_id, swap.employee_id, ], ); // Mark swap as completed await connection.execute( "UPDATE employee_swaps SET status = 'Completed', completed_at = NOW() WHERE id = ?", [swapId], ); // Fetch updated swap const [swapRows] = await connection.query( `SELECT es.*, e.name as employee_name, od.name as original_department_name, td.name as target_department_name, oc.name as original_contractor_name, tc.name as target_contractor_name, sb.name as swapped_by_name FROM employee_swaps es JOIN users e ON es.employee_id = e.id JOIN departments od ON es.original_department_id = od.id JOIN departments td ON es.target_department_id = td.id LEFT JOIN users oc ON es.original_contractor_id = oc.id LEFT JOIN users tc ON es.target_contractor_id = tc.id JOIN users sb ON es.swapped_by = sb.id WHERE es.id = ?`, [swapId], ); return (swapRows as EmployeeSwap[])[0]; }); ctx.response.body = updatedSwap; } catch (error) { console.error("Complete swap error:", error); ctx.response.status = 500; ctx.response.body = { error: "Internal server error" }; } }, ); // Cancel a swap (return employee to original department) router.put( "/:id/cancel", authenticateToken, authorize("SuperAdmin"), async (ctx) => { try { const swapId = ctx.params.id; // Get the swap record const swaps = await db.query( "SELECT * FROM employee_swaps WHERE id = ? AND status = 'Active'", [swapId], ); if (swaps.length === 0) { ctx.response.status = 404; ctx.response.body = { error: "Active swap not found" }; return; } const swap = swaps[0]; // Use transaction to ensure both operations succeed or fail together const updatedSwap = await db.transaction(async (connection) => { // Return employee to original department and contractor await connection.execute( "UPDATE users SET department_id = ?, contractor_id = ? WHERE id = ?", [ swap.original_department_id, swap.original_contractor_id, swap.employee_id, ], ); // Mark swap as cancelled await connection.execute( "UPDATE employee_swaps SET status = 'Cancelled', completed_at = NOW() WHERE id = ?", [swapId], ); // Fetch updated swap const [swapRows] = await connection.query( `SELECT es.*, e.name as employee_name, od.name as original_department_name, td.name as target_department_name, oc.name as original_contractor_name, tc.name as target_contractor_name, sb.name as swapped_by_name FROM employee_swaps es JOIN users e ON es.employee_id = e.id JOIN departments od ON es.original_department_id = od.id JOIN departments td ON es.target_department_id = td.id LEFT JOIN users oc ON es.original_contractor_id = oc.id LEFT JOIN users tc ON es.target_contractor_id = tc.id JOIN users sb ON es.swapped_by = sb.id WHERE es.id = ?`, [swapId], ); return (swapRows as EmployeeSwap[])[0]; }); ctx.response.body = updatedSwap; } catch (error) { console.error("Cancel swap error:", error); ctx.response.status = 500; ctx.response.body = { error: "Internal server error" }; } }, ); export default router;