Skip to content

Worker Payment Lifecycle & Ledger Architecture

This document describes how worker earnings, payments, and adjustments are tracked and audited in Floorix using a double-entry ledger model.

Overview

Floorix uses the worker ledger as the single source of truth for all financial transactions between a factory and its workers. Rather than attempting per-job settlement matching (which breaks down when advances, deductions, corrections, or lump-sum payments occur), payments are made to workers as a whole.

The architecture is built on three core transaction types recorded in the LedgerEntry collection:

  1. job_earning (Credit): Added automatically when a worker completes a job step. Increases the worker's balance.
  2. payment (Debit): Added when management makes a payment (Cash, Bank Transfer, etc.) to the worker. Decreases the worker's balance.
  3. adjustment (Credit or Debit): Added manually by management for bonuses, advance repayments, deductions, or accounting corrections.

Worker Balance Field

The balance field on a Worker document represents the current net amount owed:

Value Meaning
balance > 0 Factory owes the worker. Worker has accumulated net earnings.
balance = 0 Account is fully settled. Nothing is owed in either direction.
balance < 0 Worker was paid in advance (negative balance). Future completed jobs will bring the balance back toward zero or positive.

Transaction Flows

1. Job Step Completion (job_earning)

When a job step transitions to Completed in jobController.ts:

  1. Earned amount is calculated from the job item size quantities and step rate snapshot (jobDetails.rate × jobDetails.pairs).
  2. An entry is created in LedgerEntry:
    await addEntry({
      workerId,
      type: "job_earning",
      credit: amount,
      debit: 0,
      description: `${stepName} completed - ${jobName}`,
      referenceId: job.jobId,
      referenceModel: "Job",
      jobDetails: { articleName, stepName, rate, pairs },
      tenantId,
    });
    
  3. worker.balance increases by amount.
  4. An immutable audit record is stored with runningBalance.

2. Worker Payment (payment)

When management records a payment in the Ledger or Transactions page:

  1. A Transaction record is created (with amount and payment method like Cash/Transfer).
  2. A corresponding debit entry is added to LedgerEntry:
    await addEntry({
      workerId,
      type: "payment",
      credit: 0,
      debit: paymentAmount,
      description: `Payment - ${transactionType}`,
      referenceId: transaction.transactionId,
      referenceModel: "Transaction",
      tenantId,
    });
    
  3. worker.balance decreases by paymentAmount.
  4. If payment exceeds the current balance, worker.balance smoothly transitions to a negative value (advance credit).

3. Manual Adjustment (adjustment)

When an admin records a manual adjustment directly in the Ledger:

  1. Admin specifies a credit (bonus/incentive) or debit (advance/deduction/penalty) and description.
  2. addEntry records the adjustment:
    await addEntry({
      workerId,
      type: "adjustment",
      credit: credit || 0,
      debit: debit || 0,
      description: description || "Manual adjustment",
      tenantId,
    });
    
  3. worker.balance is updated accordingly (balance + credit - debit).

Reporting & UI Responsibilities

With the ledger as the single source of truth:

Business Need Where to View Underlying Data
Current balance owed to worker Worker Ledger → Closing Balance worker.balance
Opening balance for date range Worker Ledger → Opening Balance closingBalance - totalCredits + totalDebits
Detailed financial history Worker Ledger table & printout LedgerEntry collection
Record a worker payment Worker Ledger → "Make Payment" button /api/v1/transaction/create
Work completed by worker Worker Jobs Report Ledger entries of type job_earning