nhance/nonebapi.md
2026-03-31 12:04:35 +05:30

20 KiB

Non-EB Claims — External API Dev Doc & TODO

Purpose: Expose four read/write endpoints to external applications (client portals, mobile apps, integrations) for Non-EB Claims.


Endpoints to Build

# Method URL Purpose
1 POST /api/v1/non-eb-claim/create Create a new Non-EB claim
2 POST /api/v1/non-eb-claim/list List claims with optional filter params
3 GET /api/v1/non-eb-claim/history/{claim_id} Get field change history of a claim
4 POST /api/v1/non-eb-claim/{claim_id}/upload-required-doc Upload a file against a required document checklist item

Architecture Decision — New Controller

Create a separate API controller: app/Controllers/Api/NonEbClaimApiController.php

Why separate, not reusing NonEbClaimController:

  • Existing controller uses session-based auth (get_session_userid(), set_session_context()) — not safe for API
  • API needs token/key auth middleware (no session)
  • API responses must be pure JSON always — no HTML rendering
  • createClaim() currently calls get_session_userid() via model callbacks (checkAndADDCreatedByValue) — needs to be overridden for API context
  • Existing claimSearch() reads $this->request->getPost() directly — API version should accept clean JSON body

Reuse:

  • NonEbTicketMasterModel, NonEbClaimAssetModel, TicketHistoryModel, ClaimFilesModel — reuse as-is
  • claimHistory() logic — copy and adapt (remove priority label mapping that uses internal arrays)
  • getValidationRules() — reuse for create, with relaxed insured_contact_* rules since those come from external context
  • formatDatesForClaim() — reuse as-is
  • checkDuplicateNonEbClaim() — reuse as-is
  • handleAssetFileUpload() — reuse as-is
  • saveAssets() — reuse as-is

TODO Checklist

Phase 1 — Route Registration

  • Add /api/v1 route group in app/Config/Routes.php (use existing auth filter — no authMVC):
    $routes->group('api/v1', ['filter' => 'your-existing-api-filter'], function($routes) {
        $routes->group('non-eb-claim', function($routes) {
            $routes->post('create',                          'Api\NonEbClaimApiController::createClaim');
            $routes->post('list',                            'Api\NonEbClaimApiController::listClaims');
            $routes->get('history/(:num)',                   'Api\NonEbClaimApiController::claimHistory/$1');
            $routes->post('(:num)/upload-required-doc',      'Api\NonEbClaimApiController::uploadRequiredDoc/$1');
        });
    });
    
  • Confirm CI4 namespace resolution for App\Controllers\Api\NonEbClaimApiController

Phase 2 — Controller Skeleton

  • Create app/Controllers/Api/NonEbClaimApiController.php
    • Namespace: App\Controllers\Api
    • Extends BaseController, uses ResponseTrait
    • Import: NonEbTicketMasterModel, NonEbClaimAssetModel, TicketHistoryModel, TicketClaimStatusModel, ClaimFilesModel
    • No session calls — created_by should be the resolved api_client_id (or a fixed system user ID for API)
    • All responses: Content-Type: application/json

Phase 3 — API 1: Create Claim

POST /api/v1/non-eb-claim/create

Context: The user is already logged in and has selected a client policy from the interface. They are triggering a claim with only the loss details — what happened, where, and when. This is a one-shot create with no edit step. The controller is responsible for fetching all other mandatory data from the DB and assembling the full record before insert. The caller sends the minimum possible.


What the caller sends (user-facing fields only):

Field Required Type Notes
client_policy_id Yes int Selected in UI before opening the claim form; all other FK fields derived from this
nature_of_loss Yes string min 3 chars — what happened
loss_location Yes string where it happened
loss_date Yes string DD-MM-YYYY — when it happened
loss_description No string Additional details; required if asset_file is sent
loss_estimate No numeric Approximate loss value
claim_number No string ^[a-zA-Z0-9\/_-]+$ — if already known
asset_file No file xlsx/xls/csv/pdf
asset_id_code[] No array asset rows
serial_no[] No array
vehicle_no[] No array
asset_description[] No array

Surveyor fields, settlement fields, nhance_claim_ref_no, priority are not part of this create flow — they are filled by staff later via the internal web interface.


What the controller fetches and inserts (caller does NOT send these):

Field Source
client_id client_policy.client_id
branch_id client_policy.client_branch_id
policy_type_id client_policy.policy_type_id
insurer_id client_policy.insurer_id
policy_no client_policy.policy_no
acm_id client_rm where client_id = cp.client_id AND level = 3 AND is_active = 1 — first result
claim_status_id First row of ticket_claim_status where ticket_type = policy_type_id ORDER BY id ASC
insured_contact_name Logged-in user's name from auth (resolved via auth middleware)
insured_contact_number Logged-in user's mobile from auth
insured_contact_email Logged-in user's email from auth
priority Hard-coded default: 1 (Low)
created_by API system user ID (explicit — not from session)

Success Response 200:

{
  "status": true,
  "claim_id": 123,
  "message": "Non-EB Claim created successfully"
}

Error Response 400 — validation failure:

{
  "status": false,
  "message": "Input validation failed",
  "errors": {
    "nature_of_loss": "Nature of Loss is required",
    "loss_date": "Loss Date is required"
  }
}

Error Response 404 — policy not found:

{
  "status": false,
  "message": "Client policy not found or inactive"
}

Error Response 422 — policy type not allowed:

{
  "status": false,
  "message": "Only Non-EB or Marine policy types are allowed"
}

Conflict Response 409:

{
  "status": false,
  "message": "Duplicate claim found for Client + Loss Date + Policy No combination"
}

TODOs:

  • Validate only the user-facing fields — write a trimmed validation rule set for API (not reusing getValidationRules() directly since that includes staff-side fields); required: client_policy_id, nature_of_loss, loss_location, loss_date
  • Fetch client_policy row — return 404 if not found or is_active != 1
  • Validate policy_type.allocg IN ('Non-EB', 'Marine') — return 422 if EB type sent
  • Fetch acm_id from client_rm (level=3, is_active=1) for the derived client_id — set null and log warning if none found
  • Auto-set claim_status_id — first ticket_claim_status row for derived policy_type_id ORDER BY id ASC (same pattern as initiateClaim in EmployeeRestController)
  • Resolve insured contact fields from the authenticated user (name, mobile, email) via auth middleware — these are mandatory DB fields but the user does not type them
  • Handle created_by explicitly in insert data — model's beforeInsert callback calls get_session_userid() which returns null for API; CI4 uses the explicitly passed value
  • Duplicate check via checkDuplicateNonEbClaim() — runs after derivation so client_id and policy_no are already populated
  • Date format: DD-MM-YYYYformatDatesForClaim() converts to Y-m-d for DB
  • Call saveAssets() after insert if asset fields present
  • Call putHistoryAfterInsert() after insert
  • Skip sendAutoMailTrigger() for this initial create — claim is at first status, mail trigger is for status transitions; document this
  • Handle multipart/form-data when asset_file is included
  • Return claim_id in response so the caller can reference the created claim

Phase 4 — API 2: List Claims

POST /api/v1/non-eb-claim/list

Request Body (JSON):

Field Required Type Notes
page No int Default 1
per_page No int Default 20, max 100
policy_type_id No int Filter by policy type
claim_status_id No int Filter by status
client_id No int Filter by client
insurer_id No int Filter by insurer
claim_number No string LIKE search
nhance_claim_ref_no No string LIKE search
date_type No string created_date or updated_date
start_date No string DD-MM-YYYY, used with date_type
end_date No string DD-MM-YYYY, used with date_type
show_closed No bool Default false — if false, excludes Settled/Closed/Rejected/Withdrawn

Success Response 200:

{
  "status": true,
  "total": 87,
  "page": 1,
  "per_page": 20,
  "data": [
    {
      "id": 123,
      "claim_number": "CLM/2026/001",
      "nhance_claim_ref_no": "NEB/2026/00123",
      "client_name": "ABC Corp",
      "insurer_name": "New India Assurance",
      "policy_type_name": "All Risk",
      "policy_no": "POL/1234/2026",
      "status": "Claim Intimation - Insured",
      "status_display": "Claim Intimation - Insured",
      "loss_date": "15-03-2026",
      "loss_location": "Chennai",
      "nature_of_loss": "Fire damage",
      "loss_estimate": "500000",
      "insured_contact_name": "John Doe",
      "insured_contact_number": "9876543210",
      "acm_name": "Ravi Kumar",
      "created_date": "01-03-2026",
      "updated_date": "20-03-2026"
    }
  ]
}

TODOs:

  • Reuse the claimSearch() query from existing controller — extract it into a shared private method or duplicate in API controller
  • Add pagination: LIMIT + OFFSET based on page and per_page; also run a COUNT(*) variant of the same query for total
  • Accept JSON body ($this->request->getJSON(true)) not getPost() — existing controller uses getPost()
  • Cap per_page at 100 to prevent abuse
  • Strip HTML from nature_of_loss, loss_description before returning (use existing convertHtmlToText() or esc())
  • Decide which fields to expose — do not return: google_drive_links, documents_*, required_docs, is_active, created_by, updated_by
  • show_closed: false (default) mirrors the existing default list behaviour (whereNotIn on terminal statuses)
  • Add sort_by param later (optional/phase 2): loss_date, created_at, updated_at with sort_dir: asc|desc

Phase 5 — API 3: Claim History

GET /api/v1/non-eb-claim/history/{claim_id}

URL Param: claim_id — integer, required

What is shown vs hidden:

This endpoint does not expose the full raw ticket_history table. It follows the same pattern as the EB claimView API — only status progression is shown to the user, and only statuses that have a display_name in ticket_claim_status are included. Internal status names, field-level changes (surveyor, ACM, priority etc.), and who made the change are all hidden.

The response is a chronological status timeline — oldest to newest — showing when the claim moved through each user-visible stage.

Filtering logic (mirrors EB claimView):

  1. Fetch all ticket_history rows for ticket_id
  2. Keep only rows where field_name = 'claim_status_id'
  3. For each row, look up new_value against ticket_claim_status — only include it if that status has a non-null display_name
  4. Map to display_name for output — never expose raw internal claim_status string
  5. Reverse to chronological order (oldest first for timeline display)
  6. Do not expose modified_by — who changed it is internal

URL Param: claim_id — integer, required

Success Response 200:

{
  "status": true,
  "claim_id": 123,
  "history": [
    {
      "status": "Claim Intimation - Insured",
      "changed_at": "01-03-2026 10:15 AM"
    },
    {
      "status": "Under Process",
      "changed_at": "05-03-2026 02:30 PM"
    },
    {
      "status": "Claim Settled",
      "changed_at": "20-03-2026 04:45 PM"
    }
  ]
}

If a status does not have a display_name in ticket_claim_status it is silently skipped — it is an internal-only status not meant for user visibility.

Error Response 404:

{
  "status": false,
  "message": "Claim not found"
}

TODOs:

  • Do NOT reuse claimHistory() from NonEbClaimController as-is — that returns all field changes for internal staff view. Write a separate method that fetches only claim_status_id history rows
  • Fetch all ticket_history where ticket_id = claim_id AND field_name = 'claim_status_id' AND is_active = 1 ORDER BY created_at ASC
  • For each row join or look up ticket_claim_status on new_value = id — filter out rows where display_name IS NULL
  • Verify claim exists in non_eb_ticket_master (is_active = 1) before querying history — return 404 if not
  • Format created_at as DD-MM-YYYY HH:MM AM/PM (matches EB pattern: date('d-m-Y h:i A', strtotime(...)))
  • Output only status (display_name) and changed_at per row — no field_name, no old_value, no modified_by
  • Scope check: verify the claim's client_id matches the authenticated user's client before returning — prevents users from fetching other clients' claim history

Phase 6 — API 4: Upload Required Document

POST /api/v1/non-eb-claim/{claim_id}/upload-required-doc

URL Param: claim_id — integer, required

Request Body (multipart/form-data):

Field Required Type Notes
document_name Yes string Must exactly match one of the document_name values in required_docs.docs[]
file Yes file Allowed types: same as UPLOAD_EXT_CLAIM_DOCS

required_docs JSON structure (stored in non_eb_ticket_master.required_docs):

{
  "is_action_freeze": false,
  "docs": [
    { "document_name": "Invoice Copy", "document_received": false },
    { "document_name": "Survey Report", "document_received": true }
  ]
}

Behavior:

  1. Validate claim exists (non_eb_ticket_master.id = claim_id, is_active = 1) — 404 if not
  2. Fetch required_docs JSON from the ticket
  3. If required_docs is empty or null — return 422 (no checklist configured for this claim)
  4. If is_action_freeze = true — return 423 (checklist is locked, uploads not allowed)
  5. Find the doc entry where document_name matches exactly — 404 if not found in list
  6. Upload file → insert row into claim_files (ticket_type = 2, file_type = 2, ticket_id = claim_id, doc_name = document_name)
  7. Update required_docs: set document_received = true for the matched doc entry, write back to non_eb_ticket_master.required_docs
  8. Return success with the full updated required_docs object

Success Response 200:

{
  "status": true,
  "message": "Document uploaded successfully",
  "claim_id": 123,
  "document_name": "Invoice Copy",
  "required_docs": {
    "is_action_freeze": false,
    "docs": [
      { "document_name": "Invoice Copy", "document_received": true },
      { "document_name": "Survey Report", "document_received": true }
    ]
  }
}

Error Response 404 — claim not found:

{ "status": false, "message": "Claim not found" }

Error Response 422 — no checklist configured:

{ "status": false, "message": "No required documents checklist configured for this claim" }

Error Response 423 — checklist locked:

{ "status": false, "message": "Document checklist is locked for this claim" }

Error Response 404 — document_name not in list:

{ "status": false, "message": "Document 'Invoice Copy' not found in required documents list" }

TODOs:

  • Use claim_files model for file insert — same structure as existing uploadFile() in NonEbClaimController (ticket_type = 2, file_type = 2)
  • File upload path: WRITEPATH . 'uploads/claim_files/' — same as web controller
  • created_by: use API system user ID (same pattern as create endpoint)
  • document_name match is case-sensitive exact match — document this for API consumers
  • Allow re-upload if document_received is already true — overwrite the previous claim_files entry (soft-delete old, insert new) OR just insert new and keep both; decide and document
  • Do NOT expose the claim_files.url file path directly — return download URL via base_url('downloadClaimFile/') . $file_id so internal paths are not leaked
  • Validate file extension against UPLOAD_EXT_CLAIM_DOCS constant — reject unsupported types with 415
  • The required_docs update must be atomic — update the JSON and claim_files insert in a DB transaction; roll back file insert if JSON update fails

Phase 7 — Cross-cutting Concerns

  • Rate limiting: add a simple request counter per api_key per minute in a cache table or Redis. Block at 60 req/min.
  • Request logging: log every API request (api_client_id, endpoint, status_code, ip, timestamp) into an api_request_log table for audit
    CREATE TABLE api_request_log (
      id BIGINT AUTO_INCREMENT PRIMARY KEY,
      api_client_id INT,
      endpoint VARCHAR(100),
      method VARCHAR(10),
      status_code SMALLINT,
      ip_address VARCHAR(45),
      created_at DATETIME DEFAULT CURRENT_TIMESTAMP
    );
    
  • CORS headers: if external app is browser-based, add Access-Control-Allow-Origin + preflight OPTIONS handling in a filter
  • API versioning: prefix as /api/v1/non-eb-claim/... from day one — easier to version-bump later without breaking consumers
  • Error envelope: all errors must follow a consistent shape:
    { "status": false, "code": 400, "message": "...", "errors": {} }
    
  • Postman collection: create and share a Postman collection with all 4 endpoints, sample request bodies, and expected responses
  • DB changes — add to db.md:
    • api_request_log table

Field Reference — What the External App Must Know

Required IDs (need lookup endpoints or a handshake step)

These are FK IDs that the external app must send — they need to either be pre-agreed or fetched from lookup endpoints (future phase):

ID Field Lookup Source
client_id clients table
branch_id client_branch table, filtered by client_id
policy_type_id policy_type table, allocg IN ('Non-EB','Marine')
client_policy_id client_policy table, filtered by branch_id
insurer_id insurers table
acm_id user_profiles table, role = 3
claim_status_id ticket_claim_status table, filtered by ticket_type = policy_type_id

TODO (future phase): Build read-only lookup endpoints (/api/v1/lookup/clients, /api/v1/lookup/policy-types, etc.) so the external app can populate dropdowns without hardcoding IDs.


Claim Status Flow (for external app awareness)

The trigger-based DB trigger (non_eb_ticket_master_after_update) auto-logs status changes into ticket_history. History API reflects these changes. External app should not attempt to set arbitrary statuses — only statuses listed in allowed_status of the current status are valid transitions.

TODO: Document valid status transitions in a separate handshake endpoint response or static doc section.


Files to Create / Modify

File Action
app/Controllers/Api/NonEbClaimApiController.php Create
app/Config/Routes.php Modify — add /api/v1 route group
db.md Modify — add api_request_log DDL

Out of Scope (this phase)

  • Update claim via API — not requested; skip for now
  • Delete/remove claim via API — internal operation only
  • Claim file download via API — separate phase
  • IR Documents (required_docs) CRUD via API — separate phase (upload-required-doc only marks received + stores file; full checklist management is out of scope)
  • Webhook callbacks to external app on status change — separate phase