# Stop Follow-up — Collections Communication Control --- ## CRED-6139 — 2026-05-11 ### Overview Added the ability for admins to mark a consumer application as **Stop Follow-up** from the admin payment screen (`payment_v1.blade.php`). When an application is stopped, it is excluded from collections-related listings and overdue reminder flows. Two specific behaviours were introduced: 1. **Stop / Resume toggle** — a single button on the payment screen opens a modal where the admin selects `Active` or `Stopped`. When stopping, a reason is mandatory. The same modal is also used to resume follow-up by switching the radio back to `Active`. 2. **Listing filters** — applications with `follow_up_status = 'stopped'` are removed from the EMIs Scheduled page (`/admin/emis`) and the Over-Due Reminder page (`/admin/payment/over_due_reminder_v1`). --- ### Business Rules #### Button Visibility & Text | Condition | Button Visible | Button Text | |-----------|----------------|-------------| | `state ∈ {disbursed, nach registered}` AND `follow_up_status = 'stopped'` | Yes | `Stopped` | | `state ∈ {disbursed, nach registered}` AND `follow_up_status ≠ 'stopped'` (or NULL) | Yes | `Stop Follow-up` | | `state ∉ {disbursed, nach registered}` | No | — | #### Save Validation | Condition | Outcome | |-----------|---------| | Application not found OR state not in `{disbursed, nach registered}` | `404` — `Application not found or invalid state` | | `follow_up_status` not one of `{active, stopped}` | `422` — `Invalid follow-up status` | | `follow_up_status = 'stopped'` AND reason is empty / whitespace | `422` — `Reason is required when status is Stopped` | | `follow_up_status = 'active'` | Saved — `follow_up_reason`, `follow_up_stopped_by`, `follow_up_stopped_at` are cleared to `NULL` | | `follow_up_status = 'stopped'` | Saved — reason is stored; `follow_up_stopped_by = Auth user name`; `follow_up_stopped_at = now()` | #### Listing Exclusion | Page | Exclusion Logic | |------|-----------------| | `/admin/emis` (EMIs Scheduled) | `whereDoesntHave('application_additional', fn ($q) => $q->where('follow_up_status', 'stopped'))` | | `/admin/payment/over_due_reminder_v1` | `NOT EXISTS` subquery on `consumer_application_additional.follow_up_status = 'stopped'` inside the `valid_applications` CTE | Applications with `follow_up_status` set to `active` or `NULL` continue to appear normally. --- ### Logic Added / Changed #### 1. Migration — New columns on `consumer_application_additional` Added four nullable columns: `follow_up_status` (string), `follow_up_reason` (text), `follow_up_stopped_by` (string), and `follow_up_stopped_at` (timestamp). #### 2. Model — `ConsumerApplicationAdditional` Added the four new columns to `$fillable` and registered `follow_up_stopped_at` as a date attribute. The existing `ConsumerApplication::application_additional()` `hasOne` relation is reused — no relation change required. #### 3. Save Endpoint — `AccountingController::saveStopFollowUp()` New method handling both stop and resume in a single endpoint. Uses `firstOrNew` so the additional row is created on first stop and updated thereafter. #### 4. Eager-loading for the payment screen The button needs to render with the current status pre-set. Two `getPayments` controllers (`getPaymentsV1` and `getPaymentsV2`) were updated to eager-load the `application_additional` relation. #### 5. EMIs Scheduled — Eloquent filter Added a `whereDoesntHave('application_additional', ...)` filter in `EMICalculation::showEMIS()` after the source / non-source branch so it applies to both query paths. #### 6. Over-Due Reminder — SQL CTE filter Added a `NOT EXISTS` subquery against `consumer_application_additional` inside the `valid_applications` CTE in `AccountingController::getOverdueApplications()`. Because every downstream CTE (`payment_transactions_cte`, `emi_payments_cte`, etc.) joins back to `valid_applications`, the exclusion cascades through the entire query without further changes. #### 7. Blade — Button, modal, and AJAX in `payment_v1.blade.php` **Button** — rendered only for `disbursed` / `nach registered` states. Text flips based on current status: shows `Stopped` when `follow_up_status === 'stopped'`, otherwise `Stop Follow-up`. Carries `data-id`, `data-status`, and `data-reason` attributes for the modal to read. **Modal** — Active / Stopped radios; reason input shown only when `Stopped` is selected. The modal HTML lives above the `