# Commissioning — Status & Gaps

**Updated:** 20 Sep 2026  
**Related:** `COMMISSIONING_IMPLEMENTATION_PLAN.md`, `PMS_Commission_Business_Logic_EN.md`

This document is the living checklist of what is done, what Commissioning can finish alone, and what is blocked on other modules.

---

## 1. Summary

| Area | State |
|------|--------|
| Commission **engine** (rules → calculate → approve → post → fund → settlement) | **Complete** (C0–C5 on stub; real Project adapter ready) |
| Project **payment trigger** (`PaymentCleared` emit) | **Contract Payments tab** records a clear into the existing hook (24 Sep 2026). Finance spine still absent. |
| Payroll **payslip lines** / commission documents | **Blocked** on Payroll earning-component contract |
| Commissioning-owned leftovers (adjustments, CSV export, audit events, report FE) | **Closed** (20 Sep 2026) — see §4 |

**Bottom line:** Engine is ready. Real end-to-end money movement is not closed until Project emits cleared payments and Payroll/Finance consume outbound events.

---

## 2. Completed (Commissioning-owned)

### Phases C0–C5

| Phase | Delivered |
|-------|-----------|
| **C0** | Module scaffold, `ProjectContextPort`, stub context, health/preview, permissions, FE shell |
| **C1** | Dynamic `commissioning_rules`, RuleResolver, CRUD + preview-match, Rules UI |
| **C2** | Eligibility / Duration / Calculation engines, Commission Runs (draft → calculate), Runs UI |
| **C3** | Redistribution + Allocation, approve → post + ledger, snapshot immutability |
| **C4** | Employee fund, unlock schedule, exit settlement, Funds UI |
| **C5** | `RealProjectContext`, `PaymentCleared` listener + simulate command, reports JSON APIs, outbound salary/fund/separate events |

### Operational capabilities that work today

- Configure rules (scope, eligibility, %, duration, salary/fund split, unlock, payout mode)
- Manual commission run: create → calculate → approve → post
- Separate payout queue + **Mark paid** (status only; no bank/PDF document)
- Fund unlock processing + exit settlement
- Period / team / contract / employee report **JSON** aggregates
- Stub vs real Project context switch via `COMMISSIONING_PROJECT_CONTEXT`

### Events that exist

| Event | When | Consumer status |
|-------|------|-----------------|
| `PaymentCleared` (inbound) | Project emit **or** `commissioning:simulate-payment-cleared` | Commissioning listener → inbox + **draft** run |
| `CommissionPostedToSalary` | Run posted + `with_salary` immediate > 0 | Payroll queues `payroll_commission_earnings` → payslip `commission` line |
| `CommissionSeparatePayoutReady` | Run posted + `separate` immediate > 0 | Separate payout queue (pending Mark paid) |
| `CommissionSeparatePayoutPaid` | Separate line Mark paid | Payout document recorded (`commissioning_separate_payout_documents`); future Finance/GL can listen |
| `CommissionFundUpdated` | Fund credit / unlock / settlement (as implemented) | No Finance consumer yet |
| `CommissionPosted` | Every successful run post (audit) | Commissioning-owned |

---

## 3. Not completed — blocked on other modules

Do **not** treat these as Commissioning-only work.

| Gap | Owner / dependency | Why blocked |
|-----|--------------------|-------------|
| Real `PaymentCleared` emit from Project (or Finance) | Project / Finance | Payment collection spine; Commissioning already listens |
| Default production `COMMISSIONING_PROJECT_CONTEXT=real` | Ops + Project data | Needs populated contracts/shares/members + payment emit |
| Real `EmployeeLifecyclePort` (probation / exit dates) | Employee / HR | ✅ Adapter added (`RealEmployeeLifecycle`); default still follows stub unless `COMMISSIONING_EMPLOYEE_LIFECYCLE=real` (or project_context=real) |
| `getOfficeExpenseAsOf` from Project tables | Project | Port marked later; Project shell is fixed-amount, business wants % |
| Commission lines on **payroll payslip** | Payroll | ✅ `with_salary` → `payroll_commission_earnings` → payslip `commission` (once) |
| Commission **PDF / payslip / voucher** | Payroll / Commissioning | Payslip `commission` line (with_salary); separate → bank payout document on Mark paid |
| Bank/cash disbursement for separate payout / fund unlock | Commissioning / future Finance | Separate Mark paid creates `commissioning_separate_payout_documents` (auditable); no live bank transmit yet |
| Finance GL consumer for `CommissionFundUpdated` / separate payout | Finance (future) | `CommissionSeparatePayoutPaid` ready; no Modules/Finance yet |

### How payment works *today* vs *target*

| Mode | Intended | Current reality |
|------|----------|-----------------|
| `with_salary` | Add to monthly payslip | Post → queue → payslip earning `commission` (consumed once; regenerate safe) |
| `separate` | Pay outside payroll | Post → pending → **Mark paid** → bank payout document (`CSP-{run}-{allocation}`) |
| Fund unlock / exit | Unlock then pay | Internal fund balances + settlement amounts; no disbursement doc |

---

## 4. Commissioning-owned backlog (no external module required)

| Item | Priority | Status |
|------|----------|--------|
| Adjustment lifecycle: draft → approve → apply / cancel + ledger (no mutate posted items) | P0 | ✅ Done (20 Sep 2026) |
| Report **CSV** export for period / contract / employee / team | P1 | ✅ Done (20 Sep 2026) |
| `CommissionPosted` audit event on every post | P2 | ✅ Done (20 Sep 2026) |
| `CommissionFundUpdated` on unlock process + settlement post | P2 | ✅ Done (20 Sep 2026) |
| Reports FE: contract + employee views + export buttons | P3 | ✅ Done (20 Sep 2026) |
| Adjustments FE on posted run detail | P3 | ✅ Done (20 Sep 2026) |
| Commissioning-owned office expense **%** on rules + calc snapshot | P4 | Deferred (product confirm) |
| `CalculationBase::CUSTOM` / custom duration real semantics | P5 | Deferred |
| Auto-calculate after `PaymentCleared` (today: draft only) | Product decision | Deferred — may stay manual |

---

## 5. Full process map (project start → commission money)

```text
1. Create project + services
2. Create contract (value, dates)
3. Set commission shares (team / BD / PD %)
4. Assign members (division, designation, act-as)
5. Configure Commissioning rules
6. Client payment CLEARED ─────────── Contract Payments tab writes the inbox + draft run. Calculate uses this clear's team pool.
7. Draft run (auto from PaymentCleared or manual)
8. Calculate → Approve → Post ─────── ✅ Engine ready
9a. with_salary → Payroll payslip ─── ✅ Queue + consume once
9b. separate → Mark paid ──────────── ✅ + bank payout document recorded
9c. Fund credit → unlock → settle ─── ✅ Internal; payout doc missing
10. Adjustments on posted runs ─────── ✅ draft → approve → apply / cancel
11. Reports / CSV ──────────────────── ✅ JSON + CSV export
```

---

## 5b. Contract Payments (Phase A–C)

Contract → Payments records a clear through `PaymentCleared`. The same `payment_reference` updates the inbox row and reuses one draft run.

Calculating that run uses this clear only:

`clear_team_pool = cleared_amount × team_share_percent / 100`  
`member_earning = clear_team_pool × member.commission_percent / 100`

The run keeps `payment_reference` and `cleared_amount`. After calculate, Payments shows the frozen member history (name, % used, amount). Changing Team Assignment later does not rewrite those lines. From the same Payments row you can **Calculate → Approve → Post** on the existing Commissioning Run. A manual run still uses the full contract team pool. A payment-clear `payout_override` (`with_salary` | `separate`) is applied at Calculate into each item’s frozen `rule_snapshot.allocation.payout_mode` and the run meta `payout_mode`; later policy changes cannot alter that snapshot. After Post, `separate` lines appear under the clear for **Mark paid** (pending → paid; once only; requires posted run). Mark paid records an auditable bank payout document (`commissioning_separate_payout_documents`) with run / run item / allocation / contract / employee / amount / payment_reference, and emits `CommissionSeparatePayoutPaid`. `with_salary` lines queue into `payroll_commission_earnings` and appear once on the employee payslip as earning `commission`. Full lifecycle coverage: `CommissionPaymentPayoutLifecycleTest`. Posted runs stay immutable.

## 6. Suggested next work order

1. ~~Finish §4 P0–P3 inside Commissioning~~ (done 20 Sep 2026).
2. **Switch** carefully to `COMMISSIONING_PROJECT_CONTEXT=real` where demo/real contract data exists.
3. **Payroll:** `with_salary` allocations queue into `payroll_commission_earnings` and appear as payslip earning `commission` (consumed once; regenerate reverses then re-applies).
4. **Finance:** no `Modules/Finance` yet — separate Mark paid records Commissioning-owned bank payout documents and emits `CommissionSeparatePayoutPaid` for a future GL consumer. Lifecycle tests: `CommissionPaymentPayoutLifecycleTest`.
5. **HR:** replace stub employee lifecycle with real employment dates.
6. **Optional Commissioning:** office expense % on rules (P4) if product confirms without waiting on Project.

---

## 7. Quick reference — commands & config

```bash
# Fake inbound payment (until Project emits)
php artisan commissioning:simulate-payment-cleared --contract=900 --amount=100

# Driver
COMMISSIONING_PROJECT_CONTEXT=stub   # default local/CI
COMMISSIONING_PROJECT_CONTEXT=real   # when Project tables + payments are ready
```

---

## 8. Change log

| Date | Note |
|------|------|
| 20 Sep 2026 | Status & gaps doc; closed Commissioning-owned P0–P3: adjustment lifecycle, CSV export, `CommissionPosted`, fund-updated on unlock/settlement, reports/adjustments FE |
| 16 Sep 2026 | C0–C5 engine complete on stub (see implementation plan) |
