# Step 17 — Payroll — Implementation Report

**Status:** COMPLETED  
**Verified:** 12 August 2026  
**Pull request:** #29

## Employee compensation

Implemented employee compensation settings for all roadmap components:

- Basic salary
- Allowances
- Bonus
- Commission
- Overtime
- Deductions
- Advances
- Other deductions

Compensation settings support effective date, active/inactive status and notes.

All money calculations use the application's fixed-precision `MoneyMath` service.

Payroll formulas are calculated on the backend:

- Gross = Basic Salary + Allowances + Bonus + Commission + Overtime
- Total Deductions = Deductions + Advances + Other Deductions
- Net = Gross - Total Deductions

Negative components and deduction totals greater than gross pay are rejected.

## Payroll periods and immutable snapshots

Added branch-scoped payroll periods with system-generated permanent period numbers.

A new draft payroll period snapshots every active employee whose selected branch is their primary branch. Each payroll entry permanently stores:

- Employee
- Employee number snapshot
- Employee name snapshot
- Position snapshot
- All eight payroll components
- Gross pay
- Total deductions
- Net pay
- Permanent payslip number

Changing an employee's current compensation after period creation does not rewrite the historical payroll entry.

Draft payroll components can be adjusted before approval. Every edit recalculates employee and period totals on the backend.

Overlapping non-rejected payroll periods for the same branch are blocked.

## Approval workflow

Payroll statuses:

- Draft
- Approved
- Rejected
- Paid

Approval is separated from payroll preparation.

When maker-checker is enabled, the user who created a payroll period cannot approve the same period.

Approved payroll is locked against further component changes.

Rejected payroll records the permanent decision and comments without creating any financial movement.

## Payroll payment

Only an approved and unpaid payroll period can be paid.

During payment an authorized accountant selects an active financial account belonging to the payroll branch.

Payment is protected by one database transaction and row locks on:

- Payroll period
- Payroll entries
- Financial account

Before any employee is paid, the system rechecks that the account can fund the **entire approved net payroll**.

If funds are insufficient, no employee is marked paid, no account balance changes, no financial account transaction is created and no accounting entry is created.

A successful payroll payment:

1. Reduces the selected financial account by the approved total net payroll.
2. Creates a permanent `financial_account_transactions` record for every positive-net employee payment.
3. Maintains account running balances.
4. Creates a balanced accounting journal for each positive-net employee:
   - Debit: `payroll`
   - Credit: `assets`
5. Records payment account, payer and payment timestamp.
6. Marks the period and entries as paid.
7. Blocks duplicate payment.

The existing `payroll` accounting category is the payroll expense posting used by financial reporting; loan principal or unrelated flows are never treated as payroll income/expense.

## Payslips

Payslips are available only after real payroll payment.

Added:

- Printable HTML payslip
- Downloadable PDF payslip

Payslips contain:

- Business name
- Payroll period and dates
- Branch
- Employee identity snapshot
- Basic salary
- Allowances
- Bonus
- Commission
- Overtime
- Gross pay
- Deductions
- Advances
- Other deductions
- Total deductions
- Net pay
- Payment account
- Financial transaction reference
- Accounting journal reference
- Payment processor
- Payment timestamp

## Database changes

Migration: `2026_08_12_180000_create_payroll_management.php`

Created:

- `employee_compensations`
- `payroll_periods`
- `payroll_entries`

Historical payroll entries permanently retain their compensation and employee snapshots plus financial posting references.

## Permissions and duty separation

Added:

- `payroll.view`
- `payroll.compensation.manage`
- `payroll.periods.create`
- `payroll.approve`
- `payroll.pay`

Default duty grants:

- Branch Administrator: full branch payroll capability
- HR / Payroll Officer: view, compensation setup and draft payroll preparation
- Branch Manager: view and approval/rejection
- Accountant: view and payment
- Auditor: read-only payroll access
- Super Administrator: global override through the existing permission model

All payroll endpoints enforce branch access on the backend.

## User interface

Added Finance → **Payroll** navigation and screens for:

- Payroll period register
- Draft / approved / paid net-pay summaries
- Branch/status/search filtering
- Employee compensation setup
- New payroll period
- Payroll period review
- Draft employee component editing
- Approval/rejection
- Account-backed payment
- Printable payslips
- PDF payslip download

The UI follows the application's existing compact toolbar, table, form and finance patterns.

## Audit events

Added:

- `payroll.compensation.updated`
- `payroll.period.created`
- `payroll.entry.updated`
- `payroll.period.approved`
- `payroll.period.rejected`
- `payroll.period.paid`

## Verification

GitHub Actions Foundation CI on pull request #29:

- Fresh migrations: PASS
- Seeders: PASS
- Full Laravel test suite: **130 passed**
- Assertions: **1,255**
- Existing Step 1–16 regression tests: PASS
- Step 17 payroll feature tests: **6 passed**

Step 17 tests cover:

1. Compensation formulas, fixed payroll totals, period generation, draft recalculation and historical snapshot integrity.
2. Maker-checker approval, approved-period locking and rejection without financial movement.
3. Account-backed payroll payment, balanced Payroll/Assets accounting, permanent transaction references and duplicate-payment prevention.
4. Full-batch insufficient-funds rollback with no partial employee payment.
5. Payroll permission separation and branch isolation.
6. Paid-only printable and PDF payslips.

## Remaining Step 17 work

None.

## Next roadmap module

**Step 18 — Cash Reconciliation and Daily Closing**.
