Skip to content

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:

  1. jobController calculates the earned amount: Article.stepRates[stepName] × totalQuantity
  2. A LedgerEntry of type job_earning is created (credit to the worker)
  3. An entry is appended to worker.jobsPendingPayment:
    {
      "jobId": "...",
      "jobStepName": "Cutting",
      "completedDate": "2025-01-15T10:00:00Z",
      "earnedAmount": 450.0
    }
    
  4. worker.balance increases by earnedAmount

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 jobsPaid only when its full earnedAmount is 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: empty
  • jobsPaid: 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