# Foreclosure — Bounce and Penal Waive-off Update --- ## CRED-6043 — 2026-04-30 ### Overview Updated the foreclosure payment link flow in the admin payment screen (`payment_v1.blade.php`) to correctly handle cases where bounce and/or penal charges are present at the time of foreclosure. Two specific behaviours were changed: 1. **Payable amount field locking** — the amount field is no longer unconditionally locked (readonly) for all foreclosure payments. It is now locked only when neither bounce nor penal charges are due. 2. **Minimum payable amount validation** — when bounce or penal charges are present, the admin cannot share a payment link for an amount below the minimum. The minimum is computed as `total_amount_due − (bounce_charges + penal_charges)`. This validation was moved server-side so it is testable and not dependent on blade JS alone. --- ### Business Rules #### Field Lock (Payable Amount Input) | Condition | Field State | |-----------|-------------| | `is_foreclosure = 1` AND `interest_due = 0` AND `penal_charges = 0` | Locked (readonly) | | `is_foreclosure = 1` AND (`interest_due > 0` OR `penal_charges > 0`) | Editable — admin must enter an amount | | `is_foreclosure != 1` | Always editable | #### Minimum Amount Validation (Link Share) | Condition | Outcome | |-----------|---------| | `payment_type != 'foreclosure'` | No minimum enforced, link share proceeds | | `payment_type = 'foreclosure'` AND `bounce_charges = 0` AND `penal_charges = 0` | No minimum enforced | | `payment_type = 'foreclosure'` AND (`bounce_charges > 0` OR `penal_charges > 0`) | Minimum = `total_amount_due − (bounce_charges + penal_charges)` | | `payable_amount < minimum` | Link share blocked, alert shown | | `payable_amount >= minimum` | Link share proceeds | --- ### Logic Added / Changed #### 1. Field Lock — `populateView()` in `payment_v1.blade.php` **Before:** ```javascript if (is_forclosure == 1) { $('#payable_amount_for_link').attr('readonly', true); $('#payable_amount_for_link').addClass('input-disabled'); } ``` **After:** ```javascript if (is_forclosure == 1) { if (interest_due_to_foreclose == 0 && penal_due_to_foreclose == 0) { $('#payable_amount_for_link').attr('readonly', true); $('#payable_amount_for_link').addClass('input-disabled'); } } ``` The field is now locked only when the admin has nothing to adjust — i.e., no waiveable charges exist. When charges are present, the admin needs to enter the amount they intend to collect. --- #### 2. Minimum Amount Validation — `._open_link` click handler in `payment_v1.blade.php` **Before:** Inline JS computed the minimum and alerted the admin. **After:** The click handler sends an AJAX call to the server before proceeding. The server validates the amount and returns a `valid` flag. ```javascript if (payment_type == 'foreclosure') { let event = $("#link_share_view"); $.ajax({ url: site_url + 'admin/payment/validate-foreclosure-amount', type: 'POST', data: { _token: '...', payment_type: payment_type, payable_amount: $("#payable_amount_for_link").val(), bounce_charges: event.attr('bounce-due-to-foreclose') || 0, penal_charges: event.attr('penal-due-to-foreclose') || 0, total_amount_due: event.attr('data-amount-due') || 0 }, success: function (response) { if (!response.valid) { alert(response.alertMessage); return; } sendPaymentLink(type, $clickedEl); }, error: function () { sendPaymentLink(type, $clickedEl); // fail-open on server error } }); return false; } ``` --- #### 3. Server-Side Validation — `AccountingController::validateForeclosureAmount()` New method added to `AccountingController`. Called by the blade AJAX and directly by the API test runner. ```php public function validateForeclosureAmount(Request $request) { $result = (new ForeclosureValidationService())->validateForeclosurePayableAmount( $request->input('payment_type'), $request->input('payable_amount'), $request->input('bounce_charges'), $request->input('penal_charges'), $request->input('total_amount_due') ); return response()->json($result); } ``` Response shape: ```json { "valid": false, "minimumAmount": 9500, "alertMessage": "The minimum amount payable is 9500" } ``` --- #### 4. Validation Service — `ForeclosureValidationService` Single-responsibility service class holding both validation functions. Acts as the single source of truth — blade, controller, and test runner all use this. **`shouldLockPayableAmountField($is_forclosure, $interest_due, $penal_charges): bool`** Returns `true` (lock the field) only when foreclosure and both charges are zero. **`validateForeclosurePayableAmount($payment_type, $payable_amount, $bounce_charges, $penal_charges, $total_amount_due): array`** Returns `{ valid, minimumAmount, alertMessage }`. Minimum computation: ``` minimum = total_amount_due − (bounce_charges + penal_charges) if payable_amount < minimum → invalid ``` --- ### Validation Examples | `total_amount_due` | `bounce_charges` | `penal_charges` | `payable_amount` | Result | |--------------------|-----------------|-----------------|-----------------|--------| | 10,000 | 500 | 0 | 9,000 | ❌ Invalid — minimum is 9,500 | | 10,000 | 500 | 0 | 9,500 | ✅ Valid — exactly at minimum | | 10,000 | 500 | 0 | 10,000 | ✅ Valid — above minimum | | 15,000 | 400 | 600 | 13,000 | ❌ Invalid — minimum is 14,000 | | 15,000 | 400 | 600 | 14,000 | ✅ Valid — exactly at minimum | | 5,000 | 0 | 0 | 1 | ✅ Valid — no charges, no minimum enforced | --- ### API Endpoint **Route:** `POST /admin/payment/validate-foreclosure-amount` **Middleware:** `web` (requires admin session + CSRF token) **Route file:** `routes/foreclosure_validation.php` Request body: | Field | Type | Description | |-------|------|-------------| | `payment_type` | string | e.g. `"foreclosure"`, `"over_due"` | | `payable_amount` | numeric | Amount the admin entered | | `bounce_charges` | numeric | `bounce-due-to-foreclose` DOM attribute | | `penal_charges` | numeric | `penal-due-to-foreclose` DOM attribute | | `total_amount_due` | numeric | `data-amount-due` DOM attribute | --- ### Impact — Files Changed / Added | File | Type | Change | |------|------|--------| | `resources/views/admin/accounting/payment_v1.blade.php` | Modified | `populateView()`: conditional field lock; `._open_link` handler: replaced inline JS validation with AJAX call to server endpoint | | `app/Http/Controllers/Admin/AccountingController.php` | Modified | Added `validateForeclosureAmount(Request $request)` method; added `use App\Services\Foreclosure\ForeclosureValidationService` import | | `app/Services/Foreclosure/ForeclosureValidationService.php` | Added | Service class with `shouldLockPayableAmountField()` and `validateForeclosurePayableAmount()` | | `routes/foreclosure_validation.php` | Added | Route: `POST admin/payment/validate-foreclosure-amount` | | `app/Providers/RouteServiceProvider.php` | Modified | Added `mapForeclosureValidationRoutes()` method to register the new route file under `admin` prefix with `web` middleware | --- ### Related - Test suite: `app/TestRunner/Tests/CRED6043/TestSuite.php` - Test runner documentation: `documentation/5.api-test-runner.md`