(Feat): More changes
This commit is contained in:
@@ -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());
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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(
|
||||
|
||||
340
backend-deno/routes/employee-swaps.ts
Normal file
340
backend-deno/routes/employee-swaps.ts
Normal 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;
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user