Compare commits
2 Commits
513dc81931
...
ec407364d9
| Author | SHA1 | Date | |
|---|---|---|---|
| ec407364d9 | |||
| 8e6574c0b2 |
@ -1,8 +1,13 @@
|
|||||||
import { Context } from "@oak/oak";
|
import { Context } from "@oak/oak";
|
||||||
import { getDbPool } from "../../shared/db.ts";
|
import { getDbPool } from "../../shared/db.ts";
|
||||||
|
import { AppRole } from "../../shared/auth.ts"; // Importing roles for security checks
|
||||||
|
|
||||||
const amsDb = getDbPool("hrms_ams");
|
const amsDb = getDbPool("hrms_ams");
|
||||||
|
const emsDb = getDbPool("hrms_ems"); // Used for cross-database joins
|
||||||
|
|
||||||
|
/**
|
||||||
|
* EMPLOYEE: Create a new regularization request
|
||||||
|
*/
|
||||||
export const createRegularizationRequest = async (ctx: Context) => {
|
export const createRegularizationRequest = async (ctx: Context) => {
|
||||||
if (!ctx.request.hasBody) {
|
if (!ctx.request.hasBody) {
|
||||||
ctx.response.status = 400;
|
ctx.response.status = 400;
|
||||||
@ -11,40 +16,143 @@ export const createRegularizationRequest = async (ctx: Context) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const body = await ctx.request.body.json();
|
const body = await ctx.request.body.json();
|
||||||
const { attendance_id, employee_id, requested_check_in, requested_check_out, reason } = body;
|
const {
|
||||||
|
attendance_id,
|
||||||
|
employee_id,
|
||||||
|
regularization_type,
|
||||||
|
target_date,
|
||||||
|
requested_check_in,
|
||||||
|
requested_check_out,
|
||||||
|
reason,
|
||||||
|
} = body;
|
||||||
|
|
||||||
// Basic validation
|
if (!employee_id || !reason || !regularization_type || !target_date) {
|
||||||
if (!attendance_id || !employee_id || !reason || !requested_check_in) {
|
|
||||||
ctx.response.status = 400;
|
ctx.response.status = 400;
|
||||||
ctx.response.body = { success: false, message: "Missing required fields: attendance_id, employee_id, requested_check_in, and reason." };
|
ctx.response.body = {
|
||||||
|
success: false,
|
||||||
|
message:
|
||||||
|
"Missing required fields: employee_id, target_date, regularization_type, and reason.",
|
||||||
|
};
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Extract 'YYYY-MM-DD' safely from the requested_check_in string
|
// Fetch employee's branch_id from EMS so AMS knows which branch this request belongs to
|
||||||
const target_date = requested_check_in.split(' ')[0];
|
const [empRows]: any = await emsDb.execute(
|
||||||
|
`SELECT branch_id FROM employees WHERE employee_id = ?`,
|
||||||
|
[employee_id],
|
||||||
|
);
|
||||||
|
|
||||||
// Insert into the attendance_regularizations table including target_date
|
if (empRows.length === 0) {
|
||||||
|
ctx.response.status = 404;
|
||||||
|
ctx.response.body = {
|
||||||
|
success: false,
|
||||||
|
message: "Employee not found in EMS.",
|
||||||
|
};
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const branch_id = empRows[0].branch_id;
|
||||||
|
|
||||||
|
// Insert into AMS with the cached branch_id
|
||||||
await amsDb.execute(
|
await amsDb.execute(
|
||||||
`INSERT INTO attendance_regularizations
|
`INSERT INTO attendance_regularizations
|
||||||
(attendance_id, employee_id, target_date, requested_check_in, requested_check_out, reason, status)
|
(attendance_id, employee_id, branch_id, regularization_type, target_date, requested_check_in, requested_check_out, reason, status)
|
||||||
VALUES (?, ?, ?, ?, ?, ?, 'PENDING')`,
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, 'PENDING')`,
|
||||||
[attendance_id, employee_id, target_date, requested_check_in, requested_check_out || null, reason]
|
[
|
||||||
|
attendance_id || null,
|
||||||
|
employee_id,
|
||||||
|
branch_id,
|
||||||
|
regularization_type,
|
||||||
|
target_date,
|
||||||
|
requested_check_in || null,
|
||||||
|
requested_check_out || null,
|
||||||
|
reason,
|
||||||
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
ctx.response.status = 201;
|
ctx.response.status = 201;
|
||||||
ctx.response.body = {
|
ctx.response.body = {
|
||||||
success: true,
|
success: true,
|
||||||
message: "Regularization request submitted successfully and is pending approval.",
|
message:
|
||||||
|
"Regularization request submitted successfully and is pending approval.",
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to submit regularization:", error);
|
console.error("Failed to submit regularization:", error);
|
||||||
ctx.response.status = 500;
|
ctx.response.status = 500;
|
||||||
ctx.response.body = { success: false, error: error instanceof Error ? error.message : "Unknown error" };
|
ctx.response.body = {
|
||||||
|
success: false,
|
||||||
|
error: error instanceof Error ? error.message : "Unknown error",
|
||||||
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ADMIN: Fetch pending regularizations based on their assigned branches
|
||||||
|
*/
|
||||||
|
export const getPendingRegularizations = async (ctx: Context) => {
|
||||||
|
const user = ctx.state.user;
|
||||||
|
|
||||||
|
try {
|
||||||
|
let query = `
|
||||||
|
SELECT
|
||||||
|
r.regularization_id,
|
||||||
|
r.employee_id,
|
||||||
|
r.target_date,
|
||||||
|
r.regularization_type,
|
||||||
|
r.requested_check_in,
|
||||||
|
r.requested_check_out,
|
||||||
|
r.reason,
|
||||||
|
e.employee_code,
|
||||||
|
p.first_name,
|
||||||
|
p.last_name,
|
||||||
|
b.branch_name
|
||||||
|
FROM attendance_regularizations r
|
||||||
|
INNER JOIN hrms_ems.employees e ON r.employee_id = e.employee_id
|
||||||
|
INNER JOIN hrms_ems.partners p ON e.partner_id = p.partner_id
|
||||||
|
INNER JOIN hrms_ems.branches b ON r.branch_id = b.branch_id
|
||||||
|
WHERE r.status = 'PENDING'
|
||||||
|
`;
|
||||||
|
|
||||||
|
const params: any[] = [];
|
||||||
|
|
||||||
|
// If the user is an ADMIN (not SUPER_ADMIN), restrict to their managed branches
|
||||||
|
if (user.role !== AppRole.SUPER_ADMIN) {
|
||||||
|
const branches = user.managed_branches;
|
||||||
|
if (!branches || branches.length === 0) {
|
||||||
|
// If an admin has no branches assigned, return empty list
|
||||||
|
ctx.response.status = 200;
|
||||||
|
ctx.response.body = { success: true, data: [] };
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const placeholders = branches.map(() => "?").join(",");
|
||||||
|
query += ` AND r.branch_id IN (${placeholders})`;
|
||||||
|
params.push(...branches);
|
||||||
|
}
|
||||||
|
|
||||||
|
query += ` ORDER BY r.target_date DESC`;
|
||||||
|
|
||||||
|
const [rows]: any = await amsDb.execute(query, params);
|
||||||
|
|
||||||
|
ctx.response.status = 200;
|
||||||
|
ctx.response.body = {
|
||||||
|
success: true,
|
||||||
|
count: rows.length,
|
||||||
|
data: rows,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to fetch pending regularizations:", error);
|
||||||
|
ctx.response.status = 500;
|
||||||
|
ctx.response.body = {
|
||||||
|
success: false,
|
||||||
|
error: error instanceof Error ? error.message : "Unknown error",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ADMIN: Review (Approve/Reject) a regularization request
|
||||||
|
*/
|
||||||
export const reviewRegularizationRequest = async (ctx: Context) => {
|
export const reviewRegularizationRequest = async (ctx: Context) => {
|
||||||
if (!ctx.request.hasBody) {
|
if (!ctx.request.hasBody) {
|
||||||
ctx.response.status = 400;
|
ctx.response.status = 400;
|
||||||
@ -53,11 +161,15 @@ export const reviewRegularizationRequest = async (ctx: Context) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const body = await ctx.request.body.json();
|
const body = await ctx.request.body.json();
|
||||||
const { regularization_id, action, reviewed_by_id } = body; // action can be 'APPROVED' or 'REJECTED'
|
const user = ctx.state.user; // The admin reviewing it
|
||||||
|
const { regularization_id, action } = body; // action can be 'APPROVED' or 'REJECTED'
|
||||||
|
|
||||||
if (!regularization_id || !reviewed_by_id || !["APPROVED", "REJECTED"].includes(action)) {
|
if (!regularization_id || !["APPROVED", "REJECTED"].includes(action)) {
|
||||||
ctx.response.status = 400;
|
ctx.response.status = 400;
|
||||||
ctx.response.body = { success: false, message: "Invalid payload. Required: regularization_id, reviewed_by_id, action." };
|
ctx.response.body = {
|
||||||
|
success: false,
|
||||||
|
message: "Invalid payload. Required: regularization_id, action.",
|
||||||
|
};
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -68,105 +180,146 @@ export const reviewRegularizationRequest = async (ctx: Context) => {
|
|||||||
|
|
||||||
// 1. Fetch the regularization request details
|
// 1. Fetch the regularization request details
|
||||||
const [requestRows]: any = await amsConnection.execute(
|
const [requestRows]: any = await amsConnection.execute(
|
||||||
`SELECT
|
`SELECT regularization_id, attendance_id, branch_id, requested_check_in, requested_check_out, status
|
||||||
attendance_id,
|
|
||||||
DATE_FORMAT(requested_check_in, '%Y-%m-%d %H:%i:%s') as requested_check_in,
|
|
||||||
DATE_FORMAT(requested_check_out, '%Y-%m-%d %H:%i:%s') as requested_check_out,
|
|
||||||
status
|
|
||||||
FROM attendance_regularizations WHERE regularization_id = ?`,
|
FROM attendance_regularizations WHERE regularization_id = ?`,
|
||||||
[regularization_id]
|
[regularization_id],
|
||||||
);
|
);
|
||||||
|
|
||||||
if (requestRows.length === 0) {
|
if (requestRows.length === 0) {
|
||||||
ctx.response.status = 404;
|
ctx.response.status = 404;
|
||||||
ctx.response.body = { success: false, message: "Regularization request not found." };
|
ctx.response.body = {
|
||||||
|
success: false,
|
||||||
|
message: "Regularization request not found.",
|
||||||
|
};
|
||||||
await amsConnection.rollback();
|
await amsConnection.rollback();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const request = requestRows[0];
|
const request = requestRows[0];
|
||||||
|
|
||||||
if (request.status !== "PENDING") {
|
// SECURITY CHECK: Ensure the admin actually manages this branch
|
||||||
ctx.response.status = 400;
|
if (
|
||||||
ctx.response.body = { success: false, message: "This request has already been processed." };
|
user.role !== AppRole.SUPER_ADMIN &&
|
||||||
|
!user.managed_branches.includes(request.branch_id)
|
||||||
|
) {
|
||||||
|
ctx.response.status = 403;
|
||||||
|
ctx.response.body = {
|
||||||
|
success: false,
|
||||||
|
message: "Access Denied: You do not manage this branch.",
|
||||||
|
};
|
||||||
await amsConnection.rollback();
|
await amsConnection.rollback();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Update the regularization request record status and reviewer ID (Removed 'remarks')
|
if (request.status !== "PENDING") {
|
||||||
|
ctx.response.status = 400;
|
||||||
|
ctx.response.body = {
|
||||||
|
success: false,
|
||||||
|
message: "This request has already been processed.",
|
||||||
|
};
|
||||||
|
await amsConnection.rollback();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Update the regularization request record status and reviewer ID
|
||||||
await amsConnection.execute(
|
await amsConnection.execute(
|
||||||
`UPDATE attendance_regularizations
|
`UPDATE attendance_regularizations
|
||||||
SET status = ?, reviewed_by_id = ?
|
SET status = ?, reviewed_by_id = ?
|
||||||
WHERE regularization_id = ?`,
|
WHERE regularization_id = ?`,
|
||||||
[action, reviewed_by_id, regularization_id]
|
[action, user.employee_id, regularization_id],
|
||||||
);
|
);
|
||||||
|
|
||||||
// 3. If APPROVED, dynamically overwrite the target day's calculated ledger row
|
// 3. If APPROVED, dynamically recalculate and overwrite the target day's processed ledger row
|
||||||
if (action === "APPROVED") {
|
if (action === "APPROVED") {
|
||||||
let worked_hours = 0.0;
|
let worked_hours = 0.0;
|
||||||
let final_status = "FULL_DAY";
|
let final_status = "FULL_DAY";
|
||||||
let check_in_status = "ON_TIME";
|
let check_in_status = "ON_TIME";
|
||||||
|
|
||||||
// Fetch shift details to accurately re-evaluate punctuality boundaries (Defaulting to shift_id 1)
|
// Fetch shift details (Fallback pattern)
|
||||||
const [shiftRows]: any = await amsConnection.execute(
|
const [empRows]: any = await amsConnection.execute(
|
||||||
`SELECT start_time, grace_period_minutes FROM shifts WHERE shift_id = 1`
|
`SELECT company_id, branch_id FROM hrms_ems.employees WHERE employee_id = ?`,
|
||||||
|
[requestRows[0].employee_id],
|
||||||
);
|
);
|
||||||
const shift = shiftRows[0] || { start_time: "10:00:00", grace_period_minutes: 10 };
|
const emp = empRows[0];
|
||||||
|
|
||||||
|
const [shiftRows]: any = await amsConnection.execute(
|
||||||
|
`SELECT start_time, grace_period_minutes FROM shifts
|
||||||
|
WHERE (branch_id = ? OR branch_id IS NULL) AND company_id = ?
|
||||||
|
ORDER BY branch_id IS NULL ASC LIMIT 1`,
|
||||||
|
[emp.branch_id, emp.company_id],
|
||||||
|
);
|
||||||
|
const shift = shiftRows[0] ||
|
||||||
|
{ start_time: "10:00:00", grace_period_minutes: 10 };
|
||||||
|
|
||||||
if (request.requested_check_in && request.requested_check_out) {
|
if (request.requested_check_in && request.requested_check_out) {
|
||||||
// --- A. Recalculate Working Duration Metrics ---
|
const checkInMs = new Date(request.requested_check_in.replace(" ", "T"))
|
||||||
const checkInMs = new Date(request.requested_check_in.replace(' ', 'T')).getTime();
|
.getTime();
|
||||||
const checkOutMs = new Date(request.requested_check_out.replace(' ', 'T')).getTime();
|
const checkOutMs = new Date(
|
||||||
worked_hours = Math.round(((checkOutMs - checkInMs) / (1000 * 60 * 60)) * 100) / 100;
|
request.requested_check_out.replace(" ", "T"),
|
||||||
|
).getTime();
|
||||||
|
|
||||||
|
// Handle potential night shift edge case
|
||||||
|
const effectiveCheckOutMs = checkOutMs < checkInMs
|
||||||
|
? checkOutMs + (24 * 60 * 60 * 1000)
|
||||||
|
: checkOutMs;
|
||||||
|
|
||||||
|
worked_hours = Math.round(
|
||||||
|
((effectiveCheckOutMs - checkInMs) / (1000 * 60 * 60)) * 100,
|
||||||
|
) / 100;
|
||||||
|
|
||||||
if (worked_hours >= 7.0) final_status = "FULL_DAY";
|
if (worked_hours >= 7.0) final_status = "FULL_DAY";
|
||||||
else if (worked_hours >= 4.0) final_status = "HALF_DAY";
|
else if (worked_hours >= 4.0) final_status = "HALF_DAY";
|
||||||
else final_status = "ABSENT";
|
else final_status = "ABSENT";
|
||||||
|
|
||||||
// --- B. Dynamically Re-evaluate Punctuality Status (Preserves LATE flags) ---
|
const rawCheckInTimeStr = request.requested_check_in.split(" ")[1];
|
||||||
const rawCheckInTimeStr = request.requested_check_in.split(' ')[1];
|
const [punchH, punchM, punchS] = rawCheckInTimeStr.split(":").map(
|
||||||
const [punchH, punchM, punchS] = rawCheckInTimeStr.split(':').map(Number);
|
Number,
|
||||||
const [shiftH, shiftM, shiftS] = shift.start_time.split(':').map(Number);
|
);
|
||||||
|
const [shiftH, shiftM, shiftS] = shift.start_time.split(":").map(
|
||||||
|
Number,
|
||||||
|
);
|
||||||
|
|
||||||
const punchTotalSeconds = punchH * 3600 + punchM * 60 + punchS;
|
const punchTotalSeconds = punchH * 3600 + punchM * 60 + punchS;
|
||||||
const shiftCutoffSeconds = shiftH * 3600 + shiftM * 60 + (shift.grace_period_minutes * 60);
|
const shiftCutoffSeconds = shiftH * 3600 + shiftM * 60 +
|
||||||
|
(shift.grace_period_minutes * 60);
|
||||||
|
|
||||||
if (punchTotalSeconds > shiftCutoffSeconds) {
|
if (punchTotalSeconds > shiftCutoffSeconds) {
|
||||||
check_in_status = "LATE";
|
check_in_status = "LATE";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await amsConnection.execute(
|
// Only update the ledger if an actual attendance record exists
|
||||||
`UPDATE processed_daily_attendance
|
if (request.attendance_id) {
|
||||||
SET check_in = ?,
|
await amsConnection.execute(
|
||||||
check_out = ?,
|
`UPDATE processed_daily_attendance
|
||||||
worked_hours = ?,
|
SET check_in = ?, check_out = ?, worked_hours = ?, final_status = ?, check_in_status = ?
|
||||||
final_status = ?,
|
WHERE attendance_id = ?`,
|
||||||
check_in_status = ?
|
[
|
||||||
WHERE attendance_id = ?`,
|
request.requested_check_in,
|
||||||
[
|
request.requested_check_out,
|
||||||
request.requested_check_in,
|
worked_hours,
|
||||||
request.requested_check_out,
|
final_status,
|
||||||
worked_hours,
|
check_in_status,
|
||||||
final_status,
|
request.attendance_id,
|
||||||
check_in_status,
|
],
|
||||||
request.attendance_id
|
);
|
||||||
]
|
}
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
await amsConnection.commit();
|
await amsConnection.commit();
|
||||||
ctx.response.status = 200;
|
ctx.response.status = 200;
|
||||||
ctx.response.body = {
|
ctx.response.body = {
|
||||||
success: true,
|
success: true,
|
||||||
message: `Request has been successfully ${action.toLowerCase()} by admin ID ${reviewed_by_id}.`,
|
message: `Request has been successfully ${action.toLowerCase()}.`,
|
||||||
};
|
};
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
await amsConnection.rollback();
|
await amsConnection.rollback();
|
||||||
console.error("Admin review action failed:", error);
|
console.error("Admin review action failed:", error);
|
||||||
ctx.response.status = 500;
|
ctx.response.status = 500;
|
||||||
ctx.response.body = { success: false, error: error instanceof Error ? error.message : "Unknown error" };
|
ctx.response.body = {
|
||||||
|
success: false,
|
||||||
|
error: error instanceof Error ? error.message : "Unknown error",
|
||||||
|
};
|
||||||
} finally {
|
} finally {
|
||||||
amsConnection.release();
|
amsConnection.release();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,7 +6,10 @@ import {
|
|||||||
getDailyReport,
|
getDailyReport,
|
||||||
getAdminRangeReport,
|
getAdminRangeReport,
|
||||||
getSingleEmployeeRangeReport} from "./controllers/attendance.controller.ts";
|
getSingleEmployeeRangeReport} from "./controllers/attendance.controller.ts";
|
||||||
import {createRegularizationRequest, reviewRegularizationRequest} from "./controllers/regularization.controller.ts"
|
import {createRegularizationRequest,
|
||||||
|
getPendingRegularizations,
|
||||||
|
reviewRegularizationRequest} from "./controllers/regularization.controller.ts"
|
||||||
|
import { requireAuth, requireRole, AppRole } from "../shared/auth.ts";
|
||||||
|
|
||||||
const router = new Router();
|
const router = new Router();
|
||||||
const apiV1 = new Router();
|
const apiV1 = new Router();
|
||||||
@ -14,8 +17,14 @@ const apiV1 = new Router();
|
|||||||
apiV1.get("/attendance/logs", getRawLogs);
|
apiV1.get("/attendance/logs", getRawLogs);
|
||||||
apiV1.post("/attendance/process-daily", processDailyAttendance);
|
apiV1.post("/attendance/process-daily", processDailyAttendance);
|
||||||
|
|
||||||
apiV1.post("/attendance/regularize", createRegularizationRequest);
|
// Regularization Routes
|
||||||
apiV1.post("/attendance/regularize/review", reviewRegularizationRequest);
|
apiV1.post("/attendance/regularize", requireAuth, requireRole([AppRole.EMPLOYEE, AppRole.ADMIN]), createRegularizationRequest);
|
||||||
|
|
||||||
|
// Admin fetches requests assigned to their branches
|
||||||
|
apiV1.get("/attendance/regularize/pending", requireAuth, requireRole([AppRole.ADMIN, AppRole.SUPER_ADMIN]), getPendingRegularizations);
|
||||||
|
|
||||||
|
// Admin reviews requests
|
||||||
|
apiV1.post("/attendance/regularize/review", requireAuth, requireRole([AppRole.ADMIN, AppRole.SUPER_ADMIN]), reviewRegularizationRequest);
|
||||||
|
|
||||||
apiV1.get("/attendance/my-summary", getEmployeeSummary);
|
apiV1.get("/attendance/my-summary", getEmployeeSummary);
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
// shared/auth.ts
|
// shared/auth.ts
|
||||||
import { Context, Next } from "@oak/oak";
|
import { Context, Next } from "@oak/oak";
|
||||||
|
import { getDbPool } from "./db.ts";
|
||||||
|
|
||||||
export enum AppRole {
|
export enum AppRole {
|
||||||
SUPER_ADMIN = "SUPER_ADMIN",
|
SUPER_ADMIN = "SUPER_ADMIN",
|
||||||
@ -8,35 +9,49 @@ export enum AppRole {
|
|||||||
EMPLOYEE = "EMPLOYEE",
|
EMPLOYEE = "EMPLOYEE",
|
||||||
}
|
}
|
||||||
|
|
||||||
// 1. Authentication Middleware (Who are you?)
|
// 1. Authentication Middleware
|
||||||
export const requireAuth = async (ctx: Context, next: Next) => {
|
export const requireAuth = async (ctx: Context, next: Next) => {
|
||||||
// Mocking the authorization for now.
|
const isAuthenticated = true;
|
||||||
// Later, we will extract the JWT from ctx.request.headers.get("Authorization")
|
|
||||||
// and verify it with your SSO provider here.
|
|
||||||
|
|
||||||
const isAuthenticated = true; // Simulating a successful login
|
|
||||||
|
|
||||||
if (!isAuthenticated) {
|
if (!isAuthenticated) {
|
||||||
ctx.response.status = 401;
|
ctx.response.status = 401;
|
||||||
ctx.response.body = { success: false, message: "Missing or invalid token" };
|
ctx.response.body = { success: false, message: "Missing or invalid token" };
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Injecting a mock user state so the next middleware can read it
|
// FIX: Explicitly define the type as AppRole so TypeScript allows comparisons
|
||||||
|
const mockRole: AppRole = AppRole.ADMIN;
|
||||||
|
const mockEmployeeId = 135;
|
||||||
|
|
||||||
|
let managedBranches: number[] = [];
|
||||||
|
|
||||||
|
if (mockRole !== AppRole.SUPER_ADMIN) {
|
||||||
|
try {
|
||||||
|
const emsDb = getDbPool("hrms_ems");
|
||||||
|
const [rows]: any = await emsDb.execute(
|
||||||
|
`SELECT branch_id FROM branch_admins WHERE employee_id = ?`,
|
||||||
|
[mockEmployeeId]
|
||||||
|
);
|
||||||
|
managedBranches = rows.map((r: any) => r.branch_id);
|
||||||
|
|
||||||
|
if (managedBranches.length === 0) managedBranches = [1];
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to fetch branch admin mappings", error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ctx.state.user = {
|
ctx.state.user = {
|
||||||
employee_id: 135, // Example ID
|
employee_id: mockEmployeeId,
|
||||||
role: AppRole.SUPER_ADMIN, // Change this to test different access levels
|
role: mockRole,
|
||||||
branch_id: 1
|
managed_branches: managedBranches
|
||||||
};
|
};
|
||||||
|
|
||||||
await next();
|
await next();
|
||||||
};
|
};
|
||||||
|
|
||||||
// 2. Authorization Middleware (What are you allowed to do?)
|
// 2. Authorization Middleware
|
||||||
export const requireRole = (allowedRoles: AppRole[]) => {
|
export const requireRole = (allowedRoles: AppRole[]) => {
|
||||||
return async (ctx: Context, next: Next) => {
|
return async (ctx: Context, next: Next) => {
|
||||||
const userRole = ctx.state.user?.role;
|
const userRole = ctx.state.user?.role;
|
||||||
|
|
||||||
if (!userRole || !allowedRoles.includes(userRole)) {
|
if (!userRole || !allowedRoles.includes(userRole)) {
|
||||||
ctx.response.status = 403;
|
ctx.response.status = 403;
|
||||||
ctx.response.body = {
|
ctx.response.body = {
|
||||||
@ -45,7 +60,6 @@ export const requireRole = (allowedRoles: AppRole[]) => {
|
|||||||
};
|
};
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await next();
|
await next();
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
Loading…
x
Reference in New Issue
Block a user