Back to ER Diagram
Labour & Payroll

Labour & Payroll Logic

Complete labour lifecycle — Attendance → Overtime → Payroll → Payslip → GL posting

PostgreSQL
21 Tables
Schema: labour, payroll
Statutory Compliance

Overview

InfraTraq implements the complete Labour & Payroll cycle: Worker Registration → Attendance → Overtime → Payroll Run → Payslip → GL Posting. The module spans 14 labour tables and 7 payroll tables (21 total), ensuring full Indian construction compliance including PF, ESI, Professional Tax, bonus, and minimum wages per state.


Registration

Attendance

Overtime

Payroll Run

Deductions

Net Pay

GL Posting
21
Database Tables
14 + 7
Labour + Payroll
6
Statutory Deductions
Monthly
Payroll Cycle

Status States

StatusDescriptionAllowed ActionsNext States
DraftPayroll run created but not yet computedEdit, Compute Payroll, DeleteComputed
ComputedGross/deductions calculated, pending reviewReview, Recalculate, SubmitPending Approval
Pending ApprovalAwaiting finance manager / project head approvalApprove, Reject, ReturnApproved, Rejected
ApprovedPayroll approved, ready for bank file generationGenerate Bank File, Generate PayslipsDisbursed
DisbursedSalary transferred to worker bank accountsPost to GL, View PayslipsPosted
PostedGL journal entries created; payroll finalisedView, Audit, Generate Muster Roll
RejectedApproval denied; returned for correctionEdit, Recompute, ResubmitDraft
CancelledPayroll run voided before disbursementView, Audit

Payroll Lifecycle

Phase 1 — REGISTRATION Worker onboarding with trade, wage, statutory IDs
Phase 2 — ATTENDANCE Daily biometric/manual check-in with shift mapping
Phase 3 — OVERTIME Hours exceeding shift duration flagged as OT
Phase 4 — PAYROLL RUN Monthly gross calculation per wage structure
Phase 5 — DEDUCTIONS PF, ESI, PT, TDS, and advance recovery
Phase 6 — NET PAY & GL Bank file generation, payslip, and journal posting

Key Principle

  • Gross Pay = (Days Worked × Daily Wage) + OT Amount + Allowances
  • Net Pay = Gross Pay − PF(Employee) − ESI(Employee) − PT − TDS − Advance Recovery
  • Employer contributions (PF 12%, ESI 3.25%) are booked as separate expense heads
  • Muster rolls serve as statutory compliance records under Indian labour law

Database Schema

labour.worker

  • worker_id — PK, worker master record
  • trade — Worker trade (mason, carpenter, fitter, etc.)
  • skill_level — Unskilled, Semi-skilled, Skilled, Highly Skilled
  • daily_wage — Daily wage rate (must meet state minimum wage)
  • aadhaar_no — Aadhaar number for statutory identification
  • pf_number — PF/UAN number for provident fund

labour.attendance

  • attendance_id — PK, daily attendance record
  • worker_id — FK → labour.worker
  • shift_id — FK → labour.shift
  • attendance_date — Date of attendance
  • check_in, check_out — Biometric/manual timestamps
  • hours_worked — Standard hours (capped at shift duration)
  • ot_hours — Overtime hours (excess over shift)

labour.shift

  • shift_id — PK, shift definition
  • start_time, end_time — Shift start and end times
  • ot_multiplier — OT rate multiplier (typically 1.5x or 2x)

payroll.payroll_run

  • payroll_id — PK, monthly payroll batch header
  • project_id — FK → project.project
  • period_month — Payroll period (YYYY-MM)
  • total_gross — Sum of all worker gross pay
  • total_deductions — Sum of all statutory + other deductions
  • total_net — Net payable amount
  • status — draft, computed, approved, disbursed, posted

payroll.payroll_detail

  • id — PK, per-worker payroll line
  • payroll_id — FK → payroll.payroll_run
  • worker_id — FK → labour.worker
  • days_worked, basic_pay, ot_amount, gross_pay
  • pf_employee, esi_employee, tds — Statutory deductions
  • net_pay — Final disbursable amount

payroll.wage_structure

  • structure_id — PK, wage component definition
  • basic_percent — Basic wage percentage
  • da_percent — Dearness Allowance percentage
  • hra_percent — House Rent Allowance percentage
  • pf_percent — PF contribution percentage (default 12%)
  • esi_percent — ESI contribution percentage (default 0.75%)

labour.muster_roll

  • muster_id — PK, monthly muster roll summary
  • period_month — Reporting period
  • total_workers — Headcount for the period
  • total_wages — Total wages paid
  • pf_contribution — Total PF (employee + employer)

payroll.payslip

  • payslip_id — PK, generated payslip record
  • payroll_detail_id — FK → payroll.payroll_detail
  • net_pay — Net pay amount on payslip
  • pdf_url — Download link for payslip PDF

Process Flow


Daily
Attendance

Shift
Mapping

OT
Calculation

Monthly
Consolidation

GL
Posting

Bank
Transfer

Advance
Recovery

Wage
Structure

Muster Roll
(Statutory)

Statutory Deductions
PF 12%+12% | ESI 0.75%+3.25%
PT per state | TDS

Step-by-Step Logic

1

Worker Onboarding

Register worker in labour.worker with trade, skill level, daily wage, Aadhaar number, and PF/ESI numbers. Link to project and assign default shift. Verify minimum wage compliance per state notification.

2

Daily Attendance Marking

Record attendance in labour.attendance via biometric device or manual entry. Capture check_in, check_out timestamps. System auto-calculates hours_worked from shift mapping. Gate pass validation ensures worker is authorized for site entry.

3

Shift & OT Processing

Map each attendance record to labour.shift to determine standard hours. Hours exceeding shift duration are flagged as ot_hours. OT requires supervisor approval. OT amount = ot_hours × (daily_wage / shift_hours) × ot_multiplier.

4

Monthly Payroll Run (Gross Calculation)

Create payroll.payroll_run for the project/month. For each worker, compute: basic_pay = days_worked × daily_wage. Apply payroll.wage_structure to split into Basic + DA + HRA components. Add OT amount to arrive at gross_pay.

5

Statutory Deduction Computation

Calculate PF employee share (12% of Basic, capped at ₹15,000 basic), ESI employee share (0.75% of Gross, applicable if gross ≤ ₹21,000), Professional Tax per state schedule, and TDS where applicable. Employer PF (12%) and ESI (3.25%) computed separately as expense.

6

Advance & Loan Recovery

Deduct outstanding advance/loan EMIs from gross pay. Recovery amount cannot exceed 50% of net pay after statutory deductions. Maintain running balance in worker advance ledger.

7

Net Pay Calculation & Bank File Generation

Compute net_pay = gross_pay − pf_employee − esi_employee − pt − tds − advance_recovery. Generate bank transfer file (NEFT/RTGS format) for bulk disbursement. Create payroll.payslip with PDF for each worker.

8

GL Journal Posting & Muster Roll

Post payroll journal: Dr Labour Expense (by cost centre), Cr Bank, PF Payable, ESI Payable, TDS Payable. Generate labour.muster_roll as statutory compliance record with total workers, wages, and PF contributions for the period.

Code Implementation

class PayrollService {

  /** Process daily attendance — validate shift and compute hours */
  async processAttendance(projectId, date) {
    const records = await Attendance.findAll({
      project_id: projectId, attendance_date: date
    });
    for (const rec of records) {
      const shift = await Shift.findById(rec.shift_id);
      const shiftHours = (shift.end_time - shift.start_time) / 3600000;
      const worked = (rec.check_out - rec.check_in) / 3600000;
      await rec.update({
        hours_worked: Math.min(worked, shiftHours),
        ot_hours: Math.max(0, worked - shiftHours)
      });
    }
  }

  /** Calculate overtime amount for a worker in a given month */
  async calculateOT(workerId, month) {
    const worker = await Worker.findById(workerId);
    const attendance = await Attendance.findAll({
      worker_id: workerId, period: month, ot_hours: { gt: 0 }
    });
    const shift = await Shift.findById(attendance[0].shift_id);
    const shiftHrs = (shift.end_time - shift.start_time) / 3600000;
    const hourlyRate = worker.daily_wage / shiftHrs;
    const totalOTHours = attendance.reduce((s, a) => s + a.ot_hours, 0);
    return totalOTHours * hourlyRate * shift.ot_multiplier;
  }

  /** Run monthly payroll for a project — creates payroll_run + details */
  async runPayroll(projectId, month, year) {
    const workers = await Worker.findAll({ project_id: projectId, status: 'active' });
    const run = await PayrollRun.create({
      project_id: projectId, period_month: `${year}-${month}`, status: 'draft'
    });
    let totalGross = 0, totalDeductions = 0, totalNet = 0;

    for (const w of workers) {
      const daysWorked = await Attendance.count({
        worker_id: w.worker_id, period: month
      });
      const basicPay = daysWorked * w.daily_wage;
      const otAmount = await this.calculateOT(w.worker_id, month);
      const grossPay = basicPay + otAmount;
      const deductions = await this.calculateStatutoryDeductions(grossPay, w);
      const netPay = grossPay - deductions.total;

      await PayrollDetail.create({
        payroll_id: run.payroll_id, worker_id: w.worker_id,
        days_worked: daysWorked, basic_pay: basicPay,
        ot_amount: otAmount, gross_pay: grossPay,
        pf_employee: deductions.pf, esi_employee: deductions.esi,
        tds: deductions.tds, net_pay: netPay
      });
      totalGross += grossPay; totalDeductions += deductions.total; totalNet += netPay;
    }
    await run.update({ total_gross: totalGross, total_deductions: totalDeductions, total_net: totalNet });
    return run;
  }

  /** Calculate PF, ESI, PT, TDS deductions based on worker profile */
  async calculateStatutoryDeductions(grossPay, workerProfile) {
    const structure = await WageStructure.findById(workerProfile.structure_id);
    const basicComponent = grossPay * (structure.basic_percent / 100);
    const pfBasic = Math.min(basicComponent, 15000); // PF ceiling ₹15,000
    const pf = pfBasic * 0.12;                        // Employee PF 12%
    const esi = grossPay <= 21000 ? grossPay * 0.0075 : 0; // ESI if gross ≤ ₹21,000
    const pt = await ProfessionalTax.calculate(workerProfile.state, grossPay);
    const tds = await TDSCalculator.compute(grossPay * 12, workerProfile);
    return { pf, esi, pt, tds, total: pf + esi + pt + tds };
  }

  /** Generate NEFT/RTGS bank file for bulk salary disbursement */
  async generateBankFile(payrollId) {
    const details = await PayrollDetail.findAll({ payroll_id: payrollId });
    const rows = [];
    for (const d of details) {
      const worker = await Worker.findById(d.worker_id);
      rows.push({ ifsc: worker.bank_ifsc, account: worker.bank_account,
        name: worker.name, amount: d.net_pay });
    }
    return BankFileGenerator.createNEFT(rows);
  }

  /** Post payroll journal entry to GL */
  async postToGL(payrollId) {
    const run = await PayrollRun.findById(payrollId);
    const je = await JournalEntry.create({
      je_type: 'PAYROLL', source_module: 'payroll',
      total_debit: run.total_gross, total_credit: run.total_gross
    });
    // Dr: Labour Expense (by cost centre)
    await JournalLine.create({ je_id: je.je_id, account: 'LABOUR_EXPENSE', debit: run.total_gross });
    // Cr: Bank, PF Payable, ESI Payable, TDS Payable
    await JournalLine.create({ je_id: je.je_id, account: 'BANK', credit: run.total_net });
    await JournalLine.create({ je_id: je.je_id, account: 'PF_PAYABLE', credit: run.total_pf });
    await JournalLine.create({ je_id: je.je_id, account: 'ESI_PAYABLE', credit: run.total_esi });
    await JournalLine.create({ je_id: je.je_id, account: 'TDS_PAYABLE', credit: run.total_tds });
    return je;
  }

  /** Generate muster roll for statutory compliance */
  async generateMusterRoll(projectId, month) {
    const details = await PayrollDetail.findAll({ project_id: projectId, period: month });
    const totalWorkers = details.length;
    const totalWages = details.reduce((s, d) => s + d.gross_pay, 0);
    const pfContrib = details.reduce((s, d) => s + d.pf_employee, 0) * 2; // Employee + Employer
    return await MusterRoll.create({
      period_month: month, total_workers: totalWorkers,
      total_wages: totalWages, pf_contribution: pfContrib
    });
  }
}

Validation Rules

RuleConditionAction
Minimum Wage ComplianceDaily wage < state minimum wage notificationBlock worker registration, alert HR
PF CeilingBasic component > ₹15,000Cap PF computation at ₹15,000 basic
ESI CeilingGross pay > ₹21,000/monthExempt worker from ESI deduction
Attendance Hours Caphours_worked > shift durationCap regular hours at shift limit, excess to OT
OT Approval Requiredot_hours > 0 without supervisor approvalFlag OT as pending, exclude from payroll until approved
Advance Recovery LimitRecovery amount > 50% of net payCap recovery at 50%, carry balance to next month
Duplicate Payroll CheckPayroll run already exists for same project + periodBlock creation, show existing payroll run

Automated Actions & Triggers

EventSource TableAuto Action
Attendance Markedlabour.attendanceUpdate muster summary, compute hours_worked and ot_hours
Payroll Approvedpayroll.payroll_runGenerate payslips (PDF) + bank transfer file (NEFT/RTGS)
Payroll Postedpayroll.payroll_runCreate JE: Dr Labour Expense, Cr Bank/PF Payable/ESI Payable
Worker Exitlabour.workerTrigger final settlement calculation (pending wages + leave encashment + bonus)
PF/ESI Due DateSystem calendarFiling reminder notification to HR and Finance (PF by 15th, ESI by 15th)

Integration Points

Upstream (Data Sources)

  • HSE — Safety training compliance check before site entry
  • Gate Pass — Gate pass validation for attendance marking
  • Subcontractor — Contract labour tracking under CLRA Act

Downstream (Consumers)

  • Finance — GL posting of payroll journals and employer contributions
  • Tax — TDS returns, PF ECR filing, ESI returns
  • Analytics — Workforce dashboards, labour productivity metrics
  • Compliance — Muster rolls, wage registers, Form-III/IV/V

Best Practices

Implementation Guidelines

  • Use biometric attendance devices with GPS tagging for construction sites to prevent proxy attendance
  • Ensure contractor labour compliance under the Contract Labour (Regulation & Abolition) Act (CLRA Act) — maintain registers, issue employment cards
  • Update minimum wage tables promptly when state government notifications are published (typically twice a year)
  • File PF returns (ECR) by the 15th of each month; ESI contribution by the 15th — automate reminders
  • Maintain separate cost centre mapping for direct labour vs contract labour for accurate project costing
  • Generate Form-III (Register of Wages), Form-IV (Overtime), Form-V (Muster Roll) as per Building & Other Construction Workers Act

Common Pitfalls

  • Not updating minimum wages when state notifications change — leads to legal non-compliance
  • Paying PF on full basic instead of capped at ₹15,000 — excess employer contribution with no employee benefit
  • Missing ESI ceiling check — deducting ESI from workers earning above ₹21,000 gross
  • Allowing advance recovery beyond 50% — violates Payment of Wages Act provisions
  • Not maintaining separate muster rolls for contract labour — CLRA Act violation during inspections