MERGE_UAT_BUG_FIXES
This commit is contained in:
commit
64f597261c
15
.env.sample
15
.env.sample
@ -116,6 +116,21 @@ VIDAL_PRIMARY_KEY_CONSTANT =
|
||||
|
||||
MEDI_ASSIST_PRIMARY_KEY_CONSTANT =
|
||||
|
||||
#--------------------------------------------------------------------
|
||||
# TPA PRIMARY KEY CONSTANTS LIVE
|
||||
#--------------------------------------------------------------------
|
||||
|
||||
MEDI_ASSIST_PRIMARY_KEY_CONSTANT_LIVE =
|
||||
ICICI_PRIMARY_KEY_CONSTANT_LIVE =
|
||||
ABHI_PRIMARY_KEY_CONSTANT_LIVE =
|
||||
R_CARE_PRIMARY_KEY_CONSTANT_LIVE =
|
||||
FHPL_PRIMARY_KEY_CONSTANT_LIVE =
|
||||
VIDAL_PRIMARY_KEY_CONSTANT_LIVE =
|
||||
VOLO_PRIMARY_KEY_CONSTANT_LIVE =
|
||||
|
||||
# When true, claims-collection-report falls back to ticket_master only if the
|
||||
# policy TPA has no mapped dump table (or dump table missing). Dump TPAs always use claim_report.
|
||||
CLAIM_REPORT_FALLBACK_TO_TICKET_MASTER = false
|
||||
|
||||
FHPL_TOKEN_URL =
|
||||
FHPL_BASE_URL =
|
||||
|
||||
@ -1,200 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Commands;
|
||||
|
||||
use CodeIgniter\CLI\BaseCommand;
|
||||
use CodeIgniter\CLI\CLI;
|
||||
|
||||
/**
|
||||
* Seed claim_report from existing dump-sourced ticket_master rows.
|
||||
*
|
||||
* Usage:
|
||||
* php spark claim:backfill-report
|
||||
* php spark claim:backfill-report --policy=4687
|
||||
* php spark claim:backfill-report --limit=5000
|
||||
*/
|
||||
class BackfillClaimReport extends BaseCommand
|
||||
{
|
||||
protected $group = 'Claims';
|
||||
protected $name = 'claim:backfill-report';
|
||||
protected $description = 'Backfill claim_report from ticket_master dump claims';
|
||||
protected $usage = 'claim:backfill-report [--policy=ID] [--limit=N]';
|
||||
protected $options = [
|
||||
'--policy' => 'Optional client_policy_id to limit backfill',
|
||||
'--limit' => 'Batch size (default 1000)',
|
||||
];
|
||||
|
||||
public function run(array $params)
|
||||
{
|
||||
$db = db_connect();
|
||||
|
||||
if (!$db->tableExists('claim_report')) {
|
||||
CLI::error('Table claim_report does not exist. Run migrations first.');
|
||||
return EXIT_ERROR;
|
||||
}
|
||||
|
||||
if (!$db->tableExists('ticket_master')) {
|
||||
CLI::error('Table ticket_master does not exist.');
|
||||
return EXIT_ERROR;
|
||||
}
|
||||
|
||||
$policyId = (int) (CLI::getOption('policy') ?? 0);
|
||||
$limit = (int) (CLI::getOption('limit') ?? 1000);
|
||||
if ($limit <= 0) {
|
||||
$limit = 1000;
|
||||
}
|
||||
|
||||
$offset = 0;
|
||||
$totalInserted = 0;
|
||||
$totalUpdated = 0;
|
||||
$totalSkipped = 0;
|
||||
$now = date('Y-m-d H:i:s');
|
||||
|
||||
CLI::write('Backfilling claim_report from ticket_master (dump-sourced claims)...', 'yellow');
|
||||
|
||||
while (true) {
|
||||
$builder = $db->table('ticket_master')
|
||||
->where('is_active', 1)
|
||||
->groupStart()
|
||||
->where('claim_dump_ref_id IS NOT NULL', null, false)
|
||||
->orWhere('file_id IS NOT NULL', null, false)
|
||||
->groupEnd()
|
||||
->where("claim_number IS NOT NULL AND TRIM(claim_number) != ''", null, false)
|
||||
->orderBy('id', 'ASC')
|
||||
->limit($limit, $offset);
|
||||
|
||||
if ($policyId > 0) {
|
||||
$builder->where('client_policy_id', $policyId);
|
||||
}
|
||||
|
||||
$tickets = $builder->get()->getResultArray();
|
||||
if ($tickets === []) {
|
||||
break;
|
||||
}
|
||||
|
||||
$rows = [];
|
||||
foreach ($tickets as $tm) {
|
||||
$claimNumber = trim((string) ($tm['claim_number'] ?? ''));
|
||||
$clientPolicyId = (int) ($tm['client_policy_id'] ?? 0);
|
||||
if ($claimNumber === '' || $clientPolicyId <= 0) {
|
||||
$totalSkipped++;
|
||||
continue;
|
||||
}
|
||||
|
||||
$rows[] = [
|
||||
'tpa_id' => $tm['tpa_id'] ?? null,
|
||||
'client_id' => $tm['client_id'] ?? null,
|
||||
'client_policy_id' => $clientPolicyId,
|
||||
'file_id' => $tm['file_id'] ?? null,
|
||||
'ticket_id' => $tm['id'] ?? null,
|
||||
'source_table' => null,
|
||||
'source_row_id' => $tm['claim_dump_ref_id'] ?? null,
|
||||
'claim_number' => $claimNumber,
|
||||
'emp_code' => $tm['emp_code'] ?? null,
|
||||
'tpa_no' => $tm['tpa_no'] ?? null,
|
||||
'emp_id' => $tm['emp_id'] ?? null,
|
||||
'insured_emp_id' => $tm['insured_emp_id'] ?? null,
|
||||
'claim_amount' => $tm['claim_amount'] ?? null,
|
||||
'approved_amount' => $tm['approved_amount'] ?? null,
|
||||
'incurred_amount' => $tm['approved_amount'] ?? $tm['claim_amount'] ?? null,
|
||||
'si_amt' => $tm['si_amt'] ?? null,
|
||||
'tpa_claim_status' => $tm['tpa_claim_status'] ?? null,
|
||||
'claim_status_id' => $tm['claim_status_id'] ?? null,
|
||||
'tpa_claim_type' => $tm['tpa_claim_type'] ?? null,
|
||||
'tpa_ailments' => $tm['tpa_ailments'] ?? null,
|
||||
'doa' => $tm['doa'] ?? null,
|
||||
'dod' => $tm['dod'] ?? null,
|
||||
'date_of_intimat' => $tm['date_of_intimat'] ?? null,
|
||||
'settled_date' => $tm['settled_date'] ?? null,
|
||||
'approved_date' => $tm['approved_date'] ?? null,
|
||||
'claim_dump_date' => $tm['claim_dump_date'] ?? null,
|
||||
'hospital_name' => $tm['hospital_name'] ?? null,
|
||||
'hospital_city' => $tm['hospital_city'] ?? null,
|
||||
'hospital_state' => $tm['hospital_state'] ?? null,
|
||||
'hospital_pin_code'=> $tm['hospital_pin_code'] ?? null,
|
||||
'hospital_address' => $tm['hospital_address'] ?? null,
|
||||
'gender' => null,
|
||||
'age' => null,
|
||||
'relation' => $tm['relationship'] ?? null,
|
||||
'is_active' => 1,
|
||||
'created_at' => $now,
|
||||
'updated_at' => $now,
|
||||
];
|
||||
}
|
||||
|
||||
if ($rows !== []) {
|
||||
$result = $this->upsertRows($db, $rows);
|
||||
if ($result === false) {
|
||||
CLI::error('Upsert failed at offset ' . $offset);
|
||||
return EXIT_ERROR;
|
||||
}
|
||||
$totalInserted += $result['inserted'];
|
||||
$totalUpdated += $result['updated'];
|
||||
}
|
||||
|
||||
$offset += count($tickets);
|
||||
CLI::write("Processed {$offset} ticket_master rows...", 'green');
|
||||
|
||||
if (count($tickets) < $limit) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
CLI::write("Done. inserted≈{$totalInserted}, updated≈{$totalUpdated}, skipped={$totalSkipped}", 'green');
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param list<array<string, mixed>> $rows
|
||||
* @return array{inserted:int,updated:int}|false
|
||||
*/
|
||||
private function upsertRows($db, array $rows)
|
||||
{
|
||||
$columns = [
|
||||
'tpa_id', 'client_id', 'client_policy_id', 'file_id', 'ticket_id',
|
||||
'source_table', 'source_row_id', 'claim_number', 'emp_code', 'tpa_no',
|
||||
'emp_id', 'insured_emp_id', 'claim_amount', 'approved_amount', 'incurred_amount',
|
||||
'si_amt', 'tpa_claim_status', 'claim_status_id', 'tpa_claim_type', 'tpa_ailments',
|
||||
'doa', 'dod', 'date_of_intimat', 'settled_date', 'approved_date', 'claim_dump_date',
|
||||
'hospital_name', 'hospital_city', 'hospital_state', 'hospital_pin_code', 'hospital_address',
|
||||
'gender', 'age', 'relation', 'is_active', 'created_at', 'updated_at',
|
||||
];
|
||||
|
||||
$updateCols = array_values(array_diff($columns, ['client_policy_id', 'claim_number', 'created_at']));
|
||||
$inserted = 0;
|
||||
$updated = 0;
|
||||
|
||||
foreach (array_chunk($rows, 100) as $chunk) {
|
||||
$placeholders = [];
|
||||
$binds = [];
|
||||
foreach ($chunk as $row) {
|
||||
$rowPlaceholders = [];
|
||||
foreach ($columns as $col) {
|
||||
$rowPlaceholders[] = '?';
|
||||
$binds[] = $row[$col] ?? null;
|
||||
}
|
||||
$placeholders[] = '(' . implode(', ', $rowPlaceholders) . ')';
|
||||
}
|
||||
|
||||
$updates = [];
|
||||
foreach ($updateCols as $col) {
|
||||
$updates[] = '`' . $col . '` = VALUES(`' . $col . '`)';
|
||||
}
|
||||
|
||||
$sql = 'INSERT INTO `claim_report` (`' . implode('`, `', $columns) . '`) VALUES '
|
||||
. implode(', ', $placeholders)
|
||||
. ' ON DUPLICATE KEY UPDATE ' . implode(', ', $updates);
|
||||
|
||||
if ($db->query($sql, $binds) === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$affected = $db->affectedRows();
|
||||
// MySQL: 1 = insert, 2 = update existing
|
||||
$updated += (int) floor($affected / 2);
|
||||
$inserted += max(0, $affected - (2 * (int) floor($affected / 2)));
|
||||
}
|
||||
|
||||
return ['inserted' => $inserted, 'updated' => $updated];
|
||||
}
|
||||
}
|
||||
@ -1,63 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Commands;
|
||||
|
||||
use App\Libraries\ClaimReportSyncService;
|
||||
use CodeIgniter\CLI\BaseCommand;
|
||||
use CodeIgniter\CLI\CLI;
|
||||
|
||||
/**
|
||||
* Copy linked TPA dump (+ optional ticket_master) claims into claim_report.
|
||||
*
|
||||
* Usage:
|
||||
* php spark claim:sync-report
|
||||
* php spark claim:sync-report --phase1
|
||||
* php spark claim:sync-report --policy=12 --phase1
|
||||
* php spark claim:sync-report --tpa=mediassist --policy=12
|
||||
* php spark claim:sync-report --ticket-only
|
||||
*/
|
||||
class SyncClaimReportFromDump extends BaseCommand
|
||||
{
|
||||
protected $group = 'Claims';
|
||||
protected $name = 'claim:sync-report';
|
||||
protected $description = 'Sync claim_report from TPA dump tables + ticket_master';
|
||||
protected $usage = 'claim:sync-report [--policy=ID] [--tpa=NAME|all] [--limit=N] [--phase1] [--ticket-only]';
|
||||
protected $options = [
|
||||
'--policy' => 'Optional client_policy_id',
|
||||
'--tpa' => 'icici|abhi|mediassist|fhpl|rcare|vidal|all (default: all)',
|
||||
'--limit' => 'Batch size per dump query (default 500)',
|
||||
'--phase1' => 'TPA dump tables only (skip ticket_master)',
|
||||
'--ticket-only' => 'Skip dump tables; copy only from ticket_master',
|
||||
];
|
||||
|
||||
public function run(array $params)
|
||||
{
|
||||
$policyId = (int) ($params['policy'] ?? CLI::getOption('policy') ?? 0);
|
||||
$limit = (int) ($params['limit'] ?? CLI::getOption('limit') ?? 500);
|
||||
$ticketOnly = array_key_exists('ticket-only', $params) || CLI::getOption('ticket-only') !== null;
|
||||
$phase1Only = array_key_exists('phase1', $params) || CLI::getOption('phase1') !== null;
|
||||
$tpaOpt = strtolower(trim((string) ($params['tpa'] ?? CLI::getOption('tpa') ?? 'all')));
|
||||
|
||||
if ($limit <= 0) {
|
||||
$limit = 500;
|
||||
}
|
||||
|
||||
$result = (new ClaimReportSyncService())->sync($policyId, $tpaOpt, $limit, $ticketOnly, $phase1Only);
|
||||
|
||||
foreach ($result['logs'] as $line) {
|
||||
$color = 'white';
|
||||
if (str_starts_with($line, '[FAIL]')) {
|
||||
$color = 'red';
|
||||
} elseif (str_starts_with($line, '[DONE]') || str_starts_with($line, 'Done.')) {
|
||||
$color = 'green';
|
||||
} elseif (str_starts_with($line, '[SKIP]') || str_starts_with($line, 'Phase') || str_starts_with($line, 'Skipping')) {
|
||||
$color = 'yellow';
|
||||
} elseif (str_starts_with($line, ' ')) {
|
||||
$color = 'green';
|
||||
}
|
||||
CLI::write($line, $color);
|
||||
}
|
||||
|
||||
return ! empty($result['status']) ? EXIT_SUCCESS : EXIT_ERROR;
|
||||
}
|
||||
}
|
||||
@ -510,7 +510,7 @@ $routes->group("/util", ["filter" => "authMVC"], function ($routes) {
|
||||
$routes->get('all', 'ClaimReportDashboardController::all');
|
||||
$routes->get('debug', 'ClaimReportDashboardController::debug');
|
||||
$routes->get('debug/(:num)', 'ClaimReportDashboardController::debug/$1');
|
||||
$routes->match(['get', 'post'], 'sync', 'ClaimReportDashboardController::sync');
|
||||
$routes->get('sync', 'ClaimReportDashboardController::sync');
|
||||
});
|
||||
|
||||
$routes->group('enrollment-collection-v1', static function ($routes) {
|
||||
@ -607,6 +607,8 @@ $routes->group("rfq", ["filter" => "authMVC"], function ($routes) {
|
||||
$routes->post("create", "LeadsController::createRFQ");
|
||||
$routes->post("savePolicyInfo", "LeadsController::savePolicyInfo");
|
||||
$routes->post("createQCR", "LeadsController::createQCR");
|
||||
$routes->post("generateGmcQcrPdf", "LeadsController::generateGmcQcrPdf");
|
||||
$routes->get("downloadGmcQcrPdf/(:num)", "LeadsController::downloadGmcQcrPdf/$1");
|
||||
$routes->get("list/(:any)", "LeadsController::viewRFQ/$1");
|
||||
$routes->get("nonEB","LeadsController::rfqNonEB");
|
||||
// Non-EB dedicated RFQ/QCR endpoints (do not alter existing ones)
|
||||
@ -865,7 +867,6 @@ $routes->group("employeeRest", ["filter" => ['GlobalPostFileUploadGuard', 'ratel
|
||||
$routes->get('all', 'ClaimReportDashboardController::all');
|
||||
$routes->get('debug', 'ClaimReportDashboardController::debug');
|
||||
$routes->get('debug/(:num)', 'ClaimReportDashboardController::debug/$1');
|
||||
$routes->match(['get', 'post'], 'sync', 'ClaimReportDashboardController::sync');
|
||||
});
|
||||
|
||||
$routes->group('enrollment-collection-v1', static function ($routes) {
|
||||
|
||||
@ -5,11 +5,12 @@ namespace App\Controllers;
|
||||
use App\Models\ClaimReportDashboardModel;
|
||||
use App\Models\ClaimsCollectionV2DashboardModel;
|
||||
use App\Models\ClaimDumpFileModel;
|
||||
use App\Libraries\ClaimReportSyncService;
|
||||
use CodeIgniter\API\ResponseTrait;
|
||||
|
||||
/**
|
||||
* Claims Collection dashboard API using claim_report (falls back to ticket_master).
|
||||
* Claims Collection dashboard API using claim_report.
|
||||
* ticket_master fallback is env-gated (CLAIM_REPORT_FALLBACK_TO_TICKET_MASTER)
|
||||
* and only applies when the policy TPA has no dump table.
|
||||
*/
|
||||
class ClaimReportDashboardController extends BaseController
|
||||
{
|
||||
@ -153,6 +154,376 @@ class ClaimReportDashboardController extends BaseController
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync TPA dump tables → claim_report.
|
||||
* Only rows where ticket_id IS NOT NULL and is_active = 1 are copied.
|
||||
* Idempotent: existing (client_policy_id, claim_number) rows are skipped.
|
||||
* No request parameters required.
|
||||
*
|
||||
* GET /util/claims-collection-report/sync
|
||||
*/
|
||||
public function sync()
|
||||
{
|
||||
@set_time_limit(0);
|
||||
@ini_set('max_execution_time', '0');
|
||||
|
||||
$db = db_connect();
|
||||
|
||||
if (!$db->tableExists('claim_report')) {
|
||||
return $this->respond([
|
||||
'status' => false,
|
||||
'message' => 'Table claim_report does not exist. Run migrations first.',
|
||||
], 500);
|
||||
}
|
||||
|
||||
// env_key => [dump_table, claim_number_col, dump_col => claim_report_col mapping]
|
||||
$tpaTableMap = [
|
||||
'VIDAL_PRIMARY_KEY_CONSTANT' => [
|
||||
'table' => 'claims_dump_vidal',
|
||||
'claim_col' => 'insurer_claim_number',
|
||||
'mapping' => [
|
||||
'insurer_claim_number' => 'claim_number',
|
||||
'employee_number' => 'emp_code',
|
||||
'date_of_admission' => 'doa',
|
||||
'date_of_discharge' => 'dod',
|
||||
'claim_amount' => 'claim_amount',
|
||||
'approved_amount' => 'approved_amount',
|
||||
'sum_insured' => 'si_amt',
|
||||
'claim_status' => 'tpa_claim_status',
|
||||
'hospital_name' => 'hospital_name',
|
||||
'hospital_address' => 'hospital_address',
|
||||
'hospital_city' => 'hospital_city',
|
||||
'hospital_state' => 'hospital_state',
|
||||
'hospital_pincode' => 'hospital_pin_code',
|
||||
'type_of_claim' => 'tpa_claim_type',
|
||||
'diagnosis' => 'tpa_ailments',
|
||||
'tpa_claim_number' => 'tpa_no',
|
||||
],
|
||||
],
|
||||
'ABHI_PRIMARY_KEY_CONSTANT' => [
|
||||
'table' => 'claims_dump_abhi',
|
||||
'claim_col' => 'abhi_claim_no',
|
||||
'mapping' => [
|
||||
'abhi_claim_no' => 'claim_number',
|
||||
'member_code' => 'emp_code',
|
||||
'doa' => 'doa',
|
||||
'dod' => 'dod',
|
||||
'intimation_date' => 'date_of_intimat',
|
||||
'claim_status' => 'tpa_claim_status',
|
||||
'claimed_amount' => 'claim_amount',
|
||||
'hospital_name' => 'hospital_name',
|
||||
'hospital_city' => 'hospital_city',
|
||||
'hospital_state' => 'hospital_state',
|
||||
'settled_date' => 'settled_date',
|
||||
'healthcard_id' => 'tpa_no',
|
||||
'claim_type' => 'tpa_claim_type',
|
||||
'diagnosis' => 'tpa_ailments',
|
||||
'abhi_amount_less_coins_current_month' => 'approved_amount',
|
||||
'patient_age' => 'age',
|
||||
'gender' => 'gender',
|
||||
'relation' => 'relation',
|
||||
],
|
||||
],
|
||||
'MEDI_ASSIST_PRIMARY_KEY_CONSTANT' => [
|
||||
'table' => 'claims_dump_medi_assist',
|
||||
'claim_col' => 'claim_id',
|
||||
'mapping' => [
|
||||
'claim_id' => 'claim_number',
|
||||
'pribenef_employee_code' => 'emp_code',
|
||||
'date_of_admission' => 'doa',
|
||||
'date_of_discharge' => 'dod',
|
||||
'intimation_date' => 'date_of_intimat',
|
||||
'settled_date' => 'settled_date',
|
||||
'processed_date' => 'approved_date',
|
||||
'claim_status' => 'tpa_claim_status',
|
||||
'claim_amount' => 'claim_amount',
|
||||
'claim_approved_amount' => 'approved_amount',
|
||||
'hospital_name' => 'hospital_name',
|
||||
'hospital_address' => 'hospital_address',
|
||||
'hospital_city' => 'hospital_city',
|
||||
'hospital_state' => 'hospital_state',
|
||||
'hospital_pincode' => 'hospital_pin_code',
|
||||
'claim_type' => 'tpa_claim_type',
|
||||
'primary_ailment_name' => 'tpa_ailments',
|
||||
'benef_sum_insured' => 'si_amt',
|
||||
'benef_gender' => 'gender',
|
||||
'benef_age' => 'age',
|
||||
'benef_relation' => 'relation',
|
||||
'incurred_amount' => 'incurred_amount',
|
||||
],
|
||||
],
|
||||
'FHPL_PRIMARY_KEY_CONSTANT' => [
|
||||
'table' => 'claims_dump_fhpl',
|
||||
'claim_col' => 'claim_id',
|
||||
'mapping' => [
|
||||
'claim_id' => 'claim_number',
|
||||
'employee_id' => 'emp_code',
|
||||
'admission_date' => 'doa',
|
||||
'discharge_date' => 'dod',
|
||||
'claim_received_date' => 'date_of_intimat',
|
||||
'claim_passed_date' => 'approved_date',
|
||||
'settled_date' => 'settled_date',
|
||||
'current_claim_status' => 'tpa_claim_status',
|
||||
'claim_amount' => 'claim_amount',
|
||||
'settled_amount' => 'approved_amount',
|
||||
'incurred_amount' => 'incurred_amount',
|
||||
'coverage_amount' => 'si_amt',
|
||||
'provider_name' => 'hospital_name',
|
||||
'provider_address' => 'hospital_address',
|
||||
'provider_state' => 'hospital_state',
|
||||
'provider_place' => 'hospital_city',
|
||||
'provider_pincode' => 'hospital_pin_code',
|
||||
'claim_type' => 'tpa_claim_type',
|
||||
'diagnosis' => 'tpa_ailments',
|
||||
'uhid_no' => 'tpa_no',
|
||||
'gender' => 'gender',
|
||||
'years' => 'age',
|
||||
'relationship' => 'relation',
|
||||
],
|
||||
],
|
||||
'R_CARE_PRIMARY_KEY_CONSTANT' => [
|
||||
'table' => 'claims_dump_reliance',
|
||||
'claim_col' => 'cl_inward_no',
|
||||
'mapping' => [
|
||||
'cl_inward_no' => 'claim_number',
|
||||
'employee_member_id' => 'emp_code',
|
||||
'doa_opd_treatment_from' => 'doa',
|
||||
'dod_opd_treatment_to' => 'dod',
|
||||
'approved_date' => 'approved_date',
|
||||
'cheque_neft_date' => 'settled_date',
|
||||
'claimed_amount' => 'claim_amount',
|
||||
'net_sanction_amount' => 'approved_amount',
|
||||
'final_status' => 'tpa_claim_status',
|
||||
'hospital_name' => 'hospital_name',
|
||||
'hospital_state' => 'hospital_state',
|
||||
'hospital_district' => 'hospital_city',
|
||||
'uhid' => 'tpa_no',
|
||||
'diagnosis' => 'tpa_ailments',
|
||||
'member_reimbursement_cl_type' => 'tpa_claim_type',
|
||||
'gender' => 'gender',
|
||||
'age' => 'age',
|
||||
'relation' => 'relation',
|
||||
'sum_insured' => 'si_amt',
|
||||
],
|
||||
],
|
||||
'ICICI_PRIMARY_KEY_CONSTANT' => [
|
||||
'table' => 'claims_dump_icici',
|
||||
'claim_col' => 'claim_number',
|
||||
'mapping' => [
|
||||
'claim_number' => 'claim_number',
|
||||
'employee_member_id' => 'emp_code',
|
||||
'doa' => 'doa',
|
||||
'dod' => 'dod',
|
||||
'payment_date' => 'settled_date',
|
||||
'claimed_amount' => 'claim_amount',
|
||||
'net_sanct_amt' => 'approved_amount',
|
||||
'updated_status' => 'tpa_claim_status',
|
||||
'hospital_name' => 'hospital_name',
|
||||
'hospital_city' => 'hospital_city',
|
||||
'hospital_state' => 'hospital_state',
|
||||
'type_of_claim' => 'tpa_claim_type',
|
||||
'diagnosis' => 'tpa_ailments',
|
||||
'uhid' => 'tpa_no',
|
||||
'sum_insured' => 'si_amt',
|
||||
'gender' => 'gender',
|
||||
'age' => 'age',
|
||||
'relation' => 'relation',
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
// Build tpa_id → config map from env
|
||||
$tpaMap = [];
|
||||
foreach ($tpaTableMap as $envKey => $cfg) {
|
||||
$tpaId = (int) env($envKey);
|
||||
if ($tpaId > 0) {
|
||||
$tpaMap[$tpaId] = $cfg;
|
||||
}
|
||||
}
|
||||
|
||||
$totalFound = 0;
|
||||
$totalInserted = 0;
|
||||
$totalSkipped = 0;
|
||||
$totalFailed = 0;
|
||||
$tpaResults = [];
|
||||
$now = date('Y-m-d H:i:s');
|
||||
|
||||
foreach ($tpaMap as $tpaId => $cfg) {
|
||||
$dumpTable = $cfg['table'];
|
||||
$claimCol = $cfg['claim_col'];
|
||||
$mapping = $cfg['mapping'];
|
||||
|
||||
if (!$db->tableExists($dumpTable)) {
|
||||
$tpaResults[] = [
|
||||
'tpa_id' => $tpaId,
|
||||
'table' => $dumpTable,
|
||||
'status' => 'skipped',
|
||||
'detail' => 'Dump table does not exist.',
|
||||
'found' => 0,
|
||||
'inserted' => 0,
|
||||
'skipped' => 0,
|
||||
'failed' => 0,
|
||||
];
|
||||
continue;
|
||||
}
|
||||
|
||||
// Fetch all active dump rows with a ticket_id (all columns)
|
||||
$dumpRows = $db->table($dumpTable)
|
||||
->where('is_active', 1)
|
||||
->where('ticket_id IS NOT NULL', null, false)
|
||||
->get()
|
||||
->getResultArray();
|
||||
|
||||
// Pre-load all related ticket_master rows in one query
|
||||
$ticketIds = array_filter(array_column($dumpRows, 'ticket_id'));
|
||||
$ticketsById = [];
|
||||
if (!empty($ticketIds)) {
|
||||
$ticketRows = $db->table('ticket_master')
|
||||
->whereIn('id', array_values(array_unique($ticketIds)))
|
||||
->get()
|
||||
->getResultArray();
|
||||
foreach ($ticketRows as $tm) {
|
||||
$ticketsById[(int) $tm['id']] = $tm;
|
||||
}
|
||||
}
|
||||
|
||||
$found = count($dumpRows);
|
||||
$inserted = 0;
|
||||
$skipped = 0;
|
||||
$failed = 0;
|
||||
$totalFound += $found;
|
||||
|
||||
foreach ($dumpRows as $row) {
|
||||
$claimNumber = trim((string) ($row[$claimCol] ?? ''));
|
||||
$clientPolicyId = (int) ($row['client_policy_id'] ?? 0);
|
||||
|
||||
if ($claimNumber === '' || $clientPolicyId <= 0) {
|
||||
$skipped++;
|
||||
$totalSkipped++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Fetch linked ticket_master row for emp_id / insured_emp_id
|
||||
$ticket = $ticketsById[(int) ($row['ticket_id'] ?? 0)] ?? null;
|
||||
|
||||
// Check if already exists in claim_report
|
||||
$existing = $db->table('claim_report')
|
||||
->where('client_policy_id', $clientPolicyId)
|
||||
->where('claim_number', $claimNumber)
|
||||
->get()
|
||||
->getRowArray();
|
||||
|
||||
// Base row metadata from dump; employee/system IDs always from ticket_master
|
||||
$reportRow = [
|
||||
'tpa_id' => $tpaId,
|
||||
'client_id' => $row['client_id'] ?? ($ticket['client_id'] ?? null),
|
||||
'file_id' => $row['file_id'] ?? ($ticket['file_id'] ?? null),
|
||||
'ticket_id' => $row['ticket_id'],
|
||||
'source_table' => $dumpTable,
|
||||
'source_row_id' => $row['id'],
|
||||
'is_active' => 1,
|
||||
'updated_at' => $now,
|
||||
'emp_id' => $ticket['emp_id'] ?? null,
|
||||
'insured_emp_id' => $ticket['insured_emp_id'] ?? null,
|
||||
'claim_status_id' => $ticket['claim_status_id'] ?? null,
|
||||
];
|
||||
|
||||
// 1) Fill ALL claim_report data fields from ticket_master first
|
||||
$ticketCols = [
|
||||
'emp_code', 'tpa_no',
|
||||
'gender', 'age', 'relation',
|
||||
'claim_amount', 'approved_amount', 'si_amt', 'incurred_amount',
|
||||
'tpa_claim_status', 'tpa_claim_type', 'tpa_ailments',
|
||||
'doa', 'dod', 'date_of_intimat', 'settled_date', 'approved_date',
|
||||
'hospital_name', 'hospital_city', 'hospital_state',
|
||||
'hospital_pin_code', 'hospital_address',
|
||||
'claim_dump_date',
|
||||
];
|
||||
if ($ticket) {
|
||||
foreach ($ticketCols as $col) {
|
||||
$ticketVal = $ticket[$col] ?? null;
|
||||
// ticket_master uses "relationship"; claim_report uses "relation"
|
||||
if ($col === 'relation' && ($ticketVal === null || $ticketVal === '')) {
|
||||
$ticketVal = $ticket['relationship'] ?? null;
|
||||
}
|
||||
if ($ticketVal !== null && $ticketVal !== '') {
|
||||
$reportRow[$col] = $ticketVal;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 2) Dump table fills only fields still null/empty after ticket_master
|
||||
foreach ($mapping as $dumpCol => $reportCol) {
|
||||
$current = $reportRow[$reportCol] ?? null;
|
||||
if ($current !== null && $current !== '') {
|
||||
continue;
|
||||
}
|
||||
if (array_key_exists($dumpCol, $row) && $row[$dumpCol] !== null && $row[$dumpCol] !== '') {
|
||||
$reportRow[$reportCol] = $row[$dumpCol];
|
||||
}
|
||||
}
|
||||
|
||||
// incurred_amount final fallback
|
||||
if (empty($reportRow['incurred_amount'])) {
|
||||
$reportRow['incurred_amount'] = $reportRow['approved_amount'] ?? $reportRow['claim_amount'] ?? null;
|
||||
}
|
||||
|
||||
if ($existing) {
|
||||
// Update existing row with full data
|
||||
$ok = $db->table('claim_report')
|
||||
->where('client_policy_id', $clientPolicyId)
|
||||
->where('claim_number', $claimNumber)
|
||||
->update($reportRow);
|
||||
if ($ok) {
|
||||
$skipped++;
|
||||
$totalSkipped++;
|
||||
} else {
|
||||
$failed++;
|
||||
$totalFailed++;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// New insert
|
||||
$reportRow['client_policy_id'] = $clientPolicyId;
|
||||
$reportRow['claim_number'] = $claimNumber;
|
||||
$reportRow['created_at'] = $now;
|
||||
|
||||
$ok = $db->table('claim_report')->insert($reportRow);
|
||||
|
||||
if ($ok) {
|
||||
$inserted++;
|
||||
$totalInserted++;
|
||||
} else {
|
||||
$failed++;
|
||||
$totalFailed++;
|
||||
}
|
||||
}
|
||||
|
||||
$tpaResults[] = [
|
||||
'tpa_id' => $tpaId,
|
||||
'table' => $dumpTable,
|
||||
'status' => 'done',
|
||||
'found' => $found,
|
||||
'inserted' => $inserted,
|
||||
'skipped' => $skipped,
|
||||
'failed' => $failed,
|
||||
];
|
||||
}
|
||||
|
||||
return $this->respond([
|
||||
'status' => $totalFailed === 0,
|
||||
'message' => $totalFailed === 0 ? 'Sync completed successfully.' : 'Sync completed with some failures.',
|
||||
'summary' => [
|
||||
'total_found' => $totalFound,
|
||||
'total_inserted' => $totalInserted,
|
||||
'total_skipped' => $totalSkipped,
|
||||
'total_failed' => $totalFailed,
|
||||
],
|
||||
'tpa_results' => $tpaResults,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Admin check only: raw JSON on screen (no dashboard UI).
|
||||
*/
|
||||
@ -183,95 +554,4 @@ class ClaimReportDashboardController extends BaseController
|
||||
->setBody($body);
|
||||
}
|
||||
|
||||
/**
|
||||
* Run claim report sync via URL (authMVC / JWT).
|
||||
* Uses ClaimReportSyncService directly (no spark/CLI).
|
||||
*
|
||||
* Query params (all optional):
|
||||
* client_policy / client_policy_id
|
||||
* tpa=icici|abhi|mediassist|fhpl|rcare|vidal|all
|
||||
* limit=500
|
||||
* phase1=true → TPA dump tables only (skip ticket_master)
|
||||
* ticket_only=1 → ticket_master only (ignored if phase1=true)
|
||||
*/
|
||||
public function sync()
|
||||
{
|
||||
@set_time_limit(0);
|
||||
@ini_set('max_execution_time', '0');
|
||||
|
||||
$policyId = $this->resolvePolicyId();
|
||||
$tpa = strtolower(trim((string) ($this->request->getGet('tpa') ?? $this->request->getPost('tpa') ?? 'all')));
|
||||
$limit = (int) ($this->request->getGet('limit') ?? $this->request->getPost('limit') ?? 500);
|
||||
$ticketOnly = $this->isTruthyParam('ticket_only');
|
||||
$phase1Only = $this->isTruthyParam('phase1');
|
||||
|
||||
$allowedTpa = ['all', 'vidal', 'abhi', 'mediassist', 'fhpl', 'rcare', 'icici'];
|
||||
if ($tpa === '' || ! in_array($tpa, $allowedTpa, true)) {
|
||||
return $this->respond([
|
||||
'status' => false,
|
||||
'message' => 'Invalid tpa. Allowed: ' . implode(', ', $allowedTpa),
|
||||
], 422);
|
||||
}
|
||||
|
||||
if ($limit <= 0) {
|
||||
$limit = 500;
|
||||
}
|
||||
if ($limit > 5000) {
|
||||
$limit = 5000;
|
||||
}
|
||||
|
||||
$startedAt = date('Y-m-d H:i:s');
|
||||
|
||||
try {
|
||||
$result = (new ClaimReportSyncService())->sync(
|
||||
$policyId,
|
||||
$tpa,
|
||||
$limit,
|
||||
$ticketOnly,
|
||||
$phase1Only
|
||||
);
|
||||
} catch (\Throwable $e) {
|
||||
return $this->respond([
|
||||
'status' => false,
|
||||
'message' => 'Sync failed: ' . $e->getMessage(),
|
||||
'policy_id' => $policyId > 0 ? $policyId : null,
|
||||
'tpa' => $tpa,
|
||||
'limit' => $limit,
|
||||
'phase1' => $phase1Only,
|
||||
'ticket_only' => $ticketOnly,
|
||||
'started_at' => $startedAt,
|
||||
], 500);
|
||||
}
|
||||
|
||||
$ok = ! empty($result['status']);
|
||||
|
||||
return $this->respond([
|
||||
'status' => $ok,
|
||||
'message' => $result['message'] ?? ($ok ? 'Sync completed.' : 'Sync failed.'),
|
||||
'policy_id' => $policyId > 0 ? $policyId : null,
|
||||
'tpa' => $tpa,
|
||||
'limit' => $limit,
|
||||
'phase1' => $phase1Only,
|
||||
'ticket_only' => $ticketOnly && ! $phase1Only,
|
||||
'started_at' => $startedAt,
|
||||
'finished_at' => date('Y-m-d H:i:s'),
|
||||
'summary' => $result['summary'] ?? null,
|
||||
'phases' => $result['phases'] ?? [],
|
||||
'tpa_results' => $result['tpa_results'] ?? [],
|
||||
'logs' => $result['logs'] ?? [],
|
||||
], $ok ? 200 : 500);
|
||||
}
|
||||
|
||||
/**
|
||||
* True when GET/POST param is 1/true/yes (case-insensitive).
|
||||
*/
|
||||
protected function isTruthyParam(string $name): bool
|
||||
{
|
||||
$raw = $this->request->getGet($name) ?? $this->request->getPost($name);
|
||||
if ($raw === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return in_array(strtolower(trim((string) $raw)), ['1', 'true', 'yes'], true);
|
||||
}
|
||||
}
|
||||
|
||||
@ -78,6 +78,8 @@ class DigitMotorController extends BaseController
|
||||
$data['title'] = 'Digit Motor Journey';
|
||||
$data['quote'] = null;
|
||||
$data['quote_id'] = null;
|
||||
$data['vehicle_master'] = null;
|
||||
$data['policyholder'] = [];
|
||||
|
||||
if ($quoteId) {
|
||||
$data['quote'] = $this->quoteModel->getDetail((int) $quoteId);
|
||||
@ -85,6 +87,17 @@ class DigitMotorController extends BaseController
|
||||
if (!$data['quote']) {
|
||||
return redirect()->to(base_url('digit-motor/list'))->with('error', 'Quote not found.');
|
||||
}
|
||||
$data['policyholder'] = is_array($data['quote']['policyholder_details'] ?? null)
|
||||
? $data['quote']['policyholder_details']
|
||||
: [];
|
||||
$vcode = $data['quote']['vehicle']['vehicle_maincode'] ?? '';
|
||||
if ($vcode !== '') {
|
||||
try {
|
||||
$data['vehicle_master'] = $this->vehicleMaster->findActive((string) $vcode);
|
||||
} catch (\Throwable $e) {
|
||||
$data['vehicle_master'] = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $this->loadLayout('digit_motor/journey', $data);
|
||||
@ -347,6 +360,9 @@ class DigitMotorController extends BaseController
|
||||
'message' => $e->getMessage(),
|
||||
'digit_code' => $e->getDigitCode(),
|
||||
'infra' => $e->isInfraError(),
|
||||
'data' => is_array($e->getResponseBody())
|
||||
? ($e->getResponseBody()['_nhance_shell'] ?? null)
|
||||
: null,
|
||||
], min($http, 599));
|
||||
} catch (\Throwable $e) {
|
||||
log_message('error', 'DigitMotor unexpected error: ' . $e->getMessage());
|
||||
|
||||
@ -238,14 +238,15 @@ class ICICILombardController extends AdminController
|
||||
];
|
||||
|
||||
$response = call_third_party_api($url, $method, $headers, $body);
|
||||
|
||||
log_message('error', 'ICICI - generateAuthToken API URL: ' . json_encode(["url" => $url, "method" => $method, "headers" => $headers, "body" => $body], JSON_PRETTY_PRINT));
|
||||
log_message('error', 'ICICI - generateAuthToken API response: ' . json_encode($response, JSON_PRETTY_PRINT));
|
||||
|
||||
if($response['status'] != true){
|
||||
return $this->response->setJSON([
|
||||
return [
|
||||
'status' => false,
|
||||
'message' => 'Token generation failed.',
|
||||
'data' => $response
|
||||
]);
|
||||
];
|
||||
}
|
||||
|
||||
// Debug removed: return token response to caller.
|
||||
|
||||
@ -38,6 +38,7 @@ use App\Helpers\MailHelper;
|
||||
use App\Helpers\ExcelMergeHelper;
|
||||
use App\Helpers\ExcelSanitizeHelper;
|
||||
use App\Libraries\GoogleSheetLib;
|
||||
use App\Libraries\GmcQcrPdfService;
|
||||
|
||||
class LeadsController extends BaseController
|
||||
{
|
||||
@ -1840,6 +1841,59 @@ class LeadsController extends BaseController
|
||||
return $this->respond(['status' => false, 'id' => $result, 'message' => "Failed to create QCR", 'data' => $data], 200);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate GMC Quote Comparison Report PDF from saved QCR data.
|
||||
* POST /rfq/generateGmcQcrPdf body: lead_id
|
||||
*/
|
||||
public function generateGmcQcrPdf()
|
||||
{
|
||||
$leadId = (int) ($this->request->getPost('lead_id') ?? $this->request->getGet('lead_id') ?? 0);
|
||||
if ($leadId <= 0) {
|
||||
return $this->respond(['status' => false, 'message' => 'lead_id is required'], 200);
|
||||
}
|
||||
|
||||
$proposals = $this->request->getPost('proposals');
|
||||
if (! is_array($proposals)) {
|
||||
$proposals = null;
|
||||
}
|
||||
|
||||
try {
|
||||
$service = new GmcQcrPdfService($this->RFQModel, $this->leadsModel, $this->clientModel);
|
||||
$result = $service->generate($leadId, $proposals);
|
||||
return $this->respond($result, 200);
|
||||
} catch (\Throwable $e) {
|
||||
$this->myLogger->logme('error', 'generateGmcQcrPdf | ' . $e->getMessage());
|
||||
return $this->respond(['status' => false, 'message' => 'Failed to generate PDF: ' . $e->getMessage()], 200);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Download last generated GMC QCR PDF.
|
||||
* GET /rfq/downloadGmcQcrPdf/(:num)
|
||||
*/
|
||||
public function downloadGmcQcrPdf($leadId = null)
|
||||
{
|
||||
$leadId = (int) ($leadId ?? $this->request->getGet('lead_id') ?? 0);
|
||||
if ($leadId <= 0) {
|
||||
return $this->respond(['status' => false, 'message' => 'lead_id is required'], 400);
|
||||
}
|
||||
|
||||
try {
|
||||
$service = new GmcQcrPdfService($this->RFQModel, $this->leadsModel, $this->clientModel);
|
||||
$result = $service->resolveDownload($leadId);
|
||||
if (! ($result['status'] ?? false)) {
|
||||
return $this->respond($result, 404);
|
||||
}
|
||||
|
||||
return $this->response
|
||||
->download($result['file_path'], null)
|
||||
->setFileName($result['download_name']);
|
||||
} catch (\Throwable $e) {
|
||||
$this->myLogger->logme('error', 'downloadGmcQcrPdf | ' . $e->getMessage());
|
||||
return $this->respond(['status' => false, 'message' => 'Failed to download PDF: ' . $e->getMessage()], 500);
|
||||
}
|
||||
}
|
||||
|
||||
public function reorderProposalsByInsurerTotal(array $data): array
|
||||
{
|
||||
|
||||
|
||||
@ -27,6 +27,7 @@ CREATE TABLE IF NOT EXISTS motor_quote (
|
||||
end_date DATE DEFAULT NULL,
|
||||
pincode VARCHAR(6) NOT NULL,
|
||||
coverage_details JSON DEFAULT NULL,
|
||||
policyholder_details JSON DEFAULT NULL,
|
||||
premium DECIMAL(12,2) DEFAULT NULL,
|
||||
idv DECIMAL(12,2) DEFAULT NULL,
|
||||
status VARCHAR(20) NOT NULL DEFAULT 'DRAFT',
|
||||
|
||||
@ -1,444 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Libraries;
|
||||
|
||||
use App\Libraries\TpaClaimsImportFactory;
|
||||
use InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* Sync claim_report from TPA dump tables (+ optional ticket_master).
|
||||
* Safe to call from HTTP or spark (no CLI constants required).
|
||||
*/
|
||||
class ClaimReportSyncService
|
||||
{
|
||||
/**
|
||||
* @return array<string, array{env:string,table:string}>
|
||||
*/
|
||||
public function tpaConfigs(): array
|
||||
{
|
||||
return [
|
||||
'vidal' => [
|
||||
'env' => 'VIDAL_PRIMARY_KEY_CONSTANT',
|
||||
'table' => 'claims_dump_vidal',
|
||||
],
|
||||
'abhi' => [
|
||||
'env' => 'ABHI_PRIMARY_KEY_CONSTANT',
|
||||
'table' => 'claims_dump_abhi',
|
||||
],
|
||||
'mediassist' => [
|
||||
'env' => 'MEDI_ASSIST_PRIMARY_KEY_CONSTANT',
|
||||
'table' => 'claims_dump_medi_assist',
|
||||
],
|
||||
'fhpl' => [
|
||||
'env' => 'FHPL_PRIMARY_KEY_CONSTANT',
|
||||
'table' => 'claims_dump_fhpl',
|
||||
],
|
||||
'rcare' => [
|
||||
'env' => 'R_CARE_PRIMARY_KEY_CONSTANT',
|
||||
'table' => 'claims_dump_reliance',
|
||||
],
|
||||
'icici' => [
|
||||
'env' => 'ICICI_PRIMARY_KEY_CONSTANT',
|
||||
'table' => 'claims_dump_icici',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{
|
||||
* status: bool,
|
||||
* message: string,
|
||||
* summary: array{mapped:int,skipped:int,errors:int,message:?string},
|
||||
* phases: list<string>,
|
||||
* tpa_results: list<array<string,mixed>>,
|
||||
* logs: list<string>
|
||||
* }
|
||||
*/
|
||||
public function sync(
|
||||
int $policyId = 0,
|
||||
string $tpa = 'all',
|
||||
int $limit = 500,
|
||||
bool $ticketOnly = false,
|
||||
bool $phase1Only = false
|
||||
): array {
|
||||
$logs = [];
|
||||
$phases = [];
|
||||
$tpaResults = [];
|
||||
$db = db_connect();
|
||||
|
||||
$log = static function (string $line) use (&$logs): void {
|
||||
$logs[] = $line;
|
||||
};
|
||||
|
||||
if (!$db->tableExists('claim_report')) {
|
||||
$msg = 'Table claim_report does not exist. Run: php spark migrate';
|
||||
$log($msg);
|
||||
return $this->result(false, $msg, 0, 0, 1, $phases, $tpaResults, $logs);
|
||||
}
|
||||
|
||||
// phase1Only and ticketOnly are mutually exclusive; phase1 wins.
|
||||
if ($phase1Only) {
|
||||
$ticketOnly = false;
|
||||
}
|
||||
|
||||
$tpa = strtolower(trim($tpa));
|
||||
if ($tpa === '') {
|
||||
$tpa = 'all';
|
||||
}
|
||||
if ($limit <= 0) {
|
||||
$limit = 500;
|
||||
}
|
||||
|
||||
$totalMapped = 0;
|
||||
$totalSkipped = 0;
|
||||
$errors = 0;
|
||||
|
||||
if (!$ticketOnly) {
|
||||
$phase1 = 'Phase 1: sync from TPA dump tables (linked ticket_id rows)...';
|
||||
$phases[] = $phase1;
|
||||
$log($phase1);
|
||||
|
||||
$configs = $this->tpaConfigs();
|
||||
if ($tpa !== 'all') {
|
||||
if (!isset($configs[$tpa])) {
|
||||
$msg = 'Unknown tpa=' . $tpa . '. Use: ' . implode('|', array_keys($configs)) . '|all';
|
||||
$log($msg);
|
||||
return $this->result(false, $msg, 0, 0, 1, $phases, $tpaResults, $logs);
|
||||
}
|
||||
$configs = [$tpa => $configs[$tpa]];
|
||||
}
|
||||
|
||||
foreach ($configs as $key => $cfg) {
|
||||
$tpaId = (int) env($cfg['env']);
|
||||
$table = $cfg['table'];
|
||||
|
||||
if ($tpaId <= 0) {
|
||||
$detail = "{$key}: env {$cfg['env']} not set";
|
||||
$log('[SKIP] ' . $detail);
|
||||
$tpaResults[] = ['tpa' => $key, 'mapped' => 0, 'status' => 'skipped', 'detail' => $detail];
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$db->tableExists($table)) {
|
||||
$detail = "{$key}: table {$table} missing";
|
||||
$log('[SKIP] ' . $detail);
|
||||
$tpaResults[] = ['tpa' => $key, 'mapped' => 0, 'status' => 'skipped', 'detail' => $detail];
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
$service = TpaClaimsImportFactory::make($tpaId);
|
||||
} catch (InvalidArgumentException $e) {
|
||||
$detail = $key . ': ' . $e->getMessage();
|
||||
$log('[SKIP] ' . $detail);
|
||||
$tpaResults[] = ['tpa' => $key, 'mapped' => 0, 'status' => 'skipped', 'detail' => $detail];
|
||||
continue;
|
||||
}
|
||||
|
||||
$offset = 0;
|
||||
$tpaMapped = 0;
|
||||
|
||||
while (true) {
|
||||
$builder = $db->table($table)
|
||||
->select('id, file_id, ticket_id, client_policy_id')
|
||||
->where('is_active', 1)
|
||||
->where('ticket_id IS NOT NULL', null, false)
|
||||
->orderBy('id', 'ASC')
|
||||
->limit($limit, $offset);
|
||||
|
||||
if ($policyId > 0) {
|
||||
$builder->where('client_policy_id', $policyId);
|
||||
}
|
||||
|
||||
$dumpRows = $builder->get()->getResultArray();
|
||||
if ($dumpRows === []) {
|
||||
break;
|
||||
}
|
||||
|
||||
$byFile = [];
|
||||
foreach ($dumpRows as $row) {
|
||||
$fileId = (int) ($row['file_id'] ?? 0);
|
||||
$dumpId = (int) ($row['id'] ?? 0);
|
||||
if ($fileId <= 0 || $dumpId <= 0) {
|
||||
$totalSkipped++;
|
||||
continue;
|
||||
}
|
||||
$byFile[$fileId][] = $dumpId;
|
||||
}
|
||||
|
||||
foreach ($byFile as $fileId => $dumpIds) {
|
||||
$result = $service->backfillClaimReportByDumpIds((int) $fileId, $dumpIds);
|
||||
if (!$result['status']) {
|
||||
$log("[FAIL] {$key} file_id={$fileId} upsert failed");
|
||||
$errors++;
|
||||
continue;
|
||||
}
|
||||
$tpaMapped += (int) $result['count'];
|
||||
$totalMapped += (int) $result['count'];
|
||||
}
|
||||
|
||||
$offset += count($dumpRows);
|
||||
$log(" {$key}: processed {$offset} dump rows (mapped so far {$tpaMapped})");
|
||||
|
||||
if (count($dumpRows) < $limit) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$done = "[DONE] {$key} mapped≈{$tpaMapped}";
|
||||
$log($done);
|
||||
$tpaResults[] = [
|
||||
'tpa' => $key,
|
||||
'mapped' => $tpaMapped,
|
||||
'status' => 'done',
|
||||
'detail' => $done,
|
||||
];
|
||||
}
|
||||
} else {
|
||||
$phase = 'Skipping dump tables (ticket-only).';
|
||||
$phases[] = $phase;
|
||||
$log($phase);
|
||||
}
|
||||
|
||||
if ($phase1Only) {
|
||||
$skip = 'Phase 2 skipped (phase1=true — TPA dump tables only).';
|
||||
$phases[] = $skip;
|
||||
$log($skip);
|
||||
$doneMsg = "Done. dump mapped≈{$totalMapped}, skipped={$totalSkipped}, errors={$errors}";
|
||||
$log($doneMsg);
|
||||
|
||||
return $this->result(
|
||||
$errors === 0,
|
||||
$errors === 0 ? 'Phase 1 sync completed.' : 'Phase 1 sync completed with errors.',
|
||||
$totalMapped,
|
||||
$totalSkipped,
|
||||
$errors,
|
||||
$phases,
|
||||
$tpaResults,
|
||||
$logs,
|
||||
$doneMsg
|
||||
);
|
||||
}
|
||||
|
||||
$phase2 = 'Phase 2: fill gaps from ticket_master dump-sourced claims...';
|
||||
$phases[] = $phase2;
|
||||
$log($phase2);
|
||||
|
||||
$tmResult = $this->syncFromTicketMaster($db, $policyId, $limit, $log);
|
||||
if ($tmResult === false) {
|
||||
return $this->result(false, 'ticket_master sync failed', $totalMapped, $totalSkipped, $errors + 1, $phases, $tpaResults, $logs);
|
||||
}
|
||||
|
||||
$totalMapped += $tmResult['mapped'];
|
||||
$totalSkipped += $tmResult['skipped'];
|
||||
|
||||
$doneMsg = "Done. dump+ticket mapped≈{$totalMapped}, skipped={$totalSkipped}, errors={$errors}";
|
||||
$log($doneMsg);
|
||||
|
||||
return $this->result(
|
||||
$errors === 0,
|
||||
$errors === 0 ? 'Sync completed.' : 'Sync completed with errors. See summary.',
|
||||
$totalMapped,
|
||||
$totalSkipped,
|
||||
$errors,
|
||||
$phases,
|
||||
$tpaResults,
|
||||
$logs,
|
||||
$doneMsg
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param callable(string):void $log
|
||||
* @return array{mapped:int,skipped:int}|false
|
||||
*/
|
||||
private function syncFromTicketMaster($db, int $policyId, int $limit, callable $log)
|
||||
{
|
||||
if (!$db->tableExists('ticket_master')) {
|
||||
$log('ticket_master missing');
|
||||
return false;
|
||||
}
|
||||
|
||||
$offset = 0;
|
||||
$mapped = 0;
|
||||
$skipped = 0;
|
||||
$now = date('Y-m-d H:i:s');
|
||||
|
||||
while (true) {
|
||||
$builder = $db->table('ticket_master')
|
||||
->where('is_active', 1)
|
||||
->groupStart()
|
||||
->where('claim_dump_ref_id IS NOT NULL', null, false)
|
||||
->orWhere('file_id IS NOT NULL', null, false)
|
||||
->groupEnd()
|
||||
->where("claim_number IS NOT NULL AND TRIM(claim_number) != ''", null, false)
|
||||
->orderBy('id', 'ASC')
|
||||
->limit($limit, $offset);
|
||||
|
||||
if ($policyId > 0) {
|
||||
$builder->where('client_policy_id', $policyId);
|
||||
}
|
||||
|
||||
$tickets = $builder->get()->getResultArray();
|
||||
if ($tickets === []) {
|
||||
break;
|
||||
}
|
||||
|
||||
$rows = [];
|
||||
foreach ($tickets as $tm) {
|
||||
$claimNumber = trim((string) ($tm['claim_number'] ?? ''));
|
||||
$clientPolicyId = (int) ($tm['client_policy_id'] ?? 0);
|
||||
if ($claimNumber === '' || $clientPolicyId <= 0) {
|
||||
$skipped++;
|
||||
continue;
|
||||
}
|
||||
|
||||
$sourceTable = null;
|
||||
$tpaId = (int) ($tm['tpa_id'] ?? 0);
|
||||
foreach ($this->tpaConfigs() as $cfg) {
|
||||
if ($tpaId === (int) env($cfg['env'])) {
|
||||
$sourceTable = $cfg['table'];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$rows[] = [
|
||||
'tpa_id' => $tm['tpa_id'] ?? null,
|
||||
'client_id' => $tm['client_id'] ?? null,
|
||||
'client_policy_id' => $clientPolicyId,
|
||||
'file_id' => $tm['file_id'] ?? null,
|
||||
'ticket_id' => $tm['id'] ?? null,
|
||||
'source_table' => $sourceTable,
|
||||
'source_row_id' => $tm['claim_dump_ref_id'] ?? null,
|
||||
'claim_number' => $claimNumber,
|
||||
'emp_code' => $tm['emp_code'] ?? null,
|
||||
'tpa_no' => $tm['tpa_no'] ?? null,
|
||||
'emp_id' => $tm['emp_id'] ?? null,
|
||||
'insured_emp_id' => $tm['insured_emp_id'] ?? null,
|
||||
'claim_amount' => $tm['claim_amount'] ?? null,
|
||||
'approved_amount' => $tm['approved_amount'] ?? null,
|
||||
'incurred_amount' => $tm['approved_amount'] ?? $tm['claim_amount'] ?? null,
|
||||
'si_amt' => $tm['si_amt'] ?? null,
|
||||
'tpa_claim_status' => $tm['tpa_claim_status'] ?? null,
|
||||
'claim_status_id' => $tm['claim_status_id'] ?? null,
|
||||
'tpa_claim_type' => $tm['tpa_claim_type'] ?? null,
|
||||
'tpa_ailments' => $tm['tpa_ailments'] ?? null,
|
||||
'doa' => $tm['doa'] ?? null,
|
||||
'dod' => $tm['dod'] ?? null,
|
||||
'date_of_intimat' => $tm['date_of_intimat'] ?? null,
|
||||
'settled_date' => $tm['settled_date'] ?? null,
|
||||
'approved_date' => $tm['approved_date'] ?? null,
|
||||
'claim_dump_date' => $tm['claim_dump_date'] ?? null,
|
||||
'hospital_name' => $tm['hospital_name'] ?? null,
|
||||
'hospital_city' => $tm['hospital_city'] ?? null,
|
||||
'hospital_state' => $tm['hospital_state'] ?? null,
|
||||
'hospital_pin_code' => $tm['hospital_pin_code'] ?? null,
|
||||
'hospital_address' => $tm['hospital_address'] ?? null,
|
||||
'gender' => null,
|
||||
'age' => null,
|
||||
'relation' => $tm['relationship'] ?? null,
|
||||
'is_active' => 1,
|
||||
'created_at' => $now,
|
||||
'updated_at' => $now,
|
||||
];
|
||||
}
|
||||
|
||||
if ($rows !== [] && $this->upsertRows($db, $rows) === false) {
|
||||
$log('ticket_master upsert failed at offset ' . $offset);
|
||||
return false;
|
||||
}
|
||||
|
||||
$mapped += count($rows);
|
||||
$offset += count($tickets);
|
||||
$log(" ticket_master: processed {$offset} rows (mapped {$mapped})");
|
||||
|
||||
if (count($tickets) < $limit) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return ['mapped' => $mapped, 'skipped' => $skipped];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param list<array<string, mixed>> $rows
|
||||
*/
|
||||
private function upsertRows($db, array $rows): bool
|
||||
{
|
||||
$columns = [
|
||||
'tpa_id', 'client_id', 'client_policy_id', 'file_id', 'ticket_id',
|
||||
'source_table', 'source_row_id', 'claim_number', 'emp_code', 'tpa_no',
|
||||
'emp_id', 'insured_emp_id', 'claim_amount', 'approved_amount', 'incurred_amount',
|
||||
'si_amt', 'tpa_claim_status', 'claim_status_id', 'tpa_claim_type', 'tpa_ailments',
|
||||
'doa', 'dod', 'date_of_intimat', 'settled_date', 'approved_date', 'claim_dump_date',
|
||||
'hospital_name', 'hospital_city', 'hospital_state', 'hospital_pin_code', 'hospital_address',
|
||||
'gender', 'age', 'relation', 'is_active', 'created_at', 'updated_at',
|
||||
];
|
||||
|
||||
$updateCols = array_values(array_diff($columns, ['client_policy_id', 'claim_number', 'created_at']));
|
||||
|
||||
foreach (array_chunk($rows, 100) as $chunk) {
|
||||
$placeholders = [];
|
||||
$binds = [];
|
||||
foreach ($chunk as $row) {
|
||||
$rowPlaceholders = [];
|
||||
foreach ($columns as $col) {
|
||||
$rowPlaceholders[] = '?';
|
||||
$binds[] = $row[$col] ?? null;
|
||||
}
|
||||
$placeholders[] = '(' . implode(', ', $rowPlaceholders) . ')';
|
||||
}
|
||||
|
||||
$updates = [];
|
||||
foreach ($updateCols as $col) {
|
||||
if (in_array($col, ['gender', 'age', 'relation', 'approved_amount', 'incurred_amount', 'tpa_ailments', 'tpa_claim_type', 'source_table', 'source_row_id'], true)) {
|
||||
$updates[] = '`' . $col . '` = COALESCE(VALUES(`' . $col . '`), `' . $col . '`)';
|
||||
} else {
|
||||
$updates[] = '`' . $col . '` = VALUES(`' . $col . '`)';
|
||||
}
|
||||
}
|
||||
|
||||
$sql = 'INSERT INTO `claim_report` (`' . implode('`, `', $columns) . '`) VALUES '
|
||||
. implode(', ', $placeholders)
|
||||
. ' ON DUPLICATE KEY UPDATE ' . implode(', ', $updates);
|
||||
|
||||
if ($db->query($sql, $binds) === false) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param list<string> $phases
|
||||
* @param list<array<string,mixed>> $tpaResults
|
||||
* @param list<string> $logs
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
private function result(
|
||||
bool $status,
|
||||
string $message,
|
||||
int $mapped,
|
||||
int $skipped,
|
||||
int $errors,
|
||||
array $phases,
|
||||
array $tpaResults,
|
||||
array $logs,
|
||||
?string $summaryMessage = null
|
||||
): array {
|
||||
return [
|
||||
'status' => $status,
|
||||
'message' => $message,
|
||||
'summary' => [
|
||||
'mapped' => $mapped,
|
||||
'skipped' => $skipped,
|
||||
'errors' => $errors,
|
||||
'message' => $summaryMessage,
|
||||
],
|
||||
'phases' => $phases,
|
||||
'tpa_results' => $tpaResults,
|
||||
'logs' => $logs,
|
||||
];
|
||||
}
|
||||
}
|
||||
@ -41,17 +41,54 @@ class DigitExecutorService
|
||||
*/
|
||||
public function quickQuote(array $input): array
|
||||
{
|
||||
$enquiryId = $input['enquiry_id'] ?? ('NH' . date('ymdHis') . random_int(100, 999));
|
||||
$payload = $this->buildQuickQuotePayload($input, $enquiryId);
|
||||
$quoteIdIn = (int) ($input['quote_id'] ?? 0);
|
||||
$existing = $quoteIdIn > 0 ? $this->quoteModel->find($quoteIdIn) : null;
|
||||
|
||||
// Prefer existing enquiry when updating; only mint a new one for brand-new journeys.
|
||||
$enquiryId = null;
|
||||
if ($existing && !empty($existing['enquiry_id'])) {
|
||||
$enquiryId = (string) $existing['enquiry_id'];
|
||||
} elseif (!empty($input['enquiry_id']) && $input['enquiry_id'] !== '— new —') {
|
||||
$enquiryId = (string) $input['enquiry_id'];
|
||||
if (!$existing) {
|
||||
$existing = $this->quoteModel->where('enquiry_id', $enquiryId)->first();
|
||||
}
|
||||
}
|
||||
if ($enquiryId === null || $enquiryId === '') {
|
||||
$enquiryId = 'NH' . date('ymdHis') . random_int(100, 999);
|
||||
}
|
||||
|
||||
$input['quote_id'] = $existing ? (int) $existing['id'] : $quoteIdIn;
|
||||
$input['enquiry_id'] = $enquiryId;
|
||||
|
||||
$payload = $this->buildQuickQuotePayload($input, $enquiryId);
|
||||
$quoteId = $this->persistQuoteShell($input, $enquiryId, 'DRAFT');
|
||||
|
||||
$response = $this->api->post(
|
||||
$this->config->executorPath,
|
||||
$payload,
|
||||
$this->config->integrationIds['quickQuote'],
|
||||
$quoteId
|
||||
);
|
||||
try {
|
||||
$response = $this->api->post(
|
||||
$this->config->executorPath,
|
||||
$payload,
|
||||
$this->config->integrationIds['quickQuote'],
|
||||
$quoteId
|
||||
);
|
||||
} catch (DigitApiException $e) {
|
||||
// Keep shell continuity on Digit failures so retries do not spawn duplicates.
|
||||
throw new DigitApiException(
|
||||
$e->getMessage(),
|
||||
$e->getDigitCode(),
|
||||
$e->getHttpStatus(),
|
||||
[
|
||||
'_nhance_shell' => [
|
||||
'quote_id' => $quoteId,
|
||||
'enquiry_id' => $enquiryId,
|
||||
],
|
||||
'digit' => $e->getResponseBody(),
|
||||
],
|
||||
$e,
|
||||
$e->getRequestUrl(),
|
||||
$e->getRequestBody()
|
||||
);
|
||||
}
|
||||
|
||||
$premium = $this->pickNumber($response, ['grossPremium', 'premium', 'netPremium', 'totalPremium']);
|
||||
$idv = $this->pickNumber($response, ['idv', 'vehicleIDV', 'insuredDeclaredValue']);
|
||||
@ -61,10 +98,13 @@ class DigitExecutorService
|
||||
|
||||
$vehicleIdv = $response['vehicle']['vehicleIDV'] ?? $response['vehicleIDV'] ?? [];
|
||||
|
||||
// Re-quote resets create/KYC progress so journey stays consistent.
|
||||
$this->quoteModel->update($quoteId, [
|
||||
'premium' => $premium,
|
||||
'idv' => $idv,
|
||||
'status' => 'QUOTED',
|
||||
'premium' => $premium,
|
||||
'idv' => $idv,
|
||||
'status' => 'QUOTED',
|
||||
'quote_number' => null,
|
||||
'application_id' => null,
|
||||
'coverage_details' => json_encode($input['coverages'] ?? $payload['contract']['coverages'] ?? []),
|
||||
]);
|
||||
|
||||
@ -123,6 +163,21 @@ class DigitExecutorService
|
||||
$input['vehicle_identification_number'] = $vin;
|
||||
$input['engine_number'] = $engine;
|
||||
|
||||
$policyholder = [
|
||||
'first_name' => $input['first_name'] ?? null,
|
||||
'last_name' => $input['last_name'] ?? null,
|
||||
'mobile' => $input['mobile'] ?? null,
|
||||
'email' => $input['email'] ?? null,
|
||||
'pan' => $input['pan'] ?? null,
|
||||
'dob' => $input['dob'] ?? null,
|
||||
'address' => $input['address'] ?? null,
|
||||
];
|
||||
// Persist locally before Digit call so Back/reload keeps form data even if API fails.
|
||||
$this->quoteModel->update($quoteId, [
|
||||
'policyholder_details' => json_encode($policyholder),
|
||||
'coverage_details' => json_encode($input['coverages'] ?? ($detail['coverage_details'] ?? [])),
|
||||
]);
|
||||
|
||||
$payload = $this->buildCreateQuotePayload($detail, $input);
|
||||
|
||||
$response = $this->api->post(
|
||||
@ -137,12 +192,13 @@ class DigitExecutorService
|
||||
$premium = $this->pickNumber($response, ['grossPremium', 'premium', 'netPremium', 'totalPremium']) ?? $detail['premium'];
|
||||
|
||||
$this->quoteModel->update($quoteId, [
|
||||
'quote_number' => $quoteNumber,
|
||||
'application_id' => $applicationId,
|
||||
'premium' => $premium,
|
||||
'start_date' => $input['start_date'] ?? $detail['start_date'],
|
||||
'end_date' => $input['end_date'] ?? $detail['end_date'],
|
||||
'status' => 'CREATED',
|
||||
'quote_number' => $quoteNumber,
|
||||
'application_id' => $applicationId,
|
||||
'premium' => $premium,
|
||||
'start_date' => $input['start_date'] ?? $detail['start_date'],
|
||||
'end_date' => $input['end_date'] ?? $detail['end_date'],
|
||||
'status' => 'CREATED',
|
||||
'policyholder_details' => json_encode($policyholder),
|
||||
]);
|
||||
|
||||
return [
|
||||
@ -472,10 +528,18 @@ class DigitExecutorService
|
||||
|
||||
protected function persistQuoteShell(array $input, string $enquiryId, string $status): int
|
||||
{
|
||||
$existing = $this->quoteModel->where('enquiry_id', $enquiryId)->first();
|
||||
$quoteIdIn = (int) ($input['quote_id'] ?? 0);
|
||||
$existing = null;
|
||||
|
||||
if ($quoteIdIn > 0) {
|
||||
$existing = $this->quoteModel->find($quoteIdIn);
|
||||
}
|
||||
if (!$existing && $enquiryId !== '') {
|
||||
$existing = $this->quoteModel->where('enquiry_id', $enquiryId)->first();
|
||||
}
|
||||
|
||||
$quoteData = [
|
||||
'enquiry_id' => $enquiryId,
|
||||
'enquiry_id' => $existing['enquiry_id'] ?? $enquiryId,
|
||||
'policy_holder_type' => $input['policy_holder_type'] ?? 'INDIVIDUAL',
|
||||
'insurance_product_code' => (string) ($input['insurance_product_code'] ?? '20101'),
|
||||
'sub_insurance_product_code' => (string) ($input['sub_insurance_product_code'] ?? 'PB'),
|
||||
@ -487,9 +551,13 @@ class DigitExecutorService
|
||||
'end_date' => $input['end_date'] ?? null,
|
||||
'pincode' => (string) ($input['pincode'] ?? ''),
|
||||
'coverage_details' => json_encode($input['coverages'] ?? []),
|
||||
'status' => $status,
|
||||
];
|
||||
|
||||
// Only stamp DRAFT on brand-new rows; existing rows keep status until QQ success.
|
||||
if (!$existing) {
|
||||
$quoteData['status'] = $status;
|
||||
}
|
||||
|
||||
if ($existing) {
|
||||
$quoteId = (int) $existing['id'];
|
||||
$this->quoteModel->update($quoteId, $quoteData);
|
||||
|
||||
582
app/Libraries/GmcQcrPdfService.php
Normal file
582
app/Libraries/GmcQcrPdfService.php
Normal file
@ -0,0 +1,582 @@
|
||||
<?php
|
||||
|
||||
namespace App\Libraries;
|
||||
|
||||
use App\Models\ClientModel;
|
||||
use App\Models\LeadsModel;
|
||||
use App\Models\RFQModel;
|
||||
use Mpdf\Mpdf;
|
||||
use Mpdf\Output\Destination;
|
||||
|
||||
/**
|
||||
* Builds the GMC Quote Comparison Report PDF:
|
||||
* cover (client) + static front + dynamic QCR pages + static back.
|
||||
*/
|
||||
class GmcQcrPdfService
|
||||
{
|
||||
public const GMC_POLICY_TYPE_IDS = [2, 3, 4, 5];
|
||||
|
||||
private const PAGE_W_MM = 338.6667; // 960 pt
|
||||
private const PAGE_H_MM = 190.5; // 540 pt
|
||||
private const ROWS_PER_PAGE = 18;
|
||||
private const COLS_PER_PAGE = 5;
|
||||
|
||||
private RFQModel $rfqModel;
|
||||
private LeadsModel $leadsModel;
|
||||
private ClientModel $clientModel;
|
||||
|
||||
public function __construct(?RFQModel $rfqModel = null, ?LeadsModel $leadsModel = null, ?ClientModel $clientModel = null)
|
||||
{
|
||||
$this->rfqModel = $rfqModel ?? new RFQModel();
|
||||
$this->leadsModel = $leadsModel ?? new LeadsModel();
|
||||
$this->clientModel = $clientModel ?? new ClientModel();
|
||||
}
|
||||
|
||||
public static function isGmc(int $policyTypeId): bool
|
||||
{
|
||||
return in_array($policyTypeId, self::GMC_POLICY_TYPE_IDS, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{status:bool,message:string,file_name?:string,file_path?:string,download_name?:string}
|
||||
*/
|
||||
/**
|
||||
* @param int $leadId
|
||||
* @param array|null $selectedProposals If provided, only include these proposal names
|
||||
*/
|
||||
public function generate(int $leadId, ?array $selectedProposals = null): array
|
||||
{
|
||||
$lead = $this->leadsModel
|
||||
->select('leads.*, policy_type.policy_type, policy_type.long_name')
|
||||
->join('policy_type', 'leads.policy_type_id = policy_type.id', 'left')
|
||||
->where('leads.id', $leadId)
|
||||
->where('leads.is_active', 1)
|
||||
->first();
|
||||
|
||||
if (! $lead) {
|
||||
return ['status' => false, 'message' => 'Opportunity not found'];
|
||||
}
|
||||
|
||||
if (! self::isGmc((int) ($lead['policy_type_id'] ?? 0))) {
|
||||
return ['status' => false, 'message' => 'GMC QCR PDF is only available for GMC policies'];
|
||||
}
|
||||
|
||||
// Primary: explicit QCR row (type=2)
|
||||
$rfq = $this->rfqModel
|
||||
->where('lead_id', $leadId)
|
||||
->where('type', 2)
|
||||
->where('is_active', 1)
|
||||
->orderBy('id', 'desc')
|
||||
->first();
|
||||
|
||||
// Fallback: some older leads/pages show latest active RFQ row on QCR view.
|
||||
// If explicit QCR is missing, use latest active saved table JSON.
|
||||
if (! $rfq || empty($rfq['json'])) {
|
||||
$rfq = $this->rfqModel
|
||||
->where('lead_id', $leadId)
|
||||
->where('is_active', 1)
|
||||
->orderBy('id', 'desc')
|
||||
->first();
|
||||
}
|
||||
|
||||
if (! $rfq || empty($rfq['json'])) {
|
||||
return ['status' => false, 'message' => 'No saved proposal table data found. Please save this page first.'];
|
||||
}
|
||||
|
||||
$qcrJson = json_decode($rfq['json'], true);
|
||||
if (! is_array($qcrJson) || empty($qcrJson['table_data'])) {
|
||||
return ['status' => false, 'message' => 'QCR data is invalid or empty'];
|
||||
}
|
||||
|
||||
$clientName = trim((string) ($lead['client_name'] ?? 'Client'));
|
||||
$logoPath = $this->resolveClientLogoPath((int) ($lead['client_id'] ?? 0));
|
||||
$nhanceLogo = ROOTPATH . 'public/assets/images/Nhance-Logo-Final.png';
|
||||
|
||||
$coveragePages = $this->buildCoveragePages($qcrJson, $selectedProposals);
|
||||
$premiumSections = $this->buildPremiumSections($qcrJson, $selectedProposals);
|
||||
|
||||
if (empty($coveragePages) && empty($premiumSections)) {
|
||||
return ['status' => false, 'message' => 'No QCR-enabled proposals found to include in the PDF'];
|
||||
}
|
||||
|
||||
$frontPdf = ROOTPATH . 'public/assets/qcr_gmc/static_front.pdf';
|
||||
$backPdf = ROOTPATH . 'public/assets/qcr_gmc/static_back.pdf';
|
||||
if (! is_file($frontPdf) || ! is_file($backPdf)) {
|
||||
return ['status' => false, 'message' => 'Static GMC QCR template PDFs are missing'];
|
||||
}
|
||||
|
||||
$tempDir = rtrim(WRITEPATH, '/\\') . DIRECTORY_SEPARATOR . 'cache' . DIRECTORY_SEPARATOR . 'gmc_qcr_mpdf';
|
||||
if (! is_dir($tempDir)) {
|
||||
@mkdir($tempDir, 0775, true);
|
||||
}
|
||||
$mpdfNested = $tempDir . DIRECTORY_SEPARATOR . 'mpdf';
|
||||
if (! is_dir($mpdfNested)) {
|
||||
@mkdir($mpdfNested, 0775, true);
|
||||
}
|
||||
|
||||
$outDir = rtrim(WRITEPATH, '/\\') . DIRECTORY_SEPARATOR . 'uploads' . DIRECTORY_SEPARATOR . 'qcr_pdf' . DIRECTORY_SEPARATOR;
|
||||
if (! is_dir($outDir)) {
|
||||
@mkdir($outDir, 0775, true);
|
||||
}
|
||||
|
||||
$safeClient = preg_replace('/[^A-Za-z0-9_\- ]+/', '', $clientName) ?: 'Client';
|
||||
$safeClient = trim(preg_replace('/\s+/', ' ', $safeClient));
|
||||
$downloadName = $safeClient . ' GMC Quote comparison.-' . date('d-F-Y') . '.pdf';
|
||||
$storedName = 'gmc_qcr_' . $leadId . '_' . time() . '_' . bin2hex(random_bytes(3)) . '.pdf';
|
||||
$outPath = $outDir . $storedName;
|
||||
|
||||
try {
|
||||
$mpdf = new Mpdf([
|
||||
'tempDir' => $tempDir,
|
||||
'mode' => 'utf-8',
|
||||
'format' => [self::PAGE_W_MM, self::PAGE_H_MM],
|
||||
'orientation' => 'P',
|
||||
'margin_left' => 0,
|
||||
'margin_right' => 0,
|
||||
'margin_top' => 0,
|
||||
'margin_bottom'=> 0,
|
||||
]);
|
||||
|
||||
// 1) Cover
|
||||
$mpdf->AddPageByArray($this->pageArray());
|
||||
$mpdf->WriteHTML($this->renderCoverHtml($clientName, $logoPath, $nhanceLogo));
|
||||
|
||||
// 2) Static front (pages 2–7 from master)
|
||||
$this->importPdfPages($mpdf, $frontPdf, $tempDir);
|
||||
|
||||
// 3) Dynamic coverage pages — one set per proposal (Quote Asked only)
|
||||
foreach ($coveragePages as $page) {
|
||||
$rowChunks = array_chunk($page['rows'], self::ROWS_PER_PAGE);
|
||||
foreach ($rowChunks as $chunkIdx => $chunk) {
|
||||
$mpdf->AddPageByArray($this->pageArray());
|
||||
$mpdf->WriteHTML($this->renderCoverageHtml(
|
||||
$page['title'],
|
||||
$chunk,
|
||||
$nhanceLogo,
|
||||
$chunkIdx === 0
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
// 4) Quote Comparison — insurers with premium data
|
||||
if (! empty($premiumSections)) {
|
||||
$mpdf->AddPageByArray($this->pageArray());
|
||||
$mpdf->WriteHTML($this->renderPremiumHtml($premiumSections, $nhanceLogo));
|
||||
}
|
||||
|
||||
// 5) Static back (last 3)
|
||||
$this->importPdfPages($mpdf, $backPdf, $tempDir);
|
||||
|
||||
$mpdf->Output($outPath, Destination::FILE);
|
||||
} catch (\Throwable $e) {
|
||||
log_message('error', 'GmcQcrPdfService::generate failed | lead_id=' . $leadId . ' | ' . $e->getMessage());
|
||||
return ['status' => false, 'message' => 'PDF generation failed: ' . $e->getMessage()];
|
||||
}
|
||||
|
||||
if (! is_file($outPath)) {
|
||||
return ['status' => false, 'message' => 'PDF file was not created'];
|
||||
}
|
||||
|
||||
$this->persistGeneratedPath($lead, $storedName, $downloadName);
|
||||
|
||||
return [
|
||||
'status' => true,
|
||||
'message' => 'GMC QCR PDF generated successfully',
|
||||
'file_name' => $storedName,
|
||||
'file_path' => $outPath,
|
||||
'download_name' => $downloadName,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{status:bool,message:string,file_path?:string,download_name?:string}
|
||||
*/
|
||||
public function resolveDownload(int $leadId): array
|
||||
{
|
||||
$lead = $this->leadsModel->where('id', $leadId)->where('is_active', 1)->first();
|
||||
if (! $lead) {
|
||||
return ['status' => false, 'message' => 'Opportunity not found'];
|
||||
}
|
||||
if (! self::isGmc((int) ($lead['policy_type_id'] ?? 0))) {
|
||||
return ['status' => false, 'message' => 'GMC QCR PDF is only available for GMC policies'];
|
||||
}
|
||||
|
||||
$misc = [];
|
||||
if (! empty($lead['misc'])) {
|
||||
$decoded = json_decode($lead['misc'], true);
|
||||
if (is_array($decoded)) {
|
||||
$misc = $decoded;
|
||||
}
|
||||
}
|
||||
|
||||
$storedName = $misc['gmc_qcr_pdf'] ?? null;
|
||||
$downloadName = $misc['gmc_qcr_pdf_download_name'] ?? null;
|
||||
if (! $storedName) {
|
||||
return ['status' => false, 'message' => 'No generated PDF found. Please click Generate first.'];
|
||||
}
|
||||
|
||||
$path = rtrim(WRITEPATH, '/\\') . DIRECTORY_SEPARATOR . 'uploads' . DIRECTORY_SEPARATOR . 'qcr_pdf' . DIRECTORY_SEPARATOR . $storedName;
|
||||
if (! is_file($path)) {
|
||||
return ['status' => false, 'message' => 'Generated PDF file is missing. Please Generate again.'];
|
||||
}
|
||||
|
||||
if (! $downloadName) {
|
||||
$clientName = trim((string) ($lead['client_name'] ?? 'Client'));
|
||||
$downloadName = $clientName . ' GMC Quote comparison.-' . date('d-F-Y') . '.pdf';
|
||||
}
|
||||
|
||||
return [
|
||||
'status' => true,
|
||||
'message' => 'OK',
|
||||
'file_path' => $path,
|
||||
'download_name' => $downloadName,
|
||||
];
|
||||
}
|
||||
|
||||
private function pageArray(): array
|
||||
{
|
||||
return [
|
||||
'orientation' => 'P',
|
||||
'sheet-size' => [self::PAGE_W_MM, self::PAGE_H_MM],
|
||||
'margin-left' => 0,
|
||||
'margin-right' => 0,
|
||||
'margin-top' => 0,
|
||||
'margin-bottom'=> 0,
|
||||
];
|
||||
}
|
||||
|
||||
private function resolveClientLogoPath(int $clientId): ?string
|
||||
{
|
||||
if ($clientId <= 0) {
|
||||
return null;
|
||||
}
|
||||
$client = $this->clientModel->select('client_logo')->where('id', $clientId)->where('is_active', 1)->first();
|
||||
if (empty($client['client_logo'])) {
|
||||
return null;
|
||||
}
|
||||
$path = ROOTPATH . 'public/uploads/logo/' . $client['client_logo'];
|
||||
return is_file($path) ? $path : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build one coverage page set per QCR-enabled proposal.
|
||||
* Each page shows only the "Quote Asked" column (2-col table like original).
|
||||
*
|
||||
* @return list<array{title:string,rows:list<array{particular:string,value:string}>}>
|
||||
*/
|
||||
private function buildCoveragePages(array $qcrJson, ?array $selectedProposals = null): array
|
||||
{
|
||||
$proposalMeta = $qcrJson['proposal_data']['over_all_column_data'] ?? [];
|
||||
$headers = $qcrJson['table_data']['headers'] ?? [];
|
||||
$dataRows = $qcrJson['table_data']['data'] ?? [];
|
||||
|
||||
// Collect QCR-enabled proposals that have a Quote Asked column
|
||||
$proposals = [];
|
||||
foreach ($headers as $header) {
|
||||
$parent = (string) ($header['parentHeader'] ?? '');
|
||||
if (in_array($parent, ['Sno', 'Item Key', 'Particulars', 'Action', ''], true)) {
|
||||
continue;
|
||||
}
|
||||
$meta = $proposalMeta[$parent] ?? null;
|
||||
$proposalQcr = $meta === null ? 1 : (int) ($meta['qcr'] ?? 0);
|
||||
if ($proposalQcr !== 1) {
|
||||
continue;
|
||||
}
|
||||
// Filter by user-selected proposals if provided
|
||||
if ($selectedProposals !== null && ! in_array($parent, $selectedProposals, true)) {
|
||||
continue;
|
||||
}
|
||||
$subs = $header['subHeaders'] ?? [];
|
||||
if (in_array('Quote Asked', $subs, true) && ! isset($proposals[$parent])) {
|
||||
$proposals[$parent] = true;
|
||||
}
|
||||
}
|
||||
|
||||
$pages = [];
|
||||
foreach (array_keys($proposals) as $proposalName) {
|
||||
$rows = [];
|
||||
foreach ($dataRows as $row) {
|
||||
$actionQcr = 1;
|
||||
$particular = '';
|
||||
$value = '';
|
||||
|
||||
foreach (($row['data'] ?? []) as $cell) {
|
||||
$parent = (string) ($cell['parentth'] ?? '');
|
||||
$sub = (string) ($cell['subth'] ?? '');
|
||||
$raw = $cell['value'] ?? $cell['input_value'] ?? '';
|
||||
if (is_array($raw)) {
|
||||
if (isset($raw['qcr'])) {
|
||||
$actionQcr = (int) $raw['qcr'];
|
||||
}
|
||||
$raw = '';
|
||||
}
|
||||
$text = trim(html_entity_decode(strip_tags((string) $raw)));
|
||||
|
||||
if ($parent === 'Particulars') {
|
||||
$particular = $text;
|
||||
} elseif ($parent === $proposalName && $sub === 'Quote Asked') {
|
||||
$value = $text;
|
||||
} elseif ($parent === 'Action' && is_array($cell['value'] ?? $cell['input_value'] ?? null)) {
|
||||
$actionQcr = (int) (($cell['value'] ?? $cell['input_value'] ?? [])['qcr'] ?? 1);
|
||||
}
|
||||
}
|
||||
|
||||
if ($actionQcr !== 1 || $particular === '') {
|
||||
continue;
|
||||
}
|
||||
$rows[] = ['particular' => $particular, 'value' => $value];
|
||||
}
|
||||
|
||||
if (! empty($rows)) {
|
||||
$pages[] = [
|
||||
'title' => 'GMC Policy Coverage ' . $proposalName,
|
||||
'rows' => $rows,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return $pages;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build Quote Comparison sections from premium_data — insurers only (not Quote Asked).
|
||||
*
|
||||
* @return list<array{title:string,rows:list<array{insurer:string,premium:string,gst:string,total:string}>}>
|
||||
*/
|
||||
private function buildPremiumSections(array $qcrJson, ?array $selectedProposals = null): array
|
||||
{
|
||||
$premiumData = $qcrJson['premium_data']['data'] ?? [];
|
||||
$proposalMeta = $qcrJson['proposal_data']['over_all_column_data'] ?? [];
|
||||
if (! is_array($premiumData) || empty($premiumData)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$sections = [];
|
||||
foreach ($proposalMeta as $proposalName => $meta) {
|
||||
if ((int) ($meta['qcr'] ?? 0) !== 1) {
|
||||
continue;
|
||||
}
|
||||
if ($selectedProposals !== null && ! in_array($proposalName, $selectedProposals, true)) {
|
||||
continue;
|
||||
}
|
||||
$insMap = $premiumData[$proposalName] ?? [];
|
||||
if (! is_array($insMap)) {
|
||||
continue;
|
||||
}
|
||||
$sectionRows = [];
|
||||
foreach ($insMap as $insurerName => $metrics) {
|
||||
if ($insurerName === 'Quote Asked' || $insurerName === '' || ! is_array($metrics)) {
|
||||
continue;
|
||||
}
|
||||
$premium = trim((string) ($metrics['Premium'] ?? ''));
|
||||
$gst = trim((string) ($metrics['GST Amount (₹)'] ?? $metrics['GST Amount'] ?? ''));
|
||||
$total = trim((string) ($metrics['Total'] ?? ''));
|
||||
if ($premium === '' && $total === '') {
|
||||
continue;
|
||||
}
|
||||
if ($premium === 'Premium') {
|
||||
continue;
|
||||
}
|
||||
$sectionRows[] = [
|
||||
'insurer' => $insurerName,
|
||||
'premium' => $premium,
|
||||
'gst' => $gst,
|
||||
'total' => $total,
|
||||
];
|
||||
}
|
||||
if (! empty($sectionRows)) {
|
||||
$sections[] = [
|
||||
'title' => 'GMC Quote – ' . $proposalName,
|
||||
'rows' => $sectionRows,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return $sections;
|
||||
}
|
||||
|
||||
private function importPdfPages(Mpdf $mpdf, string $pdfPath, string $tempDir): void
|
||||
{
|
||||
helper('merge_pdf');
|
||||
try {
|
||||
merge_ticket_pdf_add_pdf_pages($mpdf, $pdfPath, $tempDir);
|
||||
} catch (\Throwable $e) {
|
||||
// Fallback: import page-by-page with explicit sheet size
|
||||
$pageCount = $mpdf->setSourceFile($pdfPath);
|
||||
for ($p = 1; $p <= $pageCount; $p++) {
|
||||
$tplId = $mpdf->importPage($p);
|
||||
$mpdf->AddPageByArray($this->pageArray());
|
||||
$mpdf->useTemplate($tplId, 0, 0, self::PAGE_W_MM, self::PAGE_H_MM, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function persistGeneratedPath(array $lead, string $storedName, string $downloadName): void
|
||||
{
|
||||
$misc = [];
|
||||
if (! empty($lead['misc'])) {
|
||||
$decoded = json_decode($lead['misc'], true);
|
||||
if (is_array($decoded)) {
|
||||
$misc = $decoded;
|
||||
}
|
||||
}
|
||||
|
||||
// Remove previous file if present
|
||||
if (! empty($misc['gmc_qcr_pdf'])) {
|
||||
$old = rtrim(WRITEPATH, '/\\') . DIRECTORY_SEPARATOR . 'uploads' . DIRECTORY_SEPARATOR . 'qcr_pdf' . DIRECTORY_SEPARATOR . $misc['gmc_qcr_pdf'];
|
||||
if (is_file($old) && $misc['gmc_qcr_pdf'] !== $storedName) {
|
||||
@unlink($old);
|
||||
}
|
||||
}
|
||||
|
||||
$misc['gmc_qcr_pdf'] = $storedName;
|
||||
$misc['gmc_qcr_pdf_download_name'] = $downloadName;
|
||||
$misc['gmc_qcr_pdf_generated_at'] = date('Y-m-d H:i:s');
|
||||
|
||||
$this->leadsModel->update((int) $lead['id'], [
|
||||
'misc' => json_encode($misc),
|
||||
]);
|
||||
}
|
||||
|
||||
private function escape(string $value): string
|
||||
{
|
||||
return htmlspecialchars($value, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
|
||||
}
|
||||
|
||||
private function renderCoverHtml(string $clientName, ?string $logoPath, string $nhanceLogo): string
|
||||
{
|
||||
$logoHtml = '';
|
||||
if ($logoPath) {
|
||||
$logoHtml = '<div style="margin-bottom:8mm;"><img src="' . $this->escape($logoPath) . '" style="max-height:55mm;max-width:100mm;" /></div>';
|
||||
}
|
||||
|
||||
$nhance = is_file($nhanceLogo)
|
||||
? '<img src="' . $this->escape($nhanceLogo) . '" style="height:10mm;" />'
|
||||
: '<span style="color:#0aa3a0;font-size:14pt;font-weight:bold;">Nhance</span>';
|
||||
|
||||
return '
|
||||
<html><head><style>
|
||||
@page { margin: 0; }
|
||||
body { margin:0; padding:0; font-family: freeserif, DejaVu Serif, serif; }
|
||||
.wrap { width:100%; height:190mm; position:relative; text-align:center; }
|
||||
.spacer { height:48mm; }
|
||||
.client { color:#0b7f7c; font-size:16pt; margin-bottom:6mm; font-family: freeserif, DejaVu Serif, serif; }
|
||||
.title { font-size:24pt; color:#1a2744; font-family: freeserif, DejaVu Serif, serif; }
|
||||
.title em { color:#0b7f7c; font-style:italic; }
|
||||
.footer-logo { position:absolute; left:12mm; bottom:8mm; }
|
||||
.footer-bar { position:absolute; right:0; bottom:0; width:28mm; height:8mm; background:#0aa3a0; }
|
||||
</style></head><body>
|
||||
<div class="wrap">
|
||||
<div class="spacer"> </div>
|
||||
' . $logoHtml . '
|
||||
<div class="client">' . $this->escape($clientName) . '</div>
|
||||
<div class="title"><strong>Group</strong> <em>Medical Insurance</em> <strong>Quote</strong></div>
|
||||
<div class="footer-logo">' . $nhance . '</div>
|
||||
<div class="footer-bar"></div>
|
||||
</div>
|
||||
</body></html>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Render a coverage page (2-column: Particulars + value) matching original style.
|
||||
*
|
||||
* @param list<array{particular:string,value:string}> $rows
|
||||
*/
|
||||
private function renderCoverageHtml(string $title, array $rows, string $nhanceLogo, bool $showTitle): string
|
||||
{
|
||||
$nhance = is_file($nhanceLogo)
|
||||
? '<img src="' . $this->escape($nhanceLogo) . '" style="height:8mm;" />'
|
||||
: '<span style="color:#0aa3a0;font-size:11pt;font-weight:bold;">Nhance</span>';
|
||||
|
||||
$thead = '<tr><th style="width:55%;">Particulars</th><th style="width:45%;">Expiring</th></tr>';
|
||||
|
||||
$tbody = '';
|
||||
$i = 0;
|
||||
foreach ($rows as $row) {
|
||||
$bg = ($i % 2 === 0) ? '#ffffff' : '#f7fafa';
|
||||
$tbody .= '<tr style="background:' . $bg . ';">';
|
||||
$tbody .= '<td class="part">' . $this->escape($row['particular']) . '</td>';
|
||||
$tbody .= '<td>' . $this->escape($row['value']) . '</td>';
|
||||
$tbody .= '</tr>';
|
||||
$i++;
|
||||
}
|
||||
|
||||
$titleBlock = $showTitle
|
||||
? '<div class="title"><span class="bar"></span><strong>GMC</strong> <em>' . $this->escape(str_replace('GMC ', '', $title)) . '</em></div>'
|
||||
: '<div class="title-sm"><strong>GMC</strong> <em>' . $this->escape(str_replace('GMC ', '', $title)) . '</em> <span style="color:#666;font-size:9pt;">(continued)</span></div>';
|
||||
|
||||
return '
|
||||
<html><head><style>
|
||||
body { margin:0; padding:0; font-family: dejavusans, sans-serif; color:#222; }
|
||||
.page { padding:8mm 14mm 14mm 14mm; }
|
||||
.title { font-family: freeserif, DejaVu Serif, serif; color:#1a2744; font-size:20pt; margin:0 0 5mm 0; padding-left:5mm; border-left:3mm solid #0aa3a0; }
|
||||
.title em { color:#0aa3a0; font-style:italic; }
|
||||
.title-sm { font-family: freeserif, DejaVu Serif, serif; color:#1a2744; font-size:16pt; margin:0 0 4mm 0; padding-left:5mm; border-left:3mm solid #0aa3a0; }
|
||||
.title-sm em { color:#0aa3a0; font-style:italic; }
|
||||
table { width:100%; border-collapse:collapse; table-layout:fixed; margin-top:2mm; }
|
||||
th { background:#1a7a6e; color:#fff; font-size:9pt; padding:2.8mm 3mm; text-align:center; vertical-align:middle; font-family: dejavusans, sans-serif; }
|
||||
td { font-size:8pt; padding:2.2mm 3mm; border-bottom:0.15mm solid #e0eded; text-align:center; vertical-align:middle; word-wrap:break-word; }
|
||||
td.part { text-align:center; color:#333; }
|
||||
.footer { position:absolute; left:12mm; bottom:5mm; }
|
||||
</style></head><body>
|
||||
<div class="page">
|
||||
' . $titleBlock . '
|
||||
<table><thead>' . $thead . '</thead><tbody>' . $tbody . '</tbody></table>
|
||||
</div>
|
||||
<div class="footer">' . $nhance . '</div>
|
||||
</body></html>';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param list<array{title:string,rows:list<array{insurer:string,premium:string,gst:string,total:string}>}> $sections
|
||||
*/
|
||||
private function renderPremiumHtml(array $sections, string $nhanceLogo): string
|
||||
{
|
||||
$nhance = is_file($nhanceLogo)
|
||||
? '<img src="' . $this->escape($nhanceLogo) . '" style="height:8mm;" />'
|
||||
: '<span style="color:#0aa3a0;font-size:11pt;font-weight:bold;">Nhance</span>';
|
||||
|
||||
$body = '';
|
||||
foreach ($sections as $section) {
|
||||
$body .= '<div class="section-title">' . $this->escape($section['title']) . '</div>';
|
||||
$body .= '<table><thead><tr>
|
||||
<th style="width:40%;">Insurer Name</th>
|
||||
<th style="width:20%;">Premium</th>
|
||||
<th style="width:20%;">GST</th>
|
||||
<th style="width:20%;">Total Premium</th>
|
||||
</tr></thead><tbody>';
|
||||
$i = 0;
|
||||
foreach ($section['rows'] as $row) {
|
||||
$bg = ($i % 2 === 0) ? '#ffffff' : '#f7fafa';
|
||||
$body .= '<tr style="background:' . $bg . ';">
|
||||
<td class="left">' . $this->escape($row['insurer']) . '</td>
|
||||
<td>' . $this->escape($row['premium']) . '</td>
|
||||
<td>' . $this->escape($row['gst']) . '</td>
|
||||
<td>' . $this->escape($row['total']) . '</td>
|
||||
</tr>';
|
||||
$i++;
|
||||
}
|
||||
$body .= '</tbody></table>';
|
||||
}
|
||||
|
||||
return '
|
||||
<html><head><style>
|
||||
body { margin:0; padding:0; font-family: dejavusans, sans-serif; color:#222; }
|
||||
.page { padding:8mm 14mm 14mm 14mm; }
|
||||
.title { font-family: freeserif, DejaVu Serif, serif; color:#1a2744; font-size:20pt; margin:0 0 6mm 0; border-left:3mm solid #0aa3a0; padding-left:5mm; }
|
||||
.title em { color:#0aa3a0; font-style:italic; }
|
||||
.section-title { font-size:10pt; font-weight:bold; margin:5mm 0 2mm 0; font-family: dejavusans, sans-serif; }
|
||||
table { width:100%; border-collapse:collapse; margin-bottom:4mm; }
|
||||
th { background:#1a7a6e; color:#fff; font-size:9pt; padding:2.8mm 3mm; text-align:center; font-family: dejavusans, sans-serif; }
|
||||
td { font-size:8.5pt; padding:2.5mm 3mm; border-bottom:0.15mm solid #e0eded; text-align:center; }
|
||||
td.left { text-align:left; }
|
||||
.footer { position:absolute; left:12mm; bottom:5mm; }
|
||||
</style></head><body>
|
||||
<div class="page">
|
||||
<div class="title"><strong>Quote</strong> <em>Comparison</em></div>
|
||||
' . $body . '
|
||||
</div>
|
||||
<div class="footer">' . $nhance . '</div>
|
||||
</body></html>';
|
||||
}
|
||||
}
|
||||
@ -115,18 +115,6 @@ class AbhiClaimImportService extends BaseTpaClaimImportService
|
||||
|
||||
/**
|
||||
* Extra dump → claim_report fields beyond ticketMasterMapping.
|
||||
* report_column => dump_column
|
||||
*/
|
||||
protected $claimReportEnrichment = [
|
||||
'approved_amount' => 'abhi_amount_less_coins_current_month',
|
||||
'incurred_amount' => 'claimed_amount',
|
||||
'tpa_ailments' => 'diagnosis',
|
||||
'tpa_claim_type' => 'claim_type',
|
||||
'gender' => 'gender',
|
||||
'age' => 'patient_age',
|
||||
'relation' => 'relation',
|
||||
];
|
||||
|
||||
protected $statusMapping = [
|
||||
'Settled' => 11,
|
||||
'Rejected' => 8,
|
||||
|
||||
@ -330,11 +330,6 @@ abstract class BaseTpaClaimImportService
|
||||
return $this->failTicketMasterInsert($file_id, 'No data found to process.');
|
||||
}
|
||||
|
||||
// Upsert normalized analytics rows into claim_report (same transaction)
|
||||
if (!$this->syncClaimReportForJob2($file_id, $ticketMasterData)) {
|
||||
return $this->failTicketMasterInsert($file_id, 'Claim report upsert failed');
|
||||
}
|
||||
|
||||
// 2. Commit the transaction
|
||||
$this->db->transCommit();
|
||||
|
||||
@ -1169,289 +1164,6 @@ abstract class BaseTpaClaimImportService
|
||||
->where('is_active', 1)
|
||||
->countAllResults() > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect dump row IDs processed in Job 2 and upsert claim_report rows.
|
||||
*/
|
||||
protected function syncClaimReportForJob2(int $fileId, array $ticketMasterData): bool
|
||||
{
|
||||
$dumpIds = [];
|
||||
|
||||
foreach ($ticketMasterData['mapped_array'] ?? [] as $row) {
|
||||
if (!empty($row['claim_dump_ref_id'])) {
|
||||
$dumpIds[] = (int) $row['claim_dump_ref_id'];
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($ticketMasterData['status_update_array'] ?? [] as $row) {
|
||||
if (!empty($row['claim_dump_ref_id'])) {
|
||||
$dumpIds[] = (int) $row['claim_dump_ref_id'];
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($ticketMasterData['rejected_reason_array'] ?? [] as $row) {
|
||||
// Linked existing tickets: ticket_id set, no reject reason
|
||||
if (!empty($row['id']) && !empty($row['ticket_id']) && empty($row['master_reject_reason'])) {
|
||||
$dumpIds[] = (int) $row['id'];
|
||||
}
|
||||
}
|
||||
|
||||
$dumpIds = array_values(array_unique(array_filter($dumpIds)));
|
||||
if ($dumpIds === []) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$rows = $this->mapClaimReportData($fileId, $dumpIds);
|
||||
$this->logClaimDump('info', 'CLAIM_REPORT_UPSERT', [
|
||||
'file_id' => $fileId,
|
||||
'dump_ids' => count($dumpIds),
|
||||
'report_rows' => count($rows),
|
||||
]);
|
||||
|
||||
return $this->upsertClaimReport($rows);
|
||||
}
|
||||
|
||||
/**
|
||||
* Public backfill entry: map dump row IDs for a file into claim_report.
|
||||
*
|
||||
* @param list<int> $dumpRowIds
|
||||
* @return array{status:bool,count:int}
|
||||
*/
|
||||
public function backfillClaimReportByDumpIds(int $fileId, array $dumpRowIds): array
|
||||
{
|
||||
$dumpRowIds = array_values(array_unique(array_filter(array_map('intval', $dumpRowIds))));
|
||||
if ($fileId <= 0 || $dumpRowIds === []) {
|
||||
return ['status' => true, 'count' => 0];
|
||||
}
|
||||
|
||||
$rows = $this->mapClaimReportData($fileId, $dumpRowIds);
|
||||
$ok = $this->upsertClaimReport($rows);
|
||||
|
||||
return ['status' => $ok, 'count' => count($rows)];
|
||||
}
|
||||
|
||||
/**
|
||||
* Map dump rows (+ linked ticket_master) into claim_report-shaped rows.
|
||||
*
|
||||
* @param list<int> $dumpRowIds
|
||||
* @return list<array<string, mixed>>
|
||||
*/
|
||||
protected function mapClaimReportData(int $fileId, array $dumpRowIds): array
|
||||
{
|
||||
if ($fileId <= 0 || $dumpRowIds === []) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$fileData = $this->claimDumpFileModel->where('id', $fileId)->first();
|
||||
if (empty($fileData)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$tpaId = (int) ($fileData['tpa_id'] ?? 0);
|
||||
$tpaTable = $this->tpaTableMapping[$tpaId] ?? null;
|
||||
if ($tpaTable === null || !$this->db->tableExists($tpaTable)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$dumpRows = $this->db->table($tpaTable)
|
||||
->whereIn('id', $dumpRowIds)
|
||||
->where('is_active', 1)
|
||||
->get()
|
||||
->getResultArray();
|
||||
|
||||
if ($dumpRows === []) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$ticketIds = [];
|
||||
$refIds = [];
|
||||
foreach ($dumpRows as $dump) {
|
||||
if (!empty($dump['ticket_id'])) {
|
||||
$ticketIds[] = (int) $dump['ticket_id'];
|
||||
}
|
||||
$refIds[] = (int) $dump['id'];
|
||||
}
|
||||
|
||||
$ticketsById = [];
|
||||
$ticketsByRef = [];
|
||||
if ($ticketIds !== []) {
|
||||
$ticketRows = $this->db->table('ticket_master')
|
||||
->whereIn('id', array_values(array_unique($ticketIds)))
|
||||
->get()
|
||||
->getResultArray();
|
||||
foreach ($ticketRows as $ticket) {
|
||||
$ticketsById[(int) $ticket['id']] = $ticket;
|
||||
}
|
||||
}
|
||||
if ($refIds !== []) {
|
||||
$ticketRows = $this->db->table('ticket_master')
|
||||
->where('file_id', $fileId)
|
||||
->whereIn('claim_dump_ref_id', array_values(array_unique($refIds)))
|
||||
->get()
|
||||
->getResultArray();
|
||||
foreach ($ticketRows as $ticket) {
|
||||
$ticketsByRef[(int) $ticket['claim_dump_ref_id']] = $ticket;
|
||||
$ticketsById[(int) $ticket['id']] = $ticket;
|
||||
}
|
||||
}
|
||||
|
||||
$mapping = property_exists($this, 'ticketMasterMapping') ? ($this->ticketMasterMapping ?? []) : [];
|
||||
$enrichment = property_exists($this, 'claimReportEnrichment') ? ($this->claimReportEnrichment ?? []) : [];
|
||||
|
||||
$reportFieldsFromTicketMap = [
|
||||
'claim_number', 'emp_code', 'tpa_no', 'claim_amount', 'approved_amount', 'si_amt',
|
||||
'tpa_claim_status', 'tpa_claim_type', 'tpa_ailments',
|
||||
'doa', 'dod', 'date_of_intimat', 'settled_date', 'approved_date', 'registration_date',
|
||||
'hospital_name', 'hospital_city', 'hospital_state', 'hospital_pin_code', 'hospital_address',
|
||||
'denial_reason', 'denial_date', 'utr_details', 'return_remark',
|
||||
];
|
||||
|
||||
$out = [];
|
||||
foreach ($dumpRows as $dump) {
|
||||
$dumpId = (int) ($dump['id'] ?? 0);
|
||||
$ticket = null;
|
||||
if (!empty($dump['ticket_id']) && isset($ticketsById[(int) $dump['ticket_id']])) {
|
||||
$ticket = $ticketsById[(int) $dump['ticket_id']];
|
||||
} elseif (isset($ticketsByRef[$dumpId])) {
|
||||
$ticket = $ticketsByRef[$dumpId];
|
||||
}
|
||||
|
||||
// Skip dump rows that never became / linked to a ticket
|
||||
if (empty($ticket) && empty($dump['ticket_id'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$item = [
|
||||
'tpa_id' => $tpaId ?: ($ticket['tpa_id'] ?? null),
|
||||
'client_id' => $dump['client_id'] ?? $fileData['client_id'] ?? ($ticket['client_id'] ?? null),
|
||||
'client_policy_id' => (int) ($dump['client_policy_id'] ?? $fileData['client_policy_id'] ?? ($ticket['client_policy_id'] ?? 0)),
|
||||
'file_id' => $fileId,
|
||||
'ticket_id' => $ticket['id'] ?? ($dump['ticket_id'] ?? null),
|
||||
'source_table' => $tpaTable,
|
||||
'source_row_id' => $dumpId,
|
||||
'claim_dump_date' => $fileData['claim_dump_date'] ?? ($ticket['claim_dump_date'] ?? null),
|
||||
'is_active' => 1,
|
||||
];
|
||||
|
||||
foreach ($mapping as $dumpCol => $ticketCol) {
|
||||
if (!in_array($ticketCol, $reportFieldsFromTicketMap, true)) {
|
||||
continue;
|
||||
}
|
||||
// Map ticket-shaped columns that exist on claim_report
|
||||
$reportCol = $ticketCol === 'registration_date' ? 'date_of_intimat' : $ticketCol;
|
||||
if (!array_key_exists($reportCol, $item) || $item[$reportCol] === null) {
|
||||
$item[$reportCol] = array_key_exists($dumpCol, $dump) ? $dump[$dumpCol] : null;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($enrichment as $reportCol => $dumpCol) {
|
||||
if ($dumpCol === null || $dumpCol === '') {
|
||||
continue;
|
||||
}
|
||||
$value = $dump[$dumpCol] ?? null;
|
||||
if ($value !== null && $value !== '') {
|
||||
$item[$reportCol] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
if ($ticket) {
|
||||
foreach (['emp_id', 'insured_emp_id', 'claim_status_id', 'emp_code', 'claim_number', 'claim_amount', 'approved_amount', 'tpa_claim_type', 'tpa_ailments', 'hospital_name', 'hospital_city', 'hospital_state', 'hospital_pin_code', 'hospital_address', 'doa', 'dod', 'date_of_intimat', 'settled_date', 'approved_date', 'si_amt', 'tpa_claim_status', 'tpa_no'] as $col) {
|
||||
if ((!isset($item[$col]) || $item[$col] === null || $item[$col] === '') && isset($ticket[$col]) && $ticket[$col] !== null && $ticket[$col] !== '') {
|
||||
$item[$col] = $ticket[$col];
|
||||
}
|
||||
}
|
||||
if (empty($item['ticket_id'])) {
|
||||
$item['ticket_id'] = $ticket['id'];
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($item['incurred_amount'])) {
|
||||
$item['incurred_amount'] = $item['approved_amount'] ?? $item['claim_amount'] ?? null;
|
||||
}
|
||||
|
||||
$claimNumber = trim((string) ($item['claim_number'] ?? ''));
|
||||
if ($claimNumber === '' || (int) ($item['client_policy_id'] ?? 0) <= 0) {
|
||||
continue;
|
||||
}
|
||||
$item['claim_number'] = $claimNumber;
|
||||
|
||||
// Drop fields that are not on claim_report
|
||||
unset($item['denial_reason'], $item['denial_date'], $item['utr_details'], $item['return_remark'], $item['registration_date']);
|
||||
|
||||
$out[] = $item;
|
||||
}
|
||||
|
||||
return $out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert or update claim_report by UNIQUE(client_policy_id, claim_number).
|
||||
*
|
||||
* @param list<array<string, mixed>> $rows
|
||||
*/
|
||||
protected function upsertClaimReport(array $rows): bool
|
||||
{
|
||||
if ($rows === []) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!$this->db->tableExists('claim_report')) {
|
||||
$this->logClaimDump('warning', 'CLAIM_REPORT_TABLE_MISSING', []);
|
||||
return true;
|
||||
}
|
||||
|
||||
$columns = [
|
||||
'tpa_id', 'client_id', 'client_policy_id', 'file_id', 'ticket_id',
|
||||
'source_table', 'source_row_id', 'claim_number', 'emp_code', 'tpa_no',
|
||||
'emp_id', 'insured_emp_id', 'claim_amount', 'approved_amount', 'incurred_amount',
|
||||
'si_amt', 'tpa_claim_status', 'claim_status_id', 'tpa_claim_type', 'tpa_ailments',
|
||||
'doa', 'dod', 'date_of_intimat', 'settled_date', 'approved_date', 'claim_dump_date',
|
||||
'hospital_name', 'hospital_city', 'hospital_state', 'hospital_pin_code', 'hospital_address',
|
||||
'gender', 'age', 'relation', 'is_active', 'created_at', 'updated_at',
|
||||
];
|
||||
|
||||
$updateCols = array_values(array_diff($columns, ['client_policy_id', 'claim_number', 'created_at']));
|
||||
$now = date('Y-m-d H:i:s');
|
||||
|
||||
foreach (array_chunk($rows, 100) as $chunk) {
|
||||
$placeholders = [];
|
||||
$binds = [];
|
||||
|
||||
foreach ($chunk as $row) {
|
||||
$rowPlaceholders = [];
|
||||
foreach ($columns as $col) {
|
||||
$rowPlaceholders[] = '?';
|
||||
if ($col === 'created_at' || $col === 'updated_at') {
|
||||
$binds[] = $now;
|
||||
} elseif ($col === 'is_active') {
|
||||
$binds[] = isset($row[$col]) ? (int) $row[$col] : 1;
|
||||
} else {
|
||||
$binds[] = $row[$col] ?? null;
|
||||
}
|
||||
}
|
||||
$placeholders[] = '(' . implode(', ', $rowPlaceholders) . ')';
|
||||
}
|
||||
|
||||
$updates = [];
|
||||
foreach ($updateCols as $col) {
|
||||
if ($col === 'updated_at') {
|
||||
$updates[] = '`updated_at` = VALUES(`updated_at`)';
|
||||
} else {
|
||||
$updates[] = '`' . $col . '` = VALUES(`' . $col . '`)';
|
||||
}
|
||||
}
|
||||
|
||||
$sql = 'INSERT INTO `claim_report` (`' . implode('`, `', $columns) . '`) VALUES '
|
||||
. implode(', ', $placeholders)
|
||||
. ' ON DUPLICATE KEY UPDATE ' . implode(', ', $updates);
|
||||
|
||||
if ($this->db->query($sql, $binds) === false) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Map Excel rows to TPA table structure
|
||||
|
||||
@ -177,19 +177,6 @@ class FhplClaimImportService extends BaseTpaClaimImportService
|
||||
|
||||
/**
|
||||
* Extra dump → claim_report fields beyond ticketMasterMapping.
|
||||
* report_column => dump_column
|
||||
*/
|
||||
protected $claimReportEnrichment = [
|
||||
'approved_amount' => 'settled_amount',
|
||||
'incurred_amount' => 'incurred_amount',
|
||||
'tpa_ailments' => 'diagnosis',
|
||||
'tpa_claim_type' => 'claim_type',
|
||||
'gender' => 'gender',
|
||||
'age' => 'years',
|
||||
'relation' => 'relationship',
|
||||
'si_amt' => 'coverage_amount',
|
||||
];
|
||||
|
||||
protected $statusMapping = [
|
||||
'Settled' => 11,
|
||||
'Rejected' => 8,
|
||||
|
||||
@ -106,19 +106,6 @@ class IciciClaimImportService extends BaseTpaClaimImportService
|
||||
|
||||
/**
|
||||
* Extra dump → claim_report fields beyond ticketMasterMapping.
|
||||
* report_column => dump_column
|
||||
*/
|
||||
protected $claimReportEnrichment = [
|
||||
'approved_amount' => 'net_sanct_amt',
|
||||
'incurred_amount' => 'claimed_amount',
|
||||
'tpa_ailments' => 'diagnosis',
|
||||
'tpa_claim_type' => 'type_of_claim',
|
||||
'gender' => 'gender',
|
||||
'age' => 'age',
|
||||
'relation' => 'relation',
|
||||
'si_amt' => 'sum_insured',
|
||||
];
|
||||
|
||||
protected $statusMapping = [
|
||||
'PAID' => 11,
|
||||
'SETTLED' => 11,
|
||||
|
||||
@ -157,19 +157,6 @@ class MediAssistClaimImportService extends BaseTpaClaimImportService
|
||||
|
||||
/**
|
||||
* Extra dump → claim_report fields beyond ticketMasterMapping.
|
||||
* report_column => dump_column
|
||||
*/
|
||||
protected $claimReportEnrichment = [
|
||||
'approved_amount' => 'claim_approved_amount',
|
||||
'incurred_amount' => 'incurred_amount',
|
||||
'tpa_ailments' => 'primary_ailment_name',
|
||||
'tpa_claim_type' => 'claim_type',
|
||||
'gender' => 'benef_gender',
|
||||
'age' => 'benef_age',
|
||||
'relation' => 'benef_relation',
|
||||
'si_amt' => 'benef_sum_insured',
|
||||
];
|
||||
|
||||
protected $statusMapping = [
|
||||
'Settled' => 11,
|
||||
'Rejected' => 8,
|
||||
|
||||
@ -94,19 +94,6 @@ class RcareClaimImportService extends BaseTpaClaimImportService
|
||||
|
||||
/**
|
||||
* Extra dump → claim_report fields beyond ticketMasterMapping.
|
||||
* report_column => dump_column
|
||||
*/
|
||||
protected $claimReportEnrichment = [
|
||||
'approved_amount' => 'net_sanction_amount',
|
||||
'incurred_amount' => 'claimed_amount',
|
||||
'tpa_ailments' => 'diagnosis',
|
||||
'tpa_claim_type' => 'member_reimbursement_cl_type',
|
||||
'gender' => 'gender',
|
||||
'age' => 'age',
|
||||
'relation' => 'relation',
|
||||
'si_amt' => 'sum_insured',
|
||||
];
|
||||
|
||||
protected $statusMapping = [
|
||||
'CL Paid with Settlement Letter' => 11,
|
||||
'Settled' => 11,
|
||||
|
||||
@ -350,19 +350,6 @@ class VidalClaimImportService extends BaseTpaClaimImportService
|
||||
|
||||
/**
|
||||
* Extra dump → claim_report fields beyond ticketMasterMapping.
|
||||
* report_column => dump_column
|
||||
*/
|
||||
protected $claimReportEnrichment = [
|
||||
'approved_amount' => 'approved_amount',
|
||||
'incurred_amount' => 'total_incurred_amount',
|
||||
'tpa_ailments' => 'diagnosis',
|
||||
'tpa_claim_type' => 'type_of_claim',
|
||||
'gender' => 'gender',
|
||||
'age' => 'age',
|
||||
'relation' => 'relation',
|
||||
'si_amt' => 'sum_insured',
|
||||
];
|
||||
|
||||
protected $statusMapping = [
|
||||
'Settled' => 11,
|
||||
'Rejected' => 8,
|
||||
|
||||
@ -3,7 +3,8 @@
|
||||
namespace App\Models;
|
||||
|
||||
/**
|
||||
* Claims Collection dashboard backed by claim_report with ticket_master fallback.
|
||||
* Claims Collection dashboard backed by claim_report.
|
||||
* Falls back to ticket_master only for non-dump TPAs when env flag is true.
|
||||
* Reuses V2 KPI SQL; claim fact tables are rewritten at query time.
|
||||
*/
|
||||
class ClaimReportDashboardModel extends ClaimsCollectionV2DashboardModel
|
||||
@ -13,7 +14,10 @@ class ClaimReportDashboardModel extends ClaimsCollectionV2DashboardModel
|
||||
|
||||
/**
|
||||
* Resolve claim fact table for a policy.
|
||||
* Uses claim_report when TPA dump table exists and claim_report has rows; else ticket_master.
|
||||
*
|
||||
* - TPA dump table exists → always claim_report (even if empty; never ticket_master).
|
||||
* - Dump table missing / TPA unmapped → ticket_master only when
|
||||
* CLAIM_REPORT_FALLBACK_TO_TICKET_MASTER=true; otherwise claim_report.
|
||||
*/
|
||||
public function resolveClaimsTable(int $policyId): string
|
||||
{
|
||||
@ -21,11 +25,18 @@ class ClaimReportDashboardModel extends ClaimsCollectionV2DashboardModel
|
||||
return $this->claimsTableCache[$policyId];
|
||||
}
|
||||
|
||||
$table = 'ticket_master';
|
||||
$db = \Config\Database::connect($this->DBGroup);
|
||||
$db = \Config\Database::connect($this->DBGroup);
|
||||
$fallbackEnabled = $this->isTicketMasterFallbackEnabled();
|
||||
|
||||
if (!$db->tableExists('claim_report') || $policyId <= 0) {
|
||||
return $this->claimsTableCache[$policyId] = $table;
|
||||
if ($policyId <= 0) {
|
||||
return $this->claimsTableCache[$policyId] = $fallbackEnabled
|
||||
? 'ticket_master'
|
||||
: 'claim_report';
|
||||
}
|
||||
|
||||
// Table missing — cannot query claim_report; ticket_master is the only option.
|
||||
if (!$db->tableExists('claim_report')) {
|
||||
return $this->claimsTableCache[$policyId] = 'ticket_master';
|
||||
}
|
||||
|
||||
$policy = $db->table('client_policy')
|
||||
@ -34,23 +45,26 @@ class ClaimReportDashboardModel extends ClaimsCollectionV2DashboardModel
|
||||
->get()
|
||||
->getRowArray();
|
||||
|
||||
$tpaId = (int) ($policy['tpa_id'] ?? 0);
|
||||
$tpaId = (int) ($policy['tpa_id'] ?? 0);
|
||||
$dumpTable = $this->getTpaDumpTableMap()[$tpaId] ?? null;
|
||||
|
||||
if ($dumpTable === null || !$db->tableExists($dumpTable)) {
|
||||
return $this->claimsTableCache[$policyId] = $table;
|
||||
// Mapped dump TPA with existing dump table → claim_report only.
|
||||
if ($dumpTable !== null && $db->tableExists($dumpTable)) {
|
||||
return $this->claimsTableCache[$policyId] = 'claim_report';
|
||||
}
|
||||
|
||||
$hasReportRows = $db->table('claim_report')
|
||||
->where('client_policy_id', $policyId)
|
||||
->where('is_active', 1)
|
||||
->countAllResults() > 0;
|
||||
// Non-dump / missing dump table → env-gated ticket_master fallback.
|
||||
return $this->claimsTableCache[$policyId] = $fallbackEnabled
|
||||
? 'ticket_master'
|
||||
: 'claim_report';
|
||||
}
|
||||
|
||||
if ($hasReportRows) {
|
||||
$table = 'claim_report';
|
||||
}
|
||||
|
||||
return $this->claimsTableCache[$policyId] = $table;
|
||||
protected function isTicketMasterFallbackEnabled(): bool
|
||||
{
|
||||
return filter_var(
|
||||
env('CLAIM_REPORT_FALLBACK_TO_TICKET_MASTER', false),
|
||||
FILTER_VALIDATE_BOOLEAN
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -17,7 +17,7 @@ class MotorQuoteModel extends Model
|
||||
'insurance_product_code', 'sub_insurance_product_code',
|
||||
'previous_insurer_code', 'previous_policy_expiry_date', 'external_policy_number',
|
||||
'is_ncb_transfer', 'start_date', 'end_date', 'pincode',
|
||||
'coverage_details', 'premium', 'idv', 'status',
|
||||
'coverage_details', 'policyholder_details', 'premium', 'idv', 'status',
|
||||
'created_at', 'updated_at',
|
||||
];
|
||||
protected $useTimestamps = true;
|
||||
@ -67,6 +67,12 @@ class MotorQuoteModel extends Model
|
||||
if (!empty($quote['coverage_details']) && is_string($quote['coverage_details'])) {
|
||||
$quote['coverage_details'] = json_decode($quote['coverage_details'], true);
|
||||
}
|
||||
if (!empty($quote['policyholder_details']) && is_string($quote['policyholder_details'])) {
|
||||
$quote['policyholder_details'] = json_decode($quote['policyholder_details'], true);
|
||||
}
|
||||
if (!is_array($quote['policyholder_details'] ?? null)) {
|
||||
$quote['policyholder_details'] = [];
|
||||
}
|
||||
|
||||
return $quote;
|
||||
}
|
||||
|
||||
@ -6,6 +6,11 @@ $kyc = $quote['kyc'] ?? [];
|
||||
$payment = $quote['payment'] ?? [];
|
||||
$policy = $quote['policy'] ?? [];
|
||||
$status = $quote['status'] ?? 'DRAFT';
|
||||
$policyholder = $policyholder ?? ($quote['policyholder_details'] ?? []);
|
||||
if (!is_array($policyholder)) {
|
||||
$policyholder = [];
|
||||
}
|
||||
$vehicleMaster = $vehicle_master ?? null;
|
||||
|
||||
$statusToStep = [
|
||||
'DRAFT' => 0,
|
||||
@ -17,6 +22,18 @@ $statusToStep = [
|
||||
];
|
||||
$initialStep = $statusToStep[$status] ?? 0;
|
||||
$coverage = is_array($quote['coverage_details'] ?? null) ? $quote['coverage_details'] : [];
|
||||
// Normalize addon flags from either UI-shaped or Digit-shaped coverage JSON
|
||||
$addonOn = [
|
||||
'personal_accident' => !empty($coverage['personal_accident']) || !empty($coverage['personalAccident']['selection']),
|
||||
'zero_dep' => !empty($coverage['zero_dep']) || !empty($coverage['addons']['partsDepreciation']['selection']),
|
||||
'engine_protect' => !empty($coverage['engine_protect']) || !empty($coverage['addons']['engineProtection']['selection']),
|
||||
'rsa' => !empty($coverage['rsa']) || !empty($coverage['addons']['roadSideAssistance']['selection']),
|
||||
'consumables' => !empty($coverage['consumables']) || !empty($coverage['addons']['consumables']['selection']),
|
||||
'key_protect' => !empty($coverage['key_protect']) || !empty($coverage['addons']['keyAndLockProtect']['selection']),
|
||||
];
|
||||
if ($coverage === []) {
|
||||
$addonOn['personal_accident'] = true; // default matches chip markup
|
||||
}
|
||||
?>
|
||||
<style>
|
||||
:root{
|
||||
@ -105,8 +122,10 @@ $coverage = is_array($quote['coverage_details'] ?? null) ? $quote['coverage_deta
|
||||
.dm-section:first-of-type{margin-top:0;padding-top:0;border-top:none;}
|
||||
.dm-grid{display:grid;grid-template-columns:repeat(2,1fr);gap:14px 16px;}
|
||||
.dm-grid.g3{grid-template-columns:repeat(3,1fr);}
|
||||
.dm-grid.g4{grid-template-columns:repeat(4,1fr);}
|
||||
.dm-field{display:flex;flex-direction:column;gap:6px;}
|
||||
.dm-field.full{grid-column:1/-1;}
|
||||
.dm-field.span2{grid-column:span 2;}
|
||||
.dm-field label{font-size:12px;color:var(--dm-muted);font-weight:500;margin:0;}
|
||||
.dm-field label .req{color:var(--dm-red);margin-left:2px;font-weight:700;}
|
||||
.dm-field input,.dm-field select{
|
||||
@ -188,7 +207,7 @@ $coverage = is_array($quote['coverage_details'] ?? null) ? $quote['coverage_deta
|
||||
.dm-suggest .meta{color:var(--dm-muted);font-size:11px;}
|
||||
.dm-vcode-tag{font-family:monospace;font-size:12px;color:var(--dm-teal-dark);margin-top:4px;}
|
||||
@media(max-width:768px){
|
||||
.dm-grid,.dm-grid.g3,.dm-quote-cards{grid-template-columns:1fr;}
|
||||
.dm-grid,.dm-grid.g3,.dm-grid.g4,.dm-quote-cards{grid-template-columns:1fr;}
|
||||
.dm-stop{width:52px;}
|
||||
.dm-stop-label{font-size:10px;}
|
||||
.dm-road{left:22px;right:22px;}
|
||||
@ -227,7 +246,7 @@ $coverage = is_array($quote['coverage_details'] ?? null) ? $quote['coverage_deta
|
||||
</div>
|
||||
|
||||
<div class="dm-section">Vehicle</div>
|
||||
<div class="dm-grid g3">
|
||||
<div class="dm-grid g4">
|
||||
<div class="dm-field"><label>Registration number <span class="req">*</span></label><input id="f_reg" value="<?= esc($vehicle['license_plate_number'] ?? '') ?>" placeholder="GJ04DA8726" data-required="1" data-label="Registration number"><div class="dm-err">Required</div></div>
|
||||
<div class="dm-field">
|
||||
<label>Make <span class="req">*</span></label>
|
||||
@ -268,7 +287,7 @@ $coverage = is_array($quote['coverage_details'] ?? null) ? $quote['coverage_deta
|
||||
</div>
|
||||
|
||||
<div class="dm-section">Previous policy</div>
|
||||
<div class="dm-grid g3">
|
||||
<div class="dm-grid g4">
|
||||
<div class="dm-field"><label>Previous insurer</label>
|
||||
<select id="f_prev_ins"><option value="">— none / unknown —</option></select>
|
||||
</div>
|
||||
@ -289,12 +308,12 @@ $coverage = is_array($quote['coverage_details'] ?? null) ? $quote['coverage_deta
|
||||
|
||||
<div class="dm-section">Coverage add-ons</div>
|
||||
<div class="dm-chips" id="addonChips">
|
||||
<div class="dm-chip on" data-key="personal_accident" onclick="this.classList.toggle('on')">Personal accident</div>
|
||||
<div class="dm-chip" data-key="zero_dep" onclick="this.classList.toggle('on')">Zero depreciation</div>
|
||||
<div class="dm-chip" data-key="engine_protect" onclick="this.classList.toggle('on')">Engine protect</div>
|
||||
<div class="dm-chip" data-key="rsa" onclick="this.classList.toggle('on')">Roadside assistance</div>
|
||||
<div class="dm-chip" data-key="consumables" onclick="this.classList.toggle('on')">Consumables</div>
|
||||
<div class="dm-chip" data-key="key_protect" onclick="this.classList.toggle('on')">Key & lock</div>
|
||||
<div class="dm-chip<?= !empty($addonOn['personal_accident']) ? ' on' : '' ?>" data-key="personal_accident" onclick="this.classList.toggle('on')">Personal accident</div>
|
||||
<div class="dm-chip<?= !empty($addonOn['zero_dep']) ? ' on' : '' ?>" data-key="zero_dep" onclick="this.classList.toggle('on')">Zero depreciation</div>
|
||||
<div class="dm-chip<?= !empty($addonOn['engine_protect']) ? ' on' : '' ?>" data-key="engine_protect" onclick="this.classList.toggle('on')">Engine protect</div>
|
||||
<div class="dm-chip<?= !empty($addonOn['rsa']) ? ' on' : '' ?>" data-key="rsa" onclick="this.classList.toggle('on')">Roadside assistance</div>
|
||||
<div class="dm-chip<?= !empty($addonOn['consumables']) ? ' on' : '' ?>" data-key="consumables" onclick="this.classList.toggle('on')">Consumables</div>
|
||||
<div class="dm-chip<?= !empty($addonOn['key_protect']) ? ' on' : '' ?>" data-key="key_protect" onclick="this.classList.toggle('on')">Key & lock</div>
|
||||
</div>
|
||||
|
||||
<div class="dm-actions">
|
||||
@ -330,14 +349,14 @@ $coverage = is_array($quote['coverage_details'] ?? null) ? $quote['coverage_deta
|
||||
</div>
|
||||
|
||||
<div class="dm-section">Policyholder</div>
|
||||
<div class="dm-grid">
|
||||
<div class="dm-field"><label>First name <span class="req">*</span></label><input id="f_fname" value="" data-required="1" data-label="First name"><div class="dm-err">Required</div></div>
|
||||
<div class="dm-field"><label>Last name</label><input id="f_lname" value=""></div>
|
||||
<div class="dm-field"><label>Mobile <span class="req">*</span></label><input id="f_mobile" value="" data-required="1" data-label="Mobile" data-len="10"><div class="dm-err">Required (10 digits)</div></div>
|
||||
<div class="dm-field"><label>Email <span class="req">*</span></label><input id="f_email" type="email" value="" data-required="1" data-label="Email"><div class="dm-err">Required</div></div>
|
||||
<div class="dm-field"><label>PAN <span class="req">*</span></label><input id="f_pan" value="" data-required="1" data-label="PAN"><div class="dm-err">Required</div></div>
|
||||
<div class="dm-field"><label>Date of birth <span class="req">*</span></label><input type="date" id="f_dob" value="" data-required="1" data-label="Date of birth"><div class="dm-err">Required</div></div>
|
||||
<div class="dm-field full"><label>Address <span class="req">*</span></label><input id="f_address" value="" data-required="1" data-label="Address"><div class="dm-err">Required</div></div>
|
||||
<div class="dm-grid g4">
|
||||
<div class="dm-field"><label>First name <span class="req">*</span></label><input id="f_fname" value="<?= esc($policyholder['first_name'] ?? '') ?>" data-required="1" data-label="First name"><div class="dm-err">Required</div></div>
|
||||
<div class="dm-field"><label>Last name</label><input id="f_lname" value="<?= esc($policyholder['last_name'] ?? '') ?>"></div>
|
||||
<div class="dm-field"><label>Mobile <span class="req">*</span></label><input id="f_mobile" value="<?= esc($policyholder['mobile'] ?? '') ?>" data-required="1" data-label="Mobile" data-len="10"><div class="dm-err">Required (10 digits)</div></div>
|
||||
<div class="dm-field"><label>Email <span class="req">*</span></label><input id="f_email" type="email" value="<?= esc($policyholder['email'] ?? '') ?>" data-required="1" data-label="Email"><div class="dm-err">Required</div></div>
|
||||
<div class="dm-field"><label>PAN <span class="req">*</span></label><input id="f_pan" value="<?= esc($policyholder['pan'] ?? '') ?>" data-required="1" data-label="PAN"><div class="dm-err">Required</div></div>
|
||||
<div class="dm-field"><label>Date of birth <span class="req">*</span></label><input type="date" id="f_dob" value="<?= esc($policyholder['dob'] ?? '') ?>" data-required="1" data-label="Date of birth"><div class="dm-err">Required</div></div>
|
||||
<div class="dm-field span2"><label>Address <span class="req">*</span></label><input id="f_address" value="<?= esc($policyholder['address'] ?? '') ?>" data-required="1" data-label="Address"><div class="dm-err">Required</div></div>
|
||||
</div>
|
||||
|
||||
<div class="dm-actions">
|
||||
@ -470,11 +489,16 @@ $coverage = is_array($quote['coverage_details'] ?? null) ? $quote['coverage_deta
|
||||
<script>
|
||||
window.DM = {
|
||||
quoteId: <?= $quoteId ? (int)$quoteId : 'null' ?>,
|
||||
enquiryId: <?= json_encode($quote['enquiry_id'] ?? null) ?>,
|
||||
base: '<?= rtrim(base_url('digit-motor'), '/') ?>',
|
||||
step: <?= (int)$initialStep ?>,
|
||||
selectedProduct: '<?= esc($quote['insurance_product_code'] ?? '20102') ?>',
|
||||
selectedInsurer: '<?= esc($quote['previous_insurer_code'] ?? '') ?>',
|
||||
selectedVehicleCode: '<?= esc($vehicle['vehicle_maincode'] ?? '') ?>'
|
||||
selectedVehicleCode: '<?= esc($vehicle['vehicle_maincode'] ?? '') ?>',
|
||||
vehicleMake: <?= json_encode($vehicleMaster['make'] ?? '') ?>,
|
||||
vehicleModel: <?= json_encode($vehicleMaster['model'] ?? '') ?>,
|
||||
vehicleVariant: <?= json_encode($vehicleMaster['variant'] ?? '') ?>,
|
||||
busy: false
|
||||
};
|
||||
|
||||
const dmSteps = ["Quote","Create","KYC","Pay","Policy"];
|
||||
@ -495,8 +519,23 @@ let dmSearchTimer = null;
|
||||
dmGo(window.DM.step, true);
|
||||
dmLoadMasters();
|
||||
dmBindMasterUi();
|
||||
dmHydrateVehicle();
|
||||
})();
|
||||
|
||||
function dmHydrateVehicle(){
|
||||
const make = window.DM.vehicleMake || '';
|
||||
const model = window.DM.vehicleModel || '';
|
||||
const code = window.DM.selectedVehicleCode || '';
|
||||
if (!code && !make) return;
|
||||
|
||||
if (make) {
|
||||
$('#f_make').val(make);
|
||||
dmLoadModels(make, model, code);
|
||||
} else if (code) {
|
||||
$('#f_vcode').val(code);
|
||||
$('#f_vcode_tag').text('Code ' + code);
|
||||
}
|
||||
}
|
||||
function dmFillSelect(sel, items, valueKey, labelFn, selected, emptyLabel){
|
||||
const $s = $(sel);
|
||||
$s.empty();
|
||||
@ -694,7 +733,18 @@ function dmAlert(msg, ok){
|
||||
}
|
||||
|
||||
function dmBusy(on){
|
||||
document.getElementById('dmPanel').classList.toggle('dm-loading', !!on);
|
||||
window.DM.busy = !!on;
|
||||
const panel = document.getElementById('dmPanel');
|
||||
if (panel) panel.classList.toggle('dm-loading', !!on);
|
||||
|
||||
// NHANCE global page loader (layout header)
|
||||
if (on) {
|
||||
$('.loader').show();
|
||||
$('.loader-mask').fadeIn();
|
||||
} else {
|
||||
$('.loader').fadeOut();
|
||||
$('.loader-mask').delay(350).fadeOut('slow');
|
||||
}
|
||||
}
|
||||
|
||||
function dmAddonFlags(){
|
||||
@ -777,6 +827,8 @@ function dmValidateRequired(fieldIds){
|
||||
}
|
||||
|
||||
function dmQuickQuote(){
|
||||
if (window.DM.busy) return;
|
||||
|
||||
const claimChip = document.querySelector('.dm-chip[data-group="claim"].on');
|
||||
const missing = dmValidateRequired([
|
||||
'f_reg', 'f_make', 'f_model', 'f_variant', 'f_vcode',
|
||||
@ -789,7 +841,13 @@ function dmQuickQuote(){
|
||||
return;
|
||||
}
|
||||
|
||||
const tagEnquiry = ($('#enquiryIdTag').text() || '').trim();
|
||||
const enquiryId = window.DM.enquiryId
|
||||
|| (tagEnquiry && tagEnquiry !== '— new —' ? tagEnquiry : null);
|
||||
|
||||
const body = {
|
||||
quote_id: window.DM.quoteId || null,
|
||||
enquiry_id: enquiryId,
|
||||
license_plate_number: $('#f_reg').val().trim(),
|
||||
vehicle_maincode: $('#f_vcode').val().trim(),
|
||||
registration_date: $('#f_reg_date').val(),
|
||||
@ -805,8 +863,7 @@ function dmQuickQuote(){
|
||||
previous_ncb: $('#f_prev_ncb').val() || 'ZERO',
|
||||
previous_policy_type: $('#f_prev_ptype').val() || '',
|
||||
is_claim_in_last_year: claimChip ? claimChip.dataset.val === '1' : false,
|
||||
coverages: dmAddonFlags(),
|
||||
enquiry_id: window.DM.quoteId ? ($('#enquiryIdTag').text().trim() !== '— new —' ? $('#enquiryIdTag').text().trim() : null) : null
|
||||
coverages: dmAddonFlags()
|
||||
};
|
||||
|
||||
if (body.start_date){
|
||||
@ -821,6 +878,7 @@ function dmQuickQuote(){
|
||||
.done(function(res){
|
||||
if (!res.status){ dmAlert(res.message || 'Quick quote failed'); return; }
|
||||
window.DM.quoteId = res.data.quote_id;
|
||||
window.DM.enquiryId = res.data.enquiry_id;
|
||||
$('#enquiryIdTag').text(res.data.enquiry_id);
|
||||
const prem = res.data.premium != null ? Number(res.data.premium) : null;
|
||||
const idv = res.data.idv != null ? Number(res.data.idv) : null;
|
||||
@ -840,12 +898,21 @@ function dmQuickQuote(){
|
||||
const parts = Object.keys(errs).map(function(k){ return errs[k]; });
|
||||
if (parts.length) msg = parts.join(' | ');
|
||||
}
|
||||
// If shell was created, keep continuity for retry
|
||||
if (xhr.responseJSON && xhr.responseJSON.data) {
|
||||
if (xhr.responseJSON.data.quote_id) window.DM.quoteId = xhr.responseJSON.data.quote_id;
|
||||
if (xhr.responseJSON.data.enquiry_id) {
|
||||
window.DM.enquiryId = xhr.responseJSON.data.enquiry_id;
|
||||
$('#enquiryIdTag').text(xhr.responseJSON.data.enquiry_id);
|
||||
}
|
||||
}
|
||||
dmAlert(msg);
|
||||
})
|
||||
.always(function(){ dmBusy(false); });
|
||||
}
|
||||
|
||||
function dmCreateQuote(){
|
||||
if (window.DM.busy) return;
|
||||
if (!window.DM.quoteId){ dmAlert('Run quick quote first.'); return; }
|
||||
|
||||
// Keep QQ + CQ chassis/engine in sync when editing on step 2
|
||||
|
||||
@ -73,6 +73,8 @@ function openFilterNav(){ document.getElementById('digit-filter-sidebar').style.
|
||||
function closeFilterNav(){ document.getElementById('digit-filter-sidebar').style.width = '0'; }
|
||||
|
||||
function applyFilters(){
|
||||
$('.loader').show();
|
||||
$('.loader-mask').fadeIn();
|
||||
$.ajax({
|
||||
url: '<?= base_url('digit-motor/list') ?>',
|
||||
type: 'POST',
|
||||
@ -92,6 +94,10 @@ function applyFilters(){
|
||||
error: function(){
|
||||
if (window.toastr) toastr.error('Failed to load filtered list.', 'Error');
|
||||
else alert('Failed to load filtered list.');
|
||||
},
|
||||
complete: function(){
|
||||
$('.loader').fadeOut();
|
||||
$('.loader-mask').delay(350).fadeOut('slow');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -535,6 +535,24 @@
|
||||
<button id="submitPlacement" class="btn btn-primary" onclick="checkTheTableDataChanged(6)">Placement</button>
|
||||
<button id="viewDemography" class="btn btn-primary" onclick="viewDemography()">Demography</button>
|
||||
<button id="viewDocs" class="btn btn-primary" onclick="viewDocs()">Docs</button>
|
||||
<?php if (in_array((int) ($lead_data['policy_type_id'] ?? 0), [2, 3, 4, 5], true)) { ?>
|
||||
<div class="btn-group" id="pptBtnGroup" style="display:none;">
|
||||
<button type="button" class="btn btn-primary dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
PPT
|
||||
</button>
|
||||
<div class="dropdown-menu">
|
||||
<a class="dropdown-item" href="#" onclick="startPptGeneration(); return false;">Generate</a>
|
||||
<a class="dropdown-item" href="#" onclick="downloadGmcQcrPdf(); return false;">Download</a>
|
||||
</div>
|
||||
</div>
|
||||
<?php } ?>
|
||||
|
||||
<!-- Floating Generate PDF button (hidden until proposal selection mode) -->
|
||||
<div id="pptFloatingBar" style="display:none; position:fixed; bottom:20px; left:50%; transform:translateX(-50%); z-index:9999; background:#fff; border-radius:8px; box-shadow:0 4px 20px rgba(0,0,0,0.25); padding:10px 20px; text-align:center;">
|
||||
<span style="font-size:13px; color:#555; margin-right:10px;">Select proposals to include in PDF, then click:</span>
|
||||
<button type="button" class="btn btn-success" onclick="confirmGeneratePdf()">Generate PDF</button>
|
||||
<button type="button" class="btn btn-secondary ml-2" onclick="cancelPptSelection()">Cancel</button>
|
||||
</div>
|
||||
<input type="hidden" id="lead_id" name="lead_id" value="<?= isset($lead_id) ? $lead_id : '' ?>">
|
||||
<input type="hidden" id="rfq_primaryKey" name="rfq_primaryKey" value="<?= isset($rfq_data['id']) ? $rfq_data['id'] : '' ?>">
|
||||
<input type="hidden" id="qcr_count" value="<?= isset($qcr_count) ? $qcr_count : 0 ?>">
|
||||
@ -1328,6 +1346,7 @@
|
||||
$('#submitData').text('Save RFQ');
|
||||
$('#submitMail').text('Send Insurer Mail');
|
||||
$('#submitPlacement').hide();
|
||||
$('#pptBtnGroup').hide();
|
||||
document.getElementById("second_table")?.remove();
|
||||
|
||||
if(data){
|
||||
@ -1353,6 +1372,7 @@
|
||||
$('#second_table').show();
|
||||
$('#submitPlacement').show();
|
||||
$('#submitQCRData').hide();
|
||||
$('#pptBtnGroup').show();
|
||||
}
|
||||
|
||||
if(RFQ_or_QCR == 1 && insurer_count > 0){
|
||||
@ -7942,6 +7962,141 @@ function appendMultiFileData(data) {
|
||||
|
||||
}
|
||||
|
||||
var pptSelectionMode = false;
|
||||
|
||||
function startPptGeneration() {
|
||||
pptSelectionMode = true;
|
||||
// Add checkboxes to each proposal header (top-level th in first row of rfqTable)
|
||||
let headerRow = document.querySelector('#rfqTable thead tr');
|
||||
if (!headerRow) return;
|
||||
let ths = headerRow.querySelectorAll('th');
|
||||
ths.forEach(function(th, idx) {
|
||||
// Skip Sno, Item Key, Particulars, Action columns
|
||||
let keySpan = th.querySelector('.key_name');
|
||||
let displaySpan = th.querySelector('.display_name');
|
||||
if (!keySpan && !displaySpan) return;
|
||||
let keyName = (keySpan ? keySpan.innerText.trim() : '');
|
||||
let displayName = (displaySpan ? displaySpan.innerText.trim() : '');
|
||||
if (!keyName || keyName === 'Sno') return;
|
||||
if (keyName.toLowerCase() === 'action') return;
|
||||
|
||||
// Already has checkbox?
|
||||
if (th.querySelector('.ppt-select-cb')) return;
|
||||
|
||||
let cb = document.createElement('input');
|
||||
cb.type = 'checkbox';
|
||||
cb.checked = true;
|
||||
cb.className = 'ppt-select-cb';
|
||||
cb.dataset.proposalKey = keyName;
|
||||
cb.style.cssText = 'margin-left:6px; transform:scale(1.3); vertical-align:middle; cursor:pointer;';
|
||||
cb.title = 'Include in PDF';
|
||||
th.appendChild(cb);
|
||||
});
|
||||
|
||||
$('#pptFloatingBar').fadeIn();
|
||||
toastr.info('Select the proposals you want to include in the PDF', 'PPT Generation');
|
||||
}
|
||||
|
||||
function cancelPptSelection() {
|
||||
pptSelectionMode = false;
|
||||
document.querySelectorAll('.ppt-select-cb').forEach(function(cb) { cb.remove(); });
|
||||
$('#pptFloatingBar').fadeOut();
|
||||
}
|
||||
|
||||
function confirmGeneratePdf() {
|
||||
let selected = [];
|
||||
document.querySelectorAll('.ppt-select-cb:checked').forEach(function(cb) {
|
||||
selected.push(cb.dataset.proposalKey);
|
||||
});
|
||||
|
||||
if (selected.length === 0) {
|
||||
toastr.warning('Please select at least one proposal', 'Warning');
|
||||
return;
|
||||
}
|
||||
|
||||
// Clean up selection UI
|
||||
cancelPptSelection();
|
||||
|
||||
let lead_id = $('#lead_id').val();
|
||||
if (!lead_id) {
|
||||
toastr.error('Lead ID is required', 'Error');
|
||||
return;
|
||||
}
|
||||
|
||||
$('.loader').fadeIn();
|
||||
$('.loader-mask').fadeIn();
|
||||
|
||||
$.ajax({
|
||||
url: '<?= base_url('rfq/generateGmcQcrPdf') ?>',
|
||||
type: 'POST',
|
||||
data: { lead_id: lead_id, proposals: selected },
|
||||
dataType: 'json',
|
||||
success: function (res) {
|
||||
$('.loader').fadeOut();
|
||||
$('.loader-mask').delay(350).fadeOut('slow');
|
||||
if (res && res.status) {
|
||||
toastr.success(res.message || 'PDF generated successfully', 'Success');
|
||||
} else {
|
||||
toastr.error((res && res.message) ? res.message : 'Failed to generate PDF', 'Error');
|
||||
}
|
||||
},
|
||||
error: function (xhr) {
|
||||
$('.loader').fadeOut();
|
||||
$('.loader-mask').delay(350).fadeOut('slow');
|
||||
let msg = 'Failed to generate PDF';
|
||||
try {
|
||||
const parsed = JSON.parse(xhr.responseText);
|
||||
if (parsed && parsed.message) msg = parsed.message;
|
||||
} catch (e) {}
|
||||
toastr.error(msg, 'Error');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function downloadGmcQcrPdf() {
|
||||
let lead_id = $('#lead_id').val();
|
||||
if (!lead_id) {
|
||||
toastr.error('Lead ID is required', 'Error');
|
||||
return;
|
||||
}
|
||||
|
||||
const url = '<?= base_url('rfq/downloadGmcQcrPdf/') ?>' + lead_id;
|
||||
$('.loader').fadeIn();
|
||||
$('.loader-mask').fadeIn();
|
||||
|
||||
fetch(url, { credentials: 'same-origin' })
|
||||
.then(async function (response) {
|
||||
const contentType = response.headers.get('Content-Type') || '';
|
||||
if (!response.ok || contentType.indexOf('application/json') !== -1) {
|
||||
let message = 'No generated PDF found. Please click Generate first.';
|
||||
try {
|
||||
const data = await response.json();
|
||||
if (data && data.message) message = data.message;
|
||||
} catch (e) {}
|
||||
throw new Error(message);
|
||||
}
|
||||
const disposition = response.headers.get('Content-Disposition') || '';
|
||||
let filename = 'GMC Quote comparison.pdf';
|
||||
const match = disposition.match(/filename=\"?([^\";]+)\"?/i);
|
||||
if (match && match[1]) filename = match[1];
|
||||
const blob = await response.blob();
|
||||
const link = document.createElement('a');
|
||||
link.href = window.URL.createObjectURL(blob);
|
||||
link.download = filename;
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
link.remove();
|
||||
window.URL.revokeObjectURL(link.href);
|
||||
})
|
||||
.catch(function (err) {
|
||||
toastr.error(err.message || 'Failed to download PDF', 'Error');
|
||||
})
|
||||
.finally(function () {
|
||||
$('.loader').fadeOut();
|
||||
$('.loader-mask').delay(350).fadeOut('slow');
|
||||
});
|
||||
}
|
||||
|
||||
function toggleButtons(status) {
|
||||
$("#save_placement_btn").show();
|
||||
|
||||
|
||||
BIN
public/assets/qcr_gmc/static_back.pdf
Normal file
BIN
public/assets/qcr_gmc/static_back.pdf
Normal file
Binary file not shown.
BIN
public/assets/qcr_gmc/static_front.pdf
Normal file
BIN
public/assets/qcr_gmc/static_front.pdf
Normal file
Binary file not shown.
@ -1,374 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* Smoke test: claim_report table + Claim Report dashboard.
|
||||
*
|
||||
* Run:
|
||||
* php tests/smoke_claim_report_dashboard.php [policy_id]
|
||||
* php tests/smoke_claim_report_dashboard.php 4687
|
||||
*
|
||||
* Checks:
|
||||
* - claim_report table / schema
|
||||
* - claims_source resolver (claim_report vs ticket_master)
|
||||
* - KPI model + controller (mirror of V2)
|
||||
* - routes under util/ and employeeRest/
|
||||
* - optional HTTP auth-gate reachability
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
ob_start();
|
||||
|
||||
define('FCPATH', __DIR__ . '/../public/');
|
||||
chdir(FCPATH);
|
||||
|
||||
require FCPATH . '../app/Config/Paths.php';
|
||||
$paths = new Config\Paths();
|
||||
require rtrim($paths->systemDirectory, '\\/ ') . DIRECTORY_SEPARATOR . 'bootstrap.php';
|
||||
require_once SYSTEMPATH . 'Config/DotEnv.php';
|
||||
(new CodeIgniter\Config\DotEnv(ROOTPATH))->load();
|
||||
|
||||
defined('ENVIRONMENT') || define('ENVIRONMENT', env('CI_ENVIRONMENT', 'development'));
|
||||
|
||||
$boot = APPPATH . 'Config/Boot/' . ENVIRONMENT . '.php';
|
||||
if (is_file($boot)) {
|
||||
require_once $boot;
|
||||
}
|
||||
|
||||
helper('url');
|
||||
|
||||
use App\Controllers\ClaimReportDashboardController;
|
||||
use App\Models\ClaimReportDashboardModel;
|
||||
use App\Models\ClaimsCollectionV2DashboardModel;
|
||||
use Config\Services;
|
||||
|
||||
$policyId = isset($argv[1]) ? (int) $argv[1] : 0;
|
||||
$pass = 0;
|
||||
$fail = 0;
|
||||
$results = [];
|
||||
|
||||
function ok(string $label, bool $cond, string $detail = ''): void
|
||||
{
|
||||
global $pass, $fail, $results;
|
||||
if ($cond) {
|
||||
$pass++;
|
||||
$results[] = '[PASS] ' . $label . ($detail ? " — {$detail}" : '');
|
||||
} else {
|
||||
$fail++;
|
||||
$results[] = '[FAIL] ' . $label . ($detail ? " — {$detail}" : '');
|
||||
}
|
||||
}
|
||||
|
||||
function info(string $line): void
|
||||
{
|
||||
global $results;
|
||||
$results[] = $line;
|
||||
}
|
||||
|
||||
$db = \Config\Database::connect();
|
||||
|
||||
// Default: prefer a policy that already has claim_report or ticket_master rows.
|
||||
if ($policyId <= 0) {
|
||||
$pick = null;
|
||||
if ($db->tableExists('claim_report')) {
|
||||
$pick = $db->query(
|
||||
'SELECT client_policy_id AS id FROM claim_report WHERE is_active = 1 AND client_policy_id > 0 ORDER BY id DESC LIMIT 1'
|
||||
)->getRowArray();
|
||||
}
|
||||
if (empty($pick)) {
|
||||
$pick = $db->query(
|
||||
"SELECT client_policy_id AS id FROM ticket_master
|
||||
WHERE is_active = 1 AND client_policy_id > 0
|
||||
AND (claim_dump_ref_id IS NOT NULL OR file_id IS NOT NULL)
|
||||
ORDER BY id DESC LIMIT 1"
|
||||
)->getRowArray();
|
||||
}
|
||||
if (empty($pick)) {
|
||||
$pick = $db->query('SELECT id FROM client_policy WHERE is_active = 1 ORDER BY id DESC LIMIT 1')->getRowArray();
|
||||
}
|
||||
$policyId = (int) ($pick['id'] ?? 0);
|
||||
}
|
||||
|
||||
$model = new ClaimReportDashboardModel();
|
||||
$kpiMap = ClaimsCollectionV2DashboardModel::KPI_MAP;
|
||||
|
||||
info('=== Schema ===');
|
||||
|
||||
$hasTable = $db->tableExists('claim_report');
|
||||
ok('claim_report table exists', $hasTable);
|
||||
|
||||
if ($hasTable) {
|
||||
$fields = $db->getFieldNames('claim_report');
|
||||
$required = [
|
||||
'id', 'tpa_id', 'client_id', 'client_policy_id', 'file_id', 'ticket_id',
|
||||
'source_table', 'source_row_id', 'claim_number', 'claim_amount', 'approved_amount',
|
||||
'incurred_amount', 'tpa_claim_type', 'tpa_ailments', 'claim_status_id',
|
||||
'hospital_name', 'doa', 'dod', 'claim_dump_date', 'is_active',
|
||||
];
|
||||
$missing = array_values(array_diff($required, $fields));
|
||||
ok('claim_report required columns', $missing === [], $missing === [] ? count($fields) . ' cols' : 'missing: ' . implode(', ', $missing));
|
||||
|
||||
$indexes = $db->query('SHOW INDEX FROM `claim_report`')->getResultArray();
|
||||
$indexNames = array_unique(array_column($indexes, 'Key_name'));
|
||||
ok('unique key uq_claim_report_policy_claim', in_array('uq_claim_report_policy_claim', $indexNames, true));
|
||||
}
|
||||
|
||||
info('');
|
||||
info('=== Source resolver (policy_id=' . $policyId . ') ===');
|
||||
|
||||
$policy = $policyId > 0
|
||||
? $db->table('client_policy')->select('id, tpa_id, policy_no')->where('id', $policyId)->get()->getRowArray()
|
||||
: null;
|
||||
if (empty($policy)) {
|
||||
info('[WARN] policy id ' . $policyId . ' not found — resolver/KPI checks will use empty data');
|
||||
ok('policy id resolved for test', $policyId > 0, 'policy_id=' . $policyId);
|
||||
} else {
|
||||
ok('policy exists', true, 'tpa_id=' . ($policy['tpa_id'] ?? 'null') . ' policy_no=' . ($policy['policy_no'] ?? ''));
|
||||
}
|
||||
|
||||
$tpaTableMap = [
|
||||
(int) env('VIDAL_PRIMARY_KEY_CONSTANT') => 'claims_dump_vidal',
|
||||
(int) env('ABHI_PRIMARY_KEY_CONSTANT') => 'claims_dump_abhi',
|
||||
(int) env('MEDI_ASSIST_PRIMARY_KEY_CONSTANT') => 'claims_dump_medi_assist',
|
||||
(int) env('FHPL_PRIMARY_KEY_CONSTANT') => 'claims_dump_fhpl',
|
||||
(int) env('R_CARE_PRIMARY_KEY_CONSTANT') => 'claims_dump_reliance',
|
||||
(int) env('ICICI_PRIMARY_KEY_CONSTANT') => 'claims_dump_icici',
|
||||
];
|
||||
|
||||
$tpaId = (int) ($policy['tpa_id'] ?? 0);
|
||||
$dumpTable = $tpaTableMap[$tpaId] ?? null;
|
||||
$dumpExists = $dumpTable !== null && $db->tableExists($dumpTable);
|
||||
$reportCount = $hasTable
|
||||
? (int) $db->table('claim_report')->where('client_policy_id', $policyId)->where('is_active', 1)->countAllResults()
|
||||
: 0;
|
||||
$tmCount = (int) $db->table('ticket_master')->where('client_policy_id', $policyId)->where('is_active', 1)->countAllResults();
|
||||
|
||||
info('[INFO] dump_table=' . ($dumpTable ?? 'none') . ' exists=' . ($dumpExists ? 'yes' : 'no'));
|
||||
info('[INFO] claim_report rows=' . $reportCount . ' ticket_master rows=' . $tmCount);
|
||||
|
||||
$expectedSource = ($dumpExists && $reportCount > 0) ? 'claim_report' : 'ticket_master';
|
||||
$actualSource = $model->resolveClaimsTable($policyId);
|
||||
ok('resolveClaimsTable matches expectation', $actualSource === $expectedSource, "expected={$expectedSource} actual={$actualSource}");
|
||||
|
||||
info('');
|
||||
info('=== KPI model ===');
|
||||
|
||||
ok('KPI_MAP count', count($kpiMap) === 36, (string) count($kpiMap));
|
||||
ok('id 207 maps to incurred_ratio', ($kpiMap[207] ?? '') === 'incurred_ratio');
|
||||
|
||||
try {
|
||||
$rows = $model->policy_exposure_summary($policyId);
|
||||
ok('model policy_exposure_summary', is_array($rows), 'rows=' . count($rows));
|
||||
} catch (Throwable $e) {
|
||||
ok('model policy_exposure_summary', false, $e->getMessage());
|
||||
}
|
||||
|
||||
try {
|
||||
$rows = $model->getKpi('incurred_ratio', $policyId);
|
||||
ok('model getKpi(incurred_ratio)', is_array($rows), 'rows=' . count($rows));
|
||||
} catch (Throwable $e) {
|
||||
ok('model getKpi(incurred_ratio)', false, $e->getMessage());
|
||||
}
|
||||
|
||||
try {
|
||||
$rows = $model->getKpi('total_claims', $policyId);
|
||||
ok('model getKpi(total_claims)', is_array($rows), 'rows=' . count($rows));
|
||||
} catch (Throwable $e) {
|
||||
ok('model getKpi(total_claims)', false, $e->getMessage());
|
||||
}
|
||||
|
||||
try {
|
||||
$rows = $model->getKpi('claim_amount_by_gender', $policyId);
|
||||
ok('model getKpi(claim_amount_by_gender)', is_array($rows), 'rows=' . count($rows));
|
||||
} catch (Throwable $e) {
|
||||
ok('model getKpi(claim_amount_by_gender)', false, $e->getMessage());
|
||||
}
|
||||
|
||||
$sampleKpis = [
|
||||
'policy_exposure_summary',
|
||||
'premium_as_on_date',
|
||||
'total_claims',
|
||||
'incurred_amount',
|
||||
'incurred_ratio',
|
||||
'claim_amount_by_gender',
|
||||
'top_5_hospitals_by_incurred_amount',
|
||||
'cashless_claim_amt',
|
||||
'top_10_ailments_by_claim_count',
|
||||
];
|
||||
$samplePass = 0;
|
||||
foreach ($sampleKpis as $method) {
|
||||
try {
|
||||
$rows = $model->getKpi($method, $policyId);
|
||||
if (is_array($rows)) {
|
||||
$samplePass++;
|
||||
}
|
||||
} catch (Throwable $e) {
|
||||
info('[WARN] sample KPI ' . $method . ': ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
ok('sample claim KPIs runnable', $samplePass === count($sampleKpis), "{$samplePass}/" . count($sampleKpis));
|
||||
|
||||
try {
|
||||
$all = $model->getAllKpis($policyId);
|
||||
$metaSource = $all['_meta']['claims_source'] ?? null;
|
||||
unset($all['_meta']);
|
||||
ok('model getAllKpis', count($all) === 36, 'kpis=' . count($all));
|
||||
ok('getAllKpis _meta.claims_source', $metaSource === $actualSource, (string) $metaSource);
|
||||
} catch (Throwable $e) {
|
||||
// Older MySQL without CTE support can fail on age_band (WITH ...); not claim_report-specific.
|
||||
info('[WARN] getAllKpis: ' . $e->getMessage());
|
||||
info('[SKIP] model getAllKpis — CTE/MySQL limitation; sample KPIs already verified');
|
||||
ok('resolver still available after getAllKpis skip', $actualSource !== '', 'source=' . $actualSource);
|
||||
}
|
||||
|
||||
info('');
|
||||
info('=== Controller ===');
|
||||
|
||||
$request = Services::request(null, false);
|
||||
$response = Services::response();
|
||||
$request->setGlobal('get', ['client_policy' => (string) $policyId]);
|
||||
|
||||
$controller = new ClaimReportDashboardController();
|
||||
$controller->initController($request, $response, service('logger'));
|
||||
|
||||
$slugResp = json_decode($controller->kpi('incurred_ratio')->getJSON(), true);
|
||||
ok(
|
||||
'controller kpi by slug',
|
||||
($slugResp['status'] ?? false) === true
|
||||
&& ($slugResp['kpi'] ?? '') === 'incurred_ratio'
|
||||
&& ($slugResp['claims_source'] ?? '') === $actualSource,
|
||||
'source=' . ($slugResp['claims_source'] ?? 'null')
|
||||
);
|
||||
|
||||
$idResp = json_decode($controller->kpi('207')->getJSON(), true);
|
||||
ok('controller kpi by id 207', ($idResp['status'] ?? false) === true && ($idResp['kpi_id'] ?? 0) === 207);
|
||||
|
||||
$badResp = json_decode($controller->kpi('not_a_kpi')->getJSON(), true);
|
||||
ok('controller unknown kpi 404', ($badResp['status'] ?? true) === false);
|
||||
|
||||
$missingPolicyReq = Services::request(null, false);
|
||||
$missingPolicyReq->setGlobal('get', []);
|
||||
$missingCtrl = new ClaimReportDashboardController();
|
||||
$missingCtrl->initController($missingPolicyReq, Services::response(), service('logger'));
|
||||
$missingResp = json_decode($missingCtrl->all()->getJSON(), true);
|
||||
ok('controller all requires policy', ($missingResp['status'] ?? true) === false);
|
||||
|
||||
try {
|
||||
$allResp = json_decode($controller->all()->getJSON(), true);
|
||||
ok(
|
||||
'controller all KPIs',
|
||||
($allResp['status'] ?? false) === true
|
||||
&& count($allResp['data'] ?? []) === 36
|
||||
&& ($allResp['claims_source'] ?? '') === $actualSource,
|
||||
'source=' . ($allResp['claims_source'] ?? 'null') . ' kpis=' . count($allResp['data'] ?? [])
|
||||
);
|
||||
} catch (Throwable $e) {
|
||||
info('[WARN] controller all: ' . $e->getMessage());
|
||||
info('[SKIP] controller all KPIs — CTE/MySQL limitation on age_band');
|
||||
ok('controller single-kpi path still healthy', ($slugResp['status'] ?? false) === true);
|
||||
}
|
||||
|
||||
try {
|
||||
$debugOut = $controller->debug($policyId);
|
||||
$debugBody = is_string($debugOut) ? $debugOut : $debugOut->getBody();
|
||||
$debugJson = json_decode($debugBody, true);
|
||||
ok(
|
||||
'controller debug JSON',
|
||||
($debugJson['status'] ?? false) === true
|
||||
&& isset($debugJson['data'])
|
||||
&& ($debugJson['claims_source'] ?? '') === $actualSource
|
||||
);
|
||||
} catch (Throwable $e) {
|
||||
info('[WARN] controller debug: ' . $e->getMessage());
|
||||
info('[SKIP] controller debug — CTE/MySQL limitation on age_band');
|
||||
ok('controller debug skipped safely', true);
|
||||
}
|
||||
|
||||
$previewOut = $controller->preview($policyId);
|
||||
$previewHtml = is_string($previewOut) ? $previewOut : $previewOut->getBody();
|
||||
ok(
|
||||
'controller preview HTML',
|
||||
str_contains($previewHtml, 'kpi-grid')
|
||||
&& str_contains($previewHtml, 'claims-collection-report')
|
||||
);
|
||||
|
||||
info('');
|
||||
info('=== Routes (static check of Routes.php) ===');
|
||||
|
||||
$routesFile = APPPATH . 'Config/Routes.php';
|
||||
$routesSrc = is_file($routesFile) ? (string) file_get_contents($routesFile) : '';
|
||||
ok('Routes.php readable', $routesSrc !== '');
|
||||
ok(
|
||||
'Routes.php defines claims-collection-report group',
|
||||
str_contains($routesSrc, "group('claims-collection-report'")
|
||||
);
|
||||
ok(
|
||||
'Routes.php wires ClaimReportDashboardController',
|
||||
substr_count($routesSrc, 'ClaimReportDashboardController::') >= 6,
|
||||
'refs=' . substr_count($routesSrc, 'ClaimReportDashboardController::')
|
||||
);
|
||||
ok(
|
||||
'Routes.php has util + employeeRest groups for report',
|
||||
substr_count($routesSrc, "group('claims-collection-report'") >= 2,
|
||||
'groups=' . substr_count($routesSrc, "group('claims-collection-report'")
|
||||
);
|
||||
|
||||
info('');
|
||||
info('=== Spot-check vs V2 (same policy) ===');
|
||||
|
||||
try {
|
||||
$v2 = new ClaimsCollectionV2DashboardModel();
|
||||
$v2Rows = $v2->getKpi('total_claims', $policyId);
|
||||
$crRows = $model->getKpi('total_claims', $policyId);
|
||||
ok('total_claims both return arrays', is_array($v2Rows) && is_array($crRows), 'v2=' . count($v2Rows) . ' report=' . count($crRows));
|
||||
|
||||
// When source is ticket_master, totals should match V2 closely.
|
||||
if ($actualSource === 'ticket_master' && $v2Rows !== [] && $crRows !== []) {
|
||||
$v2Val = json_encode($v2Rows[0] ?? []);
|
||||
$crVal = json_encode($crRows[0] ?? []);
|
||||
ok('total_claims matches V2 when source=ticket_master', $v2Val === $crVal, $crVal ?: 'empty');
|
||||
} else {
|
||||
info('[INFO] skip strict V2 equality (source=' . $actualSource . ')');
|
||||
ok('total_claims callable on both models', true, 'skipped equality');
|
||||
}
|
||||
} catch (Throwable $e) {
|
||||
ok('spot-check vs V2', false, $e->getMessage());
|
||||
}
|
||||
|
||||
$baseUrl = rtrim((string) env('app.baseURL', ''), '/');
|
||||
if ($baseUrl !== '') {
|
||||
info('');
|
||||
info('=== HTTP auth gate checks (no session/token) ===');
|
||||
$urls = [
|
||||
'MVC preview' => $baseUrl . '/util/claims-collection-report/preview?client_policy=' . $policyId,
|
||||
'MVC kpi slug' => $baseUrl . '/util/claims-collection-report/kpi/incurred_ratio?client_policy=' . $policyId,
|
||||
'MVC kpi id' => $baseUrl . '/util/claims-collection-report/kpi/207?client_policy=' . $policyId,
|
||||
'MVC all' => $baseUrl . '/util/claims-collection-report/all?client_policy=' . $policyId,
|
||||
'MVC debug' => $baseUrl . '/util/claims-collection-report/debug?client_policy=' . $policyId,
|
||||
'JWT kpi slug' => $baseUrl . '/employeeRest/claims-collection-report/kpi/incurred_ratio?client_policy=' . $policyId,
|
||||
'JWT debug' => $baseUrl . '/employeeRest/claims-collection-report/debug?client_policy=' . $policyId,
|
||||
];
|
||||
foreach ($urls as $label => $url) {
|
||||
$ctx = stream_context_create(['http' => ['ignore_errors' => true, 'timeout' => 10]]);
|
||||
$body = @file_get_contents($url, false, $ctx);
|
||||
$code = 0;
|
||||
if (isset($http_response_header[0]) && preg_match('/\s(\d{3})\s/', $http_response_header[0], $m)) {
|
||||
$code = (int) $m[1];
|
||||
}
|
||||
$blocked = in_array($code, [401, 403, 302, 303], true);
|
||||
$reachable = $code >= 200 && $code < 500;
|
||||
ok("HTTP {$label} (" . ($code ?: 'no connection') . ')', $blocked || $reachable, $url);
|
||||
}
|
||||
} else {
|
||||
info('[SKIP] HTTP checks — app.baseURL not set in .env');
|
||||
}
|
||||
|
||||
info('');
|
||||
info('=== Manual follow-ups ===');
|
||||
info('[HINT] Backfill: php spark claim:backfill-report --policy=' . $policyId);
|
||||
info('[HINT] After Job 2 / backfill, re-run this script and expect claims_source=claim_report when dump table exists.');
|
||||
|
||||
ob_end_clean();
|
||||
echo '=== Claim Report dashboard smoke test (policy_id=' . $policyId . ') ===' . PHP_EOL . PHP_EOL;
|
||||
echo implode(PHP_EOL, $results) . PHP_EOL;
|
||||
echo PHP_EOL . "=== Summary: {$pass} passed, {$fail} failed ===" . PHP_EOL;
|
||||
exit($fail > 0 ? 1 : 0);
|
||||
@ -1,36 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* Sync claim_report from TPA dump tables + ticket_master.
|
||||
*
|
||||
* Run:
|
||||
* php tests/sync_claim_report_from_dump.php
|
||||
* php tests/sync_claim_report_from_dump.php --policy=12
|
||||
* php tests/sync_claim_report_from_dump.php --tpa=mediassist --policy=12
|
||||
* php tests/sync_claim_report_from_dump.php --ticket-only
|
||||
* php tests/sync_claim_report_from_dump.php --limit=500
|
||||
*
|
||||
* Prefer spark (same logic):
|
||||
* php spark claim:sync-report --policy=12
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
$args = array_slice($argv, 1);
|
||||
$sparkArgs = ['claim:sync-report'];
|
||||
|
||||
foreach ($args as $arg) {
|
||||
if (str_starts_with($arg, '--')) {
|
||||
$sparkArgs[] = $arg;
|
||||
} elseif (ctype_digit($arg)) {
|
||||
$sparkArgs[] = '--policy=' . $arg;
|
||||
}
|
||||
}
|
||||
|
||||
$root = dirname(__DIR__);
|
||||
chdir($root);
|
||||
|
||||
$cmd = 'php spark ' . implode(' ', array_map('escapeshellarg', $sparkArgs));
|
||||
echo "Running: {$cmd}" . PHP_EOL . PHP_EOL;
|
||||
|
||||
passthru($cmd, $exitCode);
|
||||
exit($exitCode);
|
||||
Loading…
Reference in New Issue
Block a user