nhance_partner_be/app/Controllers/ClaimController.php
2026-08-06 17:08:13 +05:30

735 lines
28 KiB
PHP

<?php
namespace App\Controllers;
use App\Models\ClaimModel;
use App\Models\ClaimFilesModel;
use App\Models\PolicyModel;
use App\Models\EnquiryModel;
use App\Models\QuotationModel;
use App\Models\ClaimStatusModel;
use CodeIgniter\RESTful\ResourceController;
class ClaimController extends ResourceController
{
protected $db;
protected $ClaimModel;
protected $ClaimFilesModel;
protected $PolicyModel;
protected $QuotationModel;
protected $EnquiryModel;
protected $ClaimStatusModel;
private const CLAIM_UPLOAD_DIR = 'uploads/claims/';
private const CLAIM_TICKET_TYPE = 8;
public function __construct()
{
$this->db = db_connect();
$this->ClaimModel = new ClaimModel();
$this->ClaimFilesModel = new ClaimFilesModel();
$this->PolicyModel = new PolicyModel();
$this->QuotationModel = new QuotationModel();
$this->EnquiryModel = new EnquiryModel();
$this->ClaimStatusModel = new ClaimStatusModel();
}
public function claimStatusList()
{
try {
$data = $this->ClaimStatusModel
->select('id, claim_status, allowed_status, ticket_type, trigger_type')
->where('ticket_type', self::CLAIM_TICKET_TYPE)
->where('is_active', 1)
->orderBy('id', 'ASC')
->findAll();
return $this->respond(['status' => 'success', 'code' => 200, 'data' => $data], 200);
} catch (\Exception $e) {
return $this->respond(['status' => 'failed', 'code' => 500, 'data' => $e->getMessage()], 500);
}
}
public function ClaimList()
{
try {
$id = $this->request->getGet('id');
$manager_id = $this->request->getGet('manager_id');
$agent_id = $this->request->getGet('agent_id');
$policy_number = $this->request->getGet('policy_number');
$claim_type_id = $this->request->getGet('claim_type_id');
$claim_status_id = $this->request->getGet('claim_status_id');
$insurer_id = $this->request->getGet('insurer_id');
$from_date = $this->request->getGet('from_date');
$to_date = $this->request->getGet('to_date');
$builder = $this->db->table('partner_claims pc')
->select("pc.*,
i.name as insurer_name,
i.short_name as insurer_short_name,
pct.claim_type as claim_type_value,
pa.name as agent_name,
pb.name as broker_name,
tcs.claim_status as claim_status_value,
pp.start_date as policy_start_date,
pp.end_date as policy_end_date,
pp.rc_no as reg_no,
CASE
WHEN pc.reg_no IS NULL OR pc.reg_no = '' THEN pp.rc_no
ELSE pc.reg_no
END AS reg_no
")
->join('insurers i', 'i.id = pc.insurer_id', 'left')
->join('partner_claim_type_master pct', 'pct.id = pc.claim_type', 'left')
->join('partner_agent pa', 'pa.id = pc.agent_id', 'left')
->join('partner_brokers pb', 'pb.id = pc.broker_id', 'left')
->join('ticket_claim_status tcs', 'tcs.id = pc.claim_status_id AND tcs.ticket_type = ' . self::CLAIM_TICKET_TYPE, 'left')
->join('partner_policy pp', 'pp.id = pc.policy_id', 'left')
->join('vehicle v', 'v.id = pp.vehicle_id', 'left')
->where('pc.is_active', 1);
if (!empty($id)) {
$builder->where('pc.id', $id);
$claim = $builder->get()->getRowArray();
if (!$claim) {
return $this->respond(['status' => 'failed', 'code' => 404, 'data' => 'Not Found'], 404);
}
$claim = $this->formatClaimRow($claim);
$claim['files'] = $this->decorateFilesWithUrls($this->getClaimFiles((int) $claim['id']));
return $this->respond(['status' => 'success', 'code' => 200, 'data' => $claim], 200);
}
if (!empty($manager_id)) {
$builder->where('pc.manager_id', $manager_id);
}
if (!empty($agent_id)) {
$builder->where('pc.agent_id', $agent_id);
}
if (!empty($policy_number)) {
$builder->where('pc.policy_no', $policy_number);
}
if (!empty($claim_type_id)) {
$builder->where('pc.claim_type', $claim_type_id);
}
if (!empty($claim_status_id)) {
$builder->where('pc.claim_status_id', $claim_status_id);
}
if (!empty($insurer_id)) {
$builder->where('pc.insurer_id', $insurer_id);
}
if (!empty($from_date) && !empty($to_date)) {
$builder->where('pc.created_at >=', $from_date . ' 00:00:00')
->where('pc.created_at <=', $to_date . ' 23:59:59');
}
$data = $builder->orderBy('pc.id', 'DESC')->get()->getResultArray();
$filesByClaim = $this->getClaimFilesGrouped(array_column($data, 'id'));
foreach ($data as $key => $row) {
$data[$key] = $this->formatClaimRow($row);
$data[$key]['files'] = $this->decorateFilesWithUrls($filesByClaim[$row['id']] ?? []);
}
return $this->respond(['status' => 'success', 'code' => 200, 'data' => $data], 200);
} catch (\Exception $e) {
return $this->respond(['status' => 'failed', 'code' => 500, 'data' => $e->getMessage()], 500);
}
}
public function createClaim()
{
try {
$reqData = $this->request->getPost();
if (empty($reqData['policy_number']) || empty($reqData['claim_type']) || empty($reqData['policy_from'])) {
return $this->respond([
'status' => 'failed',
'code' => 400,
'data' => 'policy_number, claim_type and policy_from are required',
], 400);
}
if($reqData['policy_from'] === 'Internal') {
$policy = $this->PolicyModel
->select('partner_policy.*, Q.insurer_id, E.mobile as client_mobile, E.email as client_email, E.broker_id')
->join('partner_quotation Q', 'Q.id = partner_policy.quotation_id AND Q.status = "Accepted"', 'left')
->join('partner_enquiry E', 'E.id = partner_policy.enquiry_id', 'left')
->where('partner_policy.policy_number', $reqData['policy_number'])
->first();
if (empty($policy)) {
return $this->respond(['status' => 'failed', 'code' => 404, 'data' => 'Policy not found'], 404);
}
}
$status = $this->ClaimStatusModel
->where('ticket_type', self::CLAIM_TICKET_TYPE)
->where('is_active', 1)
->orderBy('id', 'ASC')
->first();
if (empty($status)) {
return $this->respond(['status' => 'failed', 'code' => 404, 'data' => 'Default claim status not found'], 404);
}
$claimData = [
'claim_status_id' => $reqData['claim_status_id'] ?? $status['id'],
'policy_id' => $policy['id'] ?? null,
'policy_no' => $policy['policy_number'] ?? $reqData['policy_number'] ?? null,
'claim_number' => $reqData['claim_number'] ?? null,
'policy_from' => $reqData['policy_from'],
'insurer_id' => $policy['insurer_id'] ?? $reqData['insurer_id'] ?? null,
'broker_id' => $reqData['broker_id'] ?? $policy['broker_id'] ?? null,
'insured_name' => $reqData['insured_name'] ?? $policy['insured_name'] ?? null,
'mobile' => $reqData['mobile'] ?? $policy['client_mobile'] ?? null,
'mail' => $reqData['mail'] ?? $policy['client_email'] ?? null,
'manager_id' => $policy['manager_id'] ?? $reqData['manager_id'] ?? null,
'agent_id' => $policy['agent_id'] ?? $reqData['agent_id'] ?? null,
'claim_type' => $reqData['claim_type'],
'date_of_incident' => format_date_for_database($reqData['date_of_incident'] ?? null),
'place_of_incident' => $reqData['place_of_incident'] ?? null,
'claim_description' => $reqData['claim_description'] ?? null,
'spot_surveyor_name' => $reqData['spot_surveyor_name'] ?? null,
'spot_surveyor_contact' => $reqData['spot_surveyor_contact'] ?? null,
'workshop_surveyor_name' => $reqData['workshop_surveyor_name'] ?? null,
'workshop_surveyor_contact'=> $reqData['workshop_surveyor_contact'] ?? null,
'remark' => $reqData['remark'] ?? null,
'is_active' => 1,
'created_by' => $reqData['created_by'] ?? null,
'reg_no' => $reqData['reg_no'] ?? null,
];
$this->db->transStart();
if (!$this->ClaimModel->insert($claimData)) {
$this->db->transRollback();
return $this->respond(['status' => 'failed', 'code' => 422, 'data' => $this->ClaimModel->errors()], 422);
}
$claimId = (int) $this->ClaimModel->getInsertID();
$fileResult = $this->saveClaimFiles($claimId, $reqData['created_by'] ?? null);
if ($fileResult['attempted'] > 0 && empty($fileResult['saved'])) {
$this->db->transRollback();
return $this->respond([
'status' => 'failed',
'code' => 422,
'data' => !empty($fileResult['errors']) ? $fileResult['errors'] : 'Failed to upload claim files',
], 422);
}
if ($this->db->transStatus() === false) {
return $this->respond(['status' => 'failed', 'code' => 500, 'data' => 'Failed to create claim'], 500);
}
$this->db->transComplete();
return $this->respond([
'status' => 'success',
'code' => 200,
'data' => [
'claim_id' => $claimId,
'uploaded_files' => $fileResult['saved'],
'files' => $this->decorateFilesWithUrls($this->getClaimFiles($claimId)),
],
], 200);
} catch (\Exception $e) {
return $this->respond(['status' => 'failed', 'code' => 500, 'data' => $e->getMessage()], 500);
}
}
public function updateClaim()
{
try {
$claimId = $this->request->getPost('id');
if (empty($claimId)) {
return $this->respond(['status' => 'failed', 'code' => 400, 'data' => 'id is required'], 400);
}
$reqData = $this->request->getPost();
$claim = $this->ClaimModel->where('is_active', 1)->find((int) $claimId);
if (!$claim) {
return $this->respond(['status' => 'failed', 'code' => 404, 'data' => 'Claim not found'], 404);
}
$updateData = [];
$allowedUpdateFields = [
'claim_status_id',
'insurer_id',
'broker_id',
'policy_id',
'policy_no',
'insured_name',
'mobile',
'mail',
'manager_id',
'agent_id',
'claim_type',
'place_of_incident',
'claim_description',
'spot_surveyor_name',
'spot_surveyor_contact',
'workshop_surveyor_name',
'workshop_surveyor_contact',
'remark',
'is_active',
'policy_from',
];
foreach ($allowedUpdateFields as $field) {
if (array_key_exists($field, $reqData)) {
$updateData[$field] = $reqData[$field];
}
}
if (array_key_exists('date_of_incident', $reqData)) {
$updateData['date_of_incident'] = format_date_for_database($reqData['date_of_incident']);
}
if (!empty($reqData['policy_number'])) {
$policy = $this->PolicyModel
->select('partner_policy.*, Q.insurer_id')
->join('partner_quotation Q', 'Q.id = partner_policy.quotation_id', 'left')
->where('partner_policy.policy_number', $reqData['policy_number'])
->first();
if ($policy) {
$updateData['policy_id'] = $policy['id'];
$updateData['policy_no'] = $policy['policy_number'];
if (!array_key_exists('insurer_id', $updateData)) {
$updateData['insurer_id'] = $policy['insurer_id'] ?? null;
}
}
}
if (isset($reqData['updated_by'])) {
$updateData['updated_by'] = $reqData['updated_by'];
}
if($reqData['policy_from'] === 'External') {
$updateData['reg_no'] = $reqData['reg_no'] ?? null;
$updateData['claim_number'] = $reqData['claim_number'] ?? null;
$updateData['policy_no'] = $reqData['policy_number'] ?? null;
}
if (!empty($updateData) && !$this->ClaimModel->update((int) $claimId, $updateData)) {
return $this->respond(['status' => 'failed', 'code' => 422, 'data' => $this->ClaimModel->errors()], 422);
}
$updatedBy = $reqData['updated_by'] ?? $reqData['created_by'] ?? null;
// Soft-delete only the file ids sent in remove_file_id.
$deletedFileIds = [];
if (!empty($reqData['remove_file_id'])) {
$deletedFileIds = $this->softDeleteClaimFilesByIds(
(int) $claimId,
$reqData['remove_file_id'],
$updatedBy
);
}
$fileResult = $this->saveClaimFiles((int) $claimId, $updatedBy);
if ($fileResult['attempted'] > 0 && empty($fileResult['saved'])) {
return $this->respond([
'status' => 'failed',
'code' => 422,
'data' => !empty($fileResult['errors']) ? $fileResult['errors'] : 'Failed to upload claim files',
], 422);
}
return $this->respond([
'status' => 'success',
'code' => 200,
'data' => [
'claim_id' => (int) $claimId,
'uploaded_files' => $fileResult['saved'],
'deleted_file_ids' => $deletedFileIds,
'files' => $this->decorateFilesWithUrls($this->getClaimFiles((int) $claimId)),
],
], 200);
} catch (\Exception $e) {
return $this->respond(['status' => 'failed', 'code' => 500, 'data' => $e->getMessage()], 500);
}
}
/**
* Soft delete claim by id (sets is_active = 0).
* Also soft deletes related claim files.
*/
public function deleteClaim()
{
try {
$claimId = $this->request->getGet('id');
if (empty($claimId)) {
return $this->respond(['status' => 'failed', 'code' => 400, 'data' => 'id is required'], 400);
}
$claim = $this->ClaimModel
->where('id', (int) $claimId)
->where('is_active', 1)
->first();
if (!$claim) {
return $this->respond(['status' => 'failed', 'code' => 404, 'data' => 'Claim not found'], 404);
}
$updatedBy = $this->request->getGet('updated_by');
$this->db->transStart();
$this->ClaimModel->update((int) $claimId, [
'is_active' => 0,
'updated_by' => $updatedBy ?? null,
]);
$this->ClaimFilesModel
->where('claim_id', (int) $claimId)
->where('is_active', 1)
->set([
'is_active' => 0,
'updated_by' => $updatedBy ?? null,
])
->update();
if ($this->db->transStatus() === false) {
$this->db->transRollback();
return $this->respond(['status' => 'failed', 'code' => 500, 'data' => 'Failed to delete claim'], 500);
}
$this->db->transComplete();
return $this->respond([
'status' => 'success',
'code' => 200,
'data' => 'Claim deleted successfully',
], 200);
} catch (\Exception $e) {
return $this->respond(['status' => 'failed', 'code' => 500, 'data' => $e->getMessage()], 500);
}
}
/**
* Fetch a single claim by primary id with related lookups and file preview URLs.
* Used to populate the edit form when the user clicks the edit button on a row.
*/
public function getClaimById()
{
try {
$id = $this->request->getGet('id');
if (empty($id)) {
return $this->respond(['status' => 'failed', 'code' => 400, 'data' => 'id is required'], 400);
}
$claim = $this->db->table('partner_claims pc')
->select("pc.*,
i.name as insurer_name,
i.short_name as insurer_short_name,
pct.claim_type as claim_type_value,
pa.name as agent_name,
pb.name as broker_name,
tcs.claim_status as claim_status_value,
pp.start_date as policy_start_date,
pp.end_date as policy_end_date,
CASE
WHEN pc.reg_no IS NULL OR pc.reg_no = '' THEN v.vehicle_no
ELSE pc.reg_no
END AS reg_no
")
->join('insurers i', 'i.id = pc.insurer_id', 'left')
->join('partner_claim_type_master pct', 'pct.id = pc.claim_type', 'left')
->join('partner_agent pa', 'pa.id = pc.agent_id', 'left')
->join('partner_brokers pb', 'pb.id = pc.broker_id', 'left')
->join('ticket_claim_status tcs', 'tcs.id = pc.claim_status_id AND tcs.ticket_type = ' . self::CLAIM_TICKET_TYPE, 'left')
->join('partner_policy pp', 'pp.id = pc.policy_id', 'left')
->join('vehicle v', 'v.id = pp.vehicle_id', 'left')
->where('pc.id', (int) $id)
->where('pc.is_active', 1)
->get()
->getRowArray();
if (!$claim) {
return $this->respond(['status' => 'failed', 'code' => 404, 'data' => 'Claim not found'], 404);
}
$claim = $this->formatClaimRow($claim);
$claim['files'] = $this->decorateFilesWithUrls($this->getClaimFiles((int) $claim['id']));
return $this->respond(['status' => 'success', 'code' => 200, 'data' => $claim], 200);
} catch (\Exception $e) {
return $this->respond(['status' => 'failed', 'code' => 500, 'data' => $e->getMessage()], 500);
}
}
/**
* Stream a single claim file inline (used as preview / download URL).
*/
public function downloadClaimFile()
{
try {
$fileId = $this->request->getGet('file_id');
if (empty($fileId)) {
return $this->respond(['status' => 'failed', 'code' => 400, 'data' => 'file_id is required'], 400);
}
$file = $this->ClaimFilesModel
->where('id', (int) $fileId)
->where('is_active', 1)
->first();
if (!$file) {
return $this->respond(['status' => 'failed', 'code' => 404, 'data' => 'File not found'], 404);
}
$relativePath = ltrim((string) ($file['file_path'] ?? ''), '/');
$storedName = basename($relativePath !== '' ? $relativePath : (string) ($file['file_name'] ?? ''));
if ($storedName === '' || !storage_exists('claims', '', $storedName)) {
return $this->respond(['status' => 'failed', 'code' => 404, 'data' => 'File missing on server'], 404);
}
$downloadName = $file['file_name'] ?: $storedName;
return storage_inline('claims', '', $storedName)
->setHeader('Content-Disposition', 'inline; filename="' . $downloadName . '"');
} catch (\Exception $e) {
return $this->respond(['status' => 'failed', 'code' => 500, 'data' => $e->getMessage()], 500);
}
}
private function formatClaimRow(array $row): array
{
if (!empty($row['created_at'])) {
$row['created_at'] = date('d-m-Y h:i A', strtotime($row['created_at']));
}
if (!empty($row['updated_at'])) {
$row['updated_at'] = date('d-m-Y h:i A', strtotime($row['updated_at']));
}
if (!empty($row['date_of_incident']) && $row['date_of_incident'] !== '0000-00-00') {
$row['date_of_incident'] = date('d-m-Y', strtotime($row['date_of_incident']));
} else {
$row['date_of_incident'] = null;
}
$row['policy_start_date'] = (!empty($row['policy_start_date']) && $row['policy_start_date'] !== '0000-00-00')
? date('d-m-Y', strtotime($row['policy_start_date']))
: null;
$row['policy_end_date'] = (!empty($row['policy_end_date']) && $row['policy_end_date'] !== '0000-00-00')
? date('d-m-Y', strtotime($row['policy_end_date']))
: null;
return $row;
}
private function getClaimFiles(int $claimId): array
{
return $this->ClaimFilesModel
->where('claim_id', $claimId)
->where('is_active', 1)
->orderBy('id', 'ASC')
->findAll();
}
/**
* Soft-delete active claim files by the given ids only.
* Expects remove_file_id as an array, e.g. [5, 7].
* Also accepts JSON string "[5,7]" or comma-separated "5,7" from form-data.
*
* @return int[] Soft-deleted file ids
*/
private function softDeleteClaimFilesByIds(int $claimId, $removeFileIds, $updatedBy = null): array
{
if (is_string($removeFileIds)) {
$decoded = json_decode($removeFileIds, true);
if (json_last_error() === JSON_ERROR_NONE && is_array($decoded)) {
$removeFileIds = $decoded;
} else {
$removeFileIds = array_filter(array_map('trim', explode(',', $removeFileIds)), 'strlen');
}
}
if (!is_array($removeFileIds)) {
$removeFileIds = [$removeFileIds];
}
$removeIds = [];
foreach ($removeFileIds as $id) {
if ($id !== null && $id !== '' && is_numeric($id)) {
$removeIds[] = (int) $id;
}
}
$removeIds = array_values(array_unique($removeIds));
if (empty($removeIds)) {
return [];
}
$filesToDelete = $this->ClaimFilesModel
->where('claim_id', $claimId)
->where('is_active', 1)
->whereIn('id', $removeIds)
->findAll();
if (empty($filesToDelete)) {
return [];
}
$deletedIds = array_map('intval', array_column($filesToDelete, 'id'));
$this->ClaimFilesModel
->whereIn('id', $deletedIds)
->set([
'is_active' => 0,
'updated_by' => $updatedBy,
])
->update();
return $deletedIds;
}
/**
* Add preview_url and download_url to each claim file row.
*/
private function decorateFilesWithUrls(array $files): array
{
foreach ($files as $key => $file) {
$fileId = $file['id'] ?? null;
$url = $fileId ? base_url('api/claim/downloadClaimFile?file_id=' . $fileId) : null;
$files[$key]['preview_url'] = $url;
$files[$key]['download_url'] = $url;
}
return $files;
}
private function getClaimFilesGrouped(array $claimIds): array
{
$claimIds = array_values(array_filter(array_map('intval', $claimIds)));
if (empty($claimIds)) {
return [];
}
$files = $this->ClaimFilesModel
->whereIn('claim_id', $claimIds)
->where('is_active', 1)
->orderBy('id', 'ASC')
->findAll();
$grouped = [];
foreach ($files as $file) {
$grouped[$file['claim_id']][] = $file;
}
return $grouped;
}
/**
* Collect uploaded files from claim_files field (single or multiple).
*/
private function collectClaimUploadFiles(): array
{
$filesToProcess = [];
// getFileMultiple() works when input name is claim_files[]
$multiple = $this->request->getFileMultiple('claim_files');
if (!empty($multiple) && is_array($multiple)) {
foreach ($multiple as $uploadedFile) {
if ($this->isValidUploadFile($uploadedFile)) {
$filesToProcess[] = $uploadedFile;
}
}
return $filesToProcess;
}
// Fallback: getFile() may return an array of files in some CI4 versions
$single = $this->request->getFile('claim_files');
if (is_array($single)) {
foreach ($single as $uploadedFile) {
if ($this->isValidUploadFile($uploadedFile)) {
$filesToProcess[] = $uploadedFile;
}
}
} elseif ($this->isValidUploadFile($single)) {
$filesToProcess[] = $single;
}
return $filesToProcess;
}
private function isValidUploadFile($uploadedFile): bool
{
return $uploadedFile
&& method_exists($uploadedFile, 'isValid')
&& $uploadedFile->isValid()
&& !$uploadedFile->hasMoved();
}
private function saveClaimFiles(int $claimId, $createdBy = null): array
{
$result = [
'saved' => [],
'errors' => [],
'attempted' => 0,
];
$filesToProcess = $this->collectClaimUploadFiles();
$result['attempted'] = count($filesToProcess);
foreach ($filesToProcess as $file) {
try {
$clientName = $file->getClientName() ?: null;
$clientExt = $file->getClientExtension();
$clientMime = $file->getClientMimeType();
$storedName = storage_upload_if_valid($file, 'claims', '');
if ($storedName === null) {
$result['errors'][] = $file->getErrorString() ?: 'Unable to upload file';
continue;
}
if (!$this->ClaimFilesModel->insert([
'claim_id' => $claimId,
'file_name' => $clientName ?: $storedName,
'file_path' => self::CLAIM_UPLOAD_DIR . $storedName,
'file_extension' => $clientExt,
'file_mime_type' => $clientMime,
'is_active' => 1,
'created_by' => $createdBy,
])) {
storage_delete('claims', '', $storedName);
$result['errors'][] = $this->ClaimFilesModel->errors() ?: 'Failed to save file record';
continue;
}
$result['saved'][] = $storedName;
} catch (\Exception $e) {
$result['errors'][] = $e->getMessage();
}
}
return $result;
}
}