(Feat): More changes
This commit is contained in:
10
backend/.env
10
backend/.env
@@ -1,10 +0,0 @@
|
||||
DB_HOST=localhost
|
||||
DB_USER=root
|
||||
DB_PASSWORD=admin123
|
||||
DB_NAME=work_allocation
|
||||
DB_PORT=3306
|
||||
|
||||
JWT_SECRET=work_alloc_jwt_secret_key_change_in_production_2024
|
||||
JWT_EXPIRES_IN=7d
|
||||
|
||||
PORT=3000
|
||||
@@ -1,10 +0,0 @@
|
||||
DB_HOST=localhost
|
||||
DB_USER=root
|
||||
DB_PASSWORD=your_password
|
||||
DB_NAME=work_allocation
|
||||
DB_PORT=3306
|
||||
|
||||
JWT_SECRET=your_jwt_secret_key_change_this_in_production
|
||||
JWT_EXPIRES_IN=7d
|
||||
|
||||
PORT=3000
|
||||
@@ -1,166 +0,0 @@
|
||||
# Work Allocation Backend API
|
||||
|
||||
Simple Node.js/Express backend with MySQL database for the Work Allocation System.
|
||||
|
||||
## Setup
|
||||
|
||||
### 1. Install Dependencies
|
||||
|
||||
```bash
|
||||
cd backend
|
||||
npm install
|
||||
```
|
||||
|
||||
### 2. Setup MySQL Database
|
||||
|
||||
1. Install MySQL if not already installed
|
||||
2. Create the database and tables:
|
||||
|
||||
```bash
|
||||
mysql -u root -p < database/schema.sql
|
||||
```
|
||||
|
||||
Or manually:
|
||||
|
||||
- Login to MySQL: `mysql -u root -p`
|
||||
- Run the SQL commands from `database/schema.sql`
|
||||
|
||||
### 3. Configure Environment
|
||||
|
||||
Copy `.env.example` to `.env` and update with your database credentials:
|
||||
|
||||
```bash
|
||||
cp .env.example .env
|
||||
```
|
||||
|
||||
Edit `.env`:
|
||||
|
||||
```env
|
||||
DB_HOST=localhost
|
||||
DB_USER=root
|
||||
DB_PASSWORD=your_mysql_password
|
||||
DB_NAME=work_allocation
|
||||
DB_PORT=3306
|
||||
|
||||
JWT_SECRET=your_secret_key_here
|
||||
JWT_EXPIRES_IN=7d
|
||||
|
||||
PORT=3000
|
||||
```
|
||||
|
||||
### 4. Start Server
|
||||
|
||||
Development mode (with auto-reload):
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
```
|
||||
|
||||
Production mode:
|
||||
|
||||
```bash
|
||||
npm start
|
||||
```
|
||||
|
||||
The server will run on `http://localhost:3000`
|
||||
|
||||
## Default Credentials
|
||||
|
||||
**Super Admin:**
|
||||
|
||||
- Username: `admin`
|
||||
- Password: `admin123`
|
||||
|
||||
**Note:** Change the default password immediately after first login!
|
||||
|
||||
## API Endpoints
|
||||
|
||||
### Authentication
|
||||
|
||||
- `POST /api/auth/login` - Login
|
||||
- `GET /api/auth/me` - Get current user
|
||||
- `POST /api/auth/change-password` - Change password
|
||||
|
||||
### Users
|
||||
|
||||
- `GET /api/users` - Get all users (with filters)
|
||||
- `GET /api/users/:id` - Get user by ID
|
||||
- `POST /api/users` - Create user
|
||||
- `PUT /api/users/:id` - Update user
|
||||
- `DELETE /api/users/:id` - Delete user
|
||||
|
||||
### Departments
|
||||
|
||||
- `GET /api/departments` - Get all departments
|
||||
- `GET /api/departments/:id` - Get department by ID
|
||||
- `GET /api/departments/:id/sub-departments` - Get sub-departments
|
||||
- `POST /api/departments` - Create department (SuperAdmin only)
|
||||
- `POST /api/departments/:id/sub-departments` - Create sub-department (SuperAdmin only)
|
||||
|
||||
### Work Allocations
|
||||
|
||||
- `GET /api/work-allocations` - Get all work allocations
|
||||
- `GET /api/work-allocations/:id` - Get work allocation by ID
|
||||
- `POST /api/work-allocations` - Create work allocation (Supervisor only)
|
||||
- `PUT /api/work-allocations/:id/status` - Update status (Supervisor only)
|
||||
- `DELETE /api/work-allocations/:id` - Delete work allocation (Supervisor only)
|
||||
|
||||
### Attendance
|
||||
|
||||
- `GET /api/attendance` - Get all attendance records
|
||||
- `GET /api/attendance/:id` - Get attendance by ID
|
||||
- `POST /api/attendance/check-in` - Check in employee (Supervisor only)
|
||||
- `POST /api/attendance/check-out` - Check out employee (Supervisor only)
|
||||
- `GET /api/attendance/summary/stats` - Get attendance summary
|
||||
|
||||
### Contractor Rates
|
||||
|
||||
- `GET /api/contractor-rates` - Get contractor rates
|
||||
- `GET /api/contractor-rates/contractor/:contractorId/current` - Get current rate
|
||||
- `POST /api/contractor-rates` - Set contractor rate (Supervisor/SuperAdmin only)
|
||||
|
||||
## Roles & Permissions
|
||||
|
||||
### SuperAdmin
|
||||
|
||||
- Full access to all features
|
||||
- Can create/manage all users and departments
|
||||
- Can view all data across departments
|
||||
|
||||
### Supervisor
|
||||
|
||||
- Can manage users (employees, contractors) in their department
|
||||
- Can create work allocations for their department
|
||||
- Can check in/out employees
|
||||
- Can set contractor rates
|
||||
- Can mark work as completed
|
||||
|
||||
### Contractor
|
||||
|
||||
- Can view work allocations assigned to them
|
||||
- Can view employees under them
|
||||
|
||||
### Employee
|
||||
|
||||
- Can view their own work allocations
|
||||
- Can view their attendance records
|
||||
- Can see contractor rates
|
||||
|
||||
## Database Schema
|
||||
|
||||
### Tables
|
||||
|
||||
- `departments` - Main departments (Tudki, Dana, Groundnut)
|
||||
- `sub_departments` - Sub-departments (17 for Groundnut)
|
||||
- `users` - All users (SuperAdmin, Supervisor, Contractor, Employee)
|
||||
- `contractor_rates` - Contractor rate history
|
||||
- `work_allocations` - Work assignments
|
||||
- `attendance` - Check-in/out records
|
||||
|
||||
## Development Notes
|
||||
|
||||
- The server uses ES modules (type: "module" in package.json)
|
||||
- JWT tokens are used for authentication
|
||||
- Passwords are hashed using bcryptjs
|
||||
- All timestamps are in UTC
|
||||
- The API uses role-based access control (RBAC)
|
||||
@@ -1,33 +0,0 @@
|
||||
import mysql from 'mysql2/promise';
|
||||
import dotenv from 'dotenv';
|
||||
import { fileURLToPath } from 'url';
|
||||
import { dirname, join } from 'path';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
|
||||
// Load .env from backend directory
|
||||
dotenv.config({ path: join(__dirname, '..', '.env') });
|
||||
|
||||
const pool = mysql.createPool({
|
||||
host: process.env.DB_HOST || 'localhost',
|
||||
user: process.env.DB_USER || 'root',
|
||||
password: process.env.DB_PASSWORD || 'admin123',
|
||||
database: process.env.DB_NAME || 'work_allocation',
|
||||
port: process.env.DB_PORT || 3306,
|
||||
waitForConnections: true,
|
||||
connectionLimit: 10,
|
||||
queueLimit: 0
|
||||
});
|
||||
|
||||
// Test connection
|
||||
pool.getConnection()
|
||||
.then(connection => {
|
||||
console.log('✅ Database connected successfully');
|
||||
connection.release();
|
||||
})
|
||||
.catch(err => {
|
||||
console.error('❌ Database connection failed:', err.message);
|
||||
});
|
||||
|
||||
export default pool;
|
||||
@@ -1,240 +0,0 @@
|
||||
import bcrypt from 'bcryptjs';
|
||||
import mysql from 'mysql2/promise';
|
||||
import dotenv from 'dotenv';
|
||||
|
||||
dotenv.config();
|
||||
|
||||
async function seedDatabase() {
|
||||
let connection;
|
||||
|
||||
try {
|
||||
// Connect to database with retry logic
|
||||
console.log('🔌 Connecting to database...');
|
||||
|
||||
let retries = 5;
|
||||
while (retries > 0) {
|
||||
try {
|
||||
connection = await mysql.createConnection({
|
||||
host: process.env.DB_HOST || 'localhost',
|
||||
user: process.env.DB_USER || 'root',
|
||||
password: process.env.DB_PASSWORD || 'admin123',
|
||||
database: process.env.DB_NAME || 'work_allocation',
|
||||
port: process.env.DB_PORT || 3306,
|
||||
connectTimeout: 10000,
|
||||
enableKeepAlive: true,
|
||||
keepAliveInitialDelay: 0
|
||||
});
|
||||
break;
|
||||
} catch (err) {
|
||||
retries--;
|
||||
if (retries === 0) throw err;
|
||||
console.log(` ⏳ Retrying connection... (${5 - retries}/5)`);
|
||||
await new Promise(resolve => setTimeout(resolve, 2000));
|
||||
}
|
||||
}
|
||||
|
||||
console.log('✅ Connected to database');
|
||||
console.log('');
|
||||
|
||||
// 1. Seed Departments
|
||||
console.log('📁 Seeding departments...');
|
||||
const [existingDepts] = await connection.query('SELECT COUNT(*) as count FROM departments');
|
||||
|
||||
if (existingDepts[0].count === 0) {
|
||||
await connection.query(`
|
||||
INSERT INTO departments (name) VALUES
|
||||
('Tudki'),
|
||||
('Dana'),
|
||||
('Groundnut')
|
||||
`);
|
||||
console.log(' ✅ Departments created');
|
||||
} else {
|
||||
console.log(' ℹ️ Departments already exist');
|
||||
}
|
||||
|
||||
// 2. Seed Sub-departments for Groundnut
|
||||
console.log('📂 Seeding sub-departments...');
|
||||
const [groundnutDept] = await connection.query('SELECT id FROM departments WHERE name = ?', ['Groundnut']);
|
||||
let groundnutId = null;
|
||||
|
||||
if (groundnutDept.length > 0) {
|
||||
groundnutId = groundnutDept[0].id;
|
||||
const [existingSubDepts] = await connection.query('SELECT COUNT(*) as count FROM sub_departments WHERE department_id = ?', [groundnutId]);
|
||||
|
||||
if (existingSubDepts[0].count === 0) {
|
||||
await connection.query(`
|
||||
INSERT INTO sub_departments (department_id, name, primary_activity) VALUES
|
||||
(?, 'Mufali Aavak Katai', 'Loading/Unloading'),
|
||||
(?, 'Mufali Aavak Dhang', 'Loading/Unloading'),
|
||||
(?, 'Dhang Se Katai', 'Loading/Unloading'),
|
||||
(?, 'Guthli Bori Silai Dhang', 'Loading/Unloading'),
|
||||
(?, 'Guthali dada Pala Tulai Silai Dhang', 'Loading/Unloading'),
|
||||
(?, 'Mufali Patthar Bori silai dhang', 'Loading/Unloading'),
|
||||
(?, 'Mufali Patthar Bori Utrai', 'Loading/Unloading'),
|
||||
(?, 'Bardana Bandal Loading Unloading', 'Loading/Unloading'),
|
||||
(?, 'Bardana Gatthi Loading', 'Loading/Unloading'),
|
||||
(?, 'Black Dana Loading/Unloading', 'Loading/Unloading'),
|
||||
(?, 'Pre Cleaning', 'Pre Cleaning'),
|
||||
(?, 'Destoner', 'Destoner'),
|
||||
(?, 'Water', 'Water'),
|
||||
(?, 'Decordicater', 'Decordicater & Cleaning'),
|
||||
(?, 'Round Chalna', 'Round Chalna & Cleaning'),
|
||||
(?, 'Cleaning', 'Decordicater & Cleaning'),
|
||||
(?, 'Round Chalna No.1', 'Round Chalna No.1')
|
||||
`, Array(17).fill(groundnutId));
|
||||
console.log(' ✅ Sub-departments created');
|
||||
} else {
|
||||
console.log(' ℹ️ Sub-departments already exist');
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Seed SuperAdmin
|
||||
console.log('👤 Seeding SuperAdmin user...');
|
||||
const [existingAdmin] = await connection.query('SELECT id FROM users WHERE username = ?', ['admin']);
|
||||
|
||||
const adminPassword = await bcrypt.hash('admin123', 10);
|
||||
|
||||
if (existingAdmin.length > 0) {
|
||||
await connection.query(
|
||||
'UPDATE users SET password = ?, is_active = TRUE WHERE username = ?',
|
||||
[adminPassword, 'admin']
|
||||
);
|
||||
console.log(' ✅ SuperAdmin password updated');
|
||||
} else {
|
||||
await connection.query(
|
||||
'INSERT INTO users (username, name, email, password, role, is_active) VALUES (?, ?, ?, ?, ?, ?)',
|
||||
['admin', 'Super Admin', 'admin@workallocate.com', adminPassword, 'SuperAdmin', true]
|
||||
);
|
||||
console.log(' ✅ SuperAdmin created');
|
||||
}
|
||||
|
||||
// 4. Seed Sample Supervisors
|
||||
console.log('👥 Seeding sample supervisors...');
|
||||
const [tudkiDept] = await connection.query('SELECT id FROM departments WHERE name = ?', ['Tudki']);
|
||||
const [danaDept] = await connection.query('SELECT id FROM departments WHERE name = ?', ['Dana']);
|
||||
|
||||
const supervisorPassword = await bcrypt.hash('supervisor123', 10);
|
||||
|
||||
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 }
|
||||
];
|
||||
|
||||
for (const sup of supervisors) {
|
||||
if (sup.deptId) {
|
||||
const [existing] = await connection.query('SELECT id FROM users WHERE username = ?', [sup.username]);
|
||||
if (existing.length === 0) {
|
||||
await connection.query(
|
||||
'INSERT INTO users (username, name, email, password, role, department_id, is_active) VALUES (?, ?, ?, ?, ?, ?, ?)',
|
||||
[sup.username, sup.name, sup.email, supervisorPassword, 'Supervisor', sup.deptId, true]
|
||||
);
|
||||
console.log(` ✅ ${sup.name} created`);
|
||||
} else {
|
||||
console.log(` ℹ️ ${sup.name} already exists`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 5. Seed Sample Contractors
|
||||
console.log('🏗️ Seeding sample contractors...');
|
||||
const contractorPassword = await bcrypt.hash('contractor123', 10);
|
||||
|
||||
const contractors = [
|
||||
{ username: 'contractor1', name: 'Contractor One', email: 'contractor1@workallocate.com', deptId: groundnutId },
|
||||
{ username: 'contractor2', name: 'Contractor Two', email: 'contractor2@workallocate.com', deptId: groundnutId }
|
||||
];
|
||||
|
||||
for (const con of contractors) {
|
||||
const [existing] = await connection.query('SELECT id FROM users WHERE username = ?', [con.username]);
|
||||
if (existing.length === 0) {
|
||||
await connection.query(
|
||||
'INSERT INTO users (username, name, email, password, role, department_id, is_active) VALUES (?, ?, ?, ?, ?, ?, ?)',
|
||||
[con.username, con.name, con.email, contractorPassword, 'Contractor', con.deptId, true]
|
||||
);
|
||||
console.log(` ✅ ${con.name} created`);
|
||||
} else {
|
||||
console.log(` ℹ️ ${con.name} already exists`);
|
||||
}
|
||||
}
|
||||
|
||||
// 6. Seed Sample Employees
|
||||
console.log('👷 Seeding sample employees...');
|
||||
const [contractor1] = await connection.query('SELECT id FROM users WHERE username = ?', ['contractor1']);
|
||||
const employeePassword = await bcrypt.hash('employee123', 10);
|
||||
|
||||
if (contractor1.length > 0) {
|
||||
const employees = [
|
||||
{ username: 'employee1', name: 'Employee One', email: 'employee1@workallocate.com' },
|
||||
{ username: 'employee2', name: 'Employee Two', email: 'employee2@workallocate.com' },
|
||||
{ username: 'employee3', name: 'Employee Three', email: 'employee3@workallocate.com' }
|
||||
];
|
||||
|
||||
for (const emp of employees) {
|
||||
const [existing] = await connection.query('SELECT id FROM users WHERE username = ?', [emp.username]);
|
||||
if (existing.length === 0) {
|
||||
await connection.query(
|
||||
'INSERT INTO users (username, name, email, password, role, department_id, contractor_id, is_active) VALUES (?, ?, ?, ?, ?, ?, ?, ?)',
|
||||
[emp.username, emp.name, emp.email, employeePassword, 'Employee', groundnutId, contractor1[0].id, true]
|
||||
);
|
||||
console.log(` ✅ ${emp.name} created`);
|
||||
} else {
|
||||
console.log(` ℹ️ ${emp.name} already exists`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 7. Seed Contractor Rates
|
||||
console.log('💰 Seeding contractor rates...');
|
||||
if (contractor1.length > 0) {
|
||||
const [existingRate] = await connection.query(
|
||||
'SELECT id FROM contractor_rates WHERE contractor_id = ?',
|
||||
[contractor1[0].id]
|
||||
);
|
||||
|
||||
if (existingRate.length === 0) {
|
||||
await connection.query(
|
||||
'INSERT INTO contractor_rates (contractor_id, rate, effective_date) VALUES (?, ?, CURDATE())',
|
||||
[contractor1[0].id, 500.00]
|
||||
);
|
||||
console.log(' ✅ Contractor rates created');
|
||||
} else {
|
||||
console.log(' ℹ️ Contractor rates already exist');
|
||||
}
|
||||
}
|
||||
|
||||
console.log('');
|
||||
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
||||
console.log('✅ Database seeding completed successfully!');
|
||||
console.log('');
|
||||
console.log('🔑 Default Login Credentials:');
|
||||
console.log('');
|
||||
console.log(' SuperAdmin:');
|
||||
console.log(' Username: admin');
|
||||
console.log(' Password: admin123');
|
||||
console.log('');
|
||||
console.log(' Supervisor (Groundnut):');
|
||||
console.log(' Username: supervisor_groundnut');
|
||||
console.log(' Password: supervisor123');
|
||||
console.log('');
|
||||
console.log(' Contractor:');
|
||||
console.log(' Username: contractor1');
|
||||
console.log(' Password: contractor123');
|
||||
console.log('');
|
||||
console.log(' Employee:');
|
||||
console.log(' Username: employee1');
|
||||
console.log(' Password: employee123');
|
||||
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
||||
console.log('');
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ Error seeding database:', error.message);
|
||||
process.exit(1);
|
||||
} finally {
|
||||
if (connection) {
|
||||
await connection.end();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
seedDatabase();
|
||||
@@ -30,6 +30,16 @@ CREATE TABLE IF NOT EXISTS users (
|
||||
department_id INT,
|
||||
contractor_id INT,
|
||||
is_active BOOLEAN DEFAULT TRUE,
|
||||
-- Common fields for Employee and Contractor
|
||||
phone_number VARCHAR(20),
|
||||
aadhar_number VARCHAR(12),
|
||||
bank_account_number VARCHAR(30),
|
||||
bank_name VARCHAR(100),
|
||||
bank_ifsc VARCHAR(20),
|
||||
-- Contractor-specific fields
|
||||
contractor_agreement_number VARCHAR(50),
|
||||
pf_number VARCHAR(30),
|
||||
esic_number VARCHAR(30),
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (department_id) REFERENCES departments(id) ON DELETE SET NULL,
|
||||
FOREIGN KEY (contractor_id) REFERENCES users(id) ON DELETE SET NULL
|
||||
@@ -65,13 +75,38 @@ CREATE TABLE IF NOT EXISTS attendance (
|
||||
check_in_time DATETIME,
|
||||
check_out_time DATETIME,
|
||||
work_date DATE NOT NULL,
|
||||
status ENUM('CheckedIn', 'CheckedOut', 'Absent') DEFAULT 'CheckedIn',
|
||||
status ENUM('CheckedIn', 'CheckedOut', 'Absent', 'HalfDay', 'Late') DEFAULT 'CheckedIn',
|
||||
remark VARCHAR(255),
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (employee_id) REFERENCES users(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (supervisor_id) REFERENCES users(id) ON DELETE CASCADE,
|
||||
UNIQUE KEY unique_attendance (employee_id, work_date)
|
||||
);
|
||||
|
||||
-- Create employee_swaps table for tracking employee department transfers
|
||||
CREATE TABLE IF NOT EXISTS employee_swaps (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
employee_id INT NOT NULL,
|
||||
original_department_id INT NOT NULL,
|
||||
target_department_id INT NOT NULL,
|
||||
original_contractor_id INT,
|
||||
target_contractor_id INT,
|
||||
swap_reason ENUM('LeftWork', 'Sick', 'FinishedEarly', 'Other') NOT NULL,
|
||||
reason_details VARCHAR(500),
|
||||
work_completion_percentage INT DEFAULT 0,
|
||||
swap_date DATE NOT NULL,
|
||||
swapped_by INT NOT NULL,
|
||||
status ENUM('Active', 'Completed', 'Cancelled') DEFAULT 'Active',
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
completed_at TIMESTAMP NULL,
|
||||
FOREIGN KEY (employee_id) REFERENCES users(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (original_department_id) REFERENCES departments(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (target_department_id) REFERENCES departments(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (original_contractor_id) REFERENCES users(id) ON DELETE SET NULL,
|
||||
FOREIGN KEY (target_contractor_id) REFERENCES users(id) ON DELETE SET NULL,
|
||||
FOREIGN KEY (swapped_by) REFERENCES users(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
-- Create contractor_rates table
|
||||
CREATE TABLE IF NOT EXISTS contractor_rates (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
@@ -89,6 +124,8 @@ CREATE TABLE IF NOT EXISTS contractor_rates (
|
||||
CREATE INDEX idx_users_role ON users(role);
|
||||
CREATE INDEX idx_users_department ON users(department_id);
|
||||
CREATE INDEX idx_users_contractor ON users(contractor_id);
|
||||
CREATE INDEX idx_users_phone ON users(phone_number);
|
||||
CREATE INDEX idx_users_aadhar ON users(aadhar_number);
|
||||
CREATE INDEX idx_work_allocations_employee ON work_allocations(employee_id);
|
||||
CREATE INDEX idx_work_allocations_supervisor ON work_allocations(supervisor_id);
|
||||
CREATE INDEX idx_work_allocations_contractor ON work_allocations(contractor_id);
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
-- Migration: Add activity, units, and total_amount columns to work_allocations table
|
||||
-- Run this if you have an existing database without these columns
|
||||
|
||||
ALTER TABLE work_allocations
|
||||
ADD COLUMN IF NOT EXISTS activity VARCHAR(100) AFTER sub_department_id;
|
||||
|
||||
ALTER TABLE work_allocations
|
||||
ADD COLUMN IF NOT EXISTS units DECIMAL(10, 2) AFTER rate;
|
||||
|
||||
ALTER TABLE work_allocations
|
||||
ADD COLUMN IF NOT EXISTS total_amount DECIMAL(10, 2) AFTER units;
|
||||
@@ -1,13 +0,0 @@
|
||||
-- Migration: Add sub_department_id and activity columns to contractor_rates table
|
||||
-- Run this if you have an existing database
|
||||
|
||||
-- Add sub_department_id column if it doesn't exist
|
||||
ALTER TABLE contractor_rates
|
||||
ADD COLUMN IF NOT EXISTS sub_department_id INT NULL,
|
||||
ADD COLUMN IF NOT EXISTS activity VARCHAR(200) NULL;
|
||||
|
||||
-- Add foreign key constraint for sub_department_id
|
||||
-- Note: This may fail if the constraint already exists
|
||||
ALTER TABLE contractor_rates
|
||||
ADD CONSTRAINT fk_contractor_rates_sub_department
|
||||
FOREIGN KEY (sub_department_id) REFERENCES sub_departments(id) ON DELETE SET NULL;
|
||||
18
backend/database/migrations/add_user_details.sql
Normal file
18
backend/database/migrations/add_user_details.sql
Normal file
@@ -0,0 +1,18 @@
|
||||
-- Migration: Add personal and bank details to users table
|
||||
-- Run this migration on existing databases to add the new fields
|
||||
|
||||
-- Common fields for Employee and Contractor
|
||||
ALTER TABLE users ADD COLUMN phone_number VARCHAR(20) AFTER is_active;
|
||||
ALTER TABLE users ADD COLUMN aadhar_number VARCHAR(12) AFTER phone_number;
|
||||
ALTER TABLE users ADD COLUMN bank_account_number VARCHAR(30) AFTER aadhar_number;
|
||||
ALTER TABLE users ADD COLUMN bank_name VARCHAR(100) AFTER bank_account_number;
|
||||
ALTER TABLE users ADD COLUMN bank_ifsc VARCHAR(20) AFTER bank_name;
|
||||
|
||||
-- Contractor-specific fields
|
||||
ALTER TABLE users ADD COLUMN contractor_agreement_number VARCHAR(50) AFTER bank_ifsc;
|
||||
ALTER TABLE users ADD COLUMN pf_number VARCHAR(30) AFTER contractor_agreement_number;
|
||||
ALTER TABLE users ADD COLUMN esic_number VARCHAR(30) AFTER pf_number;
|
||||
|
||||
-- Add indexes for commonly queried fields
|
||||
CREATE INDEX idx_users_phone ON users(phone_number);
|
||||
CREATE INDEX idx_users_aadhar ON users(aadhar_number);
|
||||
@@ -1,134 +0,0 @@
|
||||
-- Work Allocation System Database Schema
|
||||
|
||||
-- Create database
|
||||
CREATE DATABASE IF NOT EXISTS work_allocation;
|
||||
USE work_allocation;
|
||||
|
||||
-- Departments table
|
||||
CREATE TABLE departments (
|
||||
id INT PRIMARY KEY AUTO_INCREMENT,
|
||||
name VARCHAR(100) NOT NULL UNIQUE,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- Sub-departments table (for Groundnut department)
|
||||
CREATE TABLE sub_departments (
|
||||
id INT PRIMARY KEY AUTO_INCREMENT,
|
||||
department_id INT NOT NULL,
|
||||
name VARCHAR(200) NOT NULL,
|
||||
primary_activity VARCHAR(200) NOT NULL,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (department_id) REFERENCES departments(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
-- Users table (for all roles: SuperAdmin, Supervisor, Contractor, Employee)
|
||||
CREATE TABLE users (
|
||||
id INT PRIMARY KEY AUTO_INCREMENT,
|
||||
username VARCHAR(100) NOT NULL UNIQUE,
|
||||
name VARCHAR(200) NOT NULL,
|
||||
email VARCHAR(200) NOT NULL UNIQUE,
|
||||
password VARCHAR(255) NOT NULL,
|
||||
role ENUM('SuperAdmin', 'Supervisor', 'Contractor', 'Employee') NOT NULL,
|
||||
department_id INT,
|
||||
contractor_id INT,
|
||||
is_active BOOLEAN DEFAULT TRUE,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (department_id) REFERENCES departments(id) ON DELETE SET NULL,
|
||||
FOREIGN KEY (contractor_id) REFERENCES users(id) ON DELETE SET NULL
|
||||
);
|
||||
|
||||
-- Contractor rates table
|
||||
CREATE TABLE contractor_rates (
|
||||
id INT PRIMARY KEY AUTO_INCREMENT,
|
||||
contractor_id INT NOT NULL,
|
||||
rate DECIMAL(10, 2) NOT NULL,
|
||||
effective_date DATE NOT NULL,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (contractor_id) REFERENCES users(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
-- Work allocations table
|
||||
CREATE TABLE work_allocations (
|
||||
id INT PRIMARY KEY AUTO_INCREMENT,
|
||||
employee_id INT NOT NULL,
|
||||
supervisor_id INT NOT NULL,
|
||||
contractor_id INT NOT NULL,
|
||||
sub_department_id INT,
|
||||
description TEXT,
|
||||
assigned_date DATE NOT NULL,
|
||||
status ENUM('Pending', 'InProgress', 'Completed', 'Cancelled') DEFAULT 'Pending',
|
||||
completion_date DATE,
|
||||
rate DECIMAL(10, 2),
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (employee_id) REFERENCES users(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (supervisor_id) REFERENCES users(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (contractor_id) REFERENCES users(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (sub_department_id) REFERENCES sub_departments(id) ON DELETE SET NULL
|
||||
);
|
||||
|
||||
-- Attendance table
|
||||
CREATE TABLE attendance (
|
||||
id INT PRIMARY KEY AUTO_INCREMENT,
|
||||
employee_id INT NOT NULL,
|
||||
supervisor_id INT NOT NULL,
|
||||
check_in_time DATETIME NOT NULL,
|
||||
check_out_time DATETIME,
|
||||
work_date DATE NOT NULL,
|
||||
status ENUM('CheckedIn', 'CheckedOut') DEFAULT 'CheckedIn',
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (employee_id) REFERENCES users(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (supervisor_id) REFERENCES users(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
-- Insert default departments
|
||||
INSERT INTO departments (name) VALUES
|
||||
('Tudki'),
|
||||
('Dana'),
|
||||
('Groundnut');
|
||||
|
||||
-- Insert Groundnut sub-departments
|
||||
INSERT INTO sub_departments (department_id, name, primary_activity)
|
||||
SELECT id, 'Mufali Aavak Katai', 'Loading/Unloading' FROM departments WHERE name = 'Groundnut'
|
||||
UNION ALL
|
||||
SELECT id, 'Mufali Aavak Dhang', 'Loading/Unloading' FROM departments WHERE name = 'Groundnut'
|
||||
UNION ALL
|
||||
SELECT id, 'Dhang Se Katai', 'Loading/Unloading' FROM departments WHERE name = 'Groundnut'
|
||||
UNION ALL
|
||||
SELECT id, 'Guthli Bori Silai Dhang', 'Loading/Unloading' FROM departments WHERE name = 'Groundnut'
|
||||
UNION ALL
|
||||
SELECT id, 'Guthali dada Pala Tulai Silai Dhang', 'Loading/Unloading' FROM departments WHERE name = 'Groundnut'
|
||||
UNION ALL
|
||||
SELECT id, 'Mufali Patthar Bori silai dhang', 'Loading/Unloading' FROM departments WHERE name = 'Groundnut'
|
||||
UNION ALL
|
||||
SELECT id, 'Mufali Patthar Bori Utrai', 'Loading/Unloading' FROM departments WHERE name = 'Groundnut'
|
||||
UNION ALL
|
||||
SELECT id, 'Bardana Bandal Loading Unloading', 'Loading/Unloading' FROM departments WHERE name = 'Groundnut'
|
||||
UNION ALL
|
||||
SELECT id, 'Bardana Gatthi Loading', 'Loading/Unloading' FROM departments WHERE name = 'Groundnut'
|
||||
UNION ALL
|
||||
SELECT id, 'Black Dana Loading/Unloading', 'Loading/Unloading' FROM departments WHERE name = 'Groundnut'
|
||||
UNION ALL
|
||||
SELECT id, 'Pre Cleaning', 'Pre Cleaning' FROM departments WHERE name = 'Groundnut'
|
||||
UNION ALL
|
||||
SELECT id, 'Destoner', 'Destoner' FROM departments WHERE name = 'Groundnut'
|
||||
UNION ALL
|
||||
SELECT id, 'Water', 'Water' FROM departments WHERE name = 'Groundnut'
|
||||
UNION ALL
|
||||
SELECT id, 'Decordicater', 'Decordicater & Cleaning' FROM departments WHERE name = 'Groundnut'
|
||||
UNION ALL
|
||||
SELECT id, 'Round Chalna', 'Round Chalna & Cleaning' FROM departments WHERE name = 'Groundnut'
|
||||
UNION ALL
|
||||
SELECT id, 'Cleaning', 'Decordicater & Cleaning' FROM departments WHERE name = 'Groundnut'
|
||||
UNION ALL
|
||||
SELECT id, 'Round Chalna No.1', 'Round Chalna No.1' FROM departments WHERE name = 'Groundnut';
|
||||
|
||||
-- Insert default SuperAdmin (password: admin123)
|
||||
-- Password is hashed using bcrypt: $2a$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi
|
||||
INSERT INTO users (username, name, email, password, role) VALUES
|
||||
('admin', 'Super Admin', 'admin@workallocate.com', '$2a$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', 'SuperAdmin');
|
||||
@@ -1,72 +0,0 @@
|
||||
import bcrypt from 'bcryptjs';
|
||||
import mysql from 'mysql2/promise';
|
||||
import dotenv from 'dotenv';
|
||||
|
||||
dotenv.config();
|
||||
|
||||
async function seedAdmin() {
|
||||
let connection;
|
||||
|
||||
try {
|
||||
// Connect to database (use root for seeding)
|
||||
connection = await mysql.createConnection({
|
||||
host: process.env.DB_HOST || 'localhost',
|
||||
user: 'root',
|
||||
password: 'rootpassword',
|
||||
database: process.env.DB_NAME || 'work_allocation',
|
||||
port: process.env.DB_PORT || 3306
|
||||
});
|
||||
|
||||
console.log('✅ Connected to database');
|
||||
|
||||
// Check if admin already exists
|
||||
const [existingUsers] = await connection.query(
|
||||
'SELECT id FROM users WHERE username = ?',
|
||||
['admin']
|
||||
);
|
||||
|
||||
if (existingUsers.length > 0) {
|
||||
console.log('ℹ️ Admin user already exists, updating password...');
|
||||
|
||||
// Generate new password hash
|
||||
const passwordHash = await bcrypt.hash('admin123', 10);
|
||||
|
||||
// Update existing admin user
|
||||
await connection.query(
|
||||
'UPDATE users SET password = ? WHERE username = ?',
|
||||
[passwordHash, 'admin']
|
||||
);
|
||||
|
||||
console.log('✅ Admin password updated successfully');
|
||||
} else {
|
||||
console.log('📝 Creating admin user...');
|
||||
|
||||
// Generate password hash
|
||||
const passwordHash = await bcrypt.hash('admin123', 10);
|
||||
|
||||
// Insert admin user
|
||||
await connection.query(
|
||||
'INSERT INTO users (username, name, email, password, role) VALUES (?, ?, ?, ?, ?)',
|
||||
['admin', 'Super Admin', 'admin@workallocate.com', passwordHash, 'SuperAdmin']
|
||||
);
|
||||
|
||||
console.log('✅ Admin user created successfully');
|
||||
}
|
||||
|
||||
console.log('');
|
||||
console.log('🔑 Default Login Credentials:');
|
||||
console.log(' Username: admin');
|
||||
console.log(' Password: admin123');
|
||||
console.log('');
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ Error seeding admin user:', error.message);
|
||||
process.exit(1);
|
||||
} finally {
|
||||
if (connection) {
|
||||
await connection.end();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
seedAdmin();
|
||||
@@ -1,32 +0,0 @@
|
||||
import jwt from 'jsonwebtoken';
|
||||
|
||||
export const authenticateToken = (req, res, next) => {
|
||||
const authHeader = req.headers['authorization'];
|
||||
const token = authHeader && authHeader.split(' ')[1];
|
||||
|
||||
if (!token) {
|
||||
return res.status(401).json({ error: 'Access token required' });
|
||||
}
|
||||
|
||||
jwt.verify(token, process.env.JWT_SECRET, (err, user) => {
|
||||
if (err) {
|
||||
return res.status(403).json({ error: 'Invalid or expired token' });
|
||||
}
|
||||
req.user = user;
|
||||
next();
|
||||
});
|
||||
};
|
||||
|
||||
export const authorize = (...roles) => {
|
||||
return (req, res, next) => {
|
||||
if (!req.user) {
|
||||
return res.status(401).json({ error: 'Unauthorized' });
|
||||
}
|
||||
|
||||
if (!roles.includes(req.user.role)) {
|
||||
return res.status(403).json({ error: 'Insufficient permissions' });
|
||||
}
|
||||
|
||||
next();
|
||||
};
|
||||
};
|
||||
1120
backend/package-lock.json
generated
1120
backend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,20 +0,0 @@
|
||||
{
|
||||
"name": "work-allocation-backend",
|
||||
"version": "1.0.0",
|
||||
"description": "Simple backend for Work Allocation System",
|
||||
"main": "server.js",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"start": "node server.js",
|
||||
"dev": "node --watch server.js",
|
||||
"seed": "node database/database_seed.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"express": "^4.18.2",
|
||||
"mysql2": "^3.6.5",
|
||||
"bcryptjs": "^2.4.3",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"cors": "^2.8.5",
|
||||
"dotenv": "^16.3.1"
|
||||
}
|
||||
}
|
||||
@@ -1,259 +0,0 @@
|
||||
import express from 'express';
|
||||
import db from '../config/database.js';
|
||||
import { authenticateToken, authorize } from '../middleware/auth.js';
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
// Get all attendance records
|
||||
router.get('/', authenticateToken, async (req, res) => {
|
||||
try {
|
||||
const { employeeId, startDate, endDate, status } = req.query;
|
||||
|
||||
let query = `
|
||||
SELECT a.*,
|
||||
e.name as employee_name, e.username as employee_username,
|
||||
s.name as supervisor_name,
|
||||
d.name as department_name,
|
||||
c.name as contractor_name
|
||||
FROM attendance a
|
||||
JOIN users e ON a.employee_id = e.id
|
||||
JOIN users s ON a.supervisor_id = s.id
|
||||
LEFT JOIN departments d ON e.department_id = d.id
|
||||
LEFT JOIN users c ON e.contractor_id = c.id
|
||||
WHERE 1=1
|
||||
`;
|
||||
const params = [];
|
||||
|
||||
// Role-based filtering
|
||||
if (req.user.role === 'Supervisor') {
|
||||
query += ' AND a.supervisor_id = ?';
|
||||
params.push(req.user.id);
|
||||
} else if (req.user.role === 'Employee') {
|
||||
query += ' AND a.employee_id = ?';
|
||||
params.push(req.user.id);
|
||||
}
|
||||
|
||||
if (employeeId) {
|
||||
query += ' AND a.employee_id = ?';
|
||||
params.push(employeeId);
|
||||
}
|
||||
|
||||
if (startDate) {
|
||||
query += ' AND a.work_date >= ?';
|
||||
params.push(startDate);
|
||||
}
|
||||
|
||||
if (endDate) {
|
||||
query += ' AND a.work_date <= ?';
|
||||
params.push(endDate);
|
||||
}
|
||||
|
||||
if (status) {
|
||||
query += ' AND a.status = ?';
|
||||
params.push(status);
|
||||
}
|
||||
|
||||
query += ' ORDER BY a.work_date DESC, a.check_in_time DESC';
|
||||
|
||||
const [records] = await db.query(query, params);
|
||||
res.json(records);
|
||||
} catch (error) {
|
||||
console.error('Get attendance error:', error);
|
||||
res.status(500).json({ error: 'Internal server error' });
|
||||
}
|
||||
});
|
||||
|
||||
// Get attendance by ID
|
||||
router.get('/:id', authenticateToken, async (req, res) => {
|
||||
try {
|
||||
const [records] = await db.query(
|
||||
`SELECT a.*,
|
||||
e.name as employee_name, e.username as employee_username,
|
||||
s.name as supervisor_name,
|
||||
d.name as department_name,
|
||||
c.name as contractor_name
|
||||
FROM attendance a
|
||||
JOIN users e ON a.employee_id = e.id
|
||||
JOIN users s ON a.supervisor_id = s.id
|
||||
LEFT JOIN departments d ON e.department_id = d.id
|
||||
LEFT JOIN users c ON e.contractor_id = c.id
|
||||
WHERE a.id = ?`,
|
||||
[req.params.id]
|
||||
);
|
||||
|
||||
if (records.length === 0) {
|
||||
return res.status(404).json({ error: 'Attendance record not found' });
|
||||
}
|
||||
|
||||
res.json(records[0]);
|
||||
} catch (error) {
|
||||
console.error('Get attendance error:', error);
|
||||
res.status(500).json({ error: 'Internal server error' });
|
||||
}
|
||||
});
|
||||
|
||||
// Check in employee (Supervisor or SuperAdmin)
|
||||
router.post('/check-in', authenticateToken, authorize('Supervisor', 'SuperAdmin'), async (req, res) => {
|
||||
try {
|
||||
const { employeeId, workDate } = req.body;
|
||||
|
||||
if (!employeeId || !workDate) {
|
||||
return res.status(400).json({ error: 'Employee ID and work date required' });
|
||||
}
|
||||
|
||||
// Verify employee exists (SuperAdmin can check in any employee, Supervisor only their department)
|
||||
let employeeQuery = 'SELECT * FROM users WHERE id = ? AND role = ?';
|
||||
let employeeParams = [employeeId, 'Employee'];
|
||||
|
||||
if (req.user.role === 'Supervisor') {
|
||||
employeeQuery += ' AND department_id = ?';
|
||||
employeeParams.push(req.user.departmentId);
|
||||
}
|
||||
|
||||
const [employees] = await db.query(employeeQuery, employeeParams);
|
||||
|
||||
if (employees.length === 0) {
|
||||
return res.status(403).json({ error: 'Employee not found or not in your department' });
|
||||
}
|
||||
|
||||
// Check if already checked in today
|
||||
const [existing] = await db.query(
|
||||
'SELECT * FROM attendance WHERE employee_id = ? AND work_date = ? AND status = ?',
|
||||
[employeeId, workDate, 'CheckedIn']
|
||||
);
|
||||
|
||||
if (existing.length > 0) {
|
||||
return res.status(400).json({ error: 'Employee already checked in today' });
|
||||
}
|
||||
|
||||
const checkInTime = new Date();
|
||||
|
||||
const [result] = await db.query(
|
||||
'INSERT INTO attendance (employee_id, supervisor_id, check_in_time, work_date, status) VALUES (?, ?, ?, ?, ?)',
|
||||
[employeeId, req.user.id, checkInTime, workDate, 'CheckedIn']
|
||||
);
|
||||
|
||||
const [newRecord] = await db.query(
|
||||
`SELECT a.*,
|
||||
e.name as employee_name, e.username as employee_username,
|
||||
s.name as supervisor_name,
|
||||
d.name as department_name,
|
||||
c.name as contractor_name
|
||||
FROM attendance a
|
||||
JOIN users e ON a.employee_id = e.id
|
||||
JOIN users s ON a.supervisor_id = s.id
|
||||
LEFT JOIN departments d ON e.department_id = d.id
|
||||
LEFT JOIN users c ON e.contractor_id = c.id
|
||||
WHERE a.id = ?`,
|
||||
[result.insertId]
|
||||
);
|
||||
|
||||
res.status(201).json(newRecord[0]);
|
||||
} catch (error) {
|
||||
console.error('Check in error:', error);
|
||||
res.status(500).json({ error: 'Internal server error' });
|
||||
}
|
||||
});
|
||||
|
||||
// Check out employee (Supervisor or SuperAdmin)
|
||||
router.post('/check-out', authenticateToken, authorize('Supervisor', 'SuperAdmin'), async (req, res) => {
|
||||
try {
|
||||
const { employeeId, workDate } = req.body;
|
||||
|
||||
if (!employeeId || !workDate) {
|
||||
return res.status(400).json({ error: 'Employee ID and work date required' });
|
||||
}
|
||||
|
||||
// Find the check-in record (SuperAdmin can check out any, Supervisor only their own)
|
||||
let query = 'SELECT * FROM attendance WHERE employee_id = ? AND work_date = ? AND status = ?';
|
||||
let params = [employeeId, workDate, 'CheckedIn'];
|
||||
|
||||
if (req.user.role === 'Supervisor') {
|
||||
query += ' AND supervisor_id = ?';
|
||||
params.push(req.user.id);
|
||||
}
|
||||
|
||||
const [records] = await db.query(query, params);
|
||||
|
||||
if (records.length === 0) {
|
||||
return res.status(404).json({ error: 'No check-in record found for today' });
|
||||
}
|
||||
|
||||
const checkOutTime = new Date();
|
||||
|
||||
await db.query(
|
||||
'UPDATE attendance SET check_out_time = ?, status = ? WHERE id = ?',
|
||||
[checkOutTime, 'CheckedOut', records[0].id]
|
||||
);
|
||||
|
||||
const [updatedRecord] = await db.query(
|
||||
`SELECT a.*,
|
||||
e.name as employee_name, e.username as employee_username,
|
||||
s.name as supervisor_name,
|
||||
d.name as department_name,
|
||||
c.name as contractor_name
|
||||
FROM attendance a
|
||||
JOIN users e ON a.employee_id = e.id
|
||||
JOIN users s ON a.supervisor_id = s.id
|
||||
LEFT JOIN departments d ON e.department_id = d.id
|
||||
LEFT JOIN users c ON e.contractor_id = c.id
|
||||
WHERE a.id = ?`,
|
||||
[records[0].id]
|
||||
);
|
||||
|
||||
res.json(updatedRecord[0]);
|
||||
} catch (error) {
|
||||
console.error('Check out error:', error);
|
||||
res.status(500).json({ error: 'Internal server error' });
|
||||
}
|
||||
});
|
||||
|
||||
// Get attendance summary
|
||||
router.get('/summary/stats', authenticateToken, async (req, res) => {
|
||||
try {
|
||||
const { startDate, endDate, departmentId } = req.query;
|
||||
|
||||
let query = `
|
||||
SELECT
|
||||
COUNT(DISTINCT a.employee_id) as total_employees,
|
||||
COUNT(DISTINCT CASE WHEN a.status = 'CheckedIn' THEN a.employee_id END) as checked_in,
|
||||
COUNT(DISTINCT CASE WHEN a.status = 'CheckedOut' THEN a.employee_id END) as checked_out,
|
||||
d.name as department_name
|
||||
FROM attendance a
|
||||
JOIN users e ON a.employee_id = e.id
|
||||
LEFT JOIN departments d ON e.department_id = d.id
|
||||
WHERE 1=1
|
||||
`;
|
||||
const params = [];
|
||||
|
||||
if (req.user.role === 'Supervisor') {
|
||||
query += ' AND a.supervisor_id = ?';
|
||||
params.push(req.user.id);
|
||||
}
|
||||
|
||||
if (startDate) {
|
||||
query += ' AND a.work_date >= ?';
|
||||
params.push(startDate);
|
||||
}
|
||||
|
||||
if (endDate) {
|
||||
query += ' AND a.work_date <= ?';
|
||||
params.push(endDate);
|
||||
}
|
||||
|
||||
if (departmentId) {
|
||||
query += ' AND e.department_id = ?';
|
||||
params.push(departmentId);
|
||||
}
|
||||
|
||||
query += ' GROUP BY d.id, d.name';
|
||||
|
||||
const [summary] = await db.query(query, params);
|
||||
res.json(summary);
|
||||
} catch (error) {
|
||||
console.error('Get attendance summary error:', error);
|
||||
res.status(500).json({ error: 'Internal server error' });
|
||||
}
|
||||
});
|
||||
|
||||
export default router;
|
||||
@@ -1,114 +0,0 @@
|
||||
import express from 'express';
|
||||
import bcrypt from 'bcryptjs';
|
||||
import jwt from 'jsonwebtoken';
|
||||
import db from '../config/database.js';
|
||||
import { authenticateToken } from '../middleware/auth.js';
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
// Login
|
||||
router.post('/login', async (req, res) => {
|
||||
try {
|
||||
const { username, password } = req.body;
|
||||
|
||||
if (!username || !password) {
|
||||
return res.status(400).json({ error: 'Username and password required' });
|
||||
}
|
||||
|
||||
const [users] = await db.query(
|
||||
'SELECT * FROM users WHERE username = ? AND is_active = TRUE',
|
||||
[username]
|
||||
);
|
||||
|
||||
if (users.length === 0) {
|
||||
return res.status(401).json({ error: 'Invalid credentials' });
|
||||
}
|
||||
|
||||
const user = users[0];
|
||||
const validPassword = await bcrypt.compare(password, user.password);
|
||||
|
||||
if (!validPassword) {
|
||||
return res.status(401).json({ error: 'Invalid credentials' });
|
||||
}
|
||||
|
||||
const token = jwt.sign(
|
||||
{
|
||||
id: user.id,
|
||||
username: user.username,
|
||||
role: user.role,
|
||||
departmentId: user.department_id
|
||||
},
|
||||
process.env.JWT_SECRET,
|
||||
{ expiresIn: process.env.JWT_EXPIRES_IN || '7d' }
|
||||
);
|
||||
|
||||
res.json({
|
||||
token,
|
||||
user: {
|
||||
id: user.id,
|
||||
username: user.username,
|
||||
name: user.name,
|
||||
email: user.email,
|
||||
role: user.role,
|
||||
department_id: user.department_id,
|
||||
contractor_id: user.contractor_id,
|
||||
is_active: user.is_active
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Login error:', error);
|
||||
res.status(500).json({ error: 'Internal server error' });
|
||||
}
|
||||
});
|
||||
|
||||
// Get current user
|
||||
router.get('/me', authenticateToken, async (req, res) => {
|
||||
try {
|
||||
const [users] = await db.query(
|
||||
'SELECT id, username, name, email, role, department_id, contractor_id FROM users WHERE id = ?',
|
||||
[req.user.id]
|
||||
);
|
||||
|
||||
if (users.length === 0) {
|
||||
return res.status(404).json({ error: 'User not found' });
|
||||
}
|
||||
|
||||
res.json(users[0]);
|
||||
} catch (error) {
|
||||
console.error('Get user error:', error);
|
||||
res.status(500).json({ error: 'Internal server error' });
|
||||
}
|
||||
});
|
||||
|
||||
// Change password
|
||||
router.post('/change-password', authenticateToken, async (req, res) => {
|
||||
try {
|
||||
const { currentPassword, newPassword } = req.body;
|
||||
|
||||
if (!currentPassword || !newPassword) {
|
||||
return res.status(400).json({ error: 'Current and new password required' });
|
||||
}
|
||||
|
||||
const [users] = await db.query('SELECT password FROM users WHERE id = ?', [req.user.id]);
|
||||
|
||||
if (users.length === 0) {
|
||||
return res.status(404).json({ error: 'User not found' });
|
||||
}
|
||||
|
||||
const validPassword = await bcrypt.compare(currentPassword, users[0].password);
|
||||
|
||||
if (!validPassword) {
|
||||
return res.status(401).json({ error: 'Current password is incorrect' });
|
||||
}
|
||||
|
||||
const hashedPassword = await bcrypt.hash(newPassword, 10);
|
||||
await db.query('UPDATE users SET password = ? WHERE id = ?', [hashedPassword, req.user.id]);
|
||||
|
||||
res.json({ message: 'Password changed successfully' });
|
||||
} catch (error) {
|
||||
console.error('Change password error:', error);
|
||||
res.status(500).json({ error: 'Internal server error' });p
|
||||
}
|
||||
});
|
||||
|
||||
export default router;
|
||||
@@ -1,198 +0,0 @@
|
||||
import express from 'express';
|
||||
import db from '../config/database.js';
|
||||
import { authenticateToken, authorize } from '../middleware/auth.js';
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
// Get contractor rates
|
||||
router.get('/', authenticateToken, async (req, res) => {
|
||||
try {
|
||||
const { contractorId, subDepartmentId } = req.query;
|
||||
|
||||
let query = `
|
||||
SELECT cr.*,
|
||||
u.name as contractor_name, u.username as contractor_username,
|
||||
sd.name as sub_department_name,
|
||||
d.name as department_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 params = [];
|
||||
|
||||
if (contractorId) {
|
||||
query += ' AND cr.contractor_id = ?';
|
||||
params.push(contractorId);
|
||||
}
|
||||
|
||||
if (subDepartmentId) {
|
||||
query += ' AND cr.sub_department_id = ?';
|
||||
params.push(subDepartmentId);
|
||||
}
|
||||
|
||||
query += ' ORDER BY cr.effective_date DESC, cr.created_at DESC';
|
||||
|
||||
const [rates] = await db.query(query, params);
|
||||
res.json(rates);
|
||||
} catch (error) {
|
||||
console.error('Get contractor rates error:', error);
|
||||
res.status(500).json({ error: 'Internal server error' });
|
||||
}
|
||||
});
|
||||
|
||||
// Get current rate for a contractor + sub-department combination
|
||||
router.get('/contractor/:contractorId/current', authenticateToken, async (req, res) => {
|
||||
try {
|
||||
const { subDepartmentId } = req.query;
|
||||
|
||||
let query = `
|
||||
SELECT cr.*,
|
||||
u.name as contractor_name, u.username as contractor_username,
|
||||
sd.name as sub_department_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
|
||||
WHERE cr.contractor_id = ?
|
||||
`;
|
||||
const params = [req.params.contractorId];
|
||||
|
||||
if (subDepartmentId) {
|
||||
query += ' AND cr.sub_department_id = ?';
|
||||
params.push(subDepartmentId);
|
||||
}
|
||||
|
||||
query += ' ORDER BY cr.effective_date DESC LIMIT 1';
|
||||
|
||||
const [rates] = await db.query(query, params);
|
||||
|
||||
if (rates.length === 0) {
|
||||
return res.status(404).json({ error: 'No rate found for contractor' });
|
||||
}
|
||||
|
||||
res.json(rates[0]);
|
||||
} catch (error) {
|
||||
console.error('Get current rate error:', error);
|
||||
res.status(500).json({ error: 'Internal server error' });
|
||||
}
|
||||
});
|
||||
|
||||
// Set contractor rate (Supervisor or SuperAdmin)
|
||||
router.post('/', authenticateToken, authorize('Supervisor', 'SuperAdmin'), async (req, res) => {
|
||||
try {
|
||||
const { contractorId, subDepartmentId, activity, rate, effectiveDate } = req.body;
|
||||
|
||||
if (!contractorId || !rate || !effectiveDate) {
|
||||
return res.status(400).json({ error: 'Missing required fields (contractorId, rate, effectiveDate)' });
|
||||
}
|
||||
|
||||
// Verify contractor exists
|
||||
const [contractors] = await db.query(
|
||||
'SELECT * FROM users WHERE id = ? AND role = ?',
|
||||
[contractorId, 'Contractor']
|
||||
);
|
||||
|
||||
if (contractors.length === 0) {
|
||||
return res.status(404).json({ error: 'Contractor not found' });
|
||||
}
|
||||
|
||||
// Supervisors can only set rates for contractors in their department
|
||||
if (req.user.role === 'Supervisor' && contractors[0].department_id !== req.user.departmentId) {
|
||||
return res.status(403).json({ error: 'Contractor not in your department' });
|
||||
}
|
||||
|
||||
const [result] = await db.query(
|
||||
'INSERT INTO contractor_rates (contractor_id, sub_department_id, activity, rate, effective_date) VALUES (?, ?, ?, ?, ?)',
|
||||
[contractorId, subDepartmentId || null, activity || null, rate, effectiveDate]
|
||||
);
|
||||
|
||||
const [newRate] = await db.query(
|
||||
`SELECT cr.*,
|
||||
u.name as contractor_name, u.username as contractor_username,
|
||||
sd.name as sub_department_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
|
||||
WHERE cr.id = ?`,
|
||||
[result.insertId]
|
||||
);
|
||||
|
||||
res.status(201).json(newRate[0]);
|
||||
} catch (error) {
|
||||
console.error('Set contractor rate error:', error);
|
||||
res.status(500).json({ error: 'Internal server error' });
|
||||
}
|
||||
});
|
||||
|
||||
// Update contractor rate
|
||||
router.put('/:id', authenticateToken, authorize('Supervisor', 'SuperAdmin'), async (req, res) => {
|
||||
try {
|
||||
const { rate, activity, effectiveDate } = req.body;
|
||||
|
||||
const [existing] = await db.query('SELECT * FROM contractor_rates WHERE id = ?', [req.params.id]);
|
||||
|
||||
if (existing.length === 0) {
|
||||
return res.status(404).json({ error: 'Rate not found' });
|
||||
}
|
||||
|
||||
const updates = [];
|
||||
const params = [];
|
||||
|
||||
if (rate !== undefined) {
|
||||
updates.push('rate = ?');
|
||||
params.push(rate);
|
||||
}
|
||||
if (activity !== undefined) {
|
||||
updates.push('activity = ?');
|
||||
params.push(activity);
|
||||
}
|
||||
if (effectiveDate !== undefined) {
|
||||
updates.push('effective_date = ?');
|
||||
params.push(effectiveDate);
|
||||
}
|
||||
|
||||
if (updates.length === 0) {
|
||||
return res.status(400).json({ error: 'No fields to update' });
|
||||
}
|
||||
|
||||
params.push(req.params.id);
|
||||
|
||||
await db.query(`UPDATE contractor_rates SET ${updates.join(', ')} WHERE id = ?`, params);
|
||||
|
||||
const [updatedRate] = await db.query(
|
||||
`SELECT cr.*,
|
||||
u.name as contractor_name, u.username as contractor_username,
|
||||
sd.name as sub_department_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
|
||||
WHERE cr.id = ?`,
|
||||
[req.params.id]
|
||||
);
|
||||
|
||||
res.json(updatedRate[0]);
|
||||
} catch (error) {
|
||||
console.error('Update contractor rate error:', error);
|
||||
res.status(500).json({ error: 'Internal server error' });
|
||||
}
|
||||
});
|
||||
|
||||
// Delete contractor rate
|
||||
router.delete('/:id', authenticateToken, authorize('Supervisor', 'SuperAdmin'), async (req, res) => {
|
||||
try {
|
||||
const [existing] = await db.query('SELECT * FROM contractor_rates WHERE id = ?', [req.params.id]);
|
||||
|
||||
if (existing.length === 0) {
|
||||
return res.status(404).json({ error: 'Rate not found' });
|
||||
}
|
||||
|
||||
await db.query('DELETE FROM contractor_rates WHERE id = ?', [req.params.id]);
|
||||
res.json({ message: 'Rate deleted successfully' });
|
||||
} catch (error) {
|
||||
console.error('Delete contractor rate error:', error);
|
||||
res.status(500).json({ error: 'Internal server error' });
|
||||
}
|
||||
});
|
||||
|
||||
export default router;
|
||||
@@ -1,96 +0,0 @@
|
||||
import express from 'express';
|
||||
import db from '../config/database.js';
|
||||
import { authenticateToken, authorize } from '../middleware/auth.js';
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
// Get all departments
|
||||
router.get('/', authenticateToken, async (req, res) => {
|
||||
try {
|
||||
const [departments] = await db.query('SELECT * FROM departments ORDER BY name');
|
||||
res.json(departments);
|
||||
} catch (error) {
|
||||
console.error('Get departments error:', error);
|
||||
res.status(500).json({ error: 'Internal server error' });
|
||||
}
|
||||
});
|
||||
|
||||
// Get department by ID
|
||||
router.get('/:id', authenticateToken, async (req, res) => {
|
||||
try {
|
||||
const [departments] = await db.query('SELECT * FROM departments WHERE id = ?', [req.params.id]);
|
||||
|
||||
if (departments.length === 0) {
|
||||
return res.status(404).json({ error: 'Department not found' });
|
||||
}
|
||||
|
||||
res.json(departments[0]);
|
||||
} catch (error) {
|
||||
console.error('Get department error:', error);
|
||||
res.status(500).json({ error: 'Internal server error' });
|
||||
}
|
||||
});
|
||||
|
||||
// Get sub-departments by department ID
|
||||
router.get('/:id/sub-departments', authenticateToken, async (req, res) => {
|
||||
try {
|
||||
const [subDepartments] = await db.query(
|
||||
'SELECT * FROM sub_departments WHERE department_id = ? ORDER BY name',
|
||||
[req.params.id]
|
||||
);
|
||||
res.json(subDepartments);
|
||||
} catch (error) {
|
||||
console.error('Get sub-departments error:', error);
|
||||
res.status(500).json({ error: 'Internal server error' });
|
||||
}
|
||||
});
|
||||
|
||||
// Create department (SuperAdmin only)
|
||||
router.post('/', authenticateToken, authorize('SuperAdmin'), async (req, res) => {
|
||||
try {
|
||||
const { name } = req.body;
|
||||
|
||||
if (!name) {
|
||||
return res.status(400).json({ error: 'Department name required' });
|
||||
}
|
||||
|
||||
const [result] = await db.query('INSERT INTO departments (name) VALUES (?)', [name]);
|
||||
const [newDepartment] = await db.query('SELECT * FROM departments WHERE id = ?', [result.insertId]);
|
||||
|
||||
res.status(201).json(newDepartment[0]);
|
||||
} catch (error) {
|
||||
if (error.code === 'ER_DUP_ENTRY') {
|
||||
return res.status(400).json({ error: 'Department already exists' });
|
||||
}
|
||||
console.error('Create department error:', error);
|
||||
res.status(500).json({ error: 'Internal server error' });
|
||||
}
|
||||
});
|
||||
|
||||
// Create sub-department (SuperAdmin only)
|
||||
router.post('/:id/sub-departments', authenticateToken, authorize('SuperAdmin'), async (req, res) => {
|
||||
try {
|
||||
const { name, primaryActivity } = req.body;
|
||||
|
||||
if (!name || !primaryActivity) {
|
||||
return res.status(400).json({ error: 'Name and primary activity required' });
|
||||
}
|
||||
|
||||
const [result] = await db.query(
|
||||
'INSERT INTO sub_departments (department_id, name, primary_activity) VALUES (?, ?, ?)',
|
||||
[req.params.id, name, primaryActivity]
|
||||
);
|
||||
|
||||
const [newSubDepartment] = await db.query(
|
||||
'SELECT * FROM sub_departments WHERE id = ?',
|
||||
[result.insertId]
|
||||
);
|
||||
|
||||
res.status(201).json(newSubDepartment[0]);
|
||||
} catch (error) {
|
||||
console.error('Create sub-department error:', error);
|
||||
res.status(500).json({ error: 'Internal server error' });
|
||||
}
|
||||
});
|
||||
|
||||
export default router;
|
||||
@@ -1,236 +0,0 @@
|
||||
import express from 'express';
|
||||
import bcrypt from 'bcryptjs';
|
||||
import db from '../config/database.js';
|
||||
import { authenticateToken, authorize } from '../middleware/auth.js';
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
// Get all users (with filters)
|
||||
router.get('/', authenticateToken, async (req, res) => {
|
||||
try {
|
||||
const { role, departmentId } = req.query;
|
||||
let query = `
|
||||
SELECT u.id, u.username, u.name, u.email, u.role, u.department_id,
|
||||
u.contractor_id, u.is_active, u.created_at,
|
||||
d.name as department_name,
|
||||
c.name as contractor_name
|
||||
FROM users u
|
||||
LEFT JOIN departments d ON u.department_id = d.id
|
||||
LEFT JOIN users c ON u.contractor_id = c.id
|
||||
WHERE 1=1
|
||||
`;
|
||||
const params = [];
|
||||
|
||||
// Supervisors can only see users in their department
|
||||
if (req.user.role === 'Supervisor') {
|
||||
query += ' AND u.department_id = ?';
|
||||
params.push(req.user.departmentId);
|
||||
}
|
||||
|
||||
if (role) {
|
||||
query += ' AND u.role = ?';
|
||||
params.push(role);
|
||||
}
|
||||
|
||||
if (departmentId) {
|
||||
query += ' AND u.department_id = ?';
|
||||
params.push(departmentId);
|
||||
}
|
||||
|
||||
query += ' ORDER BY u.created_at DESC';
|
||||
|
||||
const [users] = await db.query(query, params);
|
||||
res.json(users);
|
||||
} catch (error) {
|
||||
console.error('Get users error:', error);
|
||||
res.status(500).json({ error: 'Internal server error' });
|
||||
}
|
||||
});
|
||||
|
||||
// Get user by ID
|
||||
router.get('/:id', authenticateToken, async (req, res) => {
|
||||
try {
|
||||
const [users] = await db.query(
|
||||
`SELECT u.id, u.username, u.name, u.email, u.role, u.department_id,
|
||||
u.contractor_id, u.is_active, u.created_at,
|
||||
d.name as department_name,
|
||||
c.name as contractor_name
|
||||
FROM users u
|
||||
LEFT JOIN departments d ON u.department_id = d.id
|
||||
LEFT JOIN users c ON u.contractor_id = c.id
|
||||
WHERE u.id = ?`,
|
||||
[req.params.id]
|
||||
);
|
||||
|
||||
if (users.length === 0) {
|
||||
return res.status(404).json({ error: 'User not found' });
|
||||
}
|
||||
|
||||
// Supervisors can only view users in their department
|
||||
if (req.user.role === 'Supervisor' && users[0].department_id !== req.user.departmentId) {
|
||||
return res.status(403).json({ error: 'Access denied' });
|
||||
}
|
||||
|
||||
res.json(users[0]);
|
||||
} catch (error) {
|
||||
console.error('Get user error:', error);
|
||||
res.status(500).json({ error: 'Internal server error' });
|
||||
}
|
||||
});
|
||||
|
||||
// Create user
|
||||
router.post('/', authenticateToken, authorize('SuperAdmin', 'Supervisor'), async (req, res) => {
|
||||
try {
|
||||
const { username, name, email, password, role, departmentId, contractorId } = req.body;
|
||||
|
||||
if (!username || !name || !email || !password || !role) {
|
||||
return res.status(400).json({ error: 'Missing required fields' });
|
||||
}
|
||||
|
||||
// Supervisors can only create users in their department
|
||||
if (req.user.role === 'Supervisor') {
|
||||
if (departmentId !== req.user.departmentId) {
|
||||
return res.status(403).json({ error: 'Can only create users in your department' });
|
||||
}
|
||||
if (role === 'SuperAdmin' || role === 'Supervisor') {
|
||||
return res.status(403).json({ error: 'Cannot create admin or supervisor users' });
|
||||
}
|
||||
}
|
||||
|
||||
const hashedPassword = await bcrypt.hash(password, 10);
|
||||
|
||||
const [result] = await db.query(
|
||||
'INSERT INTO users (username, name, email, password, role, department_id, contractor_id) VALUES (?, ?, ?, ?, ?, ?, ?)',
|
||||
[username, name, email, hashedPassword, role, departmentId || null, contractorId || null]
|
||||
);
|
||||
|
||||
const [newUser] = await db.query(
|
||||
`SELECT u.id, u.username, u.name, u.email, u.role, u.department_id,
|
||||
u.contractor_id, u.is_active, u.created_at,
|
||||
d.name as department_name,
|
||||
c.name as contractor_name
|
||||
FROM users u
|
||||
LEFT JOIN departments d ON u.department_id = d.id
|
||||
LEFT JOIN users c ON u.contractor_id = c.id
|
||||
WHERE u.id = ?`,
|
||||
[result.insertId]
|
||||
);
|
||||
|
||||
res.status(201).json(newUser[0]);
|
||||
} catch (error) {
|
||||
if (error.code === 'ER_DUP_ENTRY') {
|
||||
return res.status(400).json({ error: 'Username or email already exists' });
|
||||
}
|
||||
console.error('Create user error:', error);
|
||||
res.status(500).json({ error: 'Internal server error' });
|
||||
}
|
||||
});
|
||||
|
||||
// Update user
|
||||
router.put('/:id', authenticateToken, authorize('SuperAdmin', 'Supervisor'), async (req, res) => {
|
||||
try {
|
||||
const { name, email, role, departmentId, contractorId, isActive } = req.body;
|
||||
|
||||
// Check if user exists
|
||||
const [existingUsers] = await db.query('SELECT * FROM users WHERE id = ?', [req.params.id]);
|
||||
|
||||
if (existingUsers.length === 0) {
|
||||
return res.status(404).json({ error: 'User not found' });
|
||||
}
|
||||
|
||||
// Supervisors can only update users in their department
|
||||
if (req.user.role === 'Supervisor') {
|
||||
if (existingUsers[0].department_id !== req.user.departmentId) {
|
||||
return res.status(403).json({ error: 'Can only update users in your department' });
|
||||
}
|
||||
if (role === 'SuperAdmin' || role === 'Supervisor') {
|
||||
return res.status(403).json({ error: 'Cannot modify admin or supervisor roles' });
|
||||
}
|
||||
}
|
||||
|
||||
const updates = [];
|
||||
const params = [];
|
||||
|
||||
if (name !== undefined) {
|
||||
updates.push('name = ?');
|
||||
params.push(name);
|
||||
}
|
||||
if (email !== undefined) {
|
||||
updates.push('email = ?');
|
||||
params.push(email);
|
||||
}
|
||||
if (role !== undefined) {
|
||||
updates.push('role = ?');
|
||||
params.push(role);
|
||||
}
|
||||
if (departmentId !== undefined) {
|
||||
updates.push('department_id = ?');
|
||||
params.push(departmentId);
|
||||
}
|
||||
if (contractorId !== undefined) {
|
||||
updates.push('contractor_id = ?');
|
||||
params.push(contractorId);
|
||||
}
|
||||
if (isActive !== undefined) {
|
||||
updates.push('is_active = ?');
|
||||
params.push(isActive);
|
||||
}
|
||||
|
||||
if (updates.length === 0) {
|
||||
return res.status(400).json({ error: 'No fields to update' });
|
||||
}
|
||||
|
||||
params.push(req.params.id);
|
||||
|
||||
await db.query(
|
||||
`UPDATE users SET ${updates.join(', ')} WHERE id = ?`,
|
||||
params
|
||||
);
|
||||
|
||||
const [updatedUser] = await db.query(
|
||||
`SELECT u.id, u.username, u.name, u.email, u.role, u.department_id,
|
||||
u.contractor_id, u.is_active, u.created_at,
|
||||
d.name as department_name,
|
||||
c.name as contractor_name
|
||||
FROM users u
|
||||
LEFT JOIN departments d ON u.department_id = d.id
|
||||
LEFT JOIN users c ON u.contractor_id = c.id
|
||||
WHERE u.id = ?`,
|
||||
[req.params.id]
|
||||
);
|
||||
|
||||
res.json(updatedUser[0]);
|
||||
} catch (error) {
|
||||
console.error('Update user error:', error);
|
||||
res.status(500).json({ error: 'Internal server error' });
|
||||
}
|
||||
});
|
||||
|
||||
// Delete user
|
||||
router.delete('/:id', authenticateToken, authorize('SuperAdmin', 'Supervisor'), async (req, res) => {
|
||||
try {
|
||||
const [users] = await db.query('SELECT * FROM users WHERE id = ?', [req.params.id]);
|
||||
|
||||
if (users.length === 0) {
|
||||
return res.status(404).json({ error: 'User not found' });
|
||||
}
|
||||
|
||||
// Supervisors can only delete users in their department
|
||||
if (req.user.role === 'Supervisor') {
|
||||
if (users[0].department_id !== req.user.departmentId) {
|
||||
return res.status(403).json({ error: 'Can only delete users in your department' });
|
||||
}
|
||||
if (users[0].role === 'SuperAdmin' || users[0].role === 'Supervisor') {
|
||||
return res.status(403).json({ error: 'Cannot delete admin or supervisor users' });
|
||||
}
|
||||
}
|
||||
|
||||
await db.query('DELETE FROM users WHERE id = ?', [req.params.id]);
|
||||
res.json({ message: 'User deleted successfully' });
|
||||
} catch (error) {
|
||||
console.error('Delete user error:', error);
|
||||
res.status(500).json({ error: 'Internal server error' });
|
||||
}
|
||||
});
|
||||
|
||||
export default router;
|
||||
@@ -1,244 +0,0 @@
|
||||
import express from 'express';
|
||||
import db from '../config/database.js';
|
||||
import { authenticateToken, authorize } from '../middleware/auth.js';
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
// Get all work allocations
|
||||
router.get('/', authenticateToken, async (req, res) => {
|
||||
try {
|
||||
const { employeeId, status, departmentId } = req.query;
|
||||
|
||||
let query = `
|
||||
SELECT wa.*,
|
||||
e.name as employee_name, e.username as employee_username,
|
||||
s.name as supervisor_name,
|
||||
c.name as contractor_name,
|
||||
sd.name as sub_department_name,
|
||||
d.name as department_name
|
||||
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 1=1
|
||||
`;
|
||||
const params = [];
|
||||
|
||||
// Role-based filtering
|
||||
if (req.user.role === 'Supervisor') {
|
||||
query += ' AND wa.supervisor_id = ?';
|
||||
params.push(req.user.id);
|
||||
} else if (req.user.role === 'Employee') {
|
||||
query += ' AND wa.employee_id = ?';
|
||||
params.push(req.user.id);
|
||||
} else if (req.user.role === 'Contractor') {
|
||||
query += ' AND wa.contractor_id = ?';
|
||||
params.push(req.user.id);
|
||||
}
|
||||
|
||||
if (employeeId) {
|
||||
query += ' AND wa.employee_id = ?';
|
||||
params.push(employeeId);
|
||||
}
|
||||
|
||||
if (status) {
|
||||
query += ' AND wa.status = ?';
|
||||
params.push(status);
|
||||
}
|
||||
|
||||
if (departmentId) {
|
||||
query += ' AND e.department_id = ?';
|
||||
params.push(departmentId);
|
||||
}
|
||||
|
||||
query += ' ORDER BY wa.assigned_date DESC, wa.created_at DESC';
|
||||
|
||||
const [allocations] = await db.query(query, params);
|
||||
res.json(allocations);
|
||||
} catch (error) {
|
||||
console.error('Get work allocations error:', error);
|
||||
res.status(500).json({ error: 'Internal server error' });
|
||||
}
|
||||
});
|
||||
|
||||
// Get work allocation by ID
|
||||
router.get('/:id', authenticateToken, async (req, res) => {
|
||||
try {
|
||||
const [allocations] = await db.query(
|
||||
`SELECT wa.*,
|
||||
e.name as employee_name, e.username as employee_username,
|
||||
s.name as supervisor_name,
|
||||
c.name as contractor_name,
|
||||
sd.name as sub_department_name,
|
||||
d.name as department_name
|
||||
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.id = ?`,
|
||||
[req.params.id]
|
||||
);
|
||||
|
||||
if (allocations.length === 0) {
|
||||
return res.status(404).json({ error: 'Work allocation not found' });
|
||||
}
|
||||
|
||||
res.json(allocations[0]);
|
||||
} catch (error) {
|
||||
console.error('Get work allocation error:', error);
|
||||
res.status(500).json({ error: 'Internal server error' });
|
||||
}
|
||||
});
|
||||
|
||||
// Create work allocation (Supervisor or SuperAdmin)
|
||||
router.post('/', authenticateToken, authorize('Supervisor', 'SuperAdmin'), async (req, res) => {
|
||||
try {
|
||||
const { employeeId, contractorId, subDepartmentId, activity, description, assignedDate, rate, units, totalAmount, departmentId } = req.body;
|
||||
|
||||
if (!employeeId || !contractorId || !assignedDate) {
|
||||
return res.status(400).json({ error: 'Missing required fields' });
|
||||
}
|
||||
|
||||
// SuperAdmin can create for any department, Supervisor only for their own
|
||||
let targetDepartmentId = req.user.role === 'SuperAdmin' ? departmentId : req.user.departmentId;
|
||||
|
||||
// Verify employee exists (SuperAdmin can assign any employee, Supervisor only their department)
|
||||
let employeeQuery = 'SELECT * FROM users WHERE id = ?';
|
||||
let employeeParams = [employeeId];
|
||||
|
||||
if (req.user.role === 'Supervisor') {
|
||||
employeeQuery += ' AND department_id = ?';
|
||||
employeeParams.push(req.user.departmentId);
|
||||
}
|
||||
|
||||
const [employees] = await db.query(employeeQuery, employeeParams);
|
||||
|
||||
if (employees.length === 0) {
|
||||
return res.status(403).json({ error: 'Employee not found or not in your department' });
|
||||
}
|
||||
|
||||
// Use provided rate or get contractor's current rate
|
||||
let finalRate = rate;
|
||||
if (!finalRate) {
|
||||
const [rates] = await db.query(
|
||||
'SELECT rate FROM contractor_rates WHERE contractor_id = ? ORDER BY effective_date DESC LIMIT 1',
|
||||
[contractorId]
|
||||
);
|
||||
finalRate = rates.length > 0 ? rates[0].rate : null;
|
||||
}
|
||||
|
||||
const [result] = await db.query(
|
||||
`INSERT INTO work_allocations
|
||||
(employee_id, supervisor_id, contractor_id, sub_department_id, activity, description, assigned_date, rate, units, total_amount)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||
[employeeId, req.user.id, contractorId, subDepartmentId || null, activity || null, description || null, assignedDate, finalRate, units || null, totalAmount || null]
|
||||
);
|
||||
|
||||
const [newAllocation] = await db.query(
|
||||
`SELECT wa.*,
|
||||
e.name as employee_name, e.username as employee_username,
|
||||
s.name as supervisor_name,
|
||||
c.name as contractor_name,
|
||||
sd.name as sub_department_name,
|
||||
d.name as department_name
|
||||
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.id = ?`,
|
||||
[result.insertId]
|
||||
);
|
||||
|
||||
res.status(201).json(newAllocation[0]);
|
||||
} catch (error) {
|
||||
console.error('Create work allocation error:', error);
|
||||
res.status(500).json({ error: 'Internal server error' });
|
||||
}
|
||||
});
|
||||
|
||||
// Update work allocation status (Supervisor or SuperAdmin)
|
||||
router.put('/:id/status', authenticateToken, authorize('Supervisor', 'SuperAdmin'), async (req, res) => {
|
||||
try {
|
||||
const { status, completionDate } = req.body;
|
||||
|
||||
if (!status) {
|
||||
return res.status(400).json({ error: 'Status required' });
|
||||
}
|
||||
|
||||
// SuperAdmin can update any allocation, Supervisor only their own
|
||||
let query = 'SELECT * FROM work_allocations WHERE id = ?';
|
||||
let params = [req.params.id];
|
||||
|
||||
if (req.user.role === 'Supervisor') {
|
||||
query += ' AND supervisor_id = ?';
|
||||
params.push(req.user.id);
|
||||
}
|
||||
|
||||
const [allocations] = await db.query(query, params);
|
||||
|
||||
if (allocations.length === 0) {
|
||||
return res.status(403).json({ error: 'Work allocation not found or access denied' });
|
||||
}
|
||||
|
||||
await db.query(
|
||||
'UPDATE work_allocations SET status = ?, completion_date = ? WHERE id = ?',
|
||||
[status, completionDate || null, req.params.id]
|
||||
);
|
||||
|
||||
const [updatedAllocation] = await db.query(
|
||||
`SELECT wa.*,
|
||||
e.name as employee_name, e.username as employee_username,
|
||||
s.name as supervisor_name,
|
||||
c.name as contractor_name,
|
||||
sd.name as sub_department_name,
|
||||
d.name as department_name
|
||||
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.id = ?`,
|
||||
[req.params.id]
|
||||
);
|
||||
|
||||
res.json(updatedAllocation[0]);
|
||||
} catch (error) {
|
||||
console.error('Update work allocation error:', error);
|
||||
res.status(500).json({ error: 'Internal server error' });
|
||||
}
|
||||
});
|
||||
|
||||
// Delete work allocation (Supervisor or SuperAdmin)
|
||||
router.delete('/:id', authenticateToken, authorize('Supervisor', 'SuperAdmin'), async (req, res) => {
|
||||
try {
|
||||
// SuperAdmin can delete any allocation, Supervisor only their own
|
||||
let query = 'SELECT * FROM work_allocations WHERE id = ?';
|
||||
let params = [req.params.id];
|
||||
|
||||
if (req.user.role === 'Supervisor') {
|
||||
query += ' AND supervisor_id = ?';
|
||||
params.push(req.user.id);
|
||||
}
|
||||
|
||||
const [allocations] = await db.query(query, params);
|
||||
|
||||
if (allocations.length === 0) {
|
||||
return res.status(403).json({ error: 'Work allocation not found or access denied' });
|
||||
}
|
||||
|
||||
await db.query('DELETE FROM work_allocations WHERE id = ?', [req.params.id]);
|
||||
res.json({ message: 'Work allocation deleted successfully' });
|
||||
} catch (error) {
|
||||
console.error('Delete work allocation error:', error);
|
||||
res.status(500).json({ error: 'Internal server error' });
|
||||
}
|
||||
});
|
||||
|
||||
export default router;
|
||||
@@ -1,13 +0,0 @@
|
||||
import bcrypt from 'bcryptjs';
|
||||
|
||||
const password = process.argv[2] || 'admin123';
|
||||
|
||||
bcrypt.hash(password, 10, (err, hash) => {
|
||||
if (err) {
|
||||
console.error('Error hashing password:', err);
|
||||
process.exit(1);
|
||||
}
|
||||
console.log('Password:', password);
|
||||
console.log('Hash:', hash);
|
||||
console.log('\nUse this hash in the database schema or when creating users.');
|
||||
});
|
||||
@@ -1,57 +0,0 @@
|
||||
import express from 'express';
|
||||
import cors from 'cors';
|
||||
import dotenv from 'dotenv';
|
||||
import authRoutes from './routes/auth.js';
|
||||
import userRoutes from './routes/users.js';
|
||||
import departmentRoutes from './routes/departments.js';
|
||||
import workAllocationRoutes from './routes/work-allocations.js';
|
||||
import attendanceRoutes from './routes/attendance.js';
|
||||
import contractorRateRoutes from './routes/contractor-rates.js';
|
||||
|
||||
dotenv.config();
|
||||
|
||||
const app = express();
|
||||
const PORT = process.env.PORT || 3000;
|
||||
|
||||
// Middleware
|
||||
app.use(cors());
|
||||
app.use(express.json());
|
||||
app.use(express.urlencoded({ extended: true }));
|
||||
|
||||
// Request logging
|
||||
app.use((req, res, next) => {
|
||||
console.log(`${new Date().toISOString()} - ${req.method} ${req.path}`);
|
||||
next();
|
||||
});
|
||||
|
||||
// Health check
|
||||
app.get('/health', (req, res) => {
|
||||
res.json({ status: 'ok', timestamp: new Date().toISOString() });
|
||||
});
|
||||
|
||||
// API Routes
|
||||
app.use('/api/auth', authRoutes);
|
||||
app.use('/api/users', userRoutes);
|
||||
app.use('/api/departments', departmentRoutes);
|
||||
app.use('/api/work-allocations', workAllocationRoutes);
|
||||
app.use('/api/attendance', attendanceRoutes);
|
||||
app.use('/api/contractor-rates', contractorRateRoutes);
|
||||
|
||||
// Error handling middleware
|
||||
app.use((err, req, res, next) => {
|
||||
console.error('Error:', err);
|
||||
res.status(err.status || 500).json({
|
||||
error: err.message || 'Internal server error'
|
||||
});
|
||||
});
|
||||
|
||||
// 404 handler
|
||||
app.use((req, res) => {
|
||||
res.status(404).json({ error: 'Route not found' });
|
||||
});
|
||||
|
||||
// Start server
|
||||
app.listen(PORT, () => {
|
||||
console.log(`🚀 Server running on http://localhost:${PORT}`);
|
||||
console.log(`📊 Health check: http://localhost:${PORT}/health`);
|
||||
});
|
||||
Reference in New Issue
Block a user