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 @@
+
+
+
+
+
+
+
+
Patient: = esc($person->p_fname ?? '') ?>
+
UHID: = esc($person->p_code ?? '') ?>
+
IPD: = esc($ipd->ipd_code ?? '') ?>
+
Case: = esc($ipd->case_id_code ?? '') ?>
+
Insurance: = esc($ipd->ins_company_name ?? '') ?>
+
Scheme: = esc($ipd->org_insurance_comp ?? '') ?>
+
Generated: = esc($generatedAt) ?>
+
+
+
+
Preauth Sent: = (int) ($ipd->preauth_send ?? 0) === 1 ? 'Yes' : 'No' ?>
+
Documents Received: = (int) ($ipd->doc_recd ?? 0) === 1 ? 'Yes' : 'No' ?>
+
Final Bill Sent: = (int) ($ipd->final_bill_send ?? 0) === 1 ? 'Yes' : 'No' ?>
+
Approval Status: = esc((string) ($ipd->org_approved_status ?? 'Under Process')) ?>
+
Remark: = esc((string) ($ipd->remark ?? '')) ?>
+
+
+
+
No Ayushman procedures linked to this IPD package list.
+
+
+
+
+ #
+ Procedure Code
+ Procedure Name
+ Amount
+ Preauth
+ Pre Investigations
+ Post Investigations
+
+
+
+ $item) : ?>
+
+ = $index + 1 ?>
+ = esc((string) ($item['procedure_code'] ?? '')) ?>
+
+ = esc((string) ($item['procedure_name'] ?? '')) ?>
+ = esc((string) (($item['speciality_name'] ?? '') . ' [' . ($item['speciality_code'] ?? '') . ']')) ?>
+
+ = number_format((float) ($item['package_Amount'] ?? 0), 2) ?>
+ = (int) ($item['preauth_required'] ?? 0) === 1 ? 'Required' : 'No' ?>
+ = esc((string) ($item['pre_investigations'] ?? '')) ?>
+ = esc((string) ($item['post_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();
+?>
+
+
+
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;
+ ?>
= $srNo++ ?>
= esc($row->package_name ?? '') ?>
+
+ Ayushman
+
+ org_code ?? '')) : ?>
+ = esc($row->org_code ?? '') ?>
+
package_desc ?? $row->comment ?? '')) : ?>
= esc($row->package_desc ?? $row->comment ?? '') ?>
+ comment ?? '') && ($row->comment ?? '') !== ($row->package_desc ?? '')) : ?>
+ = esc($row->comment ?? '') ?>
+
@@ -115,6 +135,188 @@ $csrfHash = csrf_hash();
+
+
+
+
+
+
+
+
+ preauth_send ?? 0) === 1 ? 'checked' : '' ?>>
+ Preauth Sent
+
+
+
+
+ doc_recd ?? 0) === 1 ? 'checked' : '' ?>>
+ Documents Received
+
+
+
+
+ final_bill_send ?? 0) === 1 ? 'checked' : '' ?>>
+ Final Bill Sent
+
+
+
+ Approval Status
+
+ org_approved_status ?? 'Under Process')); ?>
+
+ >= esc($statusLabel) ?>
+
+
+
+
+ Case Remark
+
+
+
+
+
+
+
+
+ Selected Procedure
+ Speciality
+ Preauth
+ Pre Investigations
+ Post Investigations
+
+
+
+
+
+
+
+ = esc(($item['procedure_name'] ?? '') . ' [' . ($item['procedure_code'] ?? '') . ']') ?>
+ Package: = esc($item['package_name'] ?? '') ?>
+
+ = esc(($item['speciality_name'] ?? '') . ' [' . ($item['speciality_code'] ?? '') . ']') ?>
+ = (int) ($item['preauth_required'] ?? 0) === 1 ? 'Required ' : 'No ' ?>
+ = esc((string) ($item['pre_investigations'] ?? '-')) ?>
+ = esc((string) ($item['post_investigations'] ?? '-')) ?>
+
+
+
+
+ No Ayushman procedures have been added to this IPD package list yet.
+
+
+
+
+
+
+
+ Save Checklist
+ Preview Claim Sheet
+ Export Claim Sheet
+
+
+
+
+
+
+
+
+
+
Insurance: = esc($insuranceLabel !== '' ? $insuranceLabel : 'Not set') ?>
+
Scheme: = esc($schemeLabel !== '' ? $schemeLabel : 'Not set') ?>
+
Preauth Status: = esc((string) ($ipd->org_approved_status ?? 'Under Process')) ?>
+
+
+
+ Speciality
+
+ All
+
+
+ = esc(($speciality['speciality_name'] ?? '') . ' [' . ($speciality['speciality_code'] ?? '') . ']') ?>
+
+
+
+
+
+ Procedure Search
+
+
+
+ Search
+ Clear
+
+
+
Search imported Ayushman Bharat treatment list and add the selected treatment as an IPD package entry.
+
+
+
+
+ Procedure
+ Speciality
+ Mapped Package
+ Amount
+ Preauth
+
+
+
+
+
+ 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 = '= esc($csrfHash) ?>';
var ipdPackageBaseUrl = '= base_url() ?>';
var ipdPackageIpdId = = (int) $ipdId ?>;
+ var ipdAyushmanSearchUrl = '= site_url('billing/ipd/panel/' . $ipdId . '/ayushman/search') ?>';
+ var ipdAyushmanMapUrlBase = '= site_url('billing/ipd/panel/' . $ipdId . '/ayushman/map') ?>';
+ var ipdAyushmanChecklistUrl = '= site_url('billing/ipd/panel/' . $ipdId . '/ayushman/checklist') ?>';
+ var ipdAyushmanClaimSheetUrl = '= site_url('billing/ipd/panel/' . $ipdId . '/ayushman/claim-sheet') ?>';
+ 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('= base_url('IpdNew/ipd_package/' . $ipdId) ?>', '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($('
Map ').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('
Add ').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 @@
+
+
+
+
+
+
+
+ Alert Type
+
+ All Cases
+ Preauth Pending
+ Documents Pending
+ Mapping Gap
+
+
+
+
+ Show
+
+
+ Export
+
+
+
+
+
+
+
+
+
+
+
+
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: = esc($minRange) ?> to = esc($maxRange) ?>
+
Alert Filter: = esc($alertType) ?>
+
+
+
+
+
+
+
Total Cases
+
= (int) ($summary['total_cases'] ?? 0) ?>
+
+
+
+
+
+
+
Preauth Pending
+
= (int) ($summary['preauth_pending'] ?? 0) ?>
+
+
+
+
+
+
+
Documents Pending
+
= (int) ($summary['docs_pending'] ?? 0) ?>
+
+
+
+
+
+
+
Mapping Gaps
+
= (int) ($summary['mapping_gaps'] ?? 0) ?>
+
+
+
+
+
+
+
No Ayushman cases found for selected criteria.
+
+
+
+
+ #
+ Case
+ IPD
+ UHID
+ Patient
+ Insurance
+ Reg Date
+ Ayushman Proc
+ Unmapped
+ Preauth Req
+ Preauth Sent
+ Docs
+ Status
+
+
+
+ $row) : ?>
+ 0 && (int) ($row['preauth_send'] ?? 0) !== 1;
+ $docsPending = (int) ($row['doc_recd'] ?? 0) !== 1;
+ $mappingGap = (int) ($row['unmapped_count'] ?? 0) > 0;
+ ?>
+
+ = $index + 1 ?>
+ = esc($row['case_id_code'] ?? '') ?>
+ = esc($row['ipd_code'] ?? '') ?>
+ = esc($row['p_code'] ?? '') ?>
+ = esc($row['p_fname'] ?? '') ?>
+
+ = esc($row['insurance_company'] ?? '') ?>
+
+ = esc($row['Org_insurance_comp'] ?? '') ?>
+
+
+ = esc($row['str_register_date'] ?? '') ?>
+ = (int) ($row['ayushman_proc_count'] ?? 0) ?>
+ = (int) ($row['unmapped_count'] ?? 0) ?>
+ = (int) ($row['preauth_required_count'] ?? 0) ?>
+ = $preauthPending ? 'Pending ' : 'Done ' ?>
+ = $docsPending ? 'Pending ' : 'Done ' ?>
+
+ = esc((string) ($row['org_approved_status'] ?? 'Under Process')) ?>
+
+ 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 @@
+
+
+
+
+
+
+
+ Speciality
+
+ All Specialities
+
+
+ = esc(($row->speciality_name ?? '') . ' [' . ($row->speciality_code ?? '') . ']') ?>
+
+
+
+
+
+
+ Show
+
+
+
+
+ Export Excel
+
+
+
+
+
+
+
+
+
+
+
+
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:
+ = esc($specialityCode === '0' ? 'All Specialities' : $specialityCode) ?>
+
+
+ Total Unmapped Procedures: = count($rows) ?>
+
+
+
+
+
No unmapped Ayushman procedures found for the selected filter.
+
+
+
+
+ #
+ Speciality
+ Procedure Code
+ Procedure Name
+ Amount
+ Preauth
+ Pre Investigations
+ Post Investigations
+
+
+
+ $row) : ?>
+
+ = $index + 1 ?>
+ = esc(($row->speciality_name ?? '') . ' [' . ($row->speciality_code ?? '') . ']') ?>
+ = esc($row->procedure_code ?? '') ?>
+ = esc($row->procedure_name ?? '') ?>
+ = number_format((float) ($row->package_amount ?? 0), 2) ?>
+ = (int) ($row->preauth_required ?? 0) === 1 ? 'Yes' : 'No' ?>
+ = esc((string) ($row->pre_investigations ?? '')) ?>
+ = esc((string) ($row->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 @@
+
+