# Dev Log — 2026-04-01 ## NonEbClaimApiController — Bug Fix, Enhancements & API Docs --- ### 1. Fix: JSON Parse 500 Error on `createClaim` - **Problem:** `getJSON(true)` throws a `500 HTTPException` (Syntax error) when the request body is multipart/form-data (file upload), not JSON. - **Fix:** Replaced `getJSON(true)` with `json_decode(getBody()) ?? getPost()` so multipart POST data is read correctly without throwing. ### 2. Fix: JSON Parse 500 Error on `listClaims` and `listPolicies` - **Problem:** Same `getJSON(true)` issue — any malformed or missing JSON body causes a 500 instead of a graceful 400. - **Fix:** Wrapped `getJSON(true)` calls in `try/catch (\Throwable)` returning a proper `400 Invalid JSON body` response. ### 3. Add: Logging to all API endpoints Added `myLogger->logme('error', ...)` calls to all 6 endpoints under `employeeRest/api/v1/non-eb-claim` that previously had no log coverage: | Endpoint | Log event | |---|---| | `createClaim` | No ACM found, claim created with ID + user | | `listClaims` | User + page + total count | | `claimHistory` | Claim ID + history steps + user | | `listClaimStatuses` | User + status count | | `listPolicies` | User + client_md5 + branch + count | | `uploadRequiredDoc` | Transaction failure + successful upload with doc/file/user | All log tags follow the pattern `[NON_EB_API][methodName]` for easy log filtering. ### 4. Enhance: `claimHistory` endpoint — full claim detail response Previously returned only status history array. Now returns 4 keys: | Key | Source | Description | |---|---|---| | `claims_data` | `non_eb_ticket_master` + joins | Major display fields (name, policy, status, surveyor, etc.) | | `required_docs` | `non_eb_ticket_master.required_docs` (decoded) | IR documents checklist | | `claims_files` | `claim_files` table | Uploaded documents for this claim | | `ticket_data` | `ticket_history` table | Claim status change history (renamed from `history`) | Also removed early-return on empty history so all 4 keys are always present in the response. --- ### Files Changed - `app/Controllers/Api/NonEbClaimApiController.php` ### API Docs - `dev_logs/non_eb_claim_api.md` --- ### 5. Change: `uploadRequiredDoc` — rewritten to mirror `uploadIRDocs` **Old URL:** `POST /api/v1/non-eb-claim/{claim_id}/upload-required-doc` **New URL:** `POST /api/v1/non-eb-claim/upload-required-doc` Old function copied as `uploadRequiredDoc_v1` (preserved for reference). **What changed:** - Removed `claim_id` from URL; `ticket_id` now comes from POST body - Switched from single-file (`file` field) to multi-file (`claim_docs[]` field) using `multi_file_Upload` - Added `claim_doc_names[]` — index-matched names for uploaded files - `required_docs` JSON string now sent by client and stored directly to `non_eb_ticket_master.required_docs` (no server-side checklist merging) - Each inserted `claim_files` row now has `docs_for_ir = 1` - Removed TPA push (`pushClaimFiles` not called) - Success response simplified to `{status, code, message}` ### Files Changed - `app/Controllers/Api/NonEbClaimApiController.php` - `app/Config/Routes.php` ### API Docs - `nonebapidocs.md` - `dev_logs/non_eb_claim_api.md` --- ## LeadsController — Bug Fix: `createClientWithLeadData` for Non-EB Leads ### 6. Fix: `prepareClientPolicyData` crashes on Non-EB leads **Problem:** `proposel_data` is always `null` for Non-EB leads. The function unconditionally did: ```php $proposel_data = json_decode($data['proposel_data'], true); list($insurer_branch_id, $insurer_id) = explode('-', $proposel_data['insurer'], 2); ``` This throws a PHP 8 TypeError (cannot access key on null) for any Non-EB lead, making `createClientWithLeadData` silently fail. **Fix:** Split insurer resolution by `lead_form_type`: - **EB (`lead_form_type == 1`):** parse insurer from `proposel_data['insurer']` as `"{insurer_branch_id}-{insurer_id}"` — unchanged - **Non-EB:** read `insurer_id` and `insurer_branch_id` directly from lead row columns ### 7. Fix: `getPlacementJson` null-safety for Non-EB leads **Problem:** Same null `proposel_data` issue — `json_decode($data['proposel_data'], true)` returned null, and downstream `$proposel_data['proposel_name']` access would crash if a Non-EB lead had QCR data. **Fix:** `json_decode($data['proposel_data'] ?? '', true) ?? []` — `proposel_data` is now always an array, so all `??` key accesses are safe. ### Smoke Test Results | Path | Step | Result | |---|---|---| | EB | `proposel_data` decode | ✅ decodes normally | | EB | `explode('-', proposel_data['insurer'])` | ✅ `insurer_branch_id` + `insurer_id` parsed correctly | | EB | `preparePolicyTermsFromRFQ` | ✅ unaffected | | Non-EB | `proposel_data` null → `[]` | ✅ safe | | Non-EB | insurer from lead row columns | ✅ `insurer_id` + `insurer_branch_id` read directly | | Non-EB | `getPlacementJson` — no QCR data | ✅ returns null safely, policy terms skipped | | Non-EB | `getPlacementJson` — QCR data exists | ⚠️ line 4356: `$proposel_data['proposel_name']` missing `??` — pending clarification on whether Non-EB leads can have QCR data | ### Files Changed - `app/Controllers/LeadsController.php` ## Task Plan — Policy Transaction Inception Form JS Validation ### Goal Implement client-side form validation for `app/Views/policy_transaction_inception_form.php` by following the existing validation approach used in `app/Views/ticket_form_gmc.php` (centralized submit + reusable validator), but moving inception validation into a dedicated external JavaScript file. ### Current-State Notes - `policy_transaction_inception_form.php` currently contains inline submit and validation logic on `#inception_form_id` (Parsley validation + custom checks + toastr + AJAX submit flow). - `ticket_form_gmc.php` follows a cleaner pattern where submit handler delegates validation and shows first invalid field feedback. - No dedicated inception validation `.js` file currently exists. ### Proposed File Changes 1. **Create** `public/assets/js/policy_transaction_inception_validation.js` - Add a single public validation entry function (example: `window.validateInceptionFormInputs(form)`). - Keep all custom rules here (beyond HTML `required` and Parsley): - `follow_insurer_id[]` must be selected for all rows. - `pt_form_sumbit_handler` must be `1`. - CD account selection rule for client/policy status condition. - Any date/business-rule checks currently done during submit. - Return a boolean result and handle user-facing messages consistently via toastr. 2. **Refactor** `app/Views/policy_transaction_inception_form.php` - Keep submit flow in one handler, but delegate custom validations to the new JS file. - Minimize inline validation logic in view. - Ensure first invalid field is focused/scrolled for better UX. - Include the new script after shared dependencies (jQuery/Parsley/toastr), before submit logic usage. 3. **(Optional Cleanup)** Move remaining inline helper validation code to dedicated JS if it is inception-form-specific and not used elsewhere. ### Implementation Steps (Execution Order) - [x] Step 1: Create new file `public/assets/js/pages/policy_transaction_inception_validation.js`. - [x] Step 2: Extract custom validation blocks from `#inception_form_id` submit handler into reusable functions. - [x] Step 3: Expose one callable function for submit handler (`validateInceptionFormInputs`). - [x] Step 4: Update `policy_transaction_inception_form.php` to include new JS file. - [x] Step 5: Replace inline custom checks with function call and keep existing AJAX submit behavior unchanged. - [x] Step 6: Ensure invalid-field focus and warning message are preserved. - [ ] Step 7: Verify create/edit journeys and conditional sections (renewal, co-insurer, CD account, policy status). ### Validation Rules Checklist (to implement in JS) - [ ] Parsley base validation must pass. - [ ] Every `follow_insurer_id[]` select must have value. - [ ] `pt_form_sumbit_handler != 0`. - [ ] If `client_type == 1` and `policy_status == completed` and `policy_type_id > 7`, at least one `cd_ac_no_for_child[]` must be selected. - [ ] Keep existing toastr wording (or align to one consistent warning style). ### Testing Checklist - [ ] Submit with empty required fields -> blocked with field-level indication. - [ ] Submit with any empty co-insurer selector -> blocked with warning. - [ ] Submit with base premium/CD mismatch (`pt_form_sumbit_handler = 0`) -> blocked. - [ ] Submit valid data -> AJAX create request fires successfully. - [ ] Edit existing inception record -> validation still works and submit succeeds. - [ ] No regression in date conversion before submit (`policy_issue_date`, `policy_start_date`, `policy_end_date`, `renewal_date`, `rollover_date`, `month`). ### Risks / Attention Points - Large inline script currently mixes validation and business logic; refactor should avoid changing API payload or field names. - Multiple dynamic rows (`follow_insurer_id[]`, `cd_ac_no_for_child[]`) need delegated-safe selectors. - Script include order is critical (new validation JS must load before submit handler executes). ### Completion Update (Implemented) - Added `public/assets/js/pages/policy_transaction_inception_validation.js` with: - `window.validateInceptionFormInputs(form)` as centralized entrypoint - Parsley validation gate - Co-insurer (`follow_insurer_id[]`) mandatory selection check - `pt_form_sumbit_handler` (CD amount) guard check - Child CD account selection rule for applicable completed flow - First invalid field focus/scroll helper - Updated `app/Views/policy_transaction_inception_form.php`: - Included external script: `assets/js/pages/policy_transaction_inception_validation.js` - Refactored `#inception_form_id` submit handler to delegate custom validation to new file - Preserved existing AJAX submit and payload/date conversion behavior - Technical validation done: - JS syntax check passed (`node --check public/assets/js/pages/policy_transaction_inception_validation.js`)