Compare commits

..

2 Commits

8 changed files with 3410 additions and 37 deletions

2018
ems-service/AllStaff.json Normal file

File diff suppressed because it is too large Load Diff

View File

@ -9,7 +9,7 @@ export const getEmployeeContracts = async (ctx: any) => {
const [rows] = await pool.query(
`SELECT
c.contract_id,
e.work_email,
c.work_email,
c.date_joining,
c.probation_days,
c.status,

View File

@ -15,7 +15,7 @@ export const getCompanies = async (ctx: any) => {
export const getBranches = async (ctx: any) => {
try {
const [rows] = await pool.query(`SELECT branch_id, name, company_id FROM branches`);
const [rows] = await pool.query(`SELECT branch_id, branch_name,code, company_id FROM branches`);
ctx.response.status = 200;
ctx.response.body = { success: true, data: rows };
} catch (error) {
@ -37,7 +37,7 @@ export const getDepartments = async (ctx: any) => {
export const getJobs = async (ctx: any) => {
try {
const [rows] = await pool.query(`SELECT job_id, title, department_id FROM job_positions`);
const [rows] = await pool.query(`SELECT job_id, title, company_id FROM job_positions`);
ctx.response.status = 200;
ctx.response.body = { success: true, data: rows };
} catch (error) {

View File

@ -95,3 +95,92 @@ export const bulkSeedEmployees = async (ctx: Context) => {
connection.release();
}
};
export const bulkMapHierarchy = async (ctx: Context) => {
const body = ctx.request.hasBody ? await ctx.request.body.json() : {};
const { employeeHierarchy = [], departmentHierarchy = [] } = body;
if (employeeHierarchy.length === 0 && departmentHierarchy.length === 0) {
ctx.response.status = 400;
ctx.response.body = { success: false, message: "Provide hierarchy mapping arrays." };
return;
}
const connection = await emsPool.getConnection();
let contractsUpdated = 0;
let departmentsUpdated = 0;
try {
await connection.beginTransaction();
// 1. Map Employees to their Managers
for (const mapping of employeeHierarchy) {
const { emp_code, manager_code } = mapping;
if (!emp_code || !manager_code) continue;
const [result]: any = await connection.execute(
`UPDATE contracts c
JOIN employees e ON c.employee_id = e.employee_id
JOIN employees m ON m.employee_code = ?
SET c.reporting_to_id = m.employee_id
WHERE e.employee_code = ? AND c.status = 'ACTIVE'`,
[manager_code, emp_code]
);
if (result.affectedRows > 0) contractsUpdated++;
}
// 2. Map Departments to their Managers and Parent Departments
for (const dept of departmentHierarchy) {
const { department_name, manager_code, parent_department_name } = dept;
if (!department_name) continue;
// Update Department Manager
if (manager_code) {
await connection.execute(
`UPDATE departments d
JOIN employees m ON m.employee_code = ?
SET d.manager_id = m.employee_id
WHERE d.name = ?`,
[manager_code, department_name]
);
}
// Update Parent Department Hierarchy
if (parent_department_name) {
await connection.execute(
`UPDATE departments d
JOIN departments p ON p.name = ?
SET d.parent_id = p.department_id
WHERE d.name = ?`,
[parent_department_name, department_name]
);
}
departmentsUpdated++;
}
await connection.commit();
ctx.response.status = 200;
ctx.response.body = {
success: true,
message: "Hierarchy mapping completed successfully.",
metrics: {
contractsUpdated,
departmentsProcessed: departmentsUpdated
}
};
} catch (error) {
await connection.rollback();
console.error("Hierarchy mapping failed:", error);
const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
ctx.response.status = 500;
ctx.response.body = { success: false, error: errorMessage };
} finally {
connection.release();
}
};

View File

@ -0,0 +1,82 @@
// generate_mapping.ts
import data from "./AllStaff.json" with { type: "json" };
// 1. Helper to clean up messy department strings
const normalizeDept = (dept: string) => {
if (dept.toLowerCase() === "finance" || dept === "FInance") return "Finance";
if (dept.toLowerCase() === "digital marketing") return "Digital Marketing";
return dept.trim();
};
// 2. Helper to determine leadership rank based on designation
const getRank = (designation: string) => {
const title = designation.toLowerCase();
if (title.includes("cfo") || title.includes("vice president") || title.includes("co- founder")) return 100;
if (title.includes("senior manager")) return 90;
if (title.includes("manager") && !title.includes("assistant")) return 80;
if (title.includes("assistant manager")) return 70;
if (title.includes("team leader")) return 60;
return 0; // Regular employee
};
const departmentsMap = new Map();
const employeeHierarchy: any[] = [];
const departmentHierarchy: any[] = [];
// 3. First Pass: Group employees by department and find the leader
for (const emp of data) {
const deptName = normalizeDept(emp.department);
const rank = getRank(emp.designation);
if (!departmentsMap.has(deptName)) {
departmentsMap.set(deptName, { name: deptName, leaderCode: null, highestRank: -1 });
}
const currentDept = departmentsMap.get(deptName);
// If this employee has a higher rank than the current department leader, replace them
if (rank > currentDept.highestRank) {
currentDept.leaderCode = emp.emp_code;
currentDept.highestRank = rank;
}
}
// 4. Second Pass: Build the mappings
for (const emp of data) {
const deptName = normalizeDept(emp.department);
const deptInfo = departmentsMap.get(deptName);
// Only map the employee if they aren't the leader themselves and a leader exists
let managerCode = deptInfo.leaderCode;
if (managerCode === emp.emp_code) {
managerCode = null; // The top boss doesn't report to themselves in this scope
}
employeeHierarchy.push({
emp_code: emp.emp_code,
manager_code: managerCode
});
}
// 5. Build Department Hierarchy
for (const [deptName, info] of departmentsMap.entries()) {
departmentHierarchy.push({
department_name: deptName,
manager_code: info.leaderCode,
parent_department_name: null // You can manually tweak parent/child relationships later
});
}
// 6. Write the final JSON payload
const output = {
departmentHierarchy,
employeeHierarchy
};
await Deno.writeTextFile(
"./hierarchy_mapping.json",
JSON.stringify(output, null, 2)
);
console.log(`Successfully mapped ${employeeHierarchy.length} employees across ${departmentsMap.size} departments!`);
console.log("File saved to ./hierarchy_mapping.json");

File diff suppressed because it is too large Load Diff

View File

@ -5,7 +5,7 @@ import {
getEmployeeById,
updateEmployee,
deleteEmployee
} from "./controllers/employee.controller.ts";
} from "./controllers/employee.controller.ts";
import {
getCompanies,
getBranches,
@ -16,52 +16,73 @@ import {
getEmployeeContracts,
createContract
} from "./controllers/contract.controller.ts";
import {
bulkSeedEmployees
} from "./controllers/system.controller.ts"
bulkSeedEmployees,
bulkMapHierarchy
} from "./controllers/system.controller.ts";
// Shared authentication and authorization middleware
import { requireAuth, requireRole, AppRole } from "../shared/auth.ts";
const router = new Router();
// --- Employee Core Endpoints ---
// ============================================================================
// EMPLOYEE CORE DOMAIN
// Manages the corporate identity (employees) and personal data (partners).
// ============================================================================
// Retrieve all employees
router.get("/api/v1/employees", getEmployees);
// Retrieve a list of all active employees. Visible to management and administrators.
router.get("/api/v1/employees", requireAuth, requireRole([AppRole.SUPER_ADMIN, AppRole.ADMIN, AppRole.MANAGER]), getEmployees);
// Retrieve a single employee's comprehensive profile
router.get("/api/v1/employees/:id", getEmployeeById);
// Retrieve a single employee's comprehensive profile (identity, address, contract). Accessible to any authenticated user.
router.get("/api/v1/employees/:id", requireAuth, getEmployeeById);
// Create a new employee
router.post("/api/v1/employees", createEmployee);
// Create a new employee profile and initial contract. Restricted to administrators.
router.post("/api/v1/employees", requireAuth, requireRole([AppRole.SUPER_ADMIN, AppRole.ADMIN]), createEmployee);
// Update an existing employee
router.put("/api/v1/employees/:id", updateEmployee);
// Update an existing employee's personal or address data. Restricted to administrators.
router.put("/api/v1/employees/:id", requireAuth, requireRole([AppRole.SUPER_ADMIN, AppRole.ADMIN]), updateEmployee);
// Deactivate an employee
router.delete("/api/v1/employees/:id", deleteEmployee);
// Soft-delete (deactivate) an employee account. Restricted to administrators.
router.delete("/api/v1/employees/:id", requireAuth, requireRole([AppRole.SUPER_ADMIN, AppRole.ADMIN]), deleteEmployee);
// --- Contract Endpoints ---
// ============================================================================
// CONTRACT & ASSIGNMENT DOMAIN
// Manages operational assignments, reporting hierarchies, and job roles.
// ============================================================================
//Retrieve contract history for an individual employee
router.get("/api/v1/employees/:id/contracts", getEmployeeContracts);
// Retrieve the contract history for an individual employee.
router.get("/api/v1/employees/:id/contracts", requireAuth, getEmployeeContracts);
//Terminate, transition, or execute a new employment agreement.
router.post("/api/v1/contracts", createContract);
// Expire current active contracts and execute a new employment agreement.
router.post("/api/v1/contracts", requireAuth, requireRole([AppRole.SUPER_ADMIN, AppRole.ADMIN]), createContract);
// --- Organizational Lookup Endpoints ---
// ============================================================================
// ORGANIZATIONAL LOOKUP DOMAIN
// Read-only reference endpoints for the frontend UI to populate select options.
// ============================================================================
// Fetch legal entities and parent group structures.
router.get("/api/v1/companies", getCompanies);
router.get("/api/v1/companies", requireAuth, getCompanies);
//Fetch structural branch offices
router.get("/api/v1/branches", getBranches);
// Fetch structural branch offices and physical locations.
router.get("/api/v1/branches", requireAuth, getBranches);
//List corporate departments and organizational chart reporting lines
router.get("/api/v1/departments", getDepartments);
// List corporate departments and organizational chart reporting lines.
router.get("/api/v1/departments", requireAuth, getDepartments);
//List company designations and employment titles.
router.get("/api/v1/jobs", getJobs);
// List company designations and employment titles.
router.get("/api/v1/jobs", requireAuth, getJobs);
router.post("/api/v1/system/seed-employees", bulkSeedEmployees);
// ============================================================================
// SYSTEM & MIGRATION DOMAIN
// High-risk administrative endpoints for bulk data execution.
// ============================================================================
// Process raw bulk data to seed initial employee/partner structures into the DB.
router.post("/api/v1/system/seed-employees", requireAuth, requireRole([AppRole.SUPER_ADMIN]), bulkSeedEmployees);
// Execute mapping script to establish reporting_to_id and manager_id links.
router.post("/api/v1/system/map-hierarchy", requireAuth, requireRole([AppRole.SUPER_ADMIN]), bulkMapHierarchy);
export default router;

69
ems-service/routes.ts.bkp Normal file
View File

@ -0,0 +1,69 @@
import { Router } from "@oak/oak";
import {
getEmployees,
createEmployee,
getEmployeeById,
updateEmployee,
deleteEmployee
} from "./controllers/employee.controller.ts";
import {
getCompanies,
getBranches,
getDepartments,
getJobs
} from "./controllers/lookup.controller.ts";
import {
getEmployeeContracts,
createContract
} from "./controllers/contract.controller.ts";
import {
bulkSeedEmployees,
bulkMapHierarchy
} from "./controllers/system.controller.ts"
const router = new Router();
// --- Employee Core Endpoints ---
// Retrieve all employees
router.get("/api/v1/employees", getEmployees);
// Retrieve a single employee's comprehensive profile
router.get("/api/v1/employees/:id", getEmployeeById);
// Create a new employee
router.post("/api/v1/employees", createEmployee);
// Update an existing employee
router.put("/api/v1/employees/:id", updateEmployee);
// Deactivate an employee
router.delete("/api/v1/employees/:id", deleteEmployee);
// --- Contract Endpoints ---
//Retrieve contract history for an individual employee
router.get("/api/v1/employees/:id/contracts", getEmployeeContracts);
//Terminate, transition, or execute a new employment agreement.
router.post("/api/v1/contracts", createContract);
// --- Organizational Lookup Endpoints ---
// Fetch legal entities and parent group structures.
router.get("/api/v1/companies", getCompanies);
//Fetch structural branch offices
router.get("/api/v1/branches", getBranches);
//List corporate departments and organizational chart reporting lines
router.get("/api/v1/departments", getDepartments);
//List company designations and employment titles.
router.get("/api/v1/jobs", getJobs);
router.post("/api/v1/system/seed-employees", bulkSeedEmployees);
router.post("/api/v1/system/map-hierarchy", bulkMapHierarchy);
export default router;