(Feat-Fix): New Reporting system, more seeded data, fixed subdepartments and activity inversion, login page changes, etc etc
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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());
|
||||
|
||||
153
backend-deno/routes/activities.ts
Normal file
153
backend-deno/routes/activities.ts
Normal 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;
|
||||
183
backend-deno/routes/reports.ts
Normal file
183
backend-deno/routes/reports.ts
Normal 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;
|
||||
479
backend-deno/routes/standard-rates.ts
Normal file
479
backend-deno/routes/standard-rates.ts
Normal 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;
|
||||
@@ -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
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
`);
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user