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; } }