# Deduction Policy — Implementation Report

## 1. Summary

Implemented a Payroll-owned **Deduction Policy** system that evaluates rules (attendance late, fine foundation) into existing `employee_deductions`, then relies on the current installment → payslip pipeline. No second deduction engine; salary structure and tax paths untouched.

## 2. Database Changes

### New table: `deduction_policies`

| Column | Notes |
|--------|--------|
| id | PK |
| company_id | FK companies |
| source_type | string(40) |
| name | string(150) |
| code | string(50) |
| effective_date | date |
| config | JSON |
| status | string (Active/Inactive/Archived) |
| created_by / updated_by | nullable FK users |
| timestamps | |

Constraints/indexes:

- `UNIQUE(company_id, source_type, code)`
- `INDEX(company_id, source_type, status)`

### Modified table: `employee_deductions`

Added nullable/default columns:

- `source` (default `manual`)
- `deduction_policy_id` (FK → deduction_policies, nullOnDelete)
- `origin_period_year`
- `origin_period_month`
- `origin_ref`
- `calculation_snapshot` (JSON)

Indexes:

- policy + origin period
- company + origin_ref

## 3. Backend

| Layer | Artifacts |
|-------|-----------|
| Models | `DeductionPolicy`; extended `EmployeeDeduction` |
| Enums | `DeductionPolicySourceType`, `DeductionPolicyStatus`, `DeductionSource`; `DeductionType::values()` |
| Rules | `DeductionPolicyConfigRule` |
| Repositories | `DeductionPolicyRepository(+Interface)` |
| Services | `DeductionPolicyService`, `DeductionPolicyEvaluationService`, `DeductionAmountCalculator` |
| Evaluators | `AttendanceLateEvaluator`, `FineEvaluator` (+ interface) |
| HTTP | `DeductionPolicyController`, Form Requests, `DeductionPolicyResource`; extended `EmployeeDeductionResource` |
| Jobs/Events | `EvaluateDeductionPoliciesJob`, listener on `MonthFrozen`, `payroll:evaluate-deduction-policies` |
| Permissions | via `config/actions.php` |

## 4. API

| Method | Path | Permission |
|--------|------|------------|
| GET | `/api/v1/payroll/deduction-policies` | view \| manage |
| GET | `/api/v1/payroll/deduction-policies/{id}` | view \| manage |
| POST | `/api/v1/payroll/deduction-policies` | manage |
| PUT | `/api/v1/payroll/deduction-policies/{id}` | manage |
| PATCH | `/api/v1/payroll/deduction-policies/{id}/status` | manage |
| DELETE | `/api/v1/payroll/deduction-policies/{id}` | manage |
| POST | `/api/v1/payroll/deduction-policies/evaluate` | evaluate |
| POST | `/api/v1/payroll/deduction-policies/{id}/evaluate` | evaluate |

Employee deduction responses now also expose `source`, `policy`, `origin_period*`, `calculation_snapshot` (additive).

## 5. Frontend

- Route: `/payroll/config/deduction-policies`
- Page: `DeductionPolicyListPage` (list, create/edit drawer, period evaluate panel)
- API client: `deductionPolicyApi.ts`
- Config nav tab: **Deduction Policies**

## 6. Permissions

- `payroll.deduction-policy-view`
- `payroll.deduction-policy-manage`
- `payroll.deduction-policy-evaluate`

Re-sync actions/roles after deploy (`ActionSeeder` / platform sync).

## 7. Existing Behavior

| Area | Status |
|------|--------|
| loan / advance / fine / other manual deductions | Unchanged; `source` defaults to `manual` |
| Installments / entries / payslip apply | Unchanged (`applyInstallment`) |
| Salary structure deductions | Unchanged |
| Tax | Unchanged |
| Carry-forward | Unchanged |
| Attendance late metrics | Read-only via frozen `monthly_attendance_approvals.total_late_count` |

## 8. Tests

Ran in Docker (`php artisan test --filter=…`):

| Suite | Result |
|-------|--------|
| `DeductionPolicyCrudTest` | **7 passed** |
| `DeductionPolicyEvaluationTest` | **14 passed** (incl. late matrix + acceptance 6 late → 4000) |
| `DeductionAmountCalculatorTest` | **5 passed** |
| `AttendancePayrollPermissionRegistryTest` | **6 passed** |
| `EmployeeDeductionTest` | **11 passed** |
| `MonthlyAttendanceFreezeTest` | **16 passed** |

**Failed / Skipped (these filters):** none.

## 9. Risks / Limitations

- V1 auto-evaluation is **attendance_late** only; fine requires `employee_ids`.
- Damage / absence / other: config foundation only, no automatic source integration.
- Idempotency enforced transactionally (`lockForUpdate`), not DB partial unique index (MySQL-friendly).
- Period eligibility is period-end based; no mid-month proration.
- Automatic run hooks on `MonthFrozen` (per employee freeze) via queued job.
- Working-days divisor uses existing `WorkingDaysInMonthService`.

### Applicability (V1.1)

`config.applicability`:

- `mode: company` (default) — all frozen employees in company
- `mode: scoped` — match **any** of `departments` / `designations` / `employees` via current `employee_organization_assignments`

Not yet: Attendance `assignments` table reuse, branch/division/team scopes, exclusive “must match all” mode.

## 10. Future Extension

Add a new evaluator implementing `DeductionPolicyEvaluatorInterface`, register it in `DeductionPolicyEvaluationService`, extend `DeductionPolicySourceType` + config rule — no change to EmployeeDeduction / payslip application.
