Skip to content

Chapter 24: Benefits & Deductions

Book 6 (HR & Payroll) · Bizwiz ERP · Static-analysis-derived documentation Scope: everything added to or subtracted from pay besides base salary — salary advances, penalties, statutory deductions (NSSF, PAYE, SHIF, Housing Levy), and managed benefit/loan schemes (HELB, Medical Cover, Benevolent Fund). The core payroll run, employee master, commission and casuals pay live in Ch23; Sacco in Ch26.


1. Purpose

Plain language. When Bizwiz runs monthly payroll, an employee's gross pay is whittled down to net pay by a stack of deductions. This chapter covers everything in that stack that is not base salary:

  • Salary advances — the company lends an employee money now, then claws it back over several months by deducting instalments from payslips.
  • Penalties — money charged against an employee for a fault (fuel over-consumption by a driver, a mileage variance on a delivery, a short banking, a late shift). These are approved, given a monthly recovery cap, and clawed back like a mini-loan.
  • Statutory deductions — the Kenyan taxman's cut: PAYE (income tax), NSSF (pension), SHIF (health, formerly NHIF), and the Housing Levy. Bizwiz calculates these from configurable rate tables and posts them to the general ledger as liabilities the company must remit.
  • Managed schemes — opt-in or scheme-based recurring deductions with their own balances and remittance: HELB (student-loan repayment to the government agency), Medical Cover (private health-plan premiums), and the Benevolent Fund (a welfare contribution).

Each of these has the same skeleton: a per-employee enrollment/status record that says "how much per month," a linkage into the monthly payroll run that produces the actual per-payslip amount, a post-payroll step that records the recovery/contribution against a running balance, and a remittance view + GL/AP posting that lets Finance actually pay the third party.


Technical depth. The seam that unifies everything is the pivot table payroll_month_detail_deductions (model PayrollMonthDetailDeduction, aliased on PayrollMonthDetail as ->deductions()). Every deduction — statutory, scheme, advance, penalty, or user-defined — either becomes a column on payroll_month_details (the four statutory ones: paye, nssf, shif, housing_levy) or a pivot row keyed by a Deduction record's code (everything else). The engine is App\Services\PayrollCalculationService; the post-processing/recovery engine is App\Services\PostPayrollService; the GL/AP posting engine is App\Services\PostPayrollGLService.

The Deduction catalogue (deductions table) drives it all through well-known codes: DED-SALARY-ADVANCE, DED-EMPLOYEE-PENALTY, DED-HELB, DED-MEDICAL-COVER, DED-BENEVOLENT, DED-NSSF-VOLUNTARY-CONTRIBUTION, DED-LOAN-RECOVERIES, DED-SACCO-LOAN (Ch26). Each is is_active / system_reserved flagged.


2. Users & Roles

Plain language. The nav gates each area behind a permission string, with role_id == 1 (super-admin) always allowed. The three families are:

  • Advance and Penalties — needs advance-penalties___view for the group, then salary-advance___view, employee-penalties___view, and advance-penalties-transactions___view for the sub-pages.
  • Managed Deductions — the parent shows if the user has any of payroll-nssf___view, payroll-helb___view, payroll-medical-cover___view, payroll-benevolent___view; each scheme's Employee-status and Remittance pages then check their own …-employee-status___view / …-remittance___view variants.

Approvals are a distinct capability: salary advances and penalties each have an approve/reject step, and driver/operational penalties raised elsewhere land here unapproved and must be approved on the Employee-Penalties page before they ever hit a payslip.


Technical depth. Permission strings observed in resources/views/admin/includes/sidebar_includes/hr.blade.php (checked as isset($my_permissions['…']), OR-ed with $logged_user_info->role_id == 1):

Area Group perm Sub-page perms
Advance & Penalties advance-penalties___view salary-advance___view, employee-penalties___view, advance-penalties-transactions___view
NSSF payroll-nssf___view payroll-nssf-employee-status___view, payroll-nssf-remittance___view
HELB payroll-helb___view payroll-helb-employee-status___view, payroll-helb-remittance___view
Medical Cover payroll-medical-cover___view payroll-medical-cover-plans___view, payroll-medical-cover-enrollments___view, …-remittance___view
Benevolent payroll-benevolent___view payroll-benevolent-employee-status___view, …-remittance___view

Routes live in routes/modules/hr.php under the admin/hr/payroll/… prefix (advance/penalty: lines 229–270; NSSF: 443–455; HELB: 458–469; Medical Cover: 473–487; Benevolent: 489–498). Controllers: HrSalaryAdvanceController (advances and penalties and deduction balances — all three menus route to this one ~138 KB controller), NssfManagementController, HelbManagementController, MedicalCoverController, BenevolentFundManagementController. Approval writers set is_approved, approved_by, approved_at (advances) or flip EmployeePenalty.is_approved (penalties). No explicit middleware('permission:…') guard was found on these route groups in hr.php — gating appears to be view-level (nav) plus in-controller checks, so the permission strings are best read as nav visibility rather than hard route guards (see §7).


3. Processes

Plain language. There are two recurring lifecycles worth drawing: (a) how a deduction attaches to a payslip and is later recovered, and (b) how a penalty is born elsewhere in the ERP, funnelled here, approved, and recovered. Below both.

3a. Deduction → payslip → recovery → remittance

flowchart TD
    subgraph Setup["Per-employee setup (before payroll)"]
        A1[Salary advance approved + disbursed] 
        A2[EmployeeHelb / MedicalCoverEnrollment / EmployeeBenevolentFund is_active]
        A3[EmployeePenalty is_approved + monthly_pay_amount set]
    end
    subgraph Calc["PayrollCalculationService (per payslip)"]
        B1[calculateSystemDeductions] --> B2[Look up Deduction by code, is_active]
        B2 --> B3[Compute per-employee amount<br/>capped at remaining balance]
        B3 --> B4["deductions()->create pivot row<br/>with salary_advance_id / employee_penalty_id"]
        B0[Statutory: NSSF/PAYE/SHIF/HousingLevy] --> B5[Written as COLUMNS on payroll_month_details]
    end
    subgraph Post["PostPayrollService (after approval)"]
        C1[processSalaryAdvances -> SalaryAdvancePayment 'recovery']
        C2[processPenalties -> EmployeeDeductionTransaction -amount]
        C3[processHelb -> recordCredit]
        C4[processMedicalCover -> recordCredit]
        C5[processBenevolent -> BenevolentFund row]
    end
    subgraph GL["PostPayrollGLService (GL + AP)"]
        D1[handleNSSF/PAYE/SHIF/HousingLevy/Helb<br/>credit liability GL account per branch]
        D2[createXStatutoryPayment -> StatutoryPayment + items]
    end
    Setup --> Calc --> Post
    Calc --> GL
    D2 --> E[Remittance pages read StatutoryPayment / scheme transactions]

3b. Penalty lifecycle (incl. driver-comp open loop from Book 5)

flowchart LR
    subgraph Origin["Origin events (other modules)"]
        F1[DriverFuelPenalty approved<br/>FuelEntryApprovalController]
        F2[InboundDeliveryMileagePenalty approved<br/>InboundDeliveryController]
        F3[Short banking<br/>PosBankingController]
        F4[Late salesman shift<br/>EmployeePenaltyRequest approved]
        F5[Manual/bulk upload<br/>HrSalaryAdvanceController]
    end
    F1 & F2 & F3 & F4 & F5 --> G[EmployeePenalty row created<br/>penalty_type/penalty_id polymorphic<br/>is_approved = 0]
    G --> H[HR reviews on Pending Penalties page]
    H -->|approve + set monthly_pay_amount| I[EmployeePenalty.is_approved = 1]
    H -->|reject| J[rejection_reason set]
    H -->|waive| K[EmployeeDeductionTransaction type=waiver]
    I --> L[Picked up by PayrollCalculationService<br/>as DED-EMPLOYEE-PENALTY pivot row]
    L --> M[Recovered: EmployeeDeductionTransaction type=recovery -amount]

Technical depth — attachment (the load-bearing seam). PayrollCalculationService::calculateSystemDeductions() (app/Services/PayrollCalculationService.php:~200–504) runs per employee inside the payroll build. For each scheme it: (1) looks up the Deduction row by code where is_active=true; (2) if present, finds the per-employee obligation; (3) computes the amount, capped at the remaining balance; (4) writes a pivot row via $payrollMonthDetail->deductions()->create([...]); (5) adds to $totalSystemReserved. Specifics:

  • Salary advance (lines 312–352): iterates every active advance (is_approved, disbursed-or-statutory-paid, status != 'paid', ordered oldest-first). Monthly instalment = amount / repayment_months, capped at remaining_balance. One pivot row per advance, tagged salary_advance_id. Skips advances past their repayment_months window (monthsSinceApproval >= repayment_months).
  • Employee penalty (lines 354–389): only penalties with is_approved=true and a non-null positive monthly_pay_amount. Amount = min(monthly_pay_amount, outstanding_balance). One pivot row per penalty, tagged employee_penalty_id. Note the employee key here is a User id (User::where('employee_id', …)->pluck('id')), not the Employee id — penalties are stored against users.id.
  • HELB (432–453): single EmployeeHelb where is_active and status='active'; amount from getPayrollDeductionAmount() = min(monthly_deduction, current_balance).
  • Medical cover (456–478): single active MedicalCoverEnrollment; getPayrollDeductionAmount().
  • Benevolent (480–501): single active EmployeeBenevolentFund; flat monthly_deduction (no balance cap — it's an open-ended contribution).
  • Statutory (NSSF/PAYE/SHIF/Housing Levy, lines ~60–130) are not pivot rows — they're computed from rate tables and stored as columns nssf, shif, housing_levy, paye on payroll_month_details. PAYE is derived last: taxablePay = payeGross − nssf − housingLevy − shif − nssfVoluntary, then calculatePaye(...) with taxReliefAmount. SHIF has a KES 300 floor (max(round(gross*rate/100,2), 300)). NSSF voluntary contribution is a pivot row (DED-NSSF-VOLUNTARY-CONTRIBUTION).

Technical depth — recovery (PostPayrollService, runs after the month is approved). - processSalaryAdvances() (287–330): for each pivot row with a salary_advance_id, creates a SalaryAdvancePayment (type='recovery') and updates the advance status to paid (if is_fully_paid) or partially_paid. - processPenalties() (339–383): for each pivot row with employee_penalty_id, writes an EmployeeDeductionTransaction with negative amount and type='recovery'. Balance is derived, not stored: EmployeePenalty::outstanding_balance = SUM(amount) over its transactions (charges positive, recoveries/waivers negative). - processHelb() (519–567) / processMedicalCover() (572–…): call recordCredit(...) on the obligation, then checkAndMarkCompleted() when the balance hits zero. - processBenevolent() (478–514): creates a BenevolentFund transaction row stamped with payroll_month_id / payroll_month_detail_id. - Every processor wraps its body in try/catch and, on failure, writes a PostPayrollProcessingFailure row (component = salary_advance / penalty / helb / benevolent / …) so a partial failure is auditable rather than silent.


4. Tables Touched & Key Data

Plain language. The data model is a set of "obligation + ledger" pairs. Each scheme has a header row (the enrollment/status) and a transactions table that acts as its ledger; balances are usually computed from the ledger, not stored, which keeps them honest but means a corrupt ledger silently misreports a balance.


Technical depth.

Table Model Role / notable columns
payroll_month_detail_deductions PayrollMonthDetailDeduction The universal pivot. Base migration (2024_10_11) had only deduction_id, amount. Later migrations bolted on sacco_loan_id (2025_12_20), salary_advance_id + employee_penalty_id (2026_05_19), recovery_obligations_data JSON (2026_02_25), balance snapshot (2026_09_03). Quirk: the same physical row is reused to point at whichever obligation applies.
salary_advances SalaryAdvance amount, repayment_months, status, is_approved/is_disbursed, statutory_payment_id. remaining_balance = total_disbursed − total_paid, falling back to amount when no disbursement ledger row exists (legacy advances).
salary_advance_payments SalaryAdvancePayment Ledger: type ∈ {disbursement, recovery, direct-payment}. Recoveries created by PostPayrollService.
employee_penalties EmployeePenalty Polymorphic morphs('penalty') → penalty_type/penalty_id. amount is integer (rounded). monthly_pay_amount = recovery cap. is_approved added later (2025_06_18); penalty_id made nullable same day (manual penalties have no source). Quirk: employee_id holds a users.id in practice, not an employees.id.
employee_deduction_transactions EmployeeDeductionTransaction Penalty ledger. type ∈ {recovery, waiver, …}; amount signed (charges +, recoveries −). Composite index (employee_penalty_id, amount) for fast SUM.
employee_deduction_balances EmployeeDeductionBalance The "Penalty Ledger" nav page. running_balance, monthly_pay_amount, last_activity_at. Used for the aggregate per-employee penalty view / monthly-amount bulk edits.
nssfs Nssf Rate tiers (from/to bands, guarded=[]). Nssf::orderBy('from')->first() = Tier 1 max.
employee_helb EmployeeHelb monthly_deduction, is_active, status. current_balance = SUM(debits) − SUM(credits) from employee_helb_transactions.
benevolent_fund / employee_benevolent_fund BenevolentFund / EmployeeBenevolentFund Header (monthly_deduction, is_active) + contribution ledger (positive amounts). current_balance = SUM(amount).
medical_cover_plans / medical_cover_enrollments / medical_cover_transactions MedicalCoverPlan / MedicalCoverEnrollment / MedicalCoverTransaction Plan has annual_premium, company_contribution_percentage. Enrollment drives getPayrollDeductionAmount().
statutory_payments / statutory_payment_items StatutoryPayment / StatutoryPaymentItem The AP/remittance vehicle created after GL posting for PAYE/NSSF/SHIF/Housing/HELB/Benevolent/NetPay/NITA/Sacco. Has supplier, bank_id, declaration_number, status. Salary advances also link via salary_advances.statutory_payment_id (disbursement demand).
payroll_gl_mappings PayrollGlMapping component_code → gl_account_id (employee side) + employer_gl_account_id (company side) + supplier. Drives the GL/AP posting.

5. Interactions With Other Modules

Plain language. This chapter is the confluence point for several other modules: it consumes penalties raised in Fleet/Fuel/POS/Sales (Book 5), feeds the payroll run (Ch23), and pushes liabilities into the General Ledger (Book 4) and Accounts Payable.


Deductions → payroll run (Ch23). Covered in §3. The single seam is payroll_month_detail_deductions (pivot rows) + four columns on payroll_month_details. PayrollMonthController reads these back to render per-column payslip tooltips, matching pivot rows by deduction_id and, for advances/penalties with >1 row, exploding a per-item breakdown popover keyed on salary_advance_id / employee_penalty_id (PayrollMonthController.php:~197–391, 1993–2007). netPay = adjustedCashGross − (paye + nssf + shif + housingLevy + totalAllDeductions).

Remittance → GL / AP (Book 4). PostPayrollGLService::handleX() posts, per branch, a credit to a liability GL account for each statutory/scheme deduction (account resolved via PayrollGlMapping.component_code → WaChartsOfAccount; NSSF & Housing Levy additionally post an employer-match debit to the company-contribution account). Immediately after, createXStatutoryPayment() (PostPayrollGLService.php:307–360) materialises a StatutoryPayment + StatutoryPaymentItem set — this is the record the Remittance Reports pages read, and the object Finance turns into an actual outbound payment (it carries supplier, bank_id, declaration_number). So the liability is booked to the GL at payroll-post time and cleared when the StatutoryPayment is paid. HELB, Medical Cover and Benevolent recoveries additionally maintain their own scheme ledgers so their remittance pages can reconcile per-employee. (The exact wa_gl_trans write happens inside the injected $this->gl->postPayrollCredit/Debit helper — see Book 4 for that layer.)

Driver-comp open loop from Book 5 — RESOLVED. The Book 5 question was whether fuel/tyre/mileage driver penalties (DriverFuelPenalty, InboundDeliveryMileagePenalty, etc.) that merely flip a status ever reach payroll. They do, via the polymorphic EmployeePenalty funnel. When such a source penalty is approved, the approving controller also creates an EmployeePenalty row pointing back at it: - FuelEntryApprovalController::approvePenalty() (app/Http/Controllers/FuelEntryApprovalController.php:445–457): EmployeePenalty::create(['penalty_type' => 'fuel_penalty', 'penalty_id' => $penalty->id, 'employee_id' => $penalty->driver_id, 'amount' => round((actual−std)*penalty_per_liter), …]). - InboundDeliveryController (app/Http/Controllers/Admin/InboundDeliveryController.php:1855–1868): penalty_type => InboundDeliveryMileagePenalty::class, amount => round(penalty_amount). - Also: PosBankingController (short-banking → penalty_type => 'App\Model\ShortBankingComment', ~7899), ShiftController (late_shift), EmployeePenaltyRequestController (salesman shift, via $shift->employeePenalties()->save(...)), plus manual/bulk upload and "Balance brought forward" migration rows in HrSalaryAdvanceController.

Crucially, the EmployeePenalty is created with is_approved = 0 (see the InboundDelivery comment at line 1868: "the EmployeePenalty row just created is still unapproved… approval happens on the Employee Penalties page"). So the source-module approval is only step 1; an HR user must approve it here and set monthly_pay_amount before PayrollCalculationService will pick it up. That's the missing half of the Book 5 loop: the penalty flips its own status in the source module, mints an unapproved EmployeePenalty, and only becomes a real payroll deduction after a second HR approval that also sets the monthly recovery cap.

Salary-advance recovery loop. Advance is requested → approved (is_approved) → disbursed (a SalaryAdvancePayment type='disbursement', possibly via a StatutoryPayment disbursement demand referenced by salary_advances.statutory_payment_id). Each subsequent payroll: PayrollCalculationService adds a pivot row for min(amount/repayment_months, remaining_balance); PostPayrollService::processSalaryAdvances() writes a SalaryAdvancePayment type='recovery' and flips status to partially_paid/paid. remaining_balance = total_disbursed − total_paid. The Employee Ledger page (salary-advances.employee-ledger) reads these payment rows.

Sacco (Ch26). Same pivot mechanism (DED-SACCO-LOAN, DED-SACCO-LOAN-INTEREST, DED-LOAN-RECOVERIES) — deliberately out of scope here but structurally identical; the advance/penalty handling explicitly "mirrors SACCO loan handling."


6. Alternatives & Variants

Plain language. Almost everything is toggled by a Deduction catalogue row and per-employee/branch overrides, so a tenant can turn any scheme on or off, exempt individuals, and set rates centrally.


Technical depth.

  • Master on/off per scheme: each scheme only runs if its Deduction row exists with is_active=true (DED-HELB, DED-MEDICAL-COVER, DED-BENEVOLENT, DED-SALARY-ADVANCE, DED-EMPLOYEE-PENALTY). Delete/deactivate the catalogue row and the whole scheme silently stops attaching (no error) — a powerful but foot-gun-prone kill switch.
  • 3-tier hierarchy for user-defined deductions: calculateDeductionForEmployee() resolves amount from employee setting → branch setting → global default (DeductionEmployeeSetting, DeductionBranchSetting, Deduction). An employee setting with is_active=false = per-employee exemption. amount_type ∈ {fixed_amount, percentage} (percentage basis = basic_pay).
  • Mandatory vs opt-in: NSSF/PAYE/SHIF/Housing are mandatory statutory columns; NSSF has opt-out fields (nssf_tier2_opted_out, nssf_pension_provider_id on employees; bulk/import opt-out routes in NssfManagementController) — a Tier-2 opt-out snapshots the private pension provider onto the payslip. HELB/Medical/Benevolent are opt-in via their is_active enrollment rows.
  • Statutory-rate config: rates come from dedicated tables (nssfs tiers, Paye tiers, Shif rate, HousingLevy rate) validated up front — if any is missing, the run errors ("… rates not configured"). SHIF floors at KES 300.
  • Backfill/adjustment variants: NssfManagementController has backfill months + adjustStatutoryPaymentsForBackfill(), and CleanupController::backfillHelbPayrollPostings() retro-posts HELB — so historical months can be corrected outside the normal run.
  • GL split-by-branch: postCreditByBranch/postDebitByBranch split each liability across branches (amountsByBranchFromColumn / amountsByBranchFromDeductions), so multi-branch tenants get per-branch GL lines.

7. Open Questions

Code-proven (facts established from source): - Statutory (NSSF/PAYE/SHIF/Housing) are payslip columns; all other deductions are pivot rows keyed by Deduction.code. (PayrollCalculationService.) - Driver/fuel/mileage/short-banking/late-shift penalties do reach payroll, via polymorphic EmployeePenalty rows created is_approved=0, requiring a second HR approval + monthly_pay_amount. (FuelEntryApprovalController:445, InboundDeliveryController:1855, PosBankingController:7899, EmployeePenaltyRequestController:97.) - Balances are computed from ledgers, not stored, for penalties/HELB/benevolent. (model accessors.) - Each statutory/scheme deduction credits a liability GL account and spawns a StatutoryPayment for remittance. (PostPayrollGLService:307–360, 413–515.)

Inference / not fully traced (needs confirmation): - Route-level permission enforcement. The permission strings appear only in the nav Blade; I did not find middleware('permission:…') on the hr.php route groups. Whether the controllers hard-enforce these (vs. relying on nav hiding) is unconfirmed — should verify a checkPermission() call inside the controllers. - Exact wa_gl_trans shape. GL writes go through the injected $this->gl->postPayrollCredit/Debit abstraction; I traced that a liability credit is posted but not the literal wa_gl_trans row columns (that's the Book 4 GL layer). - Benevolent Fund has no balance cap in the calc (flat monthly_deduction); whether an upper limit/target exists elsewhere is unconfirmed. - EmployeePenalty.employee_id semantics — code treats it as users.id (via User::where('employee_id',…)->pluck('id')), but the migration comments/relations are inconsistent (relation points to User, while EmployeeDeductionTransaction stores both employee_id and user_id). The dual-key handling is a likely source of subtle bugs; worth a dedicated audit. - Medical Cover getPayrollDeductionAmount() internals (company-contribution split vs. employee premium) were not fully read. - Whether NSSF's triple credit/debit posting in handleNSSF (two employee credits + one employer debit) is intentional double-counting or a compensating pattern — the two postCreditByBranch calls to the same account (also mirrored in handleHousingLevy) look suspicious and warrant review.


8. Source References

Nav / routes - resources/views/admin/includes/sidebar_includes/hr.blade.php — permission strings, menu structure. - routes/modules/hr.php:229–270 (advances, penalties, deduction balances), :443–455 (NSSF), :458–469 (HELB), :473–487 (Medical Cover), :489–498 (Benevolent), :508–518 (statutory reports incl. PAYE/SHIF/NSSF/Housing Levy).

Controllers - app/Http/Controllers/Admin/HrSalaryAdvanceController.php — advances, penalties, deduction balances (penalty_Approval, waivePenalty, updatePenaltyMonthlyAmounts, "Balance brought forward" at :1605–1633). - app/Http/Controllers/Admin/NssfManagementController.php — employeeStatus, remittance (:126), opt-out, backfill (:822, :869–975). - app/Http/Controllers/Admin/HelbManagementController.php, MedicalCoverController.php, BenevolentFundManagementController.php. - app/Http/Controllers/FuelEntryApprovalController.php:445–457 — driver fuel penalty → EmployeePenalty. - app/Http/Controllers/Admin/InboundDeliveryController.php:1855–1872 — mileage penalty → EmployeePenalty. - app/Http/Controllers/PosBankingController.php:7899, EmployeePenaltyRequestController.php:97,143, Admin/ShiftController.php:1780, CleanupController.php:3494. - app/Http/Controllers/Admin/PayrollMonthController.php:197–391, 448–502, 1993–2020 — reads deductions back into payslip UI.

Services (engines) - app/Services/PayrollCalculationService.php — calculateSystemDeductions() (:200–504), statutory calc (:60–150), user-defined 3-tier (:509–559). - app/Services/PostPayrollService.php — processSalaryAdvances (:287–330), processPenalties (:339–383), processLoanRecoveries (:390–472), processBenevolent (:478–514), processHelb (:519–567), processMedicalCover (:572–…). - app/Services/PostPayrollGLService.php — postCreditByBranch/postDebitByBranch (:125–167), handlePaye/SHIF/NSSF/HousingLevy/Helb (:413–515), createXStatutoryPayment (:307–360). - app/Services/ProcessStatutoryVoucherService.php, SalaryAdvanceDirectPaymentService.php.

Models - app/Models/SalaryAdvance.php (:194–208 balance), SalaryAdvancePayment.php, EmployeePenalty.php (:36 morphTo, :66–75 outstanding), EmployeeDeductionTransaction.php, EmployeeDeductionBalance.php, Nssf.php, EmployeeHelb.php (:100–130), EmployeeHelbTransaction.php, BenevolentFund.php, EmployeeBenevolentFund.php, MedicalCoverPlan.php, MedicalCoverEnrollment.php (:132), MedicalCoverTransaction.php, PayrollMonthDetailDeduction.php, Deduction.php, DeductionEmployeeSetting.php, DeductionBranchSetting.php, PayrollGlMapping.php, StatutoryPayment.php.

Migrations - 2024_10_11_102640_create_payroll_month_detail_deductions_table.php + 2025_12_20…sacco_loan_id, 2026_05_19_100500…salary_advance_id, 2026_05_19_100600…employee_penalty_id, 2026_02_25…recovery_obligations_data, 2026_09_03…balance_snapshot. - 2025_06_11…create_employee_penalties_table.php (+ 2025_06_18…add_is_approved, …penalty_id nullable, 2025_07_12…add_columns, 2025_08_11…waive_reason). - 2025_06_12…salary_advances, …salary_advance_payments, …employee_deduction_balances, …employee_deduction_transactions. - 2026_02_27…employee_helb(_transactions), 2026_03_24…medical_cover_(plans|enrollments|transactions), 2025_09_18/2026_04_08…benevolent_fund, 2025_10_09…statutory_payments(_items), 2025_10_08…payroll_gl_mappings, 2026_02_24…nssf_opt_out_fields.