Worker Payment Lifecycle
This document describes how worker earnings are tracked and settled through the payment lifecycle.
Overview
When a worker completes a job step, they earn a wage. That earning is tracked on the worker's record until a payment is made to settle it. Payments may be partial — covering only some of the pending jobs — or they may exceed the total pending amount, creating an advance credit for future jobs.
The lifecycle has two states for completed job steps:
Job step completed
│
▼
jobsPendingPayment ──── settlement pass ────► jobsPaid
Worker Balance Field
The balance field on a Worker document represents the net payment state:
| Value | Meaning |
|---|---|
balance > 0 |
Factory owes the worker. Worker has unpaid earnings. |
balance = 0 |
Account fully settled (assuming jobsPendingPayment is also empty). |
balance < 0 |
Worker was overpaid. The negative value is advance credit available to apply against future pending jobs. |
Job Step Completion
When a job step transitions to Completed:
jobControllercalculates the earned amount:Article.stepRates[stepName] × totalQuantity- A
LedgerEntryof typejob_earningis created (credit to the worker) - An entry is appended to
worker.jobsPendingPayment:{ "jobId": "...", "jobStepName": "Cutting", "completedDate": "2025-01-15T10:00:00Z", "earnedAmount": 450.0 } worker.balanceincreases byearnedAmount
Settlement Pass
The settlement pass runs before every payment is recorded. It applies available funds against jobsPendingPayment in FIFO order (oldest completedDate first).
Phase 1 — Apply Advance Credit
If the worker has a negative balance (prior overpayment), that advance credit is applied first:
advanceCredit = abs(min(worker.balance, 0))
for each entry in jobsPendingPayment (oldest first):
if advanceCredit >= entry.earnedAmount:
move entry → jobsPaid
advanceCredit -= entry.earnedAmount
else:
stop
worker.balance += credit_consumed
Phase 2 — Apply New Payment
After advance credit is exhausted, the new payment amount is applied:
remaining = paymentAmount
for each entry in jobsPendingPayment (oldest first):
if remaining >= entry.earnedAmount:
move entry → jobsPaid
remaining -= entry.earnedAmount
else:
stop
Phase 3 — Handle Surplus
If the payment exceeds all pending jobs:
if remaining > 0 and jobsPendingPayment is empty:
worker.balance -= remaining // goes negative = advance credit
Key Rules
- A job entry is never split — it moves to
jobsPaidonly when its fullearnedAmountis covered. - Settlement is always FIFO — oldest completed job is settled first.
- Advance credit is always consumed before new payment funds are applied.
Worked Example
Initial state:
balance:-200(₹200 advance credit from prior overpayment)jobsPendingPayment:- Job A: ₹150, completed Jan 1
- Job B: ₹300, completed Jan 5
- New payment: ₹400
Phase 1 — Apply ₹200 advance credit:
- Job A (₹150): 200 ≥ 150 → move to
jobsPaid, credit remaining = 50 - Job B (₹300): 50 < 300 → stop
balance= -200 + 150 = -50
Phase 2 — Apply ₹400 payment:
- Job B (₹300): 400 ≥ 300 → move to
jobsPaid, payment remaining = 100 - No more pending jobs
Phase 3 — Surplus of ₹100:
balance= -50 - 100 = -150
Final state:
balance:-150(₹150 advance credit for future jobs)jobsPendingPayment: emptyjobsPaid: Job A, Job B
Partial Payment Example
Initial state:
balance:500(₹500 owed to worker)jobsPendingPayment:- Job A: ₹200, completed Jan 1
- Job B: ₹300, completed Jan 5
- New payment: ₹250
Phase 1 — No advance credit (balance is positive), skip.
Phase 2 — Apply ₹250 payment:
- Job A (₹200): 250 ≥ 200 → move to
jobsPaid, remaining = 50 - Job B (₹300): 50 < 300 → stop
Phase 3 — No surplus (pending is not empty), skip.
Final state:
balance:500 - 250 = 250(₹250 still owed)jobsPendingPayment: Job B (₹300)jobsPaid: Job A
Ledger Entry Types
The LedgerEntry model supports the following built-in types:
| Type | Description |
|---|---|
job_earning |
Automatically created when a job step is completed |
payment |
Created when a payment is recorded for a worker |
adjustment |
Manual adjustment created by an admin |
Additional custom types can be defined per tenant via TenantConfig.customLedgerTypes (e.g. advance, bonus, deduction). Unknown types are rejected with a 400 error.
Admin Dashboard
The admin dashboard provides:
- Worker ledger view: all entries with date filtering
- Worker summary: aggregated totals by entry type
- Manual adjustment: create a credit or debit entry with a description
- Payment recording: triggers the settlement pass automatically