diff --git a/.github/agents/hms-ci4-migrator.agent.md b/.github/agents/hms-ci4-migrator.agent.md new file mode 100644 index 0000000..a03fcb7 --- /dev/null +++ b/.github/agents/hms-ci4-migrator.agent.md @@ -0,0 +1,60 @@ +--- +description: "Use when migrating CI3 modules to CI4, porting HMS controllers/models/views, implementing OPD/IPD/Pharmacy/Diagnosis/Finance/Lab features, fixing regressions after CI3→CI4 port, adding routes or auth permissions, creating migrations, implementing AJAX modal flows, print/Excel exports, or any Hospital Management System feature work in this CodeIgniter 4 project." +name: "HMS CI4 Migrator" +tools: [read, edit, search, execute, todo] +--- +You are an expert CI3→CI4 migration engineer for a Hospital Management System (HMS). Your job is to port, implement, and debug full-stack features in this CodeIgniter 4 project. + +## Project Context + +- **Legacy source**: `old_code_ci3/` — always read this first before porting. +- **Worklog**: `WORKLOG_COPILOT.md` — check for module status, backlog, and known issues. +- **UI framework**: NiceAdmin (Bootstrap). Use consistent card/modal/button patterns from existing CI4 views. +- **AJAX pattern**: modals open via `$.ajax` → return HTML partials; list refresh via named `window.refresh*()` functions. +- **DataTables**: Simple-DataTables (not jQuery DataTables). Check existing views for column config pattern. +- **Routing**: `autoRoute = false`. Every endpoint must be explicitly registered in `app/Config/Routes.php`. +- **Auth**: Permission keys are `module.action` entries in `app/Config/AuthGroups.php`. Sidebar menu is in `app/Views/partials/sidebar.php`. +- **Audit trails**: Follow `*_update` table pattern when editing master records (e.g. `hc_items_update`, `package_update`). + +## Core Workflow + +1. **Read legacy code first.** Before writing any CI4 code, read the corresponding CI3 controller/model/view from `old_code_ci3/`. +2. **Map endpoints.** List legacy routes → CI4 equivalents. Add both legacy aliases and clean CI4 routes where needed. +3. **Port full stack per feature.** For each endpoint: Controller method → Model method → Route entry → View file. +4. **Follow existing CI4 patterns.** Read one or two existing CI4 controllers/models/views in the same module before writing new ones. +5. **Database**: Use Query Builder only — no raw SQL unless unavoidable. No DB views; use model joins. Always check if DB columns/tables exist before referencing them. +6. **Migrations**: Create numbered migration files (`app/Database/Migrations/`) for new tables; follow existing timestamp naming. +7. **Exports**: Excel via response headers (`Content-Type: application/vnd.ms-excel`); print via dedicated view with no debug toolbar. mPDF is available for PDF exports. + +## HMS Domain Knowledge + +| Module | Key Tables | CI4 Controller | +|--------|-----------|----------------| +| OPD Charges | hc_items, hc_item_type, hc_items_insurance | Item.php | +| IPD Charges | ipd_items, ipd_item_type, ipd_items_insurance | ItemIpd.php | +| Packages | package, package_group, package_insurance | Package.php | +| Diagnosis/Lab | lab_*, diagnosis_* | Diagnosis.php | +| Pharmacy | med_*, v_product_stock (view) | Medical.php | +| Finance | finance_*, bank_*, cashbook | Finance.php | +| OPD | hc_opd, hc_visit | Opd.php | +| IPD | ibd_admission, ibd_discharge | IpdPatient.php | +| Patient | hc_patient | Patient.php | + +## Constraints + +- **Never use autoRoute.** Always register routes explicitly. +- **AJAX responses**: Return JSON for data endpoints; return HTML for partial/modal endpoints. Never mix. +- **No debug toolbar in Excel/print responses.** Use `$this->response->setHeader(...)` and `return $this->response->setBody(...)`. +- **Namespace constants**: Use `constant('NAME')` after `defined('NAME')` check — never bare `NAME` in namespaced controllers. +- **File uploads**: Use `$file->move()` with try/catch; verify `public/assets/images` is writable; return JSON error on failure. +- **Legacy form keys**: Accept both old and new POST key names when porting forms that mix CI3 JS with CI4 backend. +- **Modals**: Insert modal `
` blocks outside loops to prevent duplicate IDs. +- **Report edits**: When updating a verified report, require an edit reason and persist it in the log/audit table. + +## Output Expectations + +- Produce working, runnable PHP/HTML/JS — not pseudocode. +- Include route entries whenever a new controller method is added. +- Include auth permission key entries whenever a new module action is added. +- Update `WORKLOG_COPILOT.md` with a dated summary of what was implemented after completing a significant feature. +- Keep changes minimal and focused — do not refactor unrelated code. diff --git a/app/Commands/ImportAyushmanPackageMaster.php b/app/Commands/ImportAyushmanPackageMaster.php new file mode 100644 index 0000000..5e0cdcf --- /dev/null +++ b/app/Commands/ImportAyushmanPackageMaster.php @@ -0,0 +1,133 @@ + 'Optional CSV path. Defaults to writable/ayushman/105_PackageMasterReport.csv', + ]; + protected $options = [ + '--truncate' => 'Clear ayushman_package_master before importing.', + ]; + + public function run(array $params) + { + $csvPath = $params[0] ?? WRITEPATH . 'ayushman/105_PackageMasterReport.csv'; + if (! is_file($csvPath)) { + CLI::error('CSV file not found: ' . $csvPath); + + return; + } + + $db = Database::connect(); + if (! $db->tableExists('ayushman_package_master')) { + CLI::error('Table ayushman_package_master does not exist. Run php spark migrate first.'); + + return; + } + + if (CLI::getOption('truncate')) { + $db->table('ayushman_package_master')->truncate(); + CLI::write('Existing ayushman_package_master rows truncated.', 'yellow'); + } + + $handle = fopen($csvPath, 'rb'); + if ($handle === false) { + CLI::error('Unable to open CSV file: ' . $csvPath); + + return; + } + + $rowNo = 0; + $inserted = 0; + $updated = 0; + $skipped = 0; + $now = date('Y-m-d H:i:s'); + $table = $db->table('ayushman_package_master'); + + try { + while (($row = fgetcsv($handle)) !== false) { + $rowNo++; + + if ($rowNo <= 3) { + continue; + } + + $mapped = $this->mapCsvRow($row, basename($csvPath), $now); + if ($mapped === null) { + $skipped++; + continue; + } + + $existing = $db->table('ayushman_package_master') + ->select('id') + ->where('procedure_code', $mapped['procedure_code']) + ->get(1) + ->getRowArray(); + + if (is_array($existing) && ! empty($existing['id'])) { + $id = (int) $existing['id']; + unset($mapped['created_at']); + $table->where('id', $id)->update($mapped); + $updated++; + continue; + } + + $table->insert($mapped); + $inserted++; + } + } finally { + fclose($handle); + } + + CLI::write('Ayushman package import complete', 'green'); + CLI::write('CSV: ' . $csvPath); + CLI::write('Inserted: ' . $inserted, 'green'); + CLI::write('Updated: ' . $updated, 'yellow'); + CLI::write('Skipped: ' . $skipped); + } + + private function mapCsvRow(array $row, string $sourceFile, string $timestamp): ?array + { + $row = array_pad($row, 10, ''); + + $specialityCode = trim((string) ($row[0] ?? '')); + $specialityName = trim((string) ($row[1] ?? '')); + $procedureCode = trim((string) ($row[2] ?? '')); + $procedureName = trim((string) ($row[3] ?? '')); + + if ($procedureCode === '' && $procedureName === '') { + return null; + } + + $amountText = trim((string) ($row[4] ?? '0')); + $amountText = str_replace(',', '', $amountText); + $packageAmount = is_numeric($amountText) ? (float) $amountText : 0.0; + + return [ + 'speciality_code' => substr($specialityCode, 0, 50), + 'speciality_name' => substr($specialityName, 0, 255), + 'procedure_code' => substr($procedureCode, 0, 50), + 'procedure_name' => $procedureName, + 'package_amount' => number_format($packageAmount, 2, '.', ''), + 'preauth_required' => strtolower(trim((string) ($row[5] ?? ''))) === 'yes' ? 1 : 0, + 'procedure_type' => substr(trim((string) ($row[6] ?? '')), 0, 50), + 'government_reserved' => strtolower(trim((string) ($row[7] ?? ''))) === 'yes' ? 1 : 0, + 'pre_investigations' => trim((string) ($row[8] ?? '')), + 'post_investigations' => trim((string) ($row[9] ?? '')), + 'source_file' => substr($sourceFile, 0, 255), + 'source_sheet' => 'Sheet1', + 'created_at' => $timestamp, + 'updated_at' => $timestamp, + ]; + } +} \ No newline at end of file diff --git a/app/Config/Routes.php b/app/Config/Routes.php index 7dc2b67..623fbe5 100644 --- a/app/Config/Routes.php +++ b/app/Config/Routes.php @@ -345,6 +345,7 @@ $routes->group('billing', function($routes) { $routes->get('ipd/panel/(:num)', 'Billing\\Ipd::panel/$1'); $routes->get('ipd/panel/(:num)/tab/(:segment)', 'Billing\\Ipd::panelTab/$1/$2'); $routes->post('ipd/panel/(:num)/admission', 'Billing\Ipd::updateAdmission/$1'); + $routes->post('ipd/panel/(:num)/diagnosis/include/(:num)', 'Billing\\Ipd::updateDiagnosisChargeInclude/$1/$2'); $routes->post('ipd/panel/(:num)/discharge/update', 'Billing\\Ipd::updateDischargeProcess/$1'); $routes->post('ipd/panel/(:num)/discharge/discount/(:num)', 'Billing\\Ipd::updateDischargeDiscount/$1/$2'); $routes->post('ipd/panel/(:num)/discharge/charge/(:num)', 'Billing\\Ipd::updateDischargeCharge/$1/$2'); @@ -361,6 +362,11 @@ $routes->group('billing', function($routes) { $routes->post('ipd/nursing-charge/delete/(:num)/(:num)', 'Billing\\Ipd::deleteNursingCharge/$1/$2'); $routes->post('ipd/charge/package/(:num)', 'Billing\\Ipd::updateIpdChargePackage/$1'); $routes->post('ipd/package/add/(:num)', 'Billing\\Ipd::addIpdPackage/$1'); + $routes->get('ipd/panel/(:num)/ayushman/search', 'Billing\Ipd::searchAyushmanPackages/$1'); + $routes->post('ipd/panel/(:num)/ayushman/map/(:num)', 'Billing\Ipd::saveAyushmanPackageMapping/$1/$2'); + $routes->post('ipd/panel/(:num)/ayushman/checklist', 'Billing\Ipd::saveAyushmanChecklist/$1'); + $routes->get('ipd/panel/(:num)/ayushman/claim-sheet', 'Billing\Ipd::ayushmanClaimSheet/$1'); + $routes->get('ipd/panel/(:num)/ayushman/claim-sheet/(:num)', 'Billing\Ipd::ayushmanClaimSheet/$1/$2'); $routes->post('ipd/package/update/(:num)', 'Billing\\Ipd::updateIpdPackage/$1'); $routes->post('ipd/package/delete/(:num)', 'Billing\\Ipd::deleteIpdPackage/$1'); $routes->get('ipd/payment/modal/(:num)', 'Billing\\Ipd::loadPaymentModal/$1'); @@ -700,6 +706,12 @@ $routes->get('Report/insurance_ipd_report_data/(:segment)/(:segment)/(:segment)/ $routes->get('Report/insurance_combined_report', 'Report::insurance_combined_report'); $routes->get('Report/insurance_combined_report_data/(:segment)/(:segment)/(:segment)', 'Report::insurance_combined_report_data/$1/$2/$3'); $routes->get('Report/insurance_combined_report_data/(:segment)/(:segment)/(:segment)/(:num)', 'Report::insurance_combined_report_data/$1/$2/$3/$4'); +$routes->get('Report/ayushman_unmapped_report', 'Report::ayushman_unmapped_report'); +$routes->get('Report/ayushman_unmapped_report_data/(:segment)', 'Report::ayushman_unmapped_report_data/$1'); +$routes->get('Report/ayushman_unmapped_report_data/(:segment)/(:num)', 'Report::ayushman_unmapped_report_data/$1/$2'); +$routes->get('Report/ayushman_case_dashboard', 'Report::ayushman_case_dashboard'); +$routes->get('Report/ayushman_case_dashboard_data/(:segment)/(:segment)', 'Report::ayushman_case_dashboard_data/$1/$2'); +$routes->get('Report/ayushman_case_dashboard_data/(:segment)/(:segment)/(:num)', 'Report::ayushman_case_dashboard_data/$1/$2/$3'); // Legacy Report4 compatibility for Insurance Case reports $routes->get('Report4/echs_ipd_list_main', 'Report::echs_ipd_list_main'); diff --git a/app/Controllers/Billing/Ipd.php b/app/Controllers/Billing/Ipd.php index ccd9ce8..24615c4 100644 --- a/app/Controllers/Billing/Ipd.php +++ b/app/Controllers/Billing/Ipd.php @@ -439,6 +439,9 @@ class Ipd extends BaseController } $panelData['ipd_insurance_id'] = $insCompId; $panelData['package_list'] = $this->ipdModel->getPackageListForInsurance($insCompId); + $panelData['ayushman_specialities'] = $this->ipdModel->getAyushmanSpecialities(); + $panelData['all_package_options'] = $this->ipdModel->getAllPackageOptions(); + $panelData['ayushman_checklist_items'] = $this->ipdModel->getAyushmanChecklistForIpd($ipdId); return view('billing/ipd/panel_package', $panelData); } @@ -591,6 +594,37 @@ class Ipd extends BaseController ]); } + public function updateDiagnosisChargeInclude(int $ipdId, int $invoiceId) + { + $permission = $this->requireAnyPermission([ + 'billing.access', + 'billing.ipd.invoice', + ]); + if ($permission) { + return $permission; + } + + if (! $this->request->isAJAX()) { + return $this->response->setStatusCode(400)->setJSON(['ok' => false, 'error' => 'Invalid request']); + } + + if ($ipdId <= 0 || $invoiceId <= 0) { + return $this->response->setStatusCode(422)->setJSON(['ok' => false, 'error' => 'Invalid invoice reference']); + } + + $include = (int) ($this->request->getPost('ipd_include') ?? 0) === 1 ? 1 : 0; + $updated = $this->ipdModel->updateDiagnosisChargeInclude($ipdId, $invoiceId, $include); + + if (! $updated) { + return $this->response->setStatusCode(500)->setJSON(['ok' => false, 'error' => 'Unable to update include status']); + } + + return $this->response->setJSON([ + 'ok' => true, + 'ipd_include' => $include, + ]); + } + public function updateDischargeDiscount(int $ipdId, int $slot) { $permission = $this->requireAnyPermission([ @@ -1415,6 +1449,7 @@ class Ipd extends BaseController } $packageId = (int) ($this->request->getPost('package_id') ?? 0); + $ayushmanPackageId = (int) ($this->request->getPost('ayushman_package_id') ?? 0); $packageName = (string) ($this->request->getPost('package_name') ?? ''); $comment = (string) ($this->request->getPost('comment') ?? ''); $inputAmount = (float) ($this->request->getPost('input_amount') ?? 0); @@ -1429,13 +1464,61 @@ class Ipd extends BaseController $amount = $inputAmount; $orgCode = ''; + if ($ayushmanPackageId > 0) { + $ayushmanPackage = $this->ipdModel->getAyushmanPackageById($ayushmanPackageId); + if ($ayushmanPackage === null) { + return $this->response->setStatusCode(404)->setJSON([ + 'ok' => false, + 'error' => 'Ayushman package not found.', + ]); + } + + $packageId = (int) ($ayushmanPackage['linked_package_id'] ?? 0); + $packageName = (string) ($ayushmanPackage['procedure_name'] ?? $packageName); + $amount = (float) ($ayushmanPackage['package_amount'] ?? $amount); + $orgCode = (string) ($ayushmanPackage['procedure_code'] ?? ''); + + $descParts = []; + $specialityName = trim((string) ($ayushmanPackage['speciality_name'] ?? '')); + if ($specialityName !== '') { + $descParts[] = 'Speciality: ' . $specialityName; + } + $procedureType = trim((string) ($ayushmanPackage['procedure_type'] ?? '')); + if ($procedureType !== '') { + $descParts[] = 'Type: ' . $procedureType; + } + $preauthRequired = (int) ($ayushmanPackage['preauth_required'] ?? 0) === 1 ? 'Yes' : 'No'; + $descParts[] = 'Preauth: ' . $preauthRequired; + array_unshift($descParts, 'Ayushman'); + $packageDesc = substr(implode(' | ', $descParts), 0, 250); + + $commentParts = []; + if ($orgCode !== '') { + $commentParts[] = 'Ayushman Code: ' . $orgCode; + } + $preInvestigations = trim((string) ($ayushmanPackage['pre_investigations'] ?? '')); + if ($preInvestigations !== '') { + $commentParts[] = 'Pre: ' . $preInvestigations; + } + $postInvestigations = trim((string) ($ayushmanPackage['post_investigations'] ?? '')); + if ($postInvestigations !== '') { + $commentParts[] = 'Post: ' . $postInvestigations; + } + if ($comment !== '') { + $commentParts[] = $comment; + } + $comment = substr(implode(' | ', $commentParts), 0, 250); + } + if ($packageId > 1) { $package = $this->ipdModel->getPackageWithInsurance($packageId, $insCompId); if ($package) { - $packageName = (string) ($package->ipd_pakage_name ?? $packageName); - $packageDesc = (string) ($package->Pakage_description ?? $packageDesc); + if ($ayushmanPackageId <= 0) { + $packageName = (string) ($package->ipd_pakage_name ?? $packageName); + $packageDesc = (string) ($package->Pakage_description ?? $packageDesc); + $orgCode = (string) ($package->code ?? ''); + } $amount = (float) ($package->amount1 ?? $amount); - $orgCode = (string) ($package->code ?? ''); } } @@ -1474,6 +1557,181 @@ class Ipd extends BaseController ]); } + public function searchAyushmanPackages(int $ipdId) + { + $permission = $this->requireAnyPermission([ + 'billing.access', + 'billing.ipd.invoice', + ]); + if ($permission) { + return $permission; + } + + if (! $this->request->isAJAX()) { + return $this->response->setStatusCode(400)->setJSON(['ok' => false, 'error' => 'Invalid request']); + } + + $panelData = $this->ipdModel->getIpdPanelInfo($ipdId); + if (empty($panelData)) { + return $this->response->setStatusCode(404)->setJSON(['ok' => false, 'error' => 'IPD not found']); + } + + $query = trim((string) ($this->request->getGet('q') ?? '')); + $specialityCode = trim((string) ($this->request->getGet('speciality_code') ?? '')); + $items = $this->ipdModel->searchAyushmanPackages($query, $specialityCode, 30); + + return $this->response->setJSON([ + 'ok' => true, + 'items' => $items, + ]); + } + + public function saveAyushmanPackageMapping(int $ipdId, int $ayushmanId) + { + $permission = $this->requireAnyPermission([ + 'billing.access', + 'billing.ipd.invoice', + ]); + if ($permission) { + return $permission; + } + + if (! $this->request->isAJAX()) { + return $this->response->setStatusCode(400)->setJSON(['ok' => false, 'error' => 'Invalid request']); + } + + $panelData = $this->ipdModel->getIpdPanelInfo($ipdId); + if (empty($panelData)) { + return $this->response->setStatusCode(404)->setJSON(['ok' => false, 'error' => 'IPD not found']); + } + + $linkedPackageId = (int) ($this->request->getPost('linked_package_id') ?? 0); + $updated = $this->ipdModel->updateAyushmanPackageLink($ayushmanId, $linkedPackageId); + if (! $updated) { + return $this->response->setStatusCode(404)->setJSON(['ok' => false, 'error' => 'Ayushman treatment not found']); + } + + $mappedPackage = null; + if ($linkedPackageId > 0) { + foreach ($this->ipdModel->getAllPackageOptions() as $row) { + if ((int) ($row['id'] ?? 0) === $linkedPackageId) { + $mappedPackage = trim((string) ($row['ipd_pakage_name'] ?? '')); + break; + } + } + } + + return $this->response->setJSON([ + 'ok' => true, + 'linked_package_id' => $linkedPackageId, + 'linked_package_name' => $mappedPackage, + ]); + } + + public function saveAyushmanChecklist(int $ipdId) + { + $permission = $this->requireAnyPermission([ + 'billing.access', + 'billing.ipd.invoice', + ]); + if ($permission) { + return $permission; + } + + if (! $this->request->isAJAX()) { + return $this->response->setStatusCode(400)->setJSON(['ok' => false, 'error' => 'Invalid request']); + } + + $panelData = $this->ipdModel->getIpdPanelInfo($ipdId); + if (empty($panelData)) { + return $this->response->setStatusCode(404)->setJSON(['ok' => false, 'error' => 'IPD not found']); + } + + $ipdInfo = $panelData['ipd_info'] ?? null; + $caseId = (int) ($ipdInfo->case_id ?? 0); + if ($caseId <= 0) { + return $this->response->setStatusCode(422)->setJSON(['ok' => false, 'error' => 'No organization case linked to this IPD']); + } + + $fieldMap = array_flip($this->db->getFieldNames('organization_case_master') ?? []); + $update = []; + + if (isset($fieldMap['preauth_send'])) { + $update['preauth_send'] = (int) ($this->request->getPost('preauth_send') ?? 0) === 1 ? 1 : 0; + } + if (isset($fieldMap['doc_recd'])) { + $update['doc_recd'] = (int) ($this->request->getPost('doc_recd') ?? 0) === 1 ? 1 : 0; + } + if (isset($fieldMap['final_bill_send'])) { + $update['final_bill_send'] = (int) ($this->request->getPost('final_bill_send') ?? 0) === 1 ? 1 : 0; + } + if (isset($fieldMap['org_approved_status'])) { + $update['org_approved_status'] = trim((string) ($this->request->getPost('org_approved_status') ?? 'Under Process')); + } + if (isset($fieldMap['remark'])) { + $update['remark'] = trim((string) ($this->request->getPost('remark') ?? '')); + } + + if (isset($fieldMap['org_approved_status_id']) && $this->db->tableExists('org_approved_status')) { + $statusText = (string) ($update['org_approved_status'] ?? ''); + if ($statusText !== '') { + $statusRow = $this->db->table('org_approved_status') + ->select('id') + ->where('app_status', $statusText) + ->get(1) + ->getRowArray(); + if (is_array($statusRow) && ! empty($statusRow['id'])) { + $update['org_approved_status_id'] = (int) $statusRow['id']; + } + } + } + + if (empty($update)) { + return $this->response->setJSON(['ok' => true]); + } + + $this->db->table('organization_case_master') + ->where('id', $caseId) + ->update($update); + + return $this->response->setJSON([ + 'ok' => true, + 'message' => 'Ayushman checklist updated.', + ]); + } + + public function ayushmanClaimSheet(int $ipdId, int $output = 0) + { + $permission = $this->requireAnyPermission([ + 'billing.access', + 'billing.ipd.invoice', + ]); + if ($permission) { + return $permission; + } + + $panelData = $this->ipdModel->getIpdPanelInfo($ipdId); + if (empty($panelData)) { + return $this->response->setStatusCode(404)->setBody('IPD not found'); + } + + $checklistItems = $this->ipdModel->getAyushmanChecklistForIpd($ipdId); + $data = [ + 'ipd_info' => $panelData['ipd_info'] ?? null, + 'person_info' => $panelData['person_info'] ?? null, + 'checklist_items' => $checklistItems, + 'generated_at' => date('d-m-Y H:i:s'), + ]; + + $content = view('billing/ipd/ayushman_claim_sheet', $data); + if ($output === 1) { + ExportExcel($content, 'Ayushman_Claim_Sheet_' . $ipdId); + return $this->response->setBody(''); + } + + return $this->response->setBody($content); + } + public function updateIpdPackage(int $packageItemId) { $permission = $this->requireAnyPermission([ diff --git a/app/Controllers/Report.php b/app/Controllers/Report.php index 307231b..b0c1d2f 100644 --- a/app/Controllers/Report.php +++ b/app/Controllers/Report.php @@ -900,6 +900,149 @@ class Report extends BaseController ]); } + public function ayushman_unmapped_report() + { + $specialities = []; + if ($this->db->tableExists('ayushman_package_master')) { + $specialities = $this->db->table('ayushman_package_master') + ->select('speciality_code, speciality_name') + ->groupBy('speciality_code, speciality_name') + ->orderBy('speciality_name', 'ASC') + ->get() + ->getResult(); + } + + return view('report/ayushman_unmapped_report', [ + 'specialities' => $specialities, + ]); + } + + public function ayushman_unmapped_report_data(string $specialityCode = '0', int $output = 0) + { + if (! $this->db->tableExists('ayushman_package_master')) { + return $this->response->setBody('
Ayushman package master table is not available.
'); + } + + $builder = $this->db->table('ayushman_package_master a') + ->select('a.speciality_code, a.speciality_name, a.procedure_code, a.procedure_name, a.package_amount, a.preauth_required, a.pre_investigations, a.post_investigations') + ->where('(a.linked_package_id IS NULL OR a.linked_package_id = 0)', null, false) + ->orderBy('a.speciality_name', 'ASC') + ->orderBy('a.procedure_name', 'ASC'); + + if ($specialityCode !== '0') { + $builder->where('a.speciality_code', $specialityCode); + } + + $rows = $builder->get()->getResult(); + $data = [ + 'rows' => $rows, + 'speciality_code' => $specialityCode, + ]; + + $content = view('report/ayushman_unmapped_report_table', $data); + + if ($output === 1) { + ExportExcel($content, 'Ayushman_Unmapped_Procedures_' . date('YmdHis')); + return $this->response->setBody(''); + } + + return $this->response->setBody($content); + } + + public function ayushman_case_dashboard() + { + return view('report/ayushman_case_dashboard'); + } + + public function ayushman_case_dashboard_data(string $dateRange, string $alertType = 'all', int $output = 0) + { + [$minRange, $maxRange] = $this->parseDateRange($dateRange); + + $builder = $this->db->table('organization_case_master o') + ->select('o.id as case_id, o.case_id_code, o.preauth_send, o.doc_recd, o.final_bill_send, o.org_approved_status, o.Org_insurance_comp') + ->select('i.id as ipd_id, i.ipd_code') + ->select("DATE_FORMAT(i.register_date, '%d-%m-%Y') as str_register_date", false) + ->select('p.p_code, p.p_fname') + ->select('ins.short_name as insurance_company') + ->select('(SELECT COUNT(*) FROM ipd_package ip JOIN ayushman_package_master a ON a.procedure_code = ip.org_code WHERE ip.ipd_id = i.id) as ayushman_proc_count', false) + ->select('(SELECT COUNT(*) FROM ipd_package ip JOIN ayushman_package_master a ON a.procedure_code = ip.org_code WHERE ip.ipd_id = i.id AND (a.linked_package_id IS NULL OR a.linked_package_id = 0)) as unmapped_count', false) + ->select('(SELECT COUNT(*) FROM ipd_package ip JOIN ayushman_package_master a ON a.procedure_code = ip.org_code WHERE ip.ipd_id = i.id AND a.preauth_required = 1) as preauth_required_count', false) + ->join('ipd_master i', 'o.id = i.case_id', 'inner') + ->join('patient_master p', 'i.p_id = p.id', 'inner') + ->join('hc_insurance ins', 'ins.id = o.insurance_id', 'left') + ->where('o.case_type', 1) + ->where('i.register_date >=', $minRange) + ->where('i.register_date <=', $maxRange) + ->groupStart() + ->like('o.Org_insurance_comp', 'ayushman') + ->orLike('o.Org_insurance_comp', 'pmjay') + ->orLike('ins.short_name', 'ayushman') + ->orLike('ins.ins_company_name', 'ayushman') + ->groupEnd() + ->orderBy('i.id', 'DESC'); + + $rows = $builder->get()->getResultArray(); + + $alertType = strtolower(trim($alertType)); + if (! in_array($alertType, ['all', 'preauth-pending', 'docs-pending', 'mapping-gap'], true)) { + $alertType = 'all'; + } + + $rows = array_values(array_filter($rows, static function (array $row) use ($alertType): bool { + $preauthPending = (int) ($row['preauth_required_count'] ?? 0) > 0 && (int) ($row['preauth_send'] ?? 0) !== 1; + $docsPending = (int) ($row['doc_recd'] ?? 0) !== 1; + $mappingGap = (int) ($row['unmapped_count'] ?? 0) > 0; + + if ($alertType === 'preauth-pending') { + return $preauthPending; + } + if ($alertType === 'docs-pending') { + return $docsPending; + } + if ($alertType === 'mapping-gap') { + return $mappingGap; + } + + return true; + })); + + $summary = [ + 'total_cases' => count($rows), + 'preauth_pending' => 0, + 'docs_pending' => 0, + 'mapping_gaps' => 0, + ]; + + foreach ($rows as $row) { + if ((int) ($row['preauth_required_count'] ?? 0) > 0 && (int) ($row['preauth_send'] ?? 0) !== 1) { + $summary['preauth_pending']++; + } + if ((int) ($row['doc_recd'] ?? 0) !== 1) { + $summary['docs_pending']++; + } + if ((int) ($row['unmapped_count'] ?? 0) > 0) { + $summary['mapping_gaps']++; + } + } + + $data = [ + 'rows' => $rows, + 'summary' => $summary, + 'min_range' => $minRange, + 'max_range' => $maxRange, + 'alert_type' => $alertType, + ]; + + $content = view('report/ayushman_case_dashboard_table', $data); + + if ($output === 1) { + ExportExcel($content, 'Ayushman_Case_Dashboard_' . date('YmdHis')); + return $this->response->setBody(''); + } + + return $this->response->setBody($content); + } + public function insurance_opd_report_data( string $dateRange, string $insuranceId = '0', diff --git a/app/Database/Migrations/2026-03-25-000029_CreateAyushmanPackageMasterTable.php b/app/Database/Migrations/2026-03-25-000029_CreateAyushmanPackageMasterTable.php new file mode 100644 index 0000000..5829385 --- /dev/null +++ b/app/Database/Migrations/2026-03-25-000029_CreateAyushmanPackageMasterTable.php @@ -0,0 +1,101 @@ +forge->addField([ + 'id' => [ + 'type' => 'INT', + 'unsigned' => true, + 'auto_increment' => true, + ], + 'speciality_code' => [ + 'type' => 'VARCHAR', + 'constraint' => 50, + 'default' => '', + ], + 'speciality_name' => [ + 'type' => 'VARCHAR', + 'constraint' => 255, + 'default' => '', + ], + 'procedure_code' => [ + 'type' => 'VARCHAR', + 'constraint' => 50, + 'default' => '', + ], + 'procedure_name' => [ + 'type' => 'TEXT', + 'null' => true, + ], + 'package_amount' => [ + 'type' => 'DECIMAL', + 'constraint' => '12,2', + 'default' => 0, + ], + 'preauth_required' => [ + 'type' => 'TINYINT', + 'constraint' => 1, + 'default' => 0, + ], + 'procedure_type' => [ + 'type' => 'VARCHAR', + 'constraint' => 50, + 'default' => '', + ], + 'government_reserved' => [ + 'type' => 'TINYINT', + 'constraint' => 1, + 'default' => 0, + ], + 'pre_investigations' => [ + 'type' => 'TEXT', + 'null' => true, + ], + 'post_investigations' => [ + 'type' => 'TEXT', + 'null' => true, + ], + 'linked_package_id' => [ + 'type' => 'INT', + 'unsigned' => true, + 'null' => true, + ], + 'source_file' => [ + 'type' => 'VARCHAR', + 'constraint' => 255, + 'default' => '', + ], + 'source_sheet' => [ + 'type' => 'VARCHAR', + 'constraint' => 100, + 'default' => 'Sheet1', + ], + 'created_at' => [ + 'type' => 'DATETIME', + 'null' => true, + ], + 'updated_at' => [ + 'type' => 'DATETIME', + 'null' => true, + ], + ]); + + $this->forge->addKey('id', true); + $this->forge->addUniqueKey('procedure_code'); + $this->forge->addKey('speciality_code'); + $this->forge->addKey('linked_package_id'); + $this->forge->addKey('preauth_required'); + $this->forge->createTable('ayushman_package_master', true); + } + + public function down(): void + { + $this->forge->dropTable('ayushman_package_master', true); + } +} \ No newline at end of file diff --git a/app/Models/IpdBillingModel.php b/app/Models/IpdBillingModel.php index 775a985..2b34558 100644 --- a/app/Models/IpdBillingModel.php +++ b/app/Models/IpdBillingModel.php @@ -140,9 +140,15 @@ class IpdBillingModel extends Model ->select("in_master.ins_company_name as ins_company_name", false) ->select("in_master.short_name as ins_short_name", false) ->select("o.case_id_code as case_id_code", false) + ->select("o.Org_insurance_comp as org_insurance_comp", false) + ->select("o.doc_recd as doc_recd", false) ->select("o.insurance_no as insurance_no", false) ->select("o.insurance_no_1 as insurance_no_1", false) ->select("o.insurance_no_2 as insurance_no_2", false) + ->select("o.preauth_send as preauth_send", false) + ->select("o.final_bill_send as final_bill_send", false) + ->select("o.org_approved_status as org_approved_status", false) + ->select("o.remark as remark", false) ->join('organization_case_master o', 'i.case_id = o.id', 'left') ->join('hc_insurance in_master', 'in_master.id = o.insurance_id', 'left') ->where('i.id', $ipdId) @@ -422,6 +428,123 @@ class IpdBillingModel extends Model ->getResult(); } + public function getAyushmanSpecialities(): array + { + if (! $this->db->tableExists('ayushman_package_master')) { + return []; + } + + return $this->db->table('ayushman_package_master') + ->select('speciality_code, speciality_name') + ->groupBy('speciality_code, speciality_name') + ->orderBy('speciality_name', 'ASC') + ->get() + ->getResultArray(); + } + + public function searchAyushmanPackages(string $query = '', string $specialityCode = '', int $limit = 25): array + { + if (! $this->db->tableExists('ayushman_package_master')) { + return []; + } + + $builder = $this->db->table('ayushman_package_master a') + ->select('a.id, a.speciality_code, a.speciality_name, a.procedure_code, a.procedure_name, a.package_amount, a.preauth_required, a.procedure_type, a.government_reserved, a.pre_investigations, a.post_investigations, a.linked_package_id') + ->select('p.ipd_pakage_name as linked_package_name', false) + ->join('package p', 'p.id = a.linked_package_id', 'left'); + + if ($specialityCode !== '') { + $builder->where('a.speciality_code', $specialityCode); + } + + $query = trim($query); + if ($query !== '') { + $builder->groupStart() + ->like('a.procedure_code', $query) + ->orLike('a.procedure_name', $query) + ->orLike('a.speciality_name', $query) + ->groupEnd(); + } + + $limit = max(1, min(100, $limit)); + + return $builder + ->orderBy('a.speciality_name', 'ASC') + ->orderBy('a.procedure_name', 'ASC') + ->limit($limit) + ->get() + ->getResultArray(); + } + + public function getAyushmanPackageById(int $id): ?array + { + if ($id <= 0 || ! $this->db->tableExists('ayushman_package_master')) { + return null; + } + + $row = $this->db->table('ayushman_package_master') + ->where('id', $id) + ->get() + ->getRowArray(); + + return is_array($row) && ! empty($row) ? $row : null; + } + + public function getAllPackageOptions(): array + { + return $this->db->table('package p') + ->select('p.id, p.ipd_pakage_name, p.Pakage_Min_Amount, g.pakage_group_name') + ->join('package_group g', 'g.pak_id = p.pakage_group_id', 'left') + ->orderBy('g.pakage_group_name', 'ASC') + ->orderBy('p.ipd_pakage_name', 'ASC') + ->get() + ->getResultArray(); + } + + public function updateAyushmanPackageLink(int $ayushmanId, int $linkedPackageId): bool + { + if ($ayushmanId <= 0 || ! $this->db->tableExists('ayushman_package_master')) { + return false; + } + + $exists = $this->db->table('ayushman_package_master') + ->select('id') + ->where('id', $ayushmanId) + ->get(1) + ->getRowArray(); + + if (! is_array($exists) || empty($exists)) { + return false; + } + + $update = [ + 'linked_package_id' => $linkedPackageId > 0 ? $linkedPackageId : null, + 'updated_at' => date('Y-m-d H:i:s'), + ]; + + $this->db->table('ayushman_package_master') + ->where('id', $ayushmanId) + ->update($update); + + return true; + } + + public function getAyushmanChecklistForIpd(int $ipdId): array + { + if ($ipdId <= 0 || ! $this->db->tableExists('ayushman_package_master')) { + return []; + } + + return $this->db->table('ipd_package p') + ->select('p.id as ipd_package_id, p.package_name, p.package_desc, p.comment, p.org_code, p.package_Amount') + ->select('a.id as ayushman_id, a.speciality_code, a.speciality_name, a.procedure_code, a.procedure_name, a.preauth_required, a.pre_investigations, a.post_investigations, a.linked_package_id') + ->join('ayushman_package_master a', 'a.procedure_code = p.org_code', 'inner') + ->where('p.ipd_id', $ipdId) + ->orderBy('p.id', 'DESC') + ->get() + ->getResultArray(); + } + public function getPackageWithInsurance(int $packageId, int $insuranceId): ?object { return $this->db->table('package p') @@ -453,6 +576,31 @@ class IpdBillingModel extends Model ->getResult(); } + public function updateDiagnosisChargeInclude(int $ipdId, int $invoiceId, int $include): bool + { + if ($ipdId <= 0 || $invoiceId <= 0) { + return false; + } + + $exists = $this->db->table('invoice_master') + ->select('id') + ->where('id', $invoiceId) + ->where('ipd_id', $ipdId) + ->get(1) + ->getRowArray(); + if (! is_array($exists) || empty($exists)) { + return false; + } + + $builder = $this->db->table('invoice_master'); + $builder->where('id', $invoiceId) + ->where('ipd_id', $ipdId) + ->set('ipd_include', $include > 0 ? 1 : 0) + ->update(); + + return true; + } + public function getPayments(int $ipdId): array { return $this->db->table('payment_history') diff --git a/app/Views/billing/ipd/ayushman_claim_sheet.php b/app/Views/billing/ipd/ayushman_claim_sheet.php new file mode 100644 index 0000000..cae3b16 --- /dev/null +++ b/app/Views/billing/ipd/ayushman_claim_sheet.php @@ -0,0 +1,70 @@ + + +
+
+
+
Ayushman Claim Sheet
+
+ +
+
+
+
+
Patient: p_fname ?? '') ?>
+
UHID: p_code ?? '') ?>
+
IPD: ipd_code ?? '') ?>
+
Case: case_id_code ?? '') ?>
+
Insurance: ins_company_name ?? '') ?>
+
Scheme: org_insurance_comp ?? '') ?>
+
Generated:
+
+ +
+
Preauth Sent: preauth_send ?? 0) === 1 ? 'Yes' : 'No' ?>
+
Documents Received: doc_recd ?? 0) === 1 ? 'Yes' : 'No' ?>
+
Final Bill Sent: final_bill_send ?? 0) === 1 ? 'Yes' : 'No' ?>
+
Approval Status: org_approved_status ?? 'Under Process')) ?>
+
Remark: remark ?? '')) ?>
+
+ + +
No Ayushman procedures linked to this IPD package list.
+ + + + + + + + + + + + + + + $item) : ?> + + + + + + + + + + + +
#Procedure CodeProcedure NameAmountPreauthPre InvestigationsPost Investigations
+
+
+
+ +
+
+
diff --git a/app/Views/billing/ipd/panel_diagnosis.php b/app/Views/billing/ipd/panel_diagnosis.php index 3b9ffdb..6c5ea57 100644 --- a/app/Views/billing/ipd/panel_diagnosis.php +++ b/app/Views/billing/ipd/panel_diagnosis.php @@ -1,3 +1,9 @@ +id ?? 0)); +$csrfName = csrf_token(); +$csrfHash = csrf_hash(); +?> +
@@ -20,7 +26,17 @@ - + @@ -31,3 +47,40 @@
str_date ?? '') ?> charge_list ?? '') ?> amount ?? '') ?>ipd_include ?? 0) > 0) ? 'Yes' : 'No' ?> +
+ ipd_include ?? 0) > 0) ? 'checked' : '' ?> + > +
+
+ + diff --git a/app/Views/billing/ipd/panel_package.php b/app/Views/billing/ipd/panel_package.php index e975076..3d2755e 100644 --- a/app/Views/billing/ipd/panel_package.php +++ b/app/Views/billing/ipd/panel_package.php @@ -3,6 +3,13 @@ $ipd = $ipd_info ?? null; $ipdId = (int) ($ipd->id ?? 0); $csrfName = csrf_token(); $csrfHash = csrf_hash(); +$insuranceLabel = trim((string) ($ipd->ins_company_name ?? '')); +$schemeLabel = trim((string) ($ipd->org_insurance_comp ?? '')); +$caseLabel = strtolower(trim($insuranceLabel . ' ' . $schemeLabel)); +$isAyushmanCase = strpos($caseLabel, 'ayushman') !== false || strpos($caseLabel, 'pmjay') !== false || strpos($caseLabel, 'bharat') !== false; +$ayushmanCollapseClass = $isAyushmanCase ? 'accordion-collapse collapse show' : 'accordion-collapse collapse'; +$ayushmanButtonClass = $isAyushmanCase ? 'accordion-button' : 'accordion-button collapsed'; +$ayushmanChecklistItems = $ayushman_checklist_items ?? []; ?>
@@ -24,13 +31,26 @@ $csrfHash = csrf_hash(); + package_desc ?? ''), 'Ayushman') !== false + || stripos((string) ($row->comment ?? ''), 'Ayushman Code:') !== false; + ?> package_name ?? '') ?> + + Ayushman + + org_code ?? '')) : ?> + org_code ?? '') ?> + package_desc ?? $row->comment ?? '')) : ?>
package_desc ?? $row->comment ?? '') ?> + comment ?? '') && ($row->comment ?? '') !== ($row->package_desc ?? '')) : ?> +
comment ?? '') ?> + @@ -115,6 +135,188 @@ $csrfHash = csrf_hash();
+ +
+

+ +

+
+
+
+
+
+ preauth_send ?? 0) === 1 ? 'checked' : '' ?>> + +
+
+
+
+ doc_recd ?? 0) === 1 ? 'checked' : '' ?>> + +
+
+
+
+ final_bill_send ?? 0) === 1 ? 'checked' : '' ?>> + +
+
+
+ + +
+
+ + +
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
Selected ProcedureSpecialityPreauthPre InvestigationsPost Investigations
+
+
Package:
+
Required' : 'No' ?>
No Ayushman procedures have been added to this IPD package list yet.
+
+ +
+ + + +
+
+
+
+
+

+ +

+
+
+
+
Insurance:
+
Scheme:
+
Preauth Status: org_approved_status ?? 'Under Process')) ?>
+
+
+
+ + +
+
+ + +
+
+ + +
+
+
Search imported Ayushman Bharat treatment list and add the selected treatment as an IPD package entry.
+
+ + + + + + + + + + + + + + + + +
ProcedureSpecialityMapped PackageAmountPreauth
Run a search to view Ayushman treatments.
+
+
+
+
+ +
+ Ayushman Bharat treatment search is shown automatically for cases where insurance or scheme contains Ayushman or PMJAY. +
+ + + + + + @@ -124,6 +326,66 @@ $csrfHash = csrf_hash(); var ipdPackageCsrfHash = ''; var ipdPackageBaseUrl = ''; var ipdPackageIpdId = ; + var ipdAyushmanSearchUrl = ''; + var ipdAyushmanMapUrlBase = ''; + var ipdAyushmanChecklistUrl = ''; + var ipdAyushmanClaimSheetUrl = ''; + var ipdAyushmanResults = {}; + + function normalizeText(value) { + return String(value || '') + .toLowerCase() + .replace(/[^a-z0-9\s]/g, ' ') + .replace(/\s+/g, ' ') + .trim(); + } + + function scorePackageSuggestion(procedureName, packageName) { + var p = normalizeText(procedureName); + var q = normalizeText(packageName); + if (!p || !q) { + return 0; + } + + var score = 0; + if (q.indexOf(p) >= 0 || p.indexOf(q) >= 0) { + score += 8; + } + + var pWords = p.split(' '); + var qWords = q.split(' '); + pWords.forEach(function(word) { + if (word.length >= 4 && qWords.indexOf(word) >= 0) { + score += 2; + } + }); + + return score; + } + + function ipdAyushmanSuggestPackageId(procedureName) { + var select = document.getElementById('ayushman_mapping_package_id'); + if (!select) { + return 0; + } + + var bestId = 0; + var bestScore = 0; + for (var i = 0; i < select.options.length; i++) { + var option = select.options[i]; + var id = parseInt(option.value || '0', 10); + if (id <= 0) { + continue; + } + var score = scorePackageSuggestion(procedureName, option.text || ''); + if (score > bestScore) { + bestScore = score; + bestId = id; + } + } + + return bestScore >= 4 ? bestId : 0; + } function ipdPackageReload() { load_form_div('', 'tab_package_content'); @@ -171,6 +433,210 @@ $csrfHash = csrf_hash(); }); } + function ipdAyushmanRenderResults(items) { + var tbody = $('#ayushman_search_results'); + tbody.empty(); + ipdAyushmanResults = {}; + + if (!items || !items.length) { + tbody.append('No Ayushman treatment matched the search.'); + $('#ayushman_search_summary').text('No matching treatments found.'); + return; + } + + $('#ayushman_search_summary').text(items.length + ' treatment(s) found.'); + + items.forEach(function(item) { + ipdAyushmanResults[String(item.id)] = item; + + var procedure = $('
'); + procedure.append($('
').text((item.procedure_name || '') + ' [' + (item.procedure_code || '') + ']')); + + var meta = []; + if (item.procedure_type) { + meta.push('Type: ' + item.procedure_type); + } + if (Number(item.government_reserved || 0) === 1) { + meta.push('Government Reserved'); + } + if (item.pre_investigations) { + meta.push('Pre: ' + item.pre_investigations); + } + if (item.post_investigations) { + meta.push('Post: ' + item.post_investigations); + } + if (meta.length > 0) { + procedure.append($('
').text(meta.join(' | '))); + } + + var linkedText = item.linked_package_name ? item.linked_package_name : 'Not linked'; + var linkedCell = $(''); + linkedCell.append($('
').text(linkedText)); + linkedCell.append($('').attr('onclick', 'ipdAyushmanOpenMapping(' + Number(item.id || 0) + ')')); + + var row = $(''); + row.append($('').append(procedure)); + row.append($('').text((item.speciality_name || '') + ' [' + (item.speciality_code || '') + ']')); + row.append(linkedCell); + row.append($('').text(Number(item.package_amount || 0).toFixed(2))); + row.append($('').html(Number(item.preauth_required || 0) === 1 ? 'Yes' : 'No')); + row.append($('').html('').find('button').attr('onclick', 'ipdAyushmanAdd(' + Number(item.id || 0) + ')').end()); + tbody.append(row); + }); + } + + function ipdAyushmanOpenMapping(itemId) { + var item = ipdAyushmanResults[String(itemId)] || null; + if (!item) { + alert('Ayushman treatment not found in current result list.'); + return; + } + + $('#ayushman_mapping_item_id').val(itemId); + $('#ayushman_mapping_title').text((item.procedure_name || '') + ' [' + (item.procedure_code || '') + ']'); + var selectedPackageId = Number(item.linked_package_id || 0); + var suggestedPackageId = 0; + if (selectedPackageId <= 0) { + suggestedPackageId = ipdAyushmanSuggestPackageId(item.procedure_name || ''); + if (suggestedPackageId > 0) { + selectedPackageId = suggestedPackageId; + } + } + + $('#ayushman_mapping_package_id').val(String(selectedPackageId || 0)); + + var metaText = (item.speciality_name || '') + ' | Current link: ' + (item.linked_package_name || 'Not linked'); + if (suggestedPackageId > 0) { + var suggestedText = $('#ayushman_mapping_package_id option:selected').text() || ''; + metaText += ' | Suggested: ' + suggestedText; + } + $('#ayushman_mapping_meta').text(metaText); + + var modalEl = document.getElementById('ayushmanMappingModal'); + var modal = bootstrap.Modal.getOrCreateInstance(modalEl); + modal.show(); + } + + function ipdAyushmanSaveMapping() { + var itemId = parseInt($('#ayushman_mapping_item_id').val() || '0', 10); + if (itemId <= 0) { + alert('Select an Ayushman procedure first.'); + return; + } + + var payload = {}; + payload[ipdPackageCsrfName] = ipdPackageCsrfHash; + payload.linked_package_id = $('#ayushman_mapping_package_id').val() || 0; + + $.post(ipdAyushmanMapUrlBase + '/' + itemId, payload, function(resp) { + if (!resp || !resp.ok) { + alert((resp && resp.error) ? resp.error : 'Unable to save mapping'); + return; + } + + var item = ipdAyushmanResults[String(itemId)] || null; + if (item) { + item.linked_package_id = Number(resp.linked_package_id || 0); + item.linked_package_name = resp.linked_package_name || ''; + } + + var modalEl = document.getElementById('ayushmanMappingModal'); + var modal = bootstrap.Modal.getInstance(modalEl); + if (modal) { + modal.hide(); + } + + ipdAyushmanSearch(); + }, 'json').fail(function(xhr) { + var msg = (xhr && xhr.responseJSON && xhr.responseJSON.error) ? xhr.responseJSON.error : 'Unable to save mapping'; + alert(msg); + }); + } + + function ipdAyushmanSaveChecklist() { + var payload = {}; + payload[ipdPackageCsrfName] = ipdPackageCsrfHash; + payload.preauth_send = $('#ayushman_preauth_send').is(':checked') ? 1 : 0; + payload.doc_recd = $('#ayushman_doc_recd').is(':checked') ? 1 : 0; + payload.final_bill_send = $('#ayushman_final_bill_send').is(':checked') ? 1 : 0; + payload.org_approved_status = $('#ayushman_org_status').val() || 'Under Process'; + payload.remark = $('#ayushman_case_remark').val() || ''; + + $.post(ipdAyushmanChecklistUrl, payload, function(resp) { + if (!resp || !resp.ok) { + alert((resp && resp.error) ? resp.error : 'Unable to save Ayushman checklist'); + return; + } + alert(resp.message || 'Ayushman checklist updated.'); + ipdPackageReload(); + }, 'json').fail(function(xhr) { + var msg = (xhr && xhr.responseJSON && xhr.responseJSON.error) ? xhr.responseJSON.error : 'Unable to save Ayushman checklist'; + alert(msg); + }); + } + + function ipdAyushmanClaimSheetPreview() { + window.open(ipdAyushmanClaimSheetUrl, '_blank'); + } + + function ipdAyushmanClaimSheetExport() { + window.open(ipdAyushmanClaimSheetUrl + '/1', '_blank'); + } + + function ipdAyushmanSearch() { + var specialityCode = $('#ayushman_speciality_code').val() || ''; + var query = $('#ayushman_search_query').val() || ''; + $('#ayushman_search_summary').text('Searching...'); + + $.getJSON(ipdAyushmanSearchUrl, { + speciality_code: specialityCode, + q: query + }, function(resp) { + if (!resp || !resp.ok) { + $('#ayushman_search_summary').text('Unable to load Ayushman treatments.'); + return; + } + ipdAyushmanRenderResults(resp.items || []); + }).fail(function() { + $('#ayushman_search_summary').text('Unable to load Ayushman treatments.'); + $('#ayushman_search_results').html('Search request failed.'); + }); + } + + function ipdAyushmanClear() { + $('#ayushman_speciality_code').val(''); + $('#ayushman_search_query').val(''); + $('#ayushman_search_summary').text('Search imported Ayushman Bharat treatment list and add the selected treatment as an IPD package entry.'); + $('#ayushman_search_results').html('Run a search to view Ayushman treatments.'); + } + + function ipdAyushmanAdd(itemId) { + var item = ipdAyushmanResults[String(itemId)] || null; + if (!item) { + alert('Ayushman treatment not found in current result list.'); + return; + } + + var payload = {}; + payload[ipdPackageCsrfName] = ipdPackageCsrfHash; + payload.ayushman_package_id = itemId; + payload.package_id = item.linked_package_id || 0; + payload.package_name = item.procedure_name || ''; + payload.input_amount = item.package_amount || 0; + payload.comment = ''; + + $.post(ipdPackageBaseUrl + 'billing/ipd/package/add/' + ipdPackageIpdId, payload, function(resp) { + if (resp && resp.ok) { + ipdPackageReload(); + return; + } + alert((resp && resp.error) ? resp.error : 'Unable to add Ayushman package'); + }, 'json').fail(function(xhr) { + var msg = (xhr && xhr.responseJSON && xhr.responseJSON.error) ? xhr.responseJSON.error : 'Unable to add Ayushman package'; + alert(msg); + }); + } + function ipdPackageUpdate(itemId) { var payload = {}; payload[ipdPackageCsrfName] = ipdPackageCsrfHash; @@ -193,4 +659,15 @@ $csrfHash = csrf_hash(); ipdPackageReload(); }); } + + $('#ayushman_search_query').on('keydown', function(event) { + if (event.key === 'Enter') { + event.preventDefault(); + ipdAyushmanSearch(); + } + }); + + + ipdAyushmanSearch(); + diff --git a/app/Views/report/ayushman_case_dashboard.php b/app/Views/report/ayushman_case_dashboard.php new file mode 100644 index 0000000..e1a09b7 --- /dev/null +++ b/app/Views/report/ayushman_case_dashboard.php @@ -0,0 +1,75 @@ +
+
+
+
+ Ayushman Case Dashboard +
+ +
+
+
+
+ +
+ + +
+
+
+ + +
+
+ + +
+
+
+
+ +
+
+
+
+ +

Select date and alert filter, then click Show

+
+
+
+
+
+ + diff --git a/app/Views/report/ayushman_case_dashboard_table.php b/app/Views/report/ayushman_case_dashboard_table.php new file mode 100644 index 0000000..974bbbd --- /dev/null +++ b/app/Views/report/ayushman_case_dashboard_table.php @@ -0,0 +1,105 @@ + 0, 'preauth_pending' => 0, 'docs_pending' => 0, 'mapping_gaps' => 0]; +$minRange = $min_range ?? ''; +$maxRange = $max_range ?? ''; +$alertType = $alert_type ?? 'all'; +?> + +
+
Date Range: to
+
Alert Filter:
+
+ +
+
+
+
+
Total Cases
+
+
+
+
+
+
+
+
Preauth Pending
+
+
+
+
+
+
+
+
Documents Pending
+
+
+
+
+
+
+
+
Mapping Gaps
+
+
+
+
+
+ + +
No Ayushman cases found for selected criteria.
+ + + + + + + + + + + + + + + + + + + + + $row) : ?> + 0 && (int) ($row['preauth_send'] ?? 0) !== 1; + $docsPending = (int) ($row['doc_recd'] ?? 0) !== 1; + $mappingGap = (int) ($row['unmapped_count'] ?? 0) > 0; + ?> + + + + + + + + + + + + + + + + + +
#CaseIPDUHIDPatientInsuranceReg DateAyushman ProcUnmappedPreauth ReqPreauth SentDocsStatus
+ + +
+ +
Pending' : 'Done' ?>Pending' : 'Done' ?> + + +
Mapping Gap
+ +
+ diff --git a/app/Views/report/ayushman_unmapped_report.php b/app/Views/report/ayushman_unmapped_report.php new file mode 100644 index 0000000..4a9c9fb --- /dev/null +++ b/app/Views/report/ayushman_unmapped_report.php @@ -0,0 +1,72 @@ + +
+
+
+
+ Ayushman Unmapped Procedures +
+ +
+
+
+
+ + +
+
+ +
+
+ +
+
+
+
+ +
+
+
+
+ +

Select a speciality filter and click Show to generate report

+
+
+
+
+
+ + \ No newline at end of file diff --git a/app/Views/report/ayushman_unmapped_report_table.php b/app/Views/report/ayushman_unmapped_report_table.php new file mode 100644 index 0000000..6e4672d --- /dev/null +++ b/app/Views/report/ayushman_unmapped_report_table.php @@ -0,0 +1,47 @@ + + +
+
+ Speciality Filter: + +
+
+ Total Unmapped Procedures: +
+
+ + +
No unmapped Ayushman procedures found for the selected filter.
+ + + + + + + + + + + + + + + + $row) : ?> + + + + + + + + + + + + +
#SpecialityProcedure CodeProcedure NameAmountPreauthPre InvestigationsPost Investigations
speciality_name ?? '') . ' [' . ($row->speciality_code ?? '') . ']') ?>procedure_code ?? '') ?>procedure_name ?? '') ?>package_amount ?? 0), 2) ?>preauth_required ?? 0) === 1 ? 'Yes' : 'No' ?>pre_investigations ?? '')) ?>post_investigations ?? '')) ?>
+ \ No newline at end of file diff --git a/app/Views/report/index.php b/app/Views/report/index.php index b6eaf1f..a90c2a3 100644 --- a/app/Views/report/index.php +++ b/app/Views/report/index.php @@ -80,6 +80,8 @@ diff --git a/app/Views/report/insurance_credit_main.php b/app/Views/report/insurance_credit_main.php index 1f270ed..098c353 100644 --- a/app/Views/report/insurance_credit_main.php +++ b/app/Views/report/insurance_credit_main.php @@ -60,6 +60,40 @@ + +
+
+
+
+ +
+
Ayushman Mapping Gaps
+

+ Review Ayushman procedures that are still not linked to internal package masters +

+ +
+
+
+ +
+
+
+
+ +
+
Ayushman Case Dashboard
+

+ Track preauth pending, document pending, and mapping gap alerts for Ayushman IPD cases +

+ +
+
+
@@ -73,6 +107,8 @@
  • OPD Organization Cases: Shows outpatient cases with insurance coverage (case_type = 0)
  • IPD TPA Cases: Shows inpatient cases with TPA/insurance coverage (case_type = 1)
  • Combined Summary: Consolidated view of all insurance cases with totals
  • +
  • Ayushman Mapping Gaps: Procedures imported from Ayushman Bharat package master that still need internal package mapping
  • +
  • Ayushman Case Dashboard: Action-oriented dashboard for pending preauth/documents and case-level mapping risk
  • Status Types: Pending (0), Bill Complete (1), Submitted to Organization (2)