Compare commits
No commits in common. "3d1e24d641e536260d937691a8fe3783771d8720" and "e0fdcb062aced1ef63bc77aee299a182b8acbde5" have entirely different histories.
3d1e24d641
...
e0fdcb062a
@ -1,171 +0,0 @@
|
|||||||
import { Context } from "@oak/oak";
|
|
||||||
import { getDbPool } from "../../shared/db.ts";
|
|
||||||
|
|
||||||
const amsDb = getDbPool("hrms_ams");
|
|
||||||
const emsDb = getDbPool("hrms_ems");
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetch top 20 raw biometric records for debugging/auditing
|
|
||||||
*/
|
|
||||||
export const getRawLogs = async (ctx: Context) => {
|
|
||||||
try {
|
|
||||||
const [rows] = await amsDb.execute(
|
|
||||||
"SELECT * FROM attendance_raw_logs ORDER BY attendance_time DESC LIMIT 20"
|
|
||||||
);
|
|
||||||
|
|
||||||
ctx.response.status = 200;
|
|
||||||
ctx.response.body = {
|
|
||||||
success: true,
|
|
||||||
data: rows,
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
console.error("DB Error:", error);
|
|
||||||
ctx.response.status = 500;
|
|
||||||
ctx.response.body = { success: false, message: "Database query failed" };
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Timezone-safe Daily Processing Engine
|
|
||||||
*/
|
|
||||||
export const processDailyAttendance = async (ctx: Context) => {
|
|
||||||
try {
|
|
||||||
const body = ctx.request.hasBody ? await ctx.request.body.json() : {};
|
|
||||||
const { work_date } = body;
|
|
||||||
|
|
||||||
if (!work_date) {
|
|
||||||
ctx.response.status = 400;
|
|
||||||
ctx.response.body = { success: false, message: "work_date (YYYY-MM-DD) is required." };
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 1. Fetch raw logs as strings directly from the DB to preserve device-local times
|
|
||||||
const [rawLogs]: any = await amsDb.execute(
|
|
||||||
`SELECT employee_code, DATE_FORMAT(attendance_time, '%Y-%m-%d %H:%i:%s') as attendance_time
|
|
||||||
FROM attendance_raw_logs
|
|
||||||
WHERE DATE(attendance_time) = ?
|
|
||||||
ORDER BY employee_code, attendance_time ASC`,
|
|
||||||
[work_date]
|
|
||||||
);
|
|
||||||
|
|
||||||
if (rawLogs.length === 0) {
|
|
||||||
ctx.response.status = 200;
|
|
||||||
ctx.response.body = { success: true, message: `No raw logs found for ${work_date}.` };
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Group logs by employee_code as local string arrays
|
|
||||||
const groupedLogs = rawLogs.reduce((acc: any, log: any) => {
|
|
||||||
if (!acc[log.employee_code]) acc[log.employee_code] = [];
|
|
||||||
acc[log.employee_code].push(log.attendance_time);
|
|
||||||
return acc;
|
|
||||||
}, {});
|
|
||||||
|
|
||||||
let processedCount = 0;
|
|
||||||
|
|
||||||
// 2. Process logs for each unique employee code
|
|
||||||
for (const empCode of Object.keys(groupedLogs)) {
|
|
||||||
const punches: string[] = groupedLogs[empCode];
|
|
||||||
|
|
||||||
// Map external biometric string code to internal EMS corporate entities
|
|
||||||
const [empMetaData]: any = await emsDb.execute(
|
|
||||||
`SELECT employee_id, company_id, branch_id
|
|
||||||
FROM employees
|
|
||||||
WHERE employee_code = ? AND is_active = TRUE LIMIT 1`,
|
|
||||||
[empCode]
|
|
||||||
);
|
|
||||||
|
|
||||||
if (empMetaData.length === 0) {
|
|
||||||
console.warn(`[AMS Worker] Skipping unmapped or inactive code: ${empCode}`);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { employee_id, company_id, branch_id } = empMetaData[0];
|
|
||||||
|
|
||||||
// Fetch operational shift profile rules using the Fallback pattern
|
|
||||||
const [shiftData]: any = await amsDb.execute(
|
|
||||||
`SELECT start_time, end_time, grace_period_minutes, is_night_shift
|
|
||||||
FROM shifts
|
|
||||||
WHERE company_id = ? AND (branch_id = ? OR branch_id IS NULL)
|
|
||||||
ORDER BY branch_id DESC LIMIT 1`,
|
|
||||||
[company_id, branch_id]
|
|
||||||
);
|
|
||||||
|
|
||||||
const shift = shiftData[0] || { start_time: "09:30:00", grace_period_minutes: 15, is_night_shift: false };
|
|
||||||
|
|
||||||
let check_in: string | null = null;
|
|
||||||
let check_out: string | null = null;
|
|
||||||
let worked_hours = 0.00;
|
|
||||||
let check_in_status = "ABSENT";
|
|
||||||
let final_status = "ABSENT";
|
|
||||||
|
|
||||||
if (punches.length >= 2) {
|
|
||||||
check_in = punches[0];
|
|
||||||
check_out = punches[punches.length - 1];
|
|
||||||
|
|
||||||
// Safely parse internal local duration metrics via string substitution
|
|
||||||
const firstPunchMs = new Date(check_in.replace(' ', 'T')).getTime();
|
|
||||||
const lastPunchMs = new Date(check_out.replace(' ', 'T')).getTime();
|
|
||||||
const diffMs = lastPunchMs - firstPunchMs;
|
|
||||||
worked_hours = Math.round((diffMs / (1000 * 60 * 60)) * 100) / 100;
|
|
||||||
|
|
||||||
// Perform time calculations purely in total seconds to bypass UTC offset traps
|
|
||||||
const rawCheckInTimeStr = check_in.split(' ')[1];
|
|
||||||
const [punchH, punchM, punchS] = rawCheckInTimeStr.split(':').map(Number);
|
|
||||||
const [shiftH, shiftM, shiftS] = shift.start_time.split(':').map(Number);
|
|
||||||
|
|
||||||
const punchSeconds = (punchH * 3600) + (punchM * 60) + punchS;
|
|
||||||
const shiftSeconds = (shiftH * 3600) + (shiftM * 60) + shiftS;
|
|
||||||
const graceSeconds = shift.grace_period_minutes * 60;
|
|
||||||
|
|
||||||
// Determine Punctuality Status for the Check-In ledger column
|
|
||||||
if (punchSeconds > (shiftSeconds + graceSeconds)) {
|
|
||||||
check_in_status = "LATE";
|
|
||||||
} else {
|
|
||||||
check_in_status = "ON_TIME";
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply corporate duration rules
|
|
||||||
// Evaluate Shift Duration Rules securely within ENUM compliance bounds
|
|
||||||
if (worked_hours >= 7.0) {
|
|
||||||
final_status = "FULL_DAY"; // Safely maps to the allowed database ENUM value
|
|
||||||
} else if (worked_hours >= 4.0 && worked_hours < 7.0) {
|
|
||||||
final_status = "HALF_DAY";
|
|
||||||
} else {
|
|
||||||
final_status = "ABSENT";
|
|
||||||
}
|
|
||||||
|
|
||||||
} else if (punches.length === 1) {
|
|
||||||
check_in = punches[0];
|
|
||||||
check_in_status = "MISPUNCH";
|
|
||||||
final_status = "MISPUNCH";
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. Operational Upsert into the Processed Attendance Summary ledger
|
|
||||||
await amsDb.execute(
|
|
||||||
`INSERT INTO processed_daily_attendance
|
|
||||||
(employee_id, company_id, branch_id, work_date, check_in, check_out, worked_hours, check_in_status, final_status)
|
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
||||||
ON DUPLICATE KEY UPDATE
|
|
||||||
check_in = VALUES(check_in),
|
|
||||||
check_out = VALUES(check_out),
|
|
||||||
worked_hours = VALUES(worked_hours),
|
|
||||||
check_in_status = VALUES(check_in_status),
|
|
||||||
final_status = VALUES(final_status)`,
|
|
||||||
[employee_id, company_id, branch_id, work_date, check_in, check_out, worked_hours, check_in_status, final_status]
|
|
||||||
);
|
|
||||||
|
|
||||||
processedCount++;
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.response.status = 200;
|
|
||||||
ctx.response.body = {
|
|
||||||
success: true,
|
|
||||||
message: `Successfully processed attendance for ${processedCount} employees on ${work_date}.`,
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Processing Engine Error:", error);
|
|
||||||
ctx.response.status = 500;
|
|
||||||
ctx.response.body = { success: false, message: "Internal Server Processing Error." };
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@ -1,16 +0,0 @@
|
|||||||
import { Application } from "@oak/oak";
|
|
||||||
import router from "./routes.ts";
|
|
||||||
|
|
||||||
const app = new Application();
|
|
||||||
const PORT = 8002; // Running AMS on 8002 to avoid conflict with EMS on 8001
|
|
||||||
|
|
||||||
app.use(async (ctx, next) => {
|
|
||||||
console.log(`[AMS] ${ctx.request.method} ${ctx.request.url.pathname}`);
|
|
||||||
await next();
|
|
||||||
});
|
|
||||||
|
|
||||||
app.use(router.routes());
|
|
||||||
app.use(router.allowedMethods());
|
|
||||||
|
|
||||||
console.log(`AMS Service running on http://localhost:${PORT}`);
|
|
||||||
await app.listen({ port: PORT });
|
|
||||||
@ -1,12 +0,0 @@
|
|||||||
import { Router } from "@oak/oak";
|
|
||||||
import { getRawLogs, processDailyAttendance } from "./controllers/attendance.controller.ts";
|
|
||||||
|
|
||||||
const router = new Router();
|
|
||||||
const apiV1 = new Router();
|
|
||||||
|
|
||||||
apiV1.get("/attendance/logs", getRawLogs);
|
|
||||||
apiV1.post("/attendance/process-daily", processDailyAttendance);
|
|
||||||
|
|
||||||
router.use("/api/v1", apiV1.routes(), apiV1.allowedMethods());
|
|
||||||
|
|
||||||
export default router;
|
|
||||||
@ -1,9 +1,9 @@
|
|||||||
version: '3.8'
|
version: '3.8'
|
||||||
|
|
||||||
services:
|
services:
|
||||||
hrms_db:
|
ems_mysql:
|
||||||
image: docker.io/library/mysql:8.4
|
image: docker.io/library/mysql:8.4
|
||||||
container_name: hrms_db
|
container_name: hrms_ems_db
|
||||||
environment:
|
environment:
|
||||||
MYSQL_ROOT_PASSWORD: root
|
MYSQL_ROOT_PASSWORD: root
|
||||||
MYSQL_DATABASE: hrms_ems
|
MYSQL_DATABASE: hrms_ems
|
||||||
@ -12,9 +12,9 @@ services:
|
|||||||
ports:
|
ports:
|
||||||
- "3306:3306"
|
- "3306:3306"
|
||||||
volumes:
|
volumes:
|
||||||
- ./init-db:/docker-entrypoint-initdb.d:Z
|
- ./init-db:/docker-entrypoint-initdb.d:Z # <-- Added :Z here
|
||||||
- hrms_db_data:/var/lib/mysql
|
- mysql_ems_data:/var/lib/mysql
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
hrms_db_data:
|
mysql_ems_data:
|
||||||
@ -1,6 +1,4 @@
|
|||||||
import { getDbPool } from "../../shared/db.ts"
|
import pool from "../../shared/db.ts";
|
||||||
|
|
||||||
const pool = getDbPool("hrms_ems");
|
|
||||||
|
|
||||||
export const getEmployeeContracts = async (ctx: any) => {
|
export const getEmployeeContracts = async (ctx: any) => {
|
||||||
const employeeId = ctx.params.id;
|
const employeeId = ctx.params.id;
|
||||||
|
|||||||
@ -1,6 +1,4 @@
|
|||||||
import { getDbPool } from "../../shared/db.ts"
|
import pool from "../../shared/db.ts";
|
||||||
|
|
||||||
const pool = getDbPool("hrms_ems");
|
|
||||||
|
|
||||||
export const getEmployees = async (ctx: any) => {
|
export const getEmployees = async (ctx: any) => {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@ -1,6 +1,4 @@
|
|||||||
import { getDbPool } from "../../shared/db.ts"
|
import pool from "../../shared/db.ts";
|
||||||
|
|
||||||
const pool = getDbPool("hrms_ems");
|
|
||||||
|
|
||||||
export const getCompanies = async (ctx: any) => {
|
export const getCompanies = async (ctx: any) => {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@ -1,97 +0,0 @@
|
|||||||
import { Context } from "@oak/oak";
|
|
||||||
import { getDbPool } from "../../shared/db.ts";
|
|
||||||
|
|
||||||
const emsPool = getDbPool("hrms_ems");
|
|
||||||
|
|
||||||
export const bulkSeedEmployees = async (ctx: Context) => {
|
|
||||||
const body = ctx.request.hasBody ? await ctx.request.body.json() : [];
|
|
||||||
|
|
||||||
if (!Array.isArray(body) || body.length === 0) {
|
|
||||||
ctx.response.status = 400;
|
|
||||||
ctx.response.body = { success: false, message: "Provide an array of employee logs." };
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const connection = await emsPool.getConnection();
|
|
||||||
let insertedCount = 0;
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Start transaction to keep data clean
|
|
||||||
await connection.beginTransaction();
|
|
||||||
|
|
||||||
for (const item of body) {
|
|
||||||
const { emp_code, name, designation, department, email } = item;
|
|
||||||
|
|
||||||
// Split first and last name if possible, fallback if singular name
|
|
||||||
const nameParts = name.trim().split(" ");
|
|
||||||
const firstName = nameParts[0] || "Employee";
|
|
||||||
const lastName = nameParts.slice(1).join(" ") || "LNU";
|
|
||||||
|
|
||||||
// 1. Insert into partners (identity layer) with fallback defaults
|
|
||||||
const [partnerResult]: any = await connection.execute(
|
|
||||||
`INSERT INTO partners (first_name, last_name, dob, gender, personal_email, personal_phone)
|
|
||||||
VALUES (?, ?, '1995-01-01', 'Not Specified', ?, ?)
|
|
||||||
ON DUPLICATE KEY UPDATE partner_id = LAST_INSERT_ID(partner_id)`,
|
|
||||||
[firstName, lastName, `personal.${email}`, `MOCK_${emp_code}`]
|
|
||||||
);
|
|
||||||
const partnerId = partnerResult.insertId;
|
|
||||||
|
|
||||||
// 2. Ensure the department exists, dynamically fetch or create its ID
|
|
||||||
const [deptResult]: any = await connection.execute(
|
|
||||||
`INSERT INTO departments (company_id, name)
|
|
||||||
VALUES (1, ?)
|
|
||||||
ON DUPLICATE KEY UPDATE department_id = LAST_INSERT_ID(department_id)`,
|
|
||||||
[department || "General"]
|
|
||||||
);
|
|
||||||
const departmentId = deptResult.insertId;
|
|
||||||
|
|
||||||
// 3. Ensure the job position exists, dynamically fetch or create its ID
|
|
||||||
const [jobResult]: any = await connection.execute(
|
|
||||||
`INSERT INTO job_positions (company_id, title)
|
|
||||||
VALUES (1, ?)
|
|
||||||
ON DUPLICATE KEY UPDATE job_id = LAST_INSERT_ID(job_id)`,
|
|
||||||
[designation || "Trainee"]
|
|
||||||
);
|
|
||||||
const jobId = jobResult.insertId;
|
|
||||||
|
|
||||||
// 4. Create the core Employee record mapping to the KENT code
|
|
||||||
const [empResult]: any = await connection.execute(
|
|
||||||
`INSERT INTO employees (employee_code, partner_id, company_id, branch_id, is_active)
|
|
||||||
VALUES (?, ?, 1, 1, TRUE)
|
|
||||||
ON DUPLICATE KEY UPDATE employee_id = LAST_INSERT_ID(employee_id)`,
|
|
||||||
[emp_code, partnerId]
|
|
||||||
);
|
|
||||||
const employeeId = empResult.insertId;
|
|
||||||
|
|
||||||
// 5. Establish the operational Contract record with the corporate work email
|
|
||||||
await connection.execute(
|
|
||||||
`INSERT INTO contracts (employee_id, department_id, job_id, work_email, date_joining, status, salary_structure_id)
|
|
||||||
VALUES (?, ?, ?, ?, '2026-01-01', 'ACTIVE', 100)
|
|
||||||
ON DUPLICATE KEY UPDATE work_email = VALUES(work_email)`,
|
|
||||||
[employeeId, departmentId, jobId, email]
|
|
||||||
);
|
|
||||||
|
|
||||||
insertedCount++;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Commit changes safely to EMS
|
|
||||||
await connection.commit();
|
|
||||||
|
|
||||||
ctx.response.status = 200;
|
|
||||||
ctx.response.body = {
|
|
||||||
success: true,
|
|
||||||
message: `Successfully seeded/aligned ${insertedCount} normalized employee profiles.`,
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
await connection.rollback();
|
|
||||||
console.error("Seeding failed, changes rolled back:", error);
|
|
||||||
|
|
||||||
// Fixed: Cast error to 'any' or fallback to a string to satisfy deno-ts(18046)
|
|
||||||
const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
|
|
||||||
|
|
||||||
ctx.response.status = 500;
|
|
||||||
ctx.response.body = { success: false, error: errorMessage };
|
|
||||||
} finally {
|
|
||||||
connection.release();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@ -17,10 +17,6 @@ import {
|
|||||||
createContract
|
createContract
|
||||||
} from "./controllers/contract.controller.ts";
|
} from "./controllers/contract.controller.ts";
|
||||||
|
|
||||||
import {
|
|
||||||
bulkSeedEmployees
|
|
||||||
} from "./controllers/system.controller.ts"
|
|
||||||
|
|
||||||
const router = new Router();
|
const router = new Router();
|
||||||
|
|
||||||
// --- Employee Core Endpoints ---
|
// --- Employee Core Endpoints ---
|
||||||
@ -62,6 +58,4 @@ router.get("/api/v1/departments", getDepartments);
|
|||||||
//List company designations and employment titles.
|
//List company designations and employment titles.
|
||||||
router.get("/api/v1/jobs", getJobs);
|
router.get("/api/v1/jobs", getJobs);
|
||||||
|
|
||||||
router.post("/api/v1/system/seed-employees", bulkSeedEmployees);
|
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
@ -1,63 +0,0 @@
|
|||||||
CREATE DATABASE IF NOT EXISTS hrms_ams;
|
|
||||||
USE hrms_ams;
|
|
||||||
|
|
||||||
-- Grant access to the existing admin user
|
|
||||||
GRANT ALL PRIVILEGES ON hrms_ams.* TO 'admin'@'%';
|
|
||||||
FLUSH PRIVILEGES;
|
|
||||||
|
|
||||||
-- 1. Shift Schedules (Supports the Fallback Pattern)
|
|
||||||
CREATE TABLE shifts (
|
|
||||||
shift_id INT AUTO_INCREMENT PRIMARY KEY,
|
|
||||||
company_id INT NOT NULL, -- Logical Reference from EMS
|
|
||||||
branch_id INT NULL, -- Fallback Pattern: NULL means applies company-wide
|
|
||||||
shift_name VARCHAR(50) NOT NULL,
|
|
||||||
start_time TIME NOT NULL,
|
|
||||||
end_time TIME NOT NULL,
|
|
||||||
grace_period_minutes INT DEFAULT 15,
|
|
||||||
is_night_shift BOOLEAN DEFAULT FALSE, -- Handles overnight calculations (e.g., 22:00 to 06:00)
|
|
||||||
INDEX idx_fallback_lookup (company_id, branch_id)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
|
||||||
|
|
||||||
-- 2. Raw Biometric Scan Sink (Ingested directly from KENT Device pipeline)
|
|
||||||
CREATE TABLE attendance_raw_logs (
|
|
||||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
|
||||||
attendance_time TIMESTAMP NOT NULL,
|
|
||||||
device_id VARCHAR(50) NOT NULL,
|
|
||||||
device_name VARCHAR(100) NULL,
|
|
||||||
employee_id INT NULL, -- Logical Reference from EMS
|
|
||||||
employee_code VARCHAR(20) NOT NULL, -- Cached for quick biometric mapping checks
|
|
||||||
employee_name VARCHAR(100) NULL,
|
|
||||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
INDEX idx_emp_time (employee_id, attendance_time)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
|
||||||
|
|
||||||
-- 3. Processed Attendance Summary (Populated nightly via automated Worker Engine)
|
|
||||||
CREATE TABLE processed_daily_attendance (
|
|
||||||
attendance_id INT AUTO_INCREMENT PRIMARY KEY,
|
|
||||||
employee_id INT NOT NULL, -- Logical Reference from EMS
|
|
||||||
company_id INT NOT NULL, -- Cached to accelerate scoped multi-branch analytics
|
|
||||||
branch_id INT NOT NULL, -- Cached to accelerate scoped multi-branch analytics
|
|
||||||
work_date DATE NOT NULL,
|
|
||||||
check_in TIMESTAMP NULL,
|
|
||||||
check_out TIMESTAMP NULL,
|
|
||||||
worked_hours DECIMAL(5,2) DEFAULT 0.00,
|
|
||||||
check_in_status ENUM('ON_TIME', 'LATE', 'MISPUNCH', 'ABSENT') NOT NULL,
|
|
||||||
final_status ENUM('FULL_DAY', 'HALF_DAY', 'ABSENT', 'MISPUNCH') NOT NULL,
|
|
||||||
UNIQUE KEY uq_emp_date (employee_id, work_date),
|
|
||||||
INDEX idx_reporting_lookup (company_id, branch_id, work_date)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
|
||||||
|
|
||||||
-- 4. Correction Workflow Requests
|
|
||||||
CREATE TABLE attendance_regularizations (
|
|
||||||
regularization_id INT AUTO_INCREMENT PRIMARY KEY,
|
|
||||||
employee_id INT NOT NULL, -- Logical Reference (The Applicant)
|
|
||||||
attendance_id INT NULL, -- Internal FK to processed ledger row
|
|
||||||
regularization_type ENUM('MISPUNCH', 'WFH_REQUEST', 'ON_DUTY') NOT NULL,
|
|
||||||
target_date DATE NOT NULL,
|
|
||||||
requested_check_in TIMESTAMP NULL,
|
|
||||||
requested_check_out TIMESTAMP NULL,
|
|
||||||
reason TEXT NOT NULL,
|
|
||||||
status ENUM('PENDING', 'APPROVED', 'REJECTED') DEFAULT 'PENDING',
|
|
||||||
reviewed_by_id INT NOT NULL, -- Logical Reference to Manager's employee_id
|
|
||||||
FOREIGN KEY (attendance_id) REFERENCES processed_daily_attendance(attendance_id) ON DELETE SET NULL
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
|
||||||
19
shared/db.ts
19
shared/db.ts
@ -1,23 +1,16 @@
|
|||||||
import mysql from "mysql2/promise";
|
import mysql from "mysql2/promise";
|
||||||
|
|
||||||
const dbConfig = {
|
// Create a connection pool using the credentials from docker-compose.yml
|
||||||
|
const pool = mysql.createPool({
|
||||||
host: "127.0.0.1",
|
host: "127.0.0.1",
|
||||||
port: 3306,
|
port: 3306,
|
||||||
user: "admin",
|
user: "admin",
|
||||||
password: "admin123",
|
password: "admin123",
|
||||||
|
database: "hrms_ems",
|
||||||
waitForConnections: true,
|
waitForConnections: true,
|
||||||
connectionLimit: 10,
|
connectionLimit: 10,
|
||||||
queueLimit: 0,
|
queueLimit: 0,
|
||||||
};
|
});
|
||||||
|
|
||||||
const pools: Record<string, mysql.Pool> = {};
|
// Export the pool to be used across your EMS, AMS, and LMS services
|
||||||
|
export default pool;
|
||||||
export const getDbPool = (databaseName: string): mysql.Pool => {
|
|
||||||
if (!pools[databaseName]) {
|
|
||||||
pools[databaseName] = mysql.createPool({
|
|
||||||
...dbConfig,
|
|
||||||
database: databaseName,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return pools[databaseName];
|
|
||||||
};
|
|
||||||
Loading…
x
Reference in New Issue
Block a user