# Settlement Update Logic --- ## CRED-6205 — 2026-06-16 ### Overview End-to-end hardening of the loan **settlement workflow** — from raising a settlement request through maker-checker, to PTP/installment tagging, settlement payment collection, auto status progression, and settlement letter delivery. The settlement lifecycle now runs strictly through the secondary-status state machine: ``` (disbursed | nach registered) │ (Settlement request raised + Maker-Checker approved) ▼ settlement-in-progress ──► settlement-done ──► settled │ (rejected / aborted) ▼ settlement-aborted ──► settlement-in-progress (re-open) ``` Secondary status is no longer set manually from the status dropdown — it is driven by maker-checker approval, settlement payment reconciliation, and the Accounts team. --- ### Business Rules - **Settlements entry point**: The "Settlements" button on the payment page appears only when the application `state` is `disbursed` or `nach registered`. - **Secondary status transitions** are restricted to: - `settlement-in-progress` → `settlement-done` | `settlement-aborted` - `settlement-aborted` → `settlement-in-progress` - `settlement-done` → `settled` - The old direct `nach registered`/`disbursed` → `settlement-in-progress` jump was removed; that transition now happens only via maker-checker approval. - **`closed` is blocked** from the status dropdown for `disbursed`/`nach registered` applications that already carry a secondary status (a settlement in flight must not be closed). - **Maker-Checker drives status**: - On **approve** → secondary status set to `settlement-in-progress`. - On **reject** → secondary status cleared (`null`). - **Maker-Checker routing by waiver limit**: a settlement approval request is no longer assigned to a single env-configured HOD/Lead. It is visible to **every** user whose **Maximum POS Waiver (%)** covers the request's waiver percentage (`max_pos_waiver > 0` and `max_pos_waiver >= waiver_percentage`). A user's `max_pos_waiver` is treated as a ceiling — a limit equal to the waiver still qualifies. - **Auto settlement-done**: When the full settlement amount is reconciled, the application is automatically moved to `settlement-done` and the Accounts team is notified by email to review and mark it `settled`. - **Settlement payment is the only payment type** allowed while `settlement-in-progress`; all other payment types (credit, collection/cancellation charges, pre-payment) are hidden. - **PTP / installment tagging now captures a due date per installment** (previously amount only). - **Settlement payment date validation** against tagged installment dates: - Multiple installments → payment date must be within the first and last installment dates (inclusive). - Single installment → payment date must be after the installment date. - **Maximum POS Waiver (%)** can now be configured per user (collections/approver users), excluding merchant user types (2, 48). - **Settlement letter** is issued **after** 30 working days from payment realization (wording corrected from "within"). --- ### Logic Added / Changed #### Secondary-status state machine `ConsumerApplicationController` - `$secondaryStatusArray` trimmed to the in-progress → done → settled flow (removed entry transitions from `nach registered`/`disbursed`). - Added guard removing `closed` from the allowed status array when a `disbursed`/`nach registered` application has a non-empty secondary status. #### Maker-Checker → status wiring `AccountingController` - **Approve**: merges `secondary_status = settlement-in-progress` and calls `ConsumerApplicationController::updateStatus()`. - **Reject**: clears `secondary_status` on the application before marking the maker-checker record rejected. #### Maker-Checker routing by Maximum POS Waiver Replaced the env-based single-approver routing with waiver-limit-based visibility: - `User::canApproveSettlementWaiver($waiverPercentage)` (new) — returns true when `max_pos_waiver > 0` and `max_pos_waiver >= $waiverPercentage`. - `AccountingController::triggerSettlementMakerChecker()` — no longer reads `COLLECTION_HOD_USER_ID` / `COLLECTION_LEADS_USER_ID`. It queries all eligible checkers (`max_pos_waiver >= waiver_percentage`, non-deleted) and emails all of them; the maker-checker record is stored with `requested_to_user_id = null` (no single assignee). - Approve / reject / update-remark authorization now checks `Auth::user()->canApproveSettlementWaiver($record->waiver_percentage)` instead of `Auth::id() === requested_to_user_id`. - `payment_settlement` and `settlement_logs` views — `$isChecker` uses the same helper, so Approve/Reject/remark controls appear for any qualifying user; the "Requested To" column now shows *"Approvers with POS waiver limit ≥ X%"* instead of a single user. - The `COLLECTION_HOD_USER_ID` / `COLLECTION_LEADS_USER_ID` env vars are now unused by this flow. #### Auto settlement-done + Accounts notification `SettlementController::handleSettlementFulfilled($application_id)` (new) — invoked from `LoanManagement` once the reconciliation marks the settlement fulfilled: - Sets `secondary_status = settlement-done` (source `settlement-done-auto`) via `updateStatus()`. - Emails `accounts@creditfair.in` with the application ID and link, requesting review and final `settled` marking. #### PTP / installment tagging with due dates `CollectionAction::setInstallmentDetailsAttribute()` (new mutator) - Combines posted `installments[]` amounts with `installment_dates[]` into a per-installment `{amount, date}` JSON structure (backward compatible — already-structured arrays pass through). `payment_settlement.blade.php` - Dispo Code list is context-aware: only `Settlement` when `settlement-in-progress`, otherwise the collection dispo codes. - Installment fields rendered as cards with an **Amount** and a **Due Date** datepicker; client-side validation requires both, and the installment-sum-must-equal-amount check is retained. - Edit flow pre-fills amounts and due dates from the stored structure. - **Settlement validation before save/update** (working-tree change): on submit with dispo code `Settlement`, the form first calls `/admin/payment/validate-settlement-entry` (reuses the same `validateSettlement` helper as `editPayment`, runs the reconcile API with `is_verify = 0`); if a message is returned the entry is invalid and submission is blocked. #### Settlement payment validations (add payment) `AccountingController::addPayment()` - Duplicate `transaction_id` guard for settlement payments. - Payment-date-vs-installment-date validation (range for multi-installment, after-date for single), using dates tagged on the latest `Settlement` collection action. #### Settlement Proposal Letter `AccountingController::sendSettlementProposalLetter()` - Installment dates are now read from the stored installment structure (with monthly-back-from-PTP fallback) instead of always being generated. - Email body personalised with the customer name; letter-issuance wording changed to "after 30 working days". - Proposal button replaced with a **Settlement Aborted** label when `secondary_status = settlement-aborted`. #### Settlement Letter emailing `AccountingController::emailSettlementLetter()` (new) + route `POST payment/settlement-letter/{id}/send` - Only for `settled` applications; renders the settlement letter PDF (same source as the download route) and emails it to the customer (plus optional additional email) with the standard letter body. - **Send Settlement Letter** button + modal (customer email, additional email, download link) added to both `payment_settlement` and `settlement_logs` views. #### Settlement sub-transactions display `AccountingController` (disbursement/payment detail) passes `settled_sub_transactions` (reconciled `settlement_payment` parked transactions). `payment_v1.blade.php` - Settlement transaction panel now expands to list reconciled settlement sub-transactions (amount, mode, txn id, date, CF txn id, payment-for, status). - "Settlements" button gated on `state in (disbursed, nach registered)`. #### "Settlement not received" alert guard `AccountingController::checkSettlementPayment()` - Skips the "settlement not received" alert email when a pending settlement payment exists in the maker-checker queue (status `1`). #### Maximum POS Waiver per user - `users.max_pos_waiver` column added; `User::$fillable` updated. - Numeric **Maximum POS Waiver (%)** field added to user add/edit pages, shown for all user types except merchant types 2 and 48 (toggled via JS on user-type change). #### editPayment payment dropdown `editPayment.blade.php` - When `settlement-in-progress`, the Payment dropdown offers only **Settlement Payment**; otherwise the normal payment types. --- ### Impact — Files Changed / Added | File | Type | Change | |------|------|--------| | `app/Http/Controllers/Admin/Project/SettlementController.php` | Added | `handleSettlementFulfilled()` — auto-move to `settlement-done` and email Accounts | | `app/LoanManagement.php` | Modified | Calls `SettlementController::handleSettlementFulfilled()` once reconciliation fulfils the settlement | | `app/Http/Controllers/Admin/ConsumerApplicationController.php` | Modified | Trimmed `$secondaryStatusArray` to the in-progress→done→settled flow; blocked `closed` while a secondary status is active | | `app/Http/Controllers/Admin/AccountingController.php` | Modified | Maker-checker approve/reject now sets/clears secondary status; approve/reject/remark authorization uses `canApproveSettlementWaiver()`; `triggerSettlementMakerChecker()` routes by `max_pos_waiver` instead of env HOD/Lead (emails all eligible checkers, `requested_to_user_id = null`); `addPayment()` settlement txn-duplicate + payment-date validations; proposal-letter installment-date extraction & wording; new `emailSettlementLetter()`; `checkSettlementPayment()` pending-maker-checker guard; passes `settled_sub_transactions` | | `app/CollectionAction.php` | Modified | `setInstallmentDetailsAttribute()` mutator stores per-installment `{amount, date}` | | `app/User.php` | Modified | Added `max_pos_waiver` to `$fillable`; added `canApproveSettlementWaiver()` helper | | `routes/admin.php` | Modified | Added `POST payment/settlement-letter/{id}/send` → `emailSettlementLetter` | | `resources/views/admin/accounting/payment_settlement.blade.php` | Modified | Context-aware dispo codes; installment cards with due dates + validation; edit pre-fill; settlement-letter modal/button; pre-save `validateSettlement` call (`is_verify = 0`); `$isChecker` and "Requested To" column use `max_pos_waiver` | | `resources/views/admin/accounting/settlement_logs.blade.php` | Modified | Settlement-letter modal/button; settlement-aborted label; `$isChecker` and "Requested To" column use `max_pos_waiver` | | `resources/views/admin/accounting/payment_v1.blade.php` | Modified | Settlement sub-transactions panel; "Settlements" button gated on `state` | | `resources/views/admin/accounting/editPayment.blade.php` | Modified | Payment dropdown limited to Settlement Payment while `settlement-in-progress` | | `resources/views/admin/accounting/settlement_letter.blade.php` | Modified | "after 30 working days" wording; system-generated-document note | | `resources/views/admin/user/add.blade.php` | Modified | Added Maximum POS Waiver (%) field + JS toggle | | `resources/views/admin/user/edit.blade.php` | Modified | Added Maximum POS Waiver (%) field + JS toggle | | `database/migrations/2026_06_12_173600_add_max_pos_waiver_to_users_table.php` | Added | Adds `max_pos_waiver` column to `users` table |