(Feat-Fix): New Reporting system, more seeded data, fixed subdepartments and activity inversion, login page changes, etc etc

This commit is contained in:
2025-12-18 08:15:31 +00:00
parent 916ee19677
commit ac29bb2882
24 changed files with 3306 additions and 207 deletions

View File

@@ -3,7 +3,7 @@ DB_HOST=localhost
DB_USER=root
DB_PASSWORD=admin123
DB_NAME=work_allocation
DB_PORT=3306
DB_PORT=3307
# JWT Configuration - CHANGE IN PRODUCTION!
JWT_SECRET=work_alloc_jwt_secret_key_change_in_production_2024

View File

@@ -11,6 +11,9 @@ 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";
import reportRoutes from "./routes/reports.ts";
import standardRateRoutes from "./routes/standard-rates.ts";
import activityRoutes from "./routes/activities.ts";
// Initialize database connection
await db.connect();
@@ -63,6 +66,9 @@ router.use("/api/work-allocations", workAllocationRoutes.routes(), workAllocatio
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());
router.use("/api/reports", reportRoutes.routes(), reportRoutes.allowedMethods());
router.use("/api/standard-rates", standardRateRoutes.routes(), standardRateRoutes.allowedMethods());
router.use("/api/activities", activityRoutes.routes(), activityRoutes.allowedMethods());
// Apply routes
app.use(router.routes());

View File

@@ -0,0 +1,153 @@
import { Router } from "@oak/oak";
import { db } from "../config/database.ts";
import { authenticateToken } from "../middleware/auth.ts";
const router = new Router();
interface Activity {
id: number;
sub_department_id: number;
name: string;
unit_of_measurement: string;
created_at: string;
sub_department_name?: string;
department_id?: number;
department_name?: string;
}
// Get all activities (with optional filters)
router.get("/", authenticateToken, async (ctx) => {
try {
const params = ctx.request.url.searchParams;
const subDepartmentId = params.get("subDepartmentId");
const departmentId = params.get("departmentId");
let query = `
SELECT a.id, a.sub_department_id, a.name, a.unit_of_measurement, a.created_at,
sd.name as sub_department_name,
sd.department_id,
d.name as department_name
FROM activities a
JOIN sub_departments sd ON a.sub_department_id = sd.id
JOIN departments d ON sd.department_id = d.id
WHERE 1=1
`;
const queryParams: unknown[] = [];
if (subDepartmentId) {
query += " AND a.sub_department_id = ?";
queryParams.push(subDepartmentId);
}
if (departmentId) {
query += " AND sd.department_id = ?";
queryParams.push(departmentId);
}
query += " ORDER BY d.name, sd.name, a.name";
const activities = await db.query<Activity[]>(query, queryParams);
ctx.response.body = activities;
} catch (error) {
console.error("Get activities error:", error);
ctx.response.status = 500;
ctx.response.body = { error: "Internal server error" };
}
});
// Get activity by ID
router.get("/:id", authenticateToken, async (ctx) => {
try {
const activityId = ctx.params.id;
const activities = await db.query<Activity[]>(
`SELECT a.id, a.sub_department_id, a.name, a.unit_of_measurement, a.created_at,
sd.name as sub_department_name,
sd.department_id,
d.name as department_name
FROM activities a
JOIN sub_departments sd ON a.sub_department_id = sd.id
JOIN departments d ON sd.department_id = d.id
WHERE a.id = ?`,
[activityId]
);
if (activities.length === 0) {
ctx.response.status = 404;
ctx.response.body = { error: "Activity not found" };
return;
}
ctx.response.body = activities[0];
} catch (error) {
console.error("Get activity error:", error);
ctx.response.status = 500;
ctx.response.body = { error: "Internal server error" };
}
});
// Create activity (SuperAdmin only)
router.post("/", authenticateToken, async (ctx) => {
try {
const body = await ctx.request.body.json();
const { sub_department_id, name, unit_of_measurement } = body;
if (!sub_department_id || !name) {
ctx.response.status = 400;
ctx.response.body = { error: "Sub-department ID and name are required" };
return;
}
const result = await db.execute(
"INSERT INTO activities (sub_department_id, name, unit_of_measurement) VALUES (?, ?, ?)",
[sub_department_id, name, unit_of_measurement || "Per Bag"]
);
ctx.response.status = 201;
ctx.response.body = {
id: result.lastInsertId,
message: "Activity created successfully"
};
} catch (error) {
console.error("Create activity error:", error);
ctx.response.status = 500;
ctx.response.body = { error: "Internal server error" };
}
});
// Update activity
router.put("/:id", authenticateToken, async (ctx) => {
try {
const activityId = ctx.params.id;
const body = await ctx.request.body.json();
const { name, unit_of_measurement } = body;
await db.execute(
"UPDATE activities SET name = ?, unit_of_measurement = ? WHERE id = ?",
[name, unit_of_measurement, activityId]
);
ctx.response.body = { message: "Activity updated successfully" };
} catch (error) {
console.error("Update activity error:", error);
ctx.response.status = 500;
ctx.response.body = { error: "Internal server error" };
}
});
// Delete activity
router.delete("/:id", authenticateToken, async (ctx) => {
try {
const activityId = ctx.params.id;
await db.execute("DELETE FROM activities WHERE id = ?", [activityId]);
ctx.response.body = { message: "Activity deleted successfully" };
} catch (error) {
console.error("Delete activity error:", error);
ctx.response.status = 500;
ctx.response.body = { error: "Internal server error" };
}
});
export default router;

View File

@@ -0,0 +1,183 @@
import { Router } from "@oak/oak";
import { db } from "../config/database.ts";
import { authenticateToken, authorize, getCurrentUser } from "../middleware/auth.ts";
import type { WorkAllocation } from "../types/index.ts";
const router = new Router();
// Get completed work allocations for reporting (with optional filters)
router.get("/completed-allocations", authenticateToken, authorize("Supervisor", "SuperAdmin"), async (ctx) => {
try {
const currentUser = getCurrentUser(ctx);
const params = ctx.request.url.searchParams;
const startDate = params.get("startDate");
const endDate = params.get("endDate");
const departmentId = params.get("departmentId");
const contractorId = params.get("contractorId");
const employeeId = params.get("employeeId");
let query = `
SELECT wa.*,
e.name as employee_name, e.username as employee_username,
e.phone_number as employee_phone,
s.name as supervisor_name,
c.name as contractor_name,
sd.name as sub_department_name,
d.name as department_name,
d.id as department_id
FROM work_allocations wa
JOIN users e ON wa.employee_id = e.id
JOIN users s ON wa.supervisor_id = s.id
JOIN users c ON wa.contractor_id = c.id
LEFT JOIN sub_departments sd ON wa.sub_department_id = sd.id
LEFT JOIN departments d ON e.department_id = d.id
WHERE wa.status = 'Completed'
`;
const queryParams: unknown[] = [];
// Role-based filtering - Supervisors can only see their department
if (currentUser.role === "Supervisor") {
query += " AND e.department_id = ?";
queryParams.push(currentUser.departmentId);
}
// Date range filter
if (startDate) {
query += " AND wa.completion_date >= ?";
queryParams.push(startDate);
}
if (endDate) {
query += " AND wa.completion_date <= ?";
queryParams.push(endDate);
}
// Department filter (for SuperAdmin)
if (departmentId && currentUser.role === "SuperAdmin") {
query += " AND e.department_id = ?";
queryParams.push(departmentId);
}
// Contractor filter
if (contractorId) {
query += " AND wa.contractor_id = ?";
queryParams.push(contractorId);
}
// Employee filter
if (employeeId) {
query += " AND wa.employee_id = ?";
queryParams.push(employeeId);
}
query += " ORDER BY wa.completion_date DESC, wa.created_at DESC";
const allocations = await db.query<WorkAllocation[]>(query, queryParams);
// Calculate summary stats
const totalAllocations = allocations.length;
const totalAmount = allocations.reduce((sum, a) => sum + (parseFloat(String(a.total_amount)) || parseFloat(String(a.rate)) || 0), 0);
const totalUnits = allocations.reduce((sum, a) => sum + (parseFloat(String(a.units)) || 0), 0);
ctx.response.body = {
allocations,
summary: {
totalAllocations,
totalAmount: totalAmount.toFixed(2),
totalUnits: totalUnits.toFixed(2),
}
};
} catch (error) {
console.error("Get completed allocations report error:", error);
ctx.response.status = 500;
ctx.response.body = { error: "Internal server error" };
}
});
// Get summary statistics for completed work
router.get("/summary", authenticateToken, authorize("Supervisor", "SuperAdmin"), async (ctx) => {
try {
const currentUser = getCurrentUser(ctx);
const params = ctx.request.url.searchParams;
const startDate = params.get("startDate");
const endDate = params.get("endDate");
let departmentFilter = "";
const queryParams: unknown[] = [];
if (currentUser.role === "Supervisor") {
departmentFilter = " AND e.department_id = ?";
queryParams.push(currentUser.departmentId);
}
let dateFilter = "";
if (startDate) {
dateFilter += " AND wa.completion_date >= ?";
queryParams.push(startDate);
}
if (endDate) {
dateFilter += " AND wa.completion_date <= ?";
queryParams.push(endDate);
}
// Get summary by contractor
const byContractor = await db.query<any[]>(`
SELECT
c.id as contractor_id,
c.name as contractor_name,
COUNT(*) as total_allocations,
SUM(COALESCE(wa.total_amount, wa.rate, 0)) as total_amount,
SUM(COALESCE(wa.units, 0)) as total_units
FROM work_allocations wa
JOIN users e ON wa.employee_id = e.id
JOIN users c ON wa.contractor_id = c.id
WHERE wa.status = 'Completed' ${departmentFilter} ${dateFilter}
GROUP BY c.id, c.name
ORDER BY total_amount DESC
`, queryParams);
// Get summary by sub-department
const bySubDepartment = await db.query<any[]>(`
SELECT
sd.id as sub_department_id,
sd.name as sub_department_name,
d.name as department_name,
COUNT(*) as total_allocations,
SUM(COALESCE(wa.total_amount, wa.rate, 0)) as total_amount,
SUM(COALESCE(wa.units, 0)) as total_units
FROM work_allocations wa
JOIN users e ON wa.employee_id = e.id
LEFT JOIN sub_departments sd ON wa.sub_department_id = sd.id
LEFT JOIN departments d ON sd.department_id = d.id
WHERE wa.status = 'Completed' ${departmentFilter} ${dateFilter}
GROUP BY sd.id, sd.name, d.name
ORDER BY total_amount DESC
`, queryParams);
// Get summary by activity type
const byActivity = await db.query<any[]>(`
SELECT
COALESCE(wa.activity, 'Standard') as activity,
COUNT(*) as total_allocations,
SUM(COALESCE(wa.total_amount, wa.rate, 0)) as total_amount,
SUM(COALESCE(wa.units, 0)) as total_units
FROM work_allocations wa
JOIN users e ON wa.employee_id = e.id
WHERE wa.status = 'Completed' ${departmentFilter} ${dateFilter}
GROUP BY wa.activity
ORDER BY total_amount DESC
`, queryParams);
ctx.response.body = {
byContractor,
bySubDepartment,
byActivity,
};
} catch (error) {
console.error("Get report summary error:", error);
ctx.response.status = 500;
ctx.response.body = { error: "Internal server error" };
}
});
export default router;

View File

@@ -0,0 +1,479 @@
import { Router } from "@oak/oak";
import { db } from "../config/database.ts";
import { authenticateToken, authorize, getCurrentUser } from "../middleware/auth.ts";
import { sanitizeInput } from "../middleware/security.ts";
const router = new Router();
// Standard Rate interface
interface StandardRate {
id: number;
sub_department_id: number | null;
activity: string | null;
rate: number;
effective_date: Date;
created_by: number;
created_at: Date;
sub_department_name?: string;
department_name?: string;
department_id?: number;
created_by_name?: string;
}
// Get all standard rates (default rates for comparison)
router.get("/", authenticateToken, async (ctx) => {
try {
const currentUser = getCurrentUser(ctx);
const params = ctx.request.url.searchParams;
const departmentId = params.get("departmentId");
const subDepartmentId = params.get("subDepartmentId");
const activity = params.get("activity");
let query = `
SELECT sr.*,
sd.name as sub_department_name,
d.name as department_name,
d.id as department_id,
u.name as created_by_name
FROM standard_rates sr
LEFT JOIN sub_departments sd ON sr.sub_department_id = sd.id
LEFT JOIN departments d ON sd.department_id = d.id
LEFT JOIN users u ON sr.created_by = u.id
WHERE 1=1
`;
const queryParams: unknown[] = [];
// Supervisors can only see rates for their department
if (currentUser.role === "Supervisor") {
query += " AND d.id = ?";
queryParams.push(currentUser.departmentId);
}
if (departmentId) {
query += " AND d.id = ?";
queryParams.push(departmentId);
}
if (subDepartmentId) {
query += " AND sr.sub_department_id = ?";
queryParams.push(subDepartmentId);
}
if (activity) {
query += " AND sr.activity = ?";
queryParams.push(activity);
}
query += " ORDER BY sr.effective_date DESC, sr.created_at DESC";
const rates = await db.query<StandardRate[]>(query, queryParams);
ctx.response.body = rates;
} catch (error) {
console.error("Get standard rates error:", error);
ctx.response.status = 500;
ctx.response.body = { error: "Internal server error" };
}
});
// Get all rates (contractor + standard) for SuperAdmin - all departments, sorted by date
router.get("/all-rates", authenticateToken, authorize("SuperAdmin"), async (ctx) => {
try {
const params = ctx.request.url.searchParams;
const departmentId = params.get("departmentId");
const startDate = params.get("startDate");
const endDate = params.get("endDate");
// Get contractor rates
let contractorQuery = `
SELECT
cr.id,
'contractor' as rate_type,
cr.contractor_id,
u.name as contractor_name,
cr.sub_department_id,
sd.name as sub_department_name,
d.id as department_id,
d.name as department_name,
cr.activity,
cr.rate,
cr.effective_date,
cr.created_at,
NULL as created_by,
NULL as created_by_name
FROM contractor_rates cr
JOIN users u ON cr.contractor_id = u.id
LEFT JOIN sub_departments sd ON cr.sub_department_id = sd.id
LEFT JOIN departments d ON sd.department_id = d.id
WHERE 1=1
`;
const contractorParams: unknown[] = [];
if (departmentId) {
contractorQuery += " AND d.id = ?";
contractorParams.push(departmentId);
}
if (startDate) {
contractorQuery += " AND cr.effective_date >= ?";
contractorParams.push(startDate);
}
if (endDate) {
contractorQuery += " AND cr.effective_date <= ?";
contractorParams.push(endDate);
}
// Get standard rates
let standardQuery = `
SELECT
sr.id,
'standard' as rate_type,
NULL as contractor_id,
NULL as contractor_name,
sr.sub_department_id,
sd.name as sub_department_name,
d.id as department_id,
d.name as department_name,
sr.activity,
sr.rate,
sr.effective_date,
sr.created_at,
sr.created_by,
u.name as created_by_name
FROM standard_rates sr
LEFT JOIN sub_departments sd ON sr.sub_department_id = sd.id
LEFT JOIN departments d ON sd.department_id = d.id
LEFT JOIN users u ON sr.created_by = u.id
WHERE 1=1
`;
const standardParams: unknown[] = [];
if (departmentId) {
standardQuery += " AND d.id = ?";
standardParams.push(departmentId);
}
if (startDate) {
standardQuery += " AND sr.effective_date >= ?";
standardParams.push(startDate);
}
if (endDate) {
standardQuery += " AND sr.effective_date <= ?";
standardParams.push(endDate);
}
const contractorRates = await db.query<any[]>(contractorQuery, contractorParams);
const standardRates = await db.query<any[]>(standardQuery, standardParams);
// Combine and sort by date
const allRates = [...contractorRates, ...standardRates].sort((a, b) => {
const dateA = new Date(a.effective_date).getTime();
const dateB = new Date(b.effective_date).getTime();
return dateB - dateA; // Descending order
});
ctx.response.body = {
allRates,
summary: {
totalContractorRates: contractorRates.length,
totalStandardRates: standardRates.length,
totalRates: allRates.length,
}
};
} catch (error) {
console.error("Get all rates error:", error);
ctx.response.status = 500;
ctx.response.body = { error: "Internal server error" };
}
});
// Compare contractor rates with standard rates
router.get("/compare", authenticateToken, authorize("Supervisor", "SuperAdmin"), async (ctx) => {
try {
const currentUser = getCurrentUser(ctx);
const params = ctx.request.url.searchParams;
const contractorId = params.get("contractorId");
const subDepartmentId = params.get("subDepartmentId");
let departmentFilter = "";
const queryParams: unknown[] = [];
if (currentUser.role === "Supervisor") {
departmentFilter = " AND d.id = ?";
queryParams.push(currentUser.departmentId);
}
// Get standard rates
let standardQuery = `
SELECT sr.*,
sd.name as sub_department_name,
d.name as department_name,
d.id as department_id
FROM standard_rates sr
LEFT JOIN sub_departments sd ON sr.sub_department_id = sd.id
LEFT JOIN departments d ON sd.department_id = d.id
WHERE 1=1 ${departmentFilter}
`;
if (subDepartmentId) {
standardQuery += " AND sr.sub_department_id = ?";
queryParams.push(subDepartmentId);
}
standardQuery += " ORDER BY sr.effective_date DESC";
const standardRates = await db.query<StandardRate[]>(standardQuery, queryParams);
// Get contractor rates for comparison
let contractorQuery = `
SELECT cr.*,
u.name as contractor_name,
sd.name as sub_department_name,
d.name as department_name,
d.id as department_id
FROM contractor_rates cr
JOIN users u ON cr.contractor_id = u.id
LEFT JOIN sub_departments sd ON cr.sub_department_id = sd.id
LEFT JOIN departments d ON sd.department_id = d.id
WHERE 1=1
`;
const contractorParams: unknown[] = [];
if (currentUser.role === "Supervisor") {
contractorQuery += " AND d.id = ?";
contractorParams.push(currentUser.departmentId);
}
if (contractorId) {
contractorQuery += " AND cr.contractor_id = ?";
contractorParams.push(contractorId);
}
if (subDepartmentId) {
contractorQuery += " AND cr.sub_department_id = ?";
contractorParams.push(subDepartmentId);
}
contractorQuery += " ORDER BY cr.effective_date DESC";
const contractorRates = await db.query<any[]>(contractorQuery, contractorParams);
// Build comparison data
const comparisons = contractorRates.map(cr => {
// Find matching standard rate
const matchingStandard = standardRates.find(sr =>
sr.sub_department_id === cr.sub_department_id &&
sr.activity === cr.activity
);
const standardRate = matchingStandard?.rate || 0;
const contractorRate = cr.rate || 0;
const difference = contractorRate - standardRate;
const percentageDiff = standardRate > 0 ? ((difference / standardRate) * 100).toFixed(2) : null;
return {
...cr,
standard_rate: standardRate,
difference,
percentage_difference: percentageDiff,
is_above_standard: difference > 0,
is_below_standard: difference < 0,
};
});
ctx.response.body = {
standardRates,
contractorRates,
comparisons,
};
} catch (error) {
console.error("Compare rates error:", error);
ctx.response.status = 500;
ctx.response.body = { error: "Internal server error" };
}
});
// Create standard rate (Supervisor or SuperAdmin)
router.post("/", authenticateToken, authorize("Supervisor", "SuperAdmin"), async (ctx) => {
try {
const currentUser = getCurrentUser(ctx);
const body = await ctx.request.body.json() as {
subDepartmentId?: number;
activity?: string;
rate: number;
effectiveDate: string;
};
const { subDepartmentId, activity, rate, effectiveDate } = body;
if (!rate || !effectiveDate) {
ctx.response.status = 400;
ctx.response.body = { error: "Missing required fields (rate, effectiveDate)" };
return;
}
// Verify sub-department belongs to supervisor's department if supervisor
if (subDepartmentId && currentUser.role === "Supervisor") {
const subDepts = await db.query<any[]>(
"SELECT sd.* FROM sub_departments sd JOIN departments d ON sd.department_id = d.id WHERE sd.id = ? AND d.id = ?",
[subDepartmentId, currentUser.departmentId]
);
if (subDepts.length === 0) {
ctx.response.status = 403;
ctx.response.body = { error: "Sub-department not in your department" };
return;
}
}
const sanitizedActivity = activity ? sanitizeInput(activity) : null;
const result = await db.execute(
"INSERT INTO standard_rates (sub_department_id, activity, rate, effective_date, created_by) VALUES (?, ?, ?, ?, ?)",
[subDepartmentId || null, sanitizedActivity, rate, effectiveDate, currentUser.id]
);
const newRate = await db.query<StandardRate[]>(
`SELECT sr.*,
sd.name as sub_department_name,
d.name as department_name,
u.name as created_by_name
FROM standard_rates sr
LEFT JOIN sub_departments sd ON sr.sub_department_id = sd.id
LEFT JOIN departments d ON sd.department_id = d.id
LEFT JOIN users u ON sr.created_by = u.id
WHERE sr.id = ?`,
[result.insertId]
);
ctx.response.status = 201;
ctx.response.body = newRate[0];
} catch (error) {
console.error("Create standard rate error:", error);
ctx.response.status = 500;
ctx.response.body = { error: "Internal server error" };
}
});
// Update standard rate
router.put("/:id", authenticateToken, authorize("Supervisor", "SuperAdmin"), async (ctx) => {
try {
const currentUser = getCurrentUser(ctx);
const rateId = ctx.params.id;
const body = await ctx.request.body.json() as { rate?: number; activity?: string; effectiveDate?: string };
const { rate, activity, effectiveDate } = body;
// Verify rate exists and user has access
let query = `
SELECT sr.*, d.id as department_id
FROM standard_rates sr
LEFT JOIN sub_departments sd ON sr.sub_department_id = sd.id
LEFT JOIN departments d ON sd.department_id = d.id
WHERE sr.id = ?
`;
const params: unknown[] = [rateId];
const existing = await db.query<any[]>(query, params);
if (existing.length === 0) {
ctx.response.status = 404;
ctx.response.body = { error: "Standard rate not found" };
return;
}
// Supervisors can only update rates in their department
if (currentUser.role === "Supervisor" && existing[0].department_id !== currentUser.departmentId) {
ctx.response.status = 403;
ctx.response.body = { error: "Access denied - rate not in your department" };
return;
}
const updates: string[] = [];
const updateParams: unknown[] = [];
if (rate !== undefined) {
updates.push("rate = ?");
updateParams.push(rate);
}
if (activity !== undefined) {
updates.push("activity = ?");
updateParams.push(sanitizeInput(activity));
}
if (effectiveDate !== undefined) {
updates.push("effective_date = ?");
updateParams.push(effectiveDate);
}
if (updates.length === 0) {
ctx.response.status = 400;
ctx.response.body = { error: "No fields to update" };
return;
}
updateParams.push(rateId);
await db.execute(
`UPDATE standard_rates SET ${updates.join(", ")} WHERE id = ?`,
updateParams
);
const updatedRate = await db.query<StandardRate[]>(
`SELECT sr.*,
sd.name as sub_department_name,
d.name as department_name,
u.name as created_by_name
FROM standard_rates sr
LEFT JOIN sub_departments sd ON sr.sub_department_id = sd.id
LEFT JOIN departments d ON sd.department_id = d.id
LEFT JOIN users u ON sr.created_by = u.id
WHERE sr.id = ?`,
[rateId]
);
ctx.response.body = updatedRate[0];
} catch (error) {
console.error("Update standard rate error:", error);
ctx.response.status = 500;
ctx.response.body = { error: "Internal server error" };
}
});
// Delete standard rate
router.delete("/:id", authenticateToken, authorize("Supervisor", "SuperAdmin"), async (ctx) => {
try {
const currentUser = getCurrentUser(ctx);
const rateId = ctx.params.id;
// Verify rate exists and user has access
const existing = await db.query<any[]>(
`SELECT sr.*, d.id as department_id
FROM standard_rates sr
LEFT JOIN sub_departments sd ON sr.sub_department_id = sd.id
LEFT JOIN departments d ON sd.department_id = d.id
WHERE sr.id = ?`,
[rateId]
);
if (existing.length === 0) {
ctx.response.status = 404;
ctx.response.body = { error: "Standard rate not found" };
return;
}
// Supervisors can only delete rates in their department
if (currentUser.role === "Supervisor" && existing[0].department_id !== currentUser.departmentId) {
ctx.response.status = 403;
ctx.response.body = { error: "Access denied - rate not in your department" };
return;
}
await db.execute("DELETE FROM standard_rates WHERE id = ?", [rateId]);
ctx.response.body = { message: "Standard rate deleted successfully" };
} catch (error) {
console.error("Delete standard rate error:", error);
ctx.response.status = 500;
ctx.response.body = { error: "Internal server error" };
}
});
export default router;

View File

@@ -32,54 +32,187 @@ async function seedDatabase() {
console.log(" Departments already exist");
}
// 2. Seed Sub-departments for Groundnut
console.log("📂 Seeding sub-departments...");
const groundnutDept = await db.query<{ id: number }[]>(
// 2. Seed Sub-departments and Activities for all departments
console.log("📂 Seeding sub-departments and activities...");
// Get department IDs
const tudkiDeptResult = await db.query<{ id: number }[]>(
"SELECT id FROM departments WHERE name = ?",
["Tudki"]
);
const danaDeptResult = await db.query<{ id: number }[]>(
"SELECT id FROM departments WHERE name = ?",
["Dana"]
);
const groundnutDeptResult = await db.query<{ id: number }[]>(
"SELECT id FROM departments WHERE name = ?",
["Groundnut"]
);
let groundnutId: number | null = null;
const tudkiId = tudkiDeptResult[0]?.id;
const danaId = danaDeptResult[0]?.id;
const groundnutId = groundnutDeptResult[0]?.id;
// Define sub-departments and activities per department based on activities.md
const departmentData: { [key: number]: { subDept: string; activities: { name: string; unit: string }[] }[] } = {};
if (groundnutDept.length > 0) {
groundnutId = groundnutDept[0].id;
if (groundnutId) {
departmentData[groundnutId] = [
{
subDept: "Loading/Unloading",
activities: [
{ name: "Mufali Aavak Katai (Groundnut Arrival Cutting)", unit: "Per Bag" },
{ name: "Mufali Aavak Dhaang (Groundnut Arrival Stacking)", unit: "Per Bag" },
{ name: "Dhaang Se Katai (Cutting from Stack)", unit: "Per Bag" },
{ name: "Guthli Bori Silai Dhaang (Kernel Bag Stitching Stack)", unit: "Per Bag" },
{ name: "Guthali dhada Pala Tulai Silai Dhaang / Loading", unit: "Per Bag" },
{ name: "Mufali Patthar Bori silai Dhaang", unit: "Per Bag" },
{ name: "Mufali Patthar Bori Utrai (Groundnut Stone Bag Unloading)", unit: "Per Bag" },
{ name: "Bardana Bandal Loading (Gunny Bundle Loading)", unit: "Per Bag" },
{ name: "Bardana Gatthi Loading/Unloading", unit: "Per Bag" },
{ name: "Black Dana Loading/Unloading", unit: "Per Bag" },
{ name: "Dala - Chomu & Jaipur (Branch)", unit: "Per Bag" },
]
},
{ subDept: "Pre Cleaning", activities: [{ name: "Pre Cleaner", unit: "Fixed Rate-Per Person" }] },
{ subDept: "Destoner", activities: [{ name: "Destoner", unit: "Fixed Rate-Per Person" }] },
{ subDept: "Water", activities: [{ name: "Water", unit: "Fixed Rate-Per Person" }] },
{
subDept: "Decordicater & Cleaning and Round Chalna",
activities: [
{ name: "Decordicater", unit: "Fixed Rate-Per Person" },
{ name: "Round Chalna (Round Sieving)", unit: "Fixed Rate-Per Person" },
{ name: "Cleaning", unit: "Fixed Rate-Per Person" },
]
},
{ subDept: "Round Chalna No.1", activities: [{ name: "Round Chalna No.1 (Round Sieving No.1)", unit: "Fixed Rate-Per Person" }] },
];
}
if (danaId) {
departmentData[danaId] = [
{
subDept: "Loading/Unloading",
activities: [
{ name: "Tulai Silai Loading (Weighing Stitching Loading)", unit: "Per Bag" },
{ name: "Dhaang se Loading (Loading from Stack)", unit: "Per Bag" },
{ name: "Silai Dhaang (Stitching Stack)", unit: "Per Bag" },
{ name: "Tulai Silai Dhaang Ikai No. 2 Machine ke Pass", unit: "Per Bag" },
{ name: "Dana Unloading/Dhaang (Grain Unloading/Stack)", unit: "Per Bag" },
{ name: "Dana Aavak Keep Katai (Grain Arrival Hopper Cutting)", unit: "Per Bag" },
{ name: "Kachri Dhada Pala Bharai Tulai Silai Load/Dhaang 70kg", unit: "Per Bag" },
{ name: "Kachri Dhaang se loading (Waste Loading from Stack)", unit: "Per Bag" },
{ name: "Keep Katai Khulla Katta (Hopper Cutting Open Bag)", unit: "Per Bag" },
{ name: "Keep Katai Silai Kholkar (Hopper Cutting Opening Stitched)", unit: "Per Bag" },
{ name: "Bardana Paltai (Gunny Turning)", unit: "Per Bag" },
{ name: "Ekai No. 2 me Keep Katai Khula Bag", unit: "Per Bag" },
{ name: "Ekai No. 2 me Keep Katai Silai Kholkar", unit: "Per Bag" },
{ name: "Silai Loading Company Gadi Dala Sahit", unit: "Per Bag" },
{ name: "Kachri Bharai Silai Dhaang Chatt Par", unit: "Per Bag" },
{ name: "Bardana Unloading (Gunny Unloading)", unit: "Per Bag" },
{ name: "Grading", unit: "Per Bag" },
]
},
{ subDept: "Destoner", activities: [{ name: "Destoner", unit: "Fixed Rate-Per Person" }] },
{ subDept: "Gravity", activities: [{ name: "Gravity", unit: "Fixed Rate-Per Person" }] },
{ subDept: "Tank", activities: [{ name: "Tank", unit: "Fixed Rate-Per Person" }] },
{ subDept: "Sortex", activities: [{ name: "Sortex", unit: "Fixed Rate-Per Person" }] },
{ subDept: "X-Ray", activities: [{ name: "X-Ray", unit: "Fixed Rate-Per Person" }] },
{ subDept: "Kachri", activities: [{ name: "Kachri (Waste)", unit: "Fixed Rate-Per Person" }] },
{ subDept: "Other Works", activities: [{ name: "Other Works", unit: "Fixed Rate-Per Person" }] },
];
}
if (tudkiId) {
departmentData[tudkiId] = [
{
subDept: "Loading/Unloading",
activities: [
{ name: "Dana Loading/Unloading (Grain Loading/Unloading)", unit: "Per Bag" },
{ name: "Loading/Unloading 40 Kg", unit: "Per Bag" },
{ name: "Grading Chalne se Maal Bharai Tulai Silai Dhaang", unit: "Per Bag" },
{ name: "Grading Chalne se Maal Bharai Tulai Silai Loading", unit: "Per Bag" },
{ name: "Chilka Bharai silai Dhaang (Husk Filling Stitching Stack)", unit: "Per Bag" },
{ name: "Keep katai Bahar Se (Hopper Cutting from Outside)", unit: "Per Bag" },
{ name: "Keep katai Andar Se (Hopper Cutting from Inside)", unit: "Per Bag" },
{ name: "Cartoon Banai Vacume Bharai Tulai Packing and Dhaang", unit: "Per Bag" },
{ name: "Cartoon Banai Vacume Bharai Tulai Packing and Loading", unit: "Per Bag" },
{ name: "Katta Paltai (Bag Turning)", unit: "Per Bag" },
{ name: "Dhada Pala Bharai Tulai Silai Dhaang", unit: "Per Bag" },
{ name: "Dhada Pala Bharai Tulai Silai Loading", unit: "Per Bag" },
{ name: "Sike Maal Ki Silai Dhaang Andar", unit: "Per Bag" },
{ name: "Sike Maal Ki Silai Dhaang Bahar", unit: "Per Bag" },
{ name: "Nakku Silai Dhaang Bahar (Rejection Stitching Stack Outside)", unit: "Per Bag" },
]
},
{ subDept: "Tank", activities: [{ name: "Tank", unit: "Fixed Rate-Per Person" }] },
{ subDept: "Grader (Machine)", activities: [{ name: "Grader (Machine)", unit: "Fixed Rate-Per Person" }] },
{ subDept: "Sortex", activities: [{ name: "Sortex", unit: "Fixed Rate-Per Person" }] },
{ subDept: "X-Ray", activities: [{ name: "X-Ray", unit: "Fixed Rate-Per Person" }] },
{ subDept: "Rejection", activities: [{ name: "Rejection", unit: "Fixed Rate-Per Person" }] },
{ subDept: "Store", activities: [{ name: "Store", unit: "Fixed Rate-Per Person" }] },
{ subDept: "Roster", activities: [{ name: "Roster", unit: "Fixed Rate-Per Person" }] },
{ subDept: "Blancher", activities: [{ name: "Blancher", unit: "Fixed Rate-Per Person" }] },
{ subDept: "Other Works", activities: [{ name: "Other Works", unit: "Fixed Rate-Per Person" }] },
];
}
// Check if activities table exists, if not create it
try {
await db.execute(`
CREATE TABLE IF NOT EXISTS activities (
id INT AUTO_INCREMENT PRIMARY KEY,
sub_department_id INT NOT NULL,
name VARCHAR(255) NOT NULL,
unit_of_measurement ENUM('Per Bag', 'Fixed Rate-Per Person') NOT NULL DEFAULT 'Per Bag',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (sub_department_id) REFERENCES sub_departments(id) ON DELETE CASCADE,
UNIQUE KEY unique_activity (sub_department_id, name)
)
`);
} catch (_e) {
// Table might already exist
}
// Seed sub-departments and activities for each department
for (const [deptId, subDepts] of Object.entries(departmentData)) {
const existingSubDepts = await db.query<{ count: number }[]>(
"SELECT COUNT(*) as count FROM sub_departments WHERE department_id = ?",
[groundnutId]
[deptId]
);
if (existingSubDepts[0].count === 0) {
const subDepts = [
"Mufali Aavak Katai",
"Mufali Aavak Dhang",
"Dhang Se Katai",
"Guthli Bori Silai Dhang",
"Guthali dada Pala Tulai Silai Dhang",
"Mufali Patthar Bori silai dhang",
"Mufali Patthar Bori Utrai",
"Bardana Bandal Loading Unloading",
"Bardana Gatthi Loading",
"Black Dana Loading/Unloading",
"Pre Cleaning",
"Destoner",
"Water",
"Decordicater",
"Round Chalna",
"Cleaning",
"Round Chalna No.1"
];
for (const name of subDepts) {
for (const { subDept, activities } of subDepts) {
// Insert sub-department
await db.execute(
"INSERT INTO sub_departments (department_id, name, primary_activity) VALUES (?, ?, ?)",
[groundnutId, name, "Loading/Unloading"]
"INSERT INTO sub_departments (department_id, name) VALUES (?, ?)",
[deptId, subDept]
);
// Get the sub-department ID
const subDeptResult = await db.query<{ id: number }[]>(
"SELECT id FROM sub_departments WHERE department_id = ? AND name = ?",
[deptId, subDept]
);
if (subDeptResult.length > 0) {
const subDeptId = subDeptResult[0].id;
// Insert activities for this sub-department
for (const activity of activities) {
try {
await db.execute(
"INSERT INTO activities (sub_department_id, name, unit_of_measurement) VALUES (?, ?, ?)",
[subDeptId, activity.name, activity.unit]
);
} catch (_e) {
// Activity might already exist
}
}
}
}
console.log(" ✅ Sub-departments created");
} else {
console.log(" Sub-departments already exist");
}
}
console.log(" ✅ Sub-departments and activities created");
// 3. Seed SuperAdmin
console.log("👤 Seeding SuperAdmin user...");
@@ -104,23 +237,32 @@ async function seedDatabase() {
console.log(" ✅ SuperAdmin created");
}
// 4. Seed Sample Supervisors
console.log("👥 Seeding sample supervisors...");
const tudkiDept = await db.query<{ id: number }[]>(
"SELECT id FROM departments WHERE name = ?",
["Tudki"]
);
const danaDept = await db.query<{ id: number }[]>(
"SELECT id FROM departments WHERE name = ?",
["Dana"]
);
// 4. Seed Supervisors for all departments
console.log("👥 Seeding supervisors...");
const supervisorPassword = await hashPassword("supervisor123");
const supervisors = [
{ username: "supervisor_tudki", name: "Tudki Supervisor", email: "supervisor.tudki@workallocate.com", deptId: tudkiDept[0]?.id },
{ username: "supervisor_dana", name: "Dana Supervisor", email: "supervisor.dana@workallocate.com", deptId: danaDept[0]?.id },
{ username: "supervisor_groundnut", name: "Groundnut Supervisor", email: "supervisor.groundnut@workallocate.com", deptId: groundnutId }
{
username: "rajesh.sharma.tudki",
name: "Rajesh Sharma",
email: "rajesh.sharma@workallocate.com",
deptId: tudkiId,
phone: "9414567890"
},
{
username: "sunil.verma.dana",
name: "Sunil Verma",
email: "sunil.verma@workallocate.com",
deptId: danaId,
phone: "9414567891"
},
{
username: "mahesh.agarwal.groundnut",
name: "Mahesh Agarwal",
email: "mahesh.agarwal@workallocate.com",
deptId: groundnutId,
phone: "9414567892"
}
];
for (const sup of supervisors) {
@@ -131,8 +273,8 @@ async function seedDatabase() {
);
if (existing.length === 0) {
await db.execute(
"INSERT INTO users (username, name, email, password, role, department_id, is_active) VALUES (?, ?, ?, ?, ?, ?, ?)",
[sup.username, sup.name, sup.email, supervisorPassword, "Supervisor", sup.deptId, true]
"INSERT INTO users (username, name, email, password, role, department_id, is_active, phone_number) VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
[sup.username, sup.name, sup.email, supervisorPassword, "Supervisor", sup.deptId, true, sup.phone]
);
console.log(`${sup.name} created`);
} else {
@@ -141,38 +283,97 @@ async function seedDatabase() {
}
}
// 5. Seed Sample Contractors
console.log("🏗️ Seeding sample contractors...");
// 5. Seed Contractors for all departments
console.log("🏗️ Seeding contractors...");
const contractorPassword = await hashPassword("contractor123");
const contractors = [
// Groundnut Department Contractors
{
username: "contractor1",
name: "Contractor One",
email: "contractor1@workallocate.com",
username: "ramesh.patel.gn",
name: "Ramesh Patel",
email: "ramesh.patel@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",
phone: "9829012345",
aadhar: "234567891234",
bankAccount: "50100123456789",
bankName: "HDFC Bank",
bankIfsc: "HDFC0001234",
agreementNo: "AGR-2024-002",
pfNo: "PF/GJ/12345/67891",
esicNo: "12-34-567890-123-0002"
agreementNo: "AGR-GN-2024-001",
pfNo: "RJ/JPR/12345/001",
esicNo: "12-34-567890-001-0001"
},
{
username: "kishan.meena.gn",
name: "Kishan Meena",
email: "kishan.meena@workallocate.com",
deptId: groundnutId,
phone: "9829012346",
aadhar: "345678912345",
bankAccount: "50100123456790",
bankName: "State Bank of India",
bankIfsc: "SBIN0005678",
agreementNo: "AGR-GN-2024-002",
pfNo: "RJ/JPR/12345/002",
esicNo: "12-34-567890-001-0002"
},
// Dana Department Contractors
{
username: "gopal.sharma.dana",
name: "Gopal Sharma",
email: "gopal.sharma@workallocate.com",
deptId: danaId,
phone: "9829012347",
aadhar: "456789123456",
bankAccount: "50100123456791",
bankName: "Punjab National Bank",
bankIfsc: "PUNB0009876",
agreementNo: "AGR-DN-2024-001",
pfNo: "RJ/JPR/12345/003",
esicNo: "12-34-567890-002-0001"
},
{
username: "mohan.yadav.dana",
name: "Mohan Yadav",
email: "mohan.yadav@workallocate.com",
deptId: danaId,
phone: "9829012348",
aadhar: "567891234567",
bankAccount: "50100123456792",
bankName: "Bank of Baroda",
bankIfsc: "BARB0004567",
agreementNo: "AGR-DN-2024-002",
pfNo: "RJ/JPR/12345/004",
esicNo: "12-34-567890-002-0002"
},
// Tudki Department Contractors
{
username: "suresh.kumar.tudki",
name: "Suresh Kumar",
email: "suresh.kumar@workallocate.com",
deptId: tudkiId,
phone: "9829012349",
aadhar: "678912345678",
bankAccount: "50100123456793",
bankName: "ICICI Bank",
bankIfsc: "ICIC0003456",
agreementNo: "AGR-TK-2024-001",
pfNo: "RJ/JPR/12345/005",
esicNo: "12-34-567890-003-0001"
},
{
username: "dinesh.gupta.tudki",
name: "Dinesh Gupta",
email: "dinesh.gupta@workallocate.com",
deptId: tudkiId,
phone: "9829012350",
aadhar: "789123456789",
bankAccount: "50100123456794",
bankName: "Axis Bank",
bankIfsc: "UTIB0002345",
agreementNo: "AGR-TK-2024-002",
pfNo: "RJ/JPR/12345/006",
esicNo: "12-34-567890-003-0002"
}
];
@@ -197,87 +398,299 @@ async function seedDatabase() {
}
}
// 6. Seed Sample Employees
console.log("👷 Seeding sample employees...");
const contractor1 = await db.query<{ id: number }[]>(
"SELECT id FROM users WHERE username = ?",
["contractor1"]
);
// 6. Seed Employees for all departments
console.log("👷 Seeding employees...");
const employeePassword = await hashPassword("employee123");
if (contractor1.length > 0) {
const employees = [
{
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"
}
];
// Get contractor IDs for employee assignment
const contractorIds: { [key: string]: number } = {};
for (const con of contractors) {
const result = await db.query<{ id: number }[]>(
"SELECT id FROM users WHERE username = ?",
[con.username]
);
if (result.length > 0) {
contractorIds[con.username] = result[0].id;
}
}
for (const emp of employees) {
const existing = await db.query<{ id: number }[]>(
"SELECT id FROM users WHERE username = ?",
[emp.username]
);
if (existing.length === 0) {
const employees = [
// Groundnut Department Employees - Under Ramesh Patel
{
username: "ravi.singh.gn1",
name: "Ravi Singh",
email: "ravi.singh@workallocate.com",
deptId: groundnutId,
contractorUsername: "ramesh.patel.gn",
phone: "9876501001",
aadhar: "111122223333",
bankAccount: "30100111122233",
bankName: "State Bank of India",
bankIfsc: "SBIN0001111"
},
{
username: "amit.kumar.gn2",
name: "Amit Kumar",
email: "amit.kumar@workallocate.com",
deptId: groundnutId,
contractorUsername: "ramesh.patel.gn",
phone: "9876501002",
aadhar: "222233334444",
bankAccount: "30100222233344",
bankName: "Punjab National Bank",
bankIfsc: "PUNB0002222"
},
{
username: "vijay.meena.gn3",
name: "Vijay Meena",
email: "vijay.meena@workallocate.com",
deptId: groundnutId,
contractorUsername: "ramesh.patel.gn",
phone: "9876501003",
aadhar: "333344445555",
bankAccount: "30100333344455",
bankName: "HDFC Bank",
bankIfsc: "HDFC0003333"
},
// Groundnut Department Employees - Under Kishan Meena
{
username: "sanjay.yadav.gn4",
name: "Sanjay Yadav",
email: "sanjay.yadav@workallocate.com",
deptId: groundnutId,
contractorUsername: "kishan.meena.gn",
phone: "9876501004",
aadhar: "444455556666",
bankAccount: "30100444455566",
bankName: "Bank of Baroda",
bankIfsc: "BARB0004444"
},
{
username: "prakash.sharma.gn5",
name: "Prakash Sharma",
email: "prakash.sharma@workallocate.com",
deptId: groundnutId,
contractorUsername: "kishan.meena.gn",
phone: "9876501005",
aadhar: "555566667777",
bankAccount: "30100555566677",
bankName: "ICICI Bank",
bankIfsc: "ICIC0005555"
},
// Dana Department Employees - Under Gopal Sharma
{
username: "rampal.verma.dn1",
name: "Rampal Verma",
email: "rampal.verma@workallocate.com",
deptId: danaId,
contractorUsername: "gopal.sharma.dana",
phone: "9876502001",
aadhar: "666677778888",
bankAccount: "30100666677788",
bankName: "State Bank of India",
bankIfsc: "SBIN0006666"
},
{
username: "lakhan.singh.dn2",
name: "Lakhan Singh",
email: "lakhan.singh@workallocate.com",
deptId: danaId,
contractorUsername: "gopal.sharma.dana",
phone: "9876502002",
aadhar: "777788889999",
bankAccount: "30100777788899",
bankName: "Punjab National Bank",
bankIfsc: "PUNB0007777"
},
{
username: "bharat.meena.dn3",
name: "Bharat Meena",
email: "bharat.meena@workallocate.com",
deptId: danaId,
contractorUsername: "gopal.sharma.dana",
phone: "9876502003",
aadhar: "888899990000",
bankAccount: "30100888899900",
bankName: "HDFC Bank",
bankIfsc: "HDFC0008888"
},
// Dana Department Employees - Under Mohan Yadav
{
username: "kailash.patel.dn4",
name: "Kailash Patel",
email: "kailash.patel@workallocate.com",
deptId: danaId,
contractorUsername: "mohan.yadav.dana",
phone: "9876502004",
aadhar: "999900001111",
bankAccount: "30100999900011",
bankName: "Bank of Baroda",
bankIfsc: "BARB0009999"
},
{
username: "shyam.gupta.dn5",
name: "Shyam Gupta",
email: "shyam.gupta@workallocate.com",
deptId: danaId,
contractorUsername: "mohan.yadav.dana",
phone: "9876502005",
aadhar: "000011112222",
bankAccount: "30100000011122",
bankName: "ICICI Bank",
bankIfsc: "ICIC0000000"
},
// Tudki Department Employees - Under Suresh Kumar
{
username: "ganesh.kumar.tk1",
name: "Ganesh Kumar",
email: "ganesh.kumar@workallocate.com",
deptId: tudkiId,
contractorUsername: "suresh.kumar.tudki",
phone: "9876503001",
aadhar: "112233445566",
bankAccount: "30100112233445",
bankName: "State Bank of India",
bankIfsc: "SBIN0001122"
},
{
username: "naresh.yadav.tk2",
name: "Naresh Yadav",
email: "naresh.yadav@workallocate.com",
deptId: tudkiId,
contractorUsername: "suresh.kumar.tudki",
phone: "9876503002",
aadhar: "223344556677",
bankAccount: "30100223344556",
bankName: "Punjab National Bank",
bankIfsc: "PUNB0002233"
},
{
username: "mukesh.sharma.tk3",
name: "Mukesh Sharma",
email: "mukesh.sharma@workallocate.com",
deptId: tudkiId,
contractorUsername: "suresh.kumar.tudki",
phone: "9876503003",
aadhar: "334455667788",
bankAccount: "30100334455667",
bankName: "HDFC Bank",
bankIfsc: "HDFC0003344"
},
// Tudki Department Employees - Under Dinesh Gupta
{
username: "pappu.singh.tk4",
name: "Pappu Singh",
email: "pappu.singh@workallocate.com",
deptId: tudkiId,
contractorUsername: "dinesh.gupta.tudki",
phone: "9876503004",
aadhar: "445566778899",
bankAccount: "30100445566778",
bankName: "Bank of Baroda",
bankIfsc: "BARB0004455"
},
{
username: "deepak.verma.tk5",
name: "Deepak Verma",
email: "deepak.verma@workallocate.com",
deptId: tudkiId,
contractorUsername: "dinesh.gupta.tudki",
phone: "9876503005",
aadhar: "556677889900",
bankAccount: "30100556677889",
bankName: "ICICI Bank",
bankIfsc: "ICIC0005566"
},
{
username: "rahul.meena.tk6",
name: "Rahul Meena",
email: "rahul.meena@workallocate.com",
deptId: tudkiId,
contractorUsername: "dinesh.gupta.tudki",
phone: "9876503006",
aadhar: "667788990011",
bankAccount: "30100667788990",
bankName: "Axis Bank",
bankIfsc: "UTIB0006677"
}
];
for (const emp of employees) {
const existing = await db.query<{ id: number }[]>(
"SELECT id FROM users WHERE username = ?",
[emp.username]
);
if (existing.length === 0) {
const contractorId = contractorIds[emp.contractorUsername];
if (contractorId) {
await db.execute(
`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.username, emp.name, emp.email, employeePassword, "Employee", emp.deptId, contractorId, true,
emp.phone, emp.aadhar, emp.bankAccount, emp.bankName, emp.bankIfsc]
);
console.log(`${emp.name} created`);
} else {
console.log(` ${emp.name} already exists`);
}
} else {
console.log(` ${emp.name} already exists`);
}
}
// 7. Seed Contractor Rates
// 7. Seed Contractor Rates for all contractors
console.log("💰 Seeding contractor rates...");
if (contractor1.length > 0) {
const today = new Date().toISOString().split("T")[0];
// Get all sub-departments for rate assignment
const allSubDepts = await db.query<{ id: number; name: string; department_id: number }[]>(
"SELECT id, name, department_id FROM sub_departments"
);
// Create rates for each contractor based on their department
for (const [username, contractorId] of Object.entries(contractorIds)) {
const existingRate = await db.query<{ id: number }[]>(
"SELECT id FROM contractor_rates WHERE contractor_id = ?",
[contractor1[0].id]
[contractorId]
);
if (existingRate.length === 0) {
const today = new Date().toISOString().split("T")[0];
await db.execute(
"INSERT INTO contractor_rates (contractor_id, rate, effective_date) VALUES (?, ?, ?)",
[contractor1[0].id, 500.00, today]
);
console.log(" ✅ Contractor rates created");
} else {
console.log(" Contractor rates already exist");
// Find the contractor's department
const contractor = contractors.find(c => c.username === username);
if (contractor) {
// Get sub-departments for this contractor's department
const deptSubDepts = allSubDepts.filter(sd => sd.department_id === contractor.deptId);
// Create rates for Loading/Unloading sub-department (Per Bag rates)
const loadingSubDept = deptSubDepts.find(sd => sd.name === "Loading/Unloading");
if (loadingSubDept) {
// Get activities for this sub-department
const activities = await db.query<{ id: number; name: string }[]>(
"SELECT id, name FROM activities WHERE sub_department_id = ? LIMIT 3",
[loadingSubDept.id]
);
for (const activity of activities) {
const rate = 5 + Math.random() * 3; // Random rate between 5-8 per bag
await db.execute(
"INSERT INTO contractor_rates (contractor_id, sub_department_id, activity, rate, effective_date) VALUES (?, ?, ?, ?, ?)",
[contractorId, loadingSubDept.id, activity.name, rate.toFixed(2), today]
);
}
}
// Create fixed rates for other sub-departments
const fixedSubDepts = deptSubDepts.filter(sd => sd.name !== "Loading/Unloading");
for (const subDept of fixedSubDepts.slice(0, 2)) { // Limit to 2 fixed rate sub-depts per contractor
const rate = 300 + Math.random() * 200; // Random rate between 300-500 per person
await db.execute(
"INSERT INTO contractor_rates (contractor_id, sub_department_id, rate, effective_date) VALUES (?, ?, ?, ?)",
[contractorId, subDept.id, rate.toFixed(2), today]
);
}
}
}
}
console.log(" ✅ Contractor rates created");
console.log(`
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
@@ -289,17 +702,18 @@ async function seedDatabase() {
Username: admin
Password: admin123
Supervisor (Groundnut):
Username: supervisor_groundnut
Password: supervisor123
Supervisors (password: supervisor123):
- Tudki: rajesh.sharma.tudki
- Dana: sunil.verma.dana
- Groundnut: mahesh.agarwal.groundnut
Contractor:
Username: contractor1
Password: contractor123
Contractors (password: contractor123):
- Groundnut: ramesh.patel.gn, kishan.meena.gn
- Dana: gopal.sharma.dana, mohan.yadav.dana
- Tudki: suresh.kumar.tudki, dinesh.gupta.tudki
Employee:
Username: employee1
Password: employee123
Employees (password: employee123):
- Use any employee username like ravi.singh.gn1, rampal.verma.dn1, ganesh.kumar.tk1
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
`);

View File

@@ -242,3 +242,25 @@ export interface CreateContractorRateRequest {
rate: number;
effectiveDate: string;
}
// Standard rate types
export interface StandardRate {
id: number;
sub_department_id: number | null;
activity: string | null;
rate: number;
effective_date: Date;
created_by: number;
created_at: Date;
sub_department_name?: string;
department_name?: string;
department_id?: number;
created_by_name?: string;
}
export interface CreateStandardRateRequest {
subDepartmentId?: number | null;
activity?: string | null;
rate: number;
effectiveDate: string;
}