Complete subcontractor lifecycle — WO → DMB → RA Bill → Retention → Settlement
InfraTraq implements the full Subcontractor Management Lifecycle: WO → DMB → RA Bill → Payment → Settlement. 13 subcontractor tables manage work orders, measurement books, running account bills, retention, advances, security deposits, and final settlement. Designed for the Indian construction industry standard with retention, SD (Security Deposit), and LD (Liquidated Damages) provisions.
| Status | Description | Allowed Actions | Next States |
|---|---|---|---|
| Draft | WO/DMB/RA Bill created but not yet submitted | Edit, Submit for Approval, Delete | Pending Approval |
| Pending Approval | Awaiting PM/Contracts/Finance approval | Approve, Reject, Return | Approved, Rejected |
| Approved | WO approved and commitment created; DMB/RA Bill certified | Execute, Amend, Measure | In Progress, Amended |
| Under Measurement | Active WO with DMB recording in progress | Record DMB, Certify, Generate RA Bill | Bill Generated |
| Certified | DMB quantities verified and certified by engineer | Include in RA Bill, Dispute | Billed |
| Bill Generated | RA Bill compiled from certified DMBs with deductions | Submit, Review, Correct | Pending Approval |
| Paid | RA Bill payment released via AP invoice | View, Audit, Generate Next Bill | Settled |
| Settled | Final settlement after DLP — retention & SD released | View, Audit | — |
| Cancelled | WO cancelled; commitment released, balance restored | View, Audit | — |
| Rejected | Approval denied; returned to originator for correction | Edit, Resubmit, Delete | Draft |
sc_id — PK, empanelled subcontractor registrysc_name — Subcontractor nametrade — Trade specialisation (civil, MEP, etc.)gstin — GST identification numberpan — PAN for TDS compliancewo_id — PK, work order header with contract termssc_id — FK → subcontractor.subcontractor_masterproject_id — FK → project.projectwo_number — Auto-generated WO numberoriginal_value, revised_value — Contract amountsretention_percent, sd_percent, ld_rate — Deduction termsstatus — draft, approved, in_progress, completed, cancelledid — PK, work order line items linked to BOQwo_id — FK → subcontractor.work_orderboq_item_id — FK → estimation.boq_itemdescription, uom — Scope description and unitquantity, rate, amount — Agreed quantities and ratesdmb_id — PK, Daily Measurement Book headerwo_id — FK → subcontractor.work_orderdmb_number, dmb_date — Measurement identificationmeasured_by — FK → auth.user (site engineer)total_value, status — Computed total and certification statusid — PK, measurement line items in LBHQ formatdmb_id — FK → subcontractor.dmbwo_item_id — FK → subcontractor.wo_itemlength, breadth, height, quantity — LBHQ dimensionsrate, amount — WO rate applied to measured quantitybill_id — PK, Running Account Bill headerwo_id — FK → subcontractor.work_ordersc_id — FK → subcontractor.subcontractor_masterbill_number — Sequential RA bill number per WOgross_amount, retention_amount, advance_recovery — Bill componentsnet_amount, cumulative_amount — Payable and running totalstatus — draft, certified, approved, paidid — PK, retention tracking per WOwo_id — FK → subcontractor.work_orderretention_amount — Cumulative retention deductedreleased_amount — Retention released after DLPbalance — Unreleased retention balanceid — PK, final account settlement after DLP expirywo_id — FK → subcontractor.work_ordertotal_billed, total_paid — Lifetime totalsretention_released, ld_applied — Settlement adjustmentsnet_settlement — Final payable/receivable amountProject team creates a Work Order against an empanelled subcontractor. WO items are linked to BOQ items with agreed rates. Contract terms specify retention_percent, sd_percent, ld_rate, advance amount, and payment terms. WO approval triggers a financial commitment in the commitment accounting system.
Site engineers record daily measurements in the dmb + dmb_item tables using the standard LBHQ format (Length × Breadth × Height × Quantity). Each measurement line references a wo_item_id to track against WO scope. Cumulative DMB quantities are checked against WO quantities to prevent over-measurement.
Running Account Bills are generated from certified DMBs. The system compiles cumulative quantities per WO item, computes gross_amount at WO rates, and subtracts the previous RA bill's cumulative to arrive at the current bill value. This ensures the running total never exceeds WO value.
Five types of deductions are computed: Retention (% of gross as per WO terms), Security Deposit (% of gross), Advance Recovery (proportional recovery capped at % per bill), Liquidated Damages (per contract delay terms), and Material Recovery (for materials issued to SC from project stores).
The certified RA bill goes through quantity verification by the site engineer, rate verification against WO terms, and deduction computation validation. The certifying officer confirms the net payable amount. Any disputed items are flagged for resolution before certification.
Certified RA bills follow a multi-level approval workflow: Site Engineer → Project Manager → Contracts Manager → Finance. Approval thresholds are configured per project. Bills above threshold require additional management approval. Each approval updates the bill status progressively.
Approved RA bills generate an AP invoice in the finance module. Payment is processed with TDS deduction (as per Indian income tax provisions — typically 1% u/s 194C for SC payments). GST input credit is captured. Payment updates commitment.paid_amount in the commitment accounting system.
Retention deducted from each RA bill is tracked in sc_retention. The cumulative retention balance grows with each bill. Retention is released in two stages: 50% on completion certificate and 50% after DLP expiry. Partial releases are supported with full audit trail.
On WO completion, the system starts a DLP timer (typically 12 months). During DLP, any defects reported are tracked against the SC. Rectification costs can be deducted from retained amounts. The system auto-notifies stakeholders 30 days before DLP expiry to initiate retention release.
After DLP expiry and defect clearance, the final settlement is prepared in sc_settlement. It reconciles: total billed vs. total paid, retention released, LD applied, SD returned, and any pending material recoveries. The net settlement amount is processed as the final payment, closing the WO commitment.
class SubcontractorService { /** Create a new work order for a subcontractor */ async createWorkOrder(scId, projectId, scopeItems) { const sc = await SubcontractorMaster.findById(scId); if (!sc || sc.status !== 'empanelled') throw new Error('SC not empanelled'); const totalValue = scopeItems.reduce((s, i) => s + i.quantity * i.rate, 0); await CommitmentService.checkBudgetAvailability(projectId, totalValue); const wo = await WorkOrder.create({ sc_id: scId, project_id: projectId, wo_number: await generateWONumber(projectId), original_value: totalValue, revised_value: totalValue, retention_percent: 5, sd_percent: 2.5, ld_rate: 0.5, status: 'draft' }); for (const item of scopeItems) { await WOItem.create({ wo_id: wo.wo_id, boq_item_id: item.boqItemId, description: item.description, uom: item.uom, quantity: item.quantity, rate: item.rate, amount: item.quantity * item.rate }); } return wo; } /** Record daily measurement in LBHQ format */ async recordMeasurement(woId, items) { const wo = await WorkOrder.findById(woId); const dmb = await DMB.create({ wo_id: woId, dmb_number: await generateDMBNumber(woId), dmb_date: new Date(), status: 'draft' }); let total = 0; for (const m of items) { const woItem = await WOItem.findById(m.woItemId); const qty = m.length * m.breadth * m.height * m.nos; const cumQty = await DMBItem.sumQty(m.woItemId); if (cumQty + qty > woItem.quantity) throw new Error('Exceeds WO qty'); const amount = qty * woItem.rate; await DMBItem.create({ dmb_id: dmb.dmb_id, wo_item_id: m.woItemId, length: m.length, breadth: m.breadth, height: m.height, quantity: qty, rate: woItem.rate, amount }); total += amount; } await dmb.update({ total_value: total }); return dmb; } /** Generate RA Bill from certified DMBs for a given period */ async generateRABill(woId, fromDate, toDate) { const wo = await WorkOrder.findById(woId); const certifiedDMBs = await DMB.findAll({ wo_id: woId, status: 'certified', dmb_date: { between: [fromDate, toDate] } }); const grossAmount = certifiedDMBs.reduce((s, d) => s + d.total_value, 0); const prevCumulative = await RABill.maxCumulative(woId); if (prevCumulative + grossAmount > wo.revised_value) throw new Error('Cumulative exceeds WO value'); const bill = await RABill.create({ wo_id: woId, sc_id: wo.sc_id, bill_number: await generateBillNumber(woId), gross_amount: grossAmount, cumulative_amount: prevCumulative + grossAmount, status: 'draft' }); const deductions = await this.calculateDeductions(bill.bill_id); return { bill, deductions }; } /** Calculate all deductions for an RA bill */ async calculateDeductions(billId) { const bill = await RABill.findById(billId); const wo = await WorkOrder.findById(bill.wo_id); const retention = bill.gross_amount * wo.retention_percent / 100; const sd = bill.gross_amount * wo.sd_percent / 100; const advRecovery = Math.min( bill.gross_amount * 10 / 100, await AdvanceBalance.remaining(wo.wo_id) ); const ld = await LDCalculator.compute(wo.wo_id); const materialRecovery = await MaterialIssue.unrecovered(wo.wo_id); const netAmount = bill.gross_amount - retention - sd - advRecovery - ld - materialRecovery; await bill.update({ retention_amount: retention, advance_recovery: advRecovery, net_amount: netAmount }); await SCRetention.upsert({ wo_id: wo.wo_id, retention_amount: literal('retention_amount + ' + retention) }); return { retention, sd, advRecovery, ld, materialRecovery, netAmount }; } /** Process final settlement after DLP expiry */ async processSettlement(woId) { const wo = await WorkOrder.findById(woId); const totalBilled = await RABill.sumGross(woId); const totalPaid = await RABill.sumNet(woId); const retention = await SCRetention.findByWO(woId); const ldApplied = await LDCalculator.totalLD(woId); const settlement = await SCSettlement.create({ wo_id: woId, total_billed: totalBilled, total_paid: totalPaid, retention_released: retention.balance, ld_applied: ldApplied, net_settlement: retention.balance - ldApplied }); await this.releaseRetention(woId, retention.balance); await CommitmentService.closeCommitment(woId); return settlement; } /** Release retention amount after defect liability period */ async releaseRetention(woId, amount) { const retention = await SCRetention.findByWO(woId); if (amount > retention.balance) throw new Error('Release exceeds balance'); await retention.update({ released_amount: literal('released_amount + ' + amount), balance: literal('balance - ' + amount) }); // Create AP invoice for retention release payment await APService.createInvoice({ vendor_id: (await WorkOrder.findById(woId)).sc_id, amount, type: 'retention_release', reference: 'WO-' + woId }); return retention; } }
| Rule | Condition | Action |
|---|---|---|
| DMB Quantity Ceiling | DMB cumulative quantity > WO item quantity | Block measurement entry, show remaining qty |
| RA Bill Cumulative Cap | RA bill cumulative amount > WO revised value | Block bill generation, show WO balance |
| Retention % Match | Retention deducted ≠ WO retention_percent × gross | Auto-correct to WO terms, flag discrepancy |
| LD Calculation | LD applied per contract delay clause | Compute as ld_rate × delay days × WO value / 100 |
| Advance Recovery Cap | Recovery per bill > configured % of gross | Cap recovery at max % per bill, carry forward balance |
| Material Recovery Link | Recovery amount must match issued material records | Block if unlinked, require store issue reference |
| Event | Source Table | Auto Action |
|---|---|---|
| WO Approved | subcontractor.work_order | Create commitment in finance.commitment + post JE |
| DMB Certified | subcontractor.dmb | Mark DMB items as available for billing |
| RA Bill Approved | subcontractor.ra_bill | Create AP invoice + update commitment invoiced_amount |
| Payment Released | accounts_payable.payment_voucher | Update commitment.paid_amount + sc_retention balance |
| WO Completion | subcontractor.work_order | Start defect liability period timer |
| DLP Expiry | subcontractor.work_order | Notify PM + Finance to release retention & SD |