(Feat): More changes

This commit is contained in:
2025-11-28 19:04:35 +00:00
parent 25ed1d5c56
commit 8ac2eb1944
42 changed files with 3291 additions and 3407 deletions

View File

@@ -10,6 +10,7 @@ import departmentRoutes from "./routes/departments.ts";
import workAllocationRoutes from "./routes/work-allocations.ts";
import attendanceRoutes from "./routes/attendance.ts";
import contractorRateRoutes from "./routes/contractor-rates.ts";
import employeeSwapRoutes from "./routes/employee-swaps.ts";
// Initialize database connection
await db.connect();
@@ -61,6 +62,7 @@ router.use("/api/departments", departmentRoutes.routes(), departmentRoutes.allow
router.use("/api/work-allocations", workAllocationRoutes.routes(), workAllocationRoutes.allowedMethods());
router.use("/api/attendance", attendanceRoutes.routes(), attendanceRoutes.allowedMethods());
router.use("/api/contractor-rates", contractorRateRoutes.routes(), contractorRateRoutes.allowedMethods());
router.use("/api/employee-swaps", employeeSwapRoutes.routes(), employeeSwapRoutes.allowedMethods());
// Apply routes
app.use(router.routes());

View File

@@ -1,7 +1,7 @@
import { Router } from "@oak/oak";
import { db } from "../config/database.ts";
import { authenticateToken, authorize, getCurrentUser } from "../middleware/auth.ts";
import type { Attendance, CheckInOutRequest, User } from "../types/index.ts";
import type { Attendance, CheckInOutRequest, User, UpdateAttendanceStatusRequest, AttendanceStatus } from "../types/index.ts";
const router = new Router();
@@ -237,6 +237,136 @@ router.post("/check-out", authenticateToken, authorize("Supervisor", "SuperAdmin
}
});
// Update attendance status (mark as Absent, HalfDay, Late)
router.put("/:id/status", authenticateToken, authorize("Supervisor", "SuperAdmin"), async (ctx) => {
try {
const attendanceId = ctx.params.id;
const body = await ctx.request.body.json() as UpdateAttendanceStatusRequest;
const { status, remark } = body;
// Validate status
const validStatuses: AttendanceStatus[] = ["CheckedIn", "CheckedOut", "Absent", "HalfDay", "Late"];
if (!validStatuses.includes(status)) {
ctx.response.status = 400;
ctx.response.body = { error: "Invalid status. Must be one of: CheckedIn, CheckedOut, Absent, HalfDay, Late" };
return;
}
// Check if record exists
const existing = await db.query<Attendance[]>(
"SELECT * FROM attendance WHERE id = ?",
[attendanceId]
);
if (existing.length === 0) {
ctx.response.status = 404;
ctx.response.body = { error: "Attendance record not found" };
return;
}
// Update the status
await db.execute(
"UPDATE attendance SET status = ?, remark = ? WHERE id = ?",
[status, remark || null, attendanceId]
);
const updatedRecord = await db.query<Attendance[]>(
`SELECT a.*,
e.name as employee_name, e.username as employee_username,
s.name as supervisor_name,
d.name as department_name,
c.name as contractor_name
FROM attendance a
JOIN users e ON a.employee_id = e.id
JOIN users s ON a.supervisor_id = s.id
LEFT JOIN departments d ON e.department_id = d.id
LEFT JOIN users c ON e.contractor_id = c.id
WHERE a.id = ?`,
[attendanceId]
);
ctx.response.body = updatedRecord[0];
} catch (error) {
console.error("Update attendance status error:", error);
ctx.response.status = 500;
ctx.response.body = { error: "Internal server error" };
}
});
// Mark employee as absent (create absent record)
router.post("/mark-absent", authenticateToken, authorize("Supervisor", "SuperAdmin"), async (ctx) => {
try {
const currentUser = getCurrentUser(ctx);
const body = await ctx.request.body.json();
const { employeeId, workDate, remark } = body;
if (!employeeId || !workDate) {
ctx.response.status = 400;
ctx.response.body = { error: "Employee ID and work date required" };
return;
}
// Check if record already exists for this date
const existing = await db.query<Attendance[]>(
"SELECT * FROM attendance WHERE employee_id = ? AND work_date = ?",
[employeeId, workDate]
);
if (existing.length > 0) {
// Update existing record to Absent
await db.execute(
"UPDATE attendance SET status = ?, remark = ? WHERE id = ?",
["Absent", remark || "Marked absent", existing[0].id]
);
const updatedRecord = await db.query<Attendance[]>(
`SELECT a.*,
e.name as employee_name, e.username as employee_username,
s.name as supervisor_name,
d.name as department_name,
c.name as contractor_name
FROM attendance a
JOIN users e ON a.employee_id = e.id
JOIN users s ON a.supervisor_id = s.id
LEFT JOIN departments d ON e.department_id = d.id
LEFT JOIN users c ON e.contractor_id = c.id
WHERE a.id = ?`,
[existing[0].id]
);
ctx.response.body = updatedRecord[0];
} else {
// Create new absent record
const result = await db.execute(
"INSERT INTO attendance (employee_id, supervisor_id, work_date, status, remark) VALUES (?, ?, ?, ?, ?)",
[employeeId, currentUser.id, workDate, "Absent", remark || "Marked absent"]
);
const newRecord = await db.query<Attendance[]>(
`SELECT a.*,
e.name as employee_name, e.username as employee_username,
s.name as supervisor_name,
d.name as department_name,
c.name as contractor_name
FROM attendance a
JOIN users e ON a.employee_id = e.id
JOIN users s ON a.supervisor_id = s.id
LEFT JOIN departments d ON e.department_id = d.id
LEFT JOIN users c ON e.contractor_id = c.id
WHERE a.id = ?`,
[result.insertId]
);
ctx.response.status = 201;
ctx.response.body = newRecord[0];
}
} catch (error) {
console.error("Mark absent error:", error);
ctx.response.status = 500;
ctx.response.body = { error: "Internal server error" };
}
});
// Get attendance summary
router.get("/summary/stats", authenticateToken, async (ctx) => {
try {

View File

@@ -1,7 +1,13 @@
import { Router } from "@oak/oak";
import { hash, compare } from "bcrypt";
import { hash, compare, genSalt } from "bcrypt";
import { db } from "../config/database.ts";
import { config } from "../config/env.ts";
// Helper function to hash password with proper salt generation
async function hashPassword(password: string): Promise<string> {
const salt = await genSalt(config.BCRYPT_ROUNDS);
return await hash(password, salt);
}
import { authenticateToken, generateToken, getCurrentUser } from "../middleware/auth.ts";
import { sanitizeInput, isValidEmail, isStrongPassword } from "../middleware/security.ts";
import type { User, LoginRequest, ChangePasswordRequest } from "../types/index.ts";
@@ -144,7 +150,7 @@ router.post("/change-password", authenticateToken, async (ctx) => {
}
// Hash new password with configured rounds
const hashedPassword = await hash(newPassword, config.BCRYPT_ROUNDS);
const hashedPassword = await hashPassword(newPassword);
// Update password
await db.execute(

View File

@@ -0,0 +1,340 @@
import { Router } from "@oak/oak";
import { db } from "../config/database.ts";
import { authenticateToken, authorize, getCurrentUser } from "../middleware/auth.ts";
import type { EmployeeSwap, CreateSwapRequest, User } from "../types/index.ts";
const router = new Router();
// Get all employee swaps (SuperAdmin only)
router.get("/", authenticateToken, authorize("SuperAdmin"), async (ctx) => {
try {
const params = ctx.request.url.searchParams;
const status = params.get("status");
const employeeId = params.get("employeeId");
const startDate = params.get("startDate");
const endDate = 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<EmployeeSwap[]>(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) => {
try {
const swapId = ctx.params.id;
const swaps = await db.query<EmployeeSwap[]>(
`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<User[]>(
"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<EmployeeSwap[]>(
"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;
}
// Create the swap record
const result = await db.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
]
);
// Update the employee's department and contractor
await db.execute(
"UPDATE users SET department_id = ?, contractor_id = ? WHERE id = ?",
[targetDepartmentId, targetContractorId || null, employeeId]
);
// Fetch the created swap
const newSwap = await db.query<EmployeeSwap[]>(
`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 = ?`,
[result.insertId]
);
ctx.response.status = 201;
ctx.response.body = newSwap[0];
} 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<EmployeeSwap[]>(
"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];
// Return employee to original department and contractor
await db.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 db.execute(
"UPDATE employee_swaps SET status = 'Completed', completed_at = NOW() WHERE id = ?",
[swapId]
);
// Fetch updated swap
const updatedSwap = await db.query<EmployeeSwap[]>(
`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]
);
ctx.response.body = updatedSwap[0];
} 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<EmployeeSwap[]>(
"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];
// Return employee to original department and contractor
await db.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 db.execute(
"UPDATE employee_swaps SET status = 'Cancelled', completed_at = NOW() WHERE id = ?",
[swapId]
);
// Fetch updated swap
const updatedSwap = await db.query<EmployeeSwap[]>(
`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]
);
ctx.response.body = updatedSwap[0];
} catch (error) {
console.error("Cancel swap error:", error);
ctx.response.status = 500;
ctx.response.body = { error: "Internal server error" };
}
});
export default router;

View File

@@ -1,7 +1,13 @@
import { Router } from "@oak/oak";
import { hash } from "bcrypt";
import { hash, genSalt } from "bcrypt";
import { db } from "../config/database.ts";
import { config } from "../config/env.ts";
// Helper function to hash password with proper salt generation
async function hashPassword(password: string): Promise<string> {
const salt = await genSalt(config.BCRYPT_ROUNDS);
return await hash(password, salt);
}
import { authenticateToken, authorize, getCurrentUser } from "../middleware/auth.ts";
import { sanitizeInput, isValidEmail } from "../middleware/security.ts";
import type { User, CreateUserRequest, UpdateUserRequest } from "../types/index.ts";
@@ -19,6 +25,9 @@ router.get("/", authenticateToken, async (ctx) => {
let query = `
SELECT u.id, u.username, u.name, u.email, u.role, u.department_id,
u.contractor_id, u.is_active, u.created_at,
u.phone_number, u.aadhar_number, u.bank_account_number,
u.bank_name, u.bank_ifsc,
u.contractor_agreement_number, u.pf_number, u.esic_number,
d.name as department_name,
c.name as contractor_name
FROM users u
@@ -64,6 +73,9 @@ router.get("/:id", authenticateToken, async (ctx) => {
const users = await db.query<User[]>(
`SELECT u.id, u.username, u.name, u.email, u.role, u.department_id,
u.contractor_id, u.is_active, u.created_at,
u.phone_number, u.aadhar_number, u.bank_account_number,
u.bank_name, u.bank_ifsc,
u.contractor_agreement_number, u.pf_number, u.esic_number,
d.name as department_name,
c.name as contractor_name
FROM users u
@@ -99,7 +111,11 @@ router.post("/", authenticateToken, authorize("SuperAdmin", "Supervisor"), async
try {
const currentUser = getCurrentUser(ctx);
const body = await ctx.request.body.json() as CreateUserRequest;
const { username, name, email, password, role, departmentId, contractorId } = body;
const {
username, name, email, password, role, departmentId, contractorId,
phoneNumber, aadharNumber, bankAccountNumber, bankName, bankIfsc,
contractorAgreementNumber, pfNumber, esicNumber
} = body;
// Input validation
if (!username || !name || !email || !password || !role) {
@@ -135,16 +151,28 @@ router.post("/", authenticateToken, authorize("SuperAdmin", "Supervisor"), async
}
// Hash password
const hashedPassword = await hash(password, config.BCRYPT_ROUNDS);
const hashedPassword = await hashPassword(password);
const result = await db.execute(
"INSERT INTO users (username, name, email, password, role, department_id, contractor_id) VALUES (?, ?, ?, ?, ?, ?, ?)",
[sanitizedUsername, sanitizedName, sanitizedEmail, hashedPassword, role, departmentId || null, contractorId || null]
`INSERT INTO users (username, name, email, password, role, department_id, contractor_id,
phone_number, aadhar_number, bank_account_number, bank_name, bank_ifsc,
contractor_agreement_number, pf_number, esic_number)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
[
sanitizedUsername, sanitizedName, sanitizedEmail, hashedPassword, role,
departmentId || null, contractorId || null,
phoneNumber || null, aadharNumber || null, bankAccountNumber || null,
bankName || null, bankIfsc || null,
contractorAgreementNumber || null, pfNumber || null, esicNumber || null
]
);
const newUser = await db.query<User[]>(
`SELECT u.id, u.username, u.name, u.email, u.role, u.department_id,
u.contractor_id, u.is_active, u.created_at,
u.phone_number, u.aadhar_number, u.bank_account_number,
u.bank_name, u.bank_ifsc,
u.contractor_agreement_number, u.pf_number, u.esic_number,
d.name as department_name,
c.name as contractor_name
FROM users u
@@ -175,7 +203,11 @@ router.put("/:id", authenticateToken, authorize("SuperAdmin", "Supervisor"), asy
const currentUser = getCurrentUser(ctx);
const userId = ctx.params.id;
const body = await ctx.request.body.json() as UpdateUserRequest;
const { name, email, role, departmentId, contractorId, isActive } = body;
const {
name, email, role, departmentId, contractorId, isActive,
phoneNumber, aadharNumber, bankAccountNumber, bankName, bankIfsc,
contractorAgreementNumber, pfNumber, esicNumber
} = body;
// Check if user exists
const existingUsers = await db.query<User[]>(
@@ -235,6 +267,39 @@ router.put("/:id", authenticateToken, authorize("SuperAdmin", "Supervisor"), asy
updates.push("is_active = ?");
params.push(isActive);
}
// New fields
if (phoneNumber !== undefined) {
updates.push("phone_number = ?");
params.push(phoneNumber);
}
if (aadharNumber !== undefined) {
updates.push("aadhar_number = ?");
params.push(aadharNumber);
}
if (bankAccountNumber !== undefined) {
updates.push("bank_account_number = ?");
params.push(bankAccountNumber);
}
if (bankName !== undefined) {
updates.push("bank_name = ?");
params.push(bankName);
}
if (bankIfsc !== undefined) {
updates.push("bank_ifsc = ?");
params.push(bankIfsc);
}
if (contractorAgreementNumber !== undefined) {
updates.push("contractor_agreement_number = ?");
params.push(contractorAgreementNumber);
}
if (pfNumber !== undefined) {
updates.push("pf_number = ?");
params.push(pfNumber);
}
if (esicNumber !== undefined) {
updates.push("esic_number = ?");
params.push(esicNumber);
}
if (updates.length === 0) {
ctx.response.status = 400;
@@ -252,6 +317,9 @@ router.put("/:id", authenticateToken, authorize("SuperAdmin", "Supervisor"), asy
const updatedUser = await db.query<User[]>(
`SELECT u.id, u.username, u.name, u.email, u.role, u.department_id,
u.contractor_id, u.is_active, u.created_at,
u.phone_number, u.aadhar_number, u.bank_account_number,
u.bank_name, u.bank_ifsc,
u.contractor_agreement_number, u.pf_number, u.esic_number,
d.name as department_name,
c.name as contractor_name
FROM users u

View File

@@ -1,7 +1,13 @@
import { hash } from "bcrypt";
import { hash, genSalt } from "bcrypt";
import { db } from "../config/database.ts";
import { config } from "../config/env.ts";
// Helper function to hash password with proper salt generation
async function hashPassword(password: string): Promise<string> {
const salt = await genSalt(config.BCRYPT_ROUNDS);
return await hash(password, salt);
}
async function seedDatabase() {
try {
console.log("🔌 Connecting to database...");
@@ -82,7 +88,7 @@ async function seedDatabase() {
["admin"]
);
const adminPassword = await hash("admin123", config.BCRYPT_ROUNDS);
const adminPassword = await hashPassword("admin123");
if (existingAdmin.length > 0) {
await db.execute(
@@ -109,7 +115,7 @@ async function seedDatabase() {
["Dana"]
);
const supervisorPassword = await hash("supervisor123", config.BCRYPT_ROUNDS);
const supervisorPassword = await hashPassword("supervisor123");
const supervisors = [
{ username: "supervisor_tudki", name: "Tudki Supervisor", email: "supervisor.tudki@workallocate.com", deptId: tudkiDept[0]?.id },
@@ -137,11 +143,37 @@ async function seedDatabase() {
// 5. Seed Sample Contractors
console.log("🏗️ Seeding sample contractors...");
const contractorPassword = await hash("contractor123", config.BCRYPT_ROUNDS);
const contractorPassword = await hashPassword("contractor123");
const contractors = [
{ username: "contractor1", name: "Contractor One", email: "contractor1@workallocate.com", deptId: groundnutId },
{ username: "contractor2", name: "Contractor Two", email: "contractor2@workallocate.com", deptId: groundnutId }
{
username: "contractor1",
name: "Contractor One",
email: "contractor1@workallocate.com",
deptId: groundnutId,
phone: "9876543210",
aadhar: "123456789012",
bankAccount: "1234567890123456",
bankName: "State Bank of India",
bankIfsc: "SBIN0001234",
agreementNo: "AGR-2024-001",
pfNo: "PF/GJ/12345/67890",
esicNo: "12-34-567890-123-0001"
},
{
username: "contractor2",
name: "Contractor Two",
email: "contractor2@workallocate.com",
deptId: groundnutId,
phone: "9876543211",
aadhar: "234567890123",
bankAccount: "2345678901234567",
bankName: "HDFC Bank",
bankIfsc: "HDFC0001234",
agreementNo: "AGR-2024-002",
pfNo: "PF/GJ/12345/67891",
esicNo: "12-34-567890-123-0002"
}
];
for (const con of contractors) {
@@ -151,8 +183,13 @@ async function seedDatabase() {
);
if (existing.length === 0) {
await db.execute(
"INSERT INTO users (username, name, email, password, role, department_id, is_active) VALUES (?, ?, ?, ?, ?, ?, ?)",
[con.username, con.name, con.email, contractorPassword, "Contractor", con.deptId, true]
`INSERT INTO users (username, name, email, password, role, department_id, is_active,
phone_number, aadhar_number, bank_account_number, bank_name, bank_ifsc,
contractor_agreement_number, pf_number, esic_number)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
[con.username, con.name, con.email, contractorPassword, "Contractor", con.deptId, true,
con.phone, con.aadhar, con.bankAccount, con.bankName, con.bankIfsc,
con.agreementNo, con.pfNo, con.esicNo]
);
console.log(`${con.name} created`);
} else {
@@ -166,13 +203,40 @@ async function seedDatabase() {
"SELECT id FROM users WHERE username = ?",
["contractor1"]
);
const employeePassword = await hash("employee123", config.BCRYPT_ROUNDS);
const employeePassword = await hashPassword("employee123");
if (contractor1.length > 0) {
const employees = [
{ username: "employee1", name: "Employee One", email: "employee1@workallocate.com" },
{ username: "employee2", name: "Employee Two", email: "employee2@workallocate.com" },
{ username: "employee3", name: "Employee Three", email: "employee3@workallocate.com" }
{
username: "employee1",
name: "Employee One",
email: "employee1@workallocate.com",
phone: "9876543220",
aadhar: "345678901234",
bankAccount: "3456789012345678",
bankName: "Punjab National Bank",
bankIfsc: "PUNB0001234"
},
{
username: "employee2",
name: "Employee Two",
email: "employee2@workallocate.com",
phone: "9876543221",
aadhar: "456789012345",
bankAccount: "4567890123456789",
bankName: "Bank of Baroda",
bankIfsc: "BARB0001234"
},
{
username: "employee3",
name: "Employee Three",
email: "employee3@workallocate.com",
phone: "9876543222",
aadhar: "567890123456",
bankAccount: "5678901234567890",
bankName: "ICICI Bank",
bankIfsc: "ICIC0001234"
}
];
for (const emp of employees) {
@@ -182,8 +246,11 @@ async function seedDatabase() {
);
if (existing.length === 0) {
await db.execute(
"INSERT INTO users (username, name, email, password, role, department_id, contractor_id, is_active) VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
[emp.username, emp.name, emp.email, employeePassword, "Employee", groundnutId, contractor1[0].id, true]
`INSERT INTO users (username, name, email, password, role, department_id, contractor_id, is_active,
phone_number, aadhar_number, bank_account_number, bank_name, bank_ifsc)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
[emp.username, emp.name, emp.email, employeePassword, "Employee", groundnutId, contractor1[0].id, true,
emp.phone, emp.aadhar, emp.bankAccount, emp.bankName, emp.bankIfsc]
);
console.log(`${emp.name} created`);
} else {

View File

@@ -14,6 +14,16 @@ export interface User {
created_at: Date;
department_name?: string;
contractor_name?: string;
// Common fields for Employee and Contractor
phone_number?: string | null;
aadhar_number?: string | null;
bank_account_number?: string | null;
bank_name?: string | null;
bank_ifsc?: string | null;
// Contractor-specific fields
contractor_agreement_number?: string | null;
pf_number?: string | null;
esic_number?: string | null;
}
export interface JWTPayload {
@@ -66,7 +76,7 @@ export interface WorkAllocation {
}
// Attendance types
export type AttendanceStatus = "CheckedIn" | "CheckedOut" | "Absent";
export type AttendanceStatus = "CheckedIn" | "CheckedOut" | "Absent" | "HalfDay" | "Late";
export interface Attendance {
id: number;
@@ -76,6 +86,7 @@ export interface Attendance {
check_out_time: Date | null;
work_date: Date;
status: AttendanceStatus;
remark?: string | null;
created_at: Date;
employee_name?: string;
supervisor_name?: string;
@@ -83,6 +94,49 @@ export interface Attendance {
contractor_name?: string;
}
// Employee swap types
export type SwapReason = "LeftWork" | "Sick" | "FinishedEarly" | "Other";
export type SwapStatus = "Active" | "Completed" | "Cancelled";
export interface EmployeeSwap {
id: number;
employee_id: number;
original_department_id: number;
target_department_id: number;
original_contractor_id: number | null;
target_contractor_id: number | null;
swap_reason: SwapReason;
reason_details: string | null;
work_completion_percentage: number;
swap_date: Date;
swapped_by: number;
status: SwapStatus;
created_at: Date;
completed_at: Date | null;
// Joined fields
employee_name?: string;
original_department_name?: string;
target_department_name?: string;
original_contractor_name?: string;
target_contractor_name?: string;
swapped_by_name?: string;
}
export interface CreateSwapRequest {
employeeId: number;
targetDepartmentId: number;
targetContractorId?: number;
swapReason: SwapReason;
reasonDetails?: string;
workCompletionPercentage?: number;
swapDate: string;
}
export interface UpdateAttendanceStatusRequest {
status: AttendanceStatus;
remark?: string;
}
// Contractor rate types
export interface ContractorRate {
id: number;
@@ -127,6 +181,16 @@ export interface CreateUserRequest {
role: UserRole;
departmentId?: number | null;
contractorId?: number | null;
// Common fields for Employee and Contractor
phoneNumber?: string | null;
aadharNumber?: string | null;
bankAccountNumber?: string | null;
bankName?: string | null;
bankIfsc?: string | null;
// Contractor-specific fields
contractorAgreementNumber?: string | null;
pfNumber?: string | null;
esicNumber?: string | null;
}
export interface UpdateUserRequest {
@@ -136,6 +200,16 @@ export interface UpdateUserRequest {
departmentId?: number | null;
contractorId?: number | null;
isActive?: boolean;
// Common fields for Employee and Contractor
phoneNumber?: string | null;
aadharNumber?: string | null;
bankAccountNumber?: string | null;
bankName?: string | null;
bankIfsc?: string | null;
// Contractor-specific fields
contractorAgreementNumber?: string | null;
pfNumber?: string | null;
esicNumber?: string | null;
}
export interface ChangePasswordRequest {