3584 lines
132 KiB
PHP
3584 lines
132 KiB
PHP
<?php
|
|
|
|
namespace App\Controllers\Billing;
|
|
|
|
use App\Controllers\BaseController;
|
|
use App\Libraries\AbdmWorkTaskService;
|
|
use App\Libraries\BridgeSyncService;
|
|
use App\Models\BedAssignmentHistoryModel;
|
|
use App\Models\ItemIpdModel;
|
|
use App\Models\IpdBillingModel;
|
|
use App\Models\IpdModel;
|
|
use App\Models\NursingBedsideItemModel;
|
|
use App\Models\PaymentModel;
|
|
use Mpdf\Mpdf;
|
|
|
|
class Ipd extends BaseController
|
|
{
|
|
protected IpdBillingModel $ipdModel;
|
|
protected BedAssignmentHistoryModel $bedAssignmentModel;
|
|
protected ItemIpdModel $itemIpdModel;
|
|
protected IpdModel $ipdEditModel;
|
|
protected NursingBedsideItemModel $nursingBedsideItemModel;
|
|
|
|
public function __construct()
|
|
{
|
|
$this->ipdModel = new IpdBillingModel();
|
|
$this->bedAssignmentModel = new BedAssignmentHistoryModel();
|
|
$this->itemIpdModel = new ItemIpdModel();
|
|
$this->ipdEditModel = new IpdModel();
|
|
$this->nursingBedsideItemModel = new NursingBedsideItemModel();
|
|
helper(['common', 'form', 'age']);
|
|
}
|
|
|
|
public function index()
|
|
{
|
|
$permission = $this->requireAnyPermission([
|
|
'billing.access',
|
|
'billing.ipd.access',
|
|
]);
|
|
if ($permission) {
|
|
return $permission;
|
|
}
|
|
|
|
return view('billing/ipd/dashboard');
|
|
}
|
|
|
|
public function currentAdmission()
|
|
{
|
|
$permission = $this->requireAnyPermission([
|
|
'billing.access',
|
|
'billing.ipd.current-admission',
|
|
]);
|
|
if ($permission) {
|
|
return $permission;
|
|
}
|
|
|
|
$data['records'] = $this->ipdModel->getCurrentAdmissions();
|
|
|
|
return view('billing/ipd/current_admission', $data);
|
|
}
|
|
|
|
public function ipdInvoices()
|
|
{
|
|
$permission = $this->requireAnyPermission([
|
|
'billing.access',
|
|
'billing.ipd.invoice',
|
|
]);
|
|
if ($permission) {
|
|
return $permission;
|
|
}
|
|
|
|
$data['ipdCode'] = (string) ($this->request->getGet('ipd_code') ?? '');
|
|
|
|
return view('billing/ipd/invoice_list', $data);
|
|
}
|
|
|
|
public function getIpdTable()
|
|
{
|
|
$permission = $this->requireAnyPermission([
|
|
'billing.access',
|
|
'billing.ipd.invoice',
|
|
]);
|
|
if ($permission) {
|
|
return $permission;
|
|
}
|
|
|
|
if (! $this->request->isAJAX()) {
|
|
return $this->response->setStatusCode(400)->setJSON(['error' => 'Invalid request']);
|
|
}
|
|
|
|
$request = $this->request->getPost();
|
|
$payload = $this->ipdModel->getIpdTableData($request);
|
|
|
|
return $this->response->setJSON($payload);
|
|
}
|
|
|
|
public function cashBalance()
|
|
{
|
|
$permission = $this->requireAnyPermission([
|
|
'billing.access',
|
|
'billing.ipd.cash-balance',
|
|
]);
|
|
if ($permission) {
|
|
return $permission;
|
|
}
|
|
|
|
return view('billing/ipd/cash_balance');
|
|
}
|
|
|
|
public function cashBalanceReport()
|
|
{
|
|
$permission = $this->requireAnyPermission([
|
|
'billing.access',
|
|
'billing.ipd.cash-balance',
|
|
]);
|
|
if ($permission) {
|
|
return $permission;
|
|
}
|
|
|
|
$range = (string) ($this->request->getGet('range') ?? '');
|
|
[$start, $end] = $this->parseDateRange($range);
|
|
|
|
$rows = $this->ipdModel->getCashBalanceReport($start, $end);
|
|
$totals = $this->calculateCashBalanceTotals($rows);
|
|
|
|
return view('billing/ipd/cash_balance_table', [
|
|
'rows' => $rows,
|
|
'totals' => $totals,
|
|
]);
|
|
}
|
|
|
|
public function cashBalanceExport()
|
|
{
|
|
$permission = $this->requireAnyPermission([
|
|
'billing.access',
|
|
'billing.ipd.export',
|
|
]);
|
|
if ($permission) {
|
|
return $permission;
|
|
}
|
|
|
|
$range = (string) ($this->request->getGet('range') ?? '');
|
|
[$start, $end] = $this->parseDateRange($range);
|
|
|
|
$rows = $this->ipdModel->getCashBalanceReport($start, $end);
|
|
$totals = $this->calculateCashBalanceTotals($rows);
|
|
|
|
$content = view('billing/ipd/cash_balance_table', [
|
|
'rows' => $rows,
|
|
'totals' => $totals,
|
|
]);
|
|
|
|
ExportExcel($content, 'IPD_Balance_List');
|
|
|
|
return $this->response->setBody('');
|
|
}
|
|
|
|
public function panel(int $ipdId)
|
|
{
|
|
$permission = $this->requireAnyPermission([
|
|
'billing.access',
|
|
'billing.ipd.invoice',
|
|
]);
|
|
if ($permission) {
|
|
return $permission;
|
|
}
|
|
|
|
try {
|
|
$panelData = $this->ipdModel->getIpdPanelInfo($ipdId);
|
|
} catch (\Throwable $e) {
|
|
log_message('error', 'IPD panel load failed for IPD #' . $ipdId . ': ' . $e->getMessage());
|
|
|
|
return $this->response->setStatusCode(500)->setBody('Unable to load IPD panel');
|
|
}
|
|
|
|
if (empty($panelData)) {
|
|
return $this->response->setStatusCode(404)->setBody('IPD not found');
|
|
}
|
|
|
|
// Keep billing snapshot fresh on every panel open (legacy behavior parity).
|
|
$this->recalculateIpdBillingSafely($ipdId, 'panel.open');
|
|
$panelData = $this->ipdModel->getIpdPanelInfo($ipdId);
|
|
|
|
return view('billing/ipd/panel', $panelData);
|
|
}
|
|
|
|
public function admit()
|
|
{
|
|
$permission = $this->requireAnyPermission([
|
|
'billing.access',
|
|
'billing.ipd.access',
|
|
'billing.ipd.current-admission',
|
|
'ipd_nursing.view',
|
|
]);
|
|
if ($permission) {
|
|
return $permission;
|
|
}
|
|
|
|
$defaultType = strtolower(trim((string) ($this->request->getGet('type') ?? '')));
|
|
if (! in_array($defaultType, ['ipd', 'daycare', 'emergency'], true)) {
|
|
$defaultType = 'emergency';
|
|
}
|
|
|
|
return view('billing/ipd/admit_patient', [
|
|
'default_type' => $defaultType,
|
|
]);
|
|
}
|
|
|
|
public function searchPatientForAdmit()
|
|
{
|
|
$permission = $this->requireAnyPermission([
|
|
'billing.access',
|
|
'billing.ipd.access',
|
|
'billing.ipd.current-admission',
|
|
'ipd_nursing.view',
|
|
]);
|
|
if ($permission) {
|
|
return $this->response->setStatusCode(403)->setJSON(['error' => 'Permission denied']);
|
|
}
|
|
|
|
$term = trim((string) ($this->request->getGet('q') ?? $this->request->getPost('q') ?? ''));
|
|
if ($term === '') {
|
|
return $this->response->setJSON([]);
|
|
}
|
|
|
|
$termClean = preg_replace('/[^A-Za-z0-9 _.@\/-]/', '', $term);
|
|
if ($termClean === '') {
|
|
return $this->response->setJSON([]);
|
|
}
|
|
|
|
$builder = $this->db->table('patient_master p');
|
|
$builder->select('p.id, p.p_code, p.p_fname, p.p_rname, p.gender, p.age, p.dob, p.mphone1, p.mphone2');
|
|
$builder->groupStart()
|
|
->like('p.p_fname', $termClean)
|
|
->orLike('p.p_rname', $termClean)
|
|
->orLike('p.p_code', $termClean)
|
|
->orLike('p.mphone1', $termClean)
|
|
->orLike('p.mphone2', $termClean)
|
|
->groupEnd();
|
|
$builder->limit(20);
|
|
$results = $builder->get()->getResultArray();
|
|
|
|
$patientIds = array_column($results, 'id');
|
|
$activeIpds = [];
|
|
if (! empty($patientIds)) {
|
|
$rows = $this->db->table('ipd_master')
|
|
->select('id, p_id, ipd_code, admission_type, ipd_status')
|
|
->whereIn('p_id', $patientIds)
|
|
->where('ipd_status', 0)
|
|
->get()
|
|
->getResultArray();
|
|
foreach ($rows as $r) {
|
|
if (empty($r['ipd_code'])) {
|
|
$r['ipd_code'] = '#' . $r['id'];
|
|
}
|
|
$activeIpds[(int) $r['p_id']] = $r;
|
|
}
|
|
}
|
|
|
|
foreach ($results as &$pt) {
|
|
$genderVal = (int) ($pt['gender'] ?? 0);
|
|
$pt['gender_text'] = ($genderVal === 1) ? 'Male' : (($genderVal === 2) ? 'Female' : 'Other');
|
|
$fullName = trim(($pt['p_fname'] ?? '') . ' ' . ($pt['p_rname'] ?? ''));
|
|
$pt['patient_name'] = $fullName !== '' ? $fullName : 'Unnamed';
|
|
$pt['active_ipd'] = $activeIpds[(int) $pt['id']] ?? null;
|
|
}
|
|
unset($pt);
|
|
|
|
return $this->response->setJSON($results);
|
|
}
|
|
|
|
public function legacyAddIpd(int $patientId)
|
|
{
|
|
$permission = $this->requireAnyPermission([
|
|
'billing.access',
|
|
'billing.ipd.current-admission',
|
|
'billing.ipd.invoice',
|
|
]);
|
|
if ($permission) {
|
|
return $permission;
|
|
}
|
|
|
|
$patientId = (int) $patientId;
|
|
if ($patientId <= 0) {
|
|
return redirect()->to(base_url('billing/ipd/current-admission'));
|
|
}
|
|
|
|
$activeIpd = $this->db->table('ipd_master')
|
|
->select('id')
|
|
->where('p_id', $patientId)
|
|
->where('ipd_status', 0)
|
|
->orderBy('id', 'DESC')
|
|
->get(1)
|
|
->getRowArray();
|
|
|
|
if (! empty($activeIpd['id'])) {
|
|
return redirect()->to(base_url('billing/ipd/panel/' . (int) $activeIpd['id']));
|
|
}
|
|
|
|
$data = $this->buildLegacyIpdRegistrationData($patientId);
|
|
if (empty($data['person_info'])) {
|
|
return $this->response->setStatusCode(404)->setBody('Patient not found');
|
|
}
|
|
|
|
$defaultType = strtolower(trim((string) ($this->request->getGet('type') ?? 'ipd')));
|
|
if (! in_array($defaultType, ['ipd', 'daycare', 'emergency'], true)) {
|
|
$defaultType = 'ipd';
|
|
}
|
|
$data['default_admission_type'] = $defaultType;
|
|
|
|
return view('billing/ipd/legacy_registration', $data);
|
|
}
|
|
|
|
public function legacyAddNew()
|
|
{
|
|
$permission = $this->requireAnyPermission([
|
|
'billing.access',
|
|
'billing.ipd.current-admission',
|
|
'billing.ipd.invoice',
|
|
]);
|
|
if ($permission) {
|
|
return $permission;
|
|
}
|
|
|
|
if (! $this->request->isAJAX()) {
|
|
return $this->response->setStatusCode(400)->setJSON([
|
|
'insertid' => 0,
|
|
'error_text' => 'Invalid request',
|
|
]);
|
|
}
|
|
|
|
$patientId = (int) ($this->request->getPost('pid') ?? 0);
|
|
$docList = $this->request->getPost('doc_id');
|
|
$docList = is_array($docList) ? array_values(array_filter(array_map('intval', $docList), static fn ($id) => $id > 0)) : [];
|
|
|
|
if ($patientId <= 0 || empty($docList)) {
|
|
return $this->response->setJSON([
|
|
'insertid' => 0,
|
|
'error_text' => 'Please select at least one doctor.',
|
|
]);
|
|
}
|
|
|
|
$registerDateInput = (string) ($this->request->getPost('res_date') ?? '');
|
|
$registerDate = $registerDateInput !== '' ? str_to_MysqlDate($registerDateInput) : date('Y-m-d');
|
|
|
|
$admissionType = strtolower(trim((string) ($this->request->getPost('admission_type') ?? 'ipd')));
|
|
if (! in_array($admissionType, ['ipd', 'daycare', 'emergency'], true)) {
|
|
$admissionType = 'ipd';
|
|
}
|
|
|
|
$triageLevel = (int) ($this->request->getPost('triage_level') ?? 0);
|
|
$isMlc = (int) ($this->request->getPost('optionsRadios_mlc') ?? 0);
|
|
$mlcNumber = trim((string) ($this->request->getPost('mlc_number') ?? ''));
|
|
$policeStation = trim((string) ($this->request->getPost('police_station') ?? ''));
|
|
$policeConstable = trim((string) ($this->request->getPost('police_constable_details') ?? ''));
|
|
$broughtBy = trim((string) ($this->request->getPost('brought_by') ?? ''));
|
|
$daycareProcedure = trim((string) ($this->request->getPost('daycare_procedure_name') ?? ''));
|
|
|
|
$master = [
|
|
'p_id' => $patientId,
|
|
'P_name' => (string) ($this->request->getPost('pname') ?? ''),
|
|
'relation' => (string) ($this->request->getPost('r_relation') ?? ''),
|
|
'contact_person_Name' => (string) ($this->request->getPost('rp_name') ?? ''),
|
|
'insurance_id' => (int) ($this->request->getPost('insur_list') ?? 0),
|
|
'policy_no' => (string) ($this->request->getPost('insur_no') ?? ''),
|
|
'register_date' => $registerDate,
|
|
'problem' => (string) ($this->request->getPost('problem') ?? ''),
|
|
'remark' => (string) ($this->request->getPost('remark') ?? ''),
|
|
'P_mobile1' => (string) ($this->request->getPost('phone1') ?? ''),
|
|
'P_mobile2' => (string) ($this->request->getPost('phone2') ?? ''),
|
|
'case_type' => $isMlc,
|
|
'case_id' => (int) ($this->request->getPost('optionsRadios_org') ?? 0),
|
|
'reg_time' => (string) ($this->request->getPost('res_time') ?? date('H:i')),
|
|
'refer_by' => (int) ($this->request->getPost('refer_by_list') ?? 0),
|
|
'dept_id' => (int) ($this->request->getPost('dept_id') ?? 0),
|
|
'admission_type' => $admissionType,
|
|
'triage_level' => $triageLevel > 0 ? $triageLevel : null,
|
|
'triage_time' => $triageLevel > 0 ? date('Y-m-d H:i:s') : null,
|
|
'is_mlc' => $isMlc,
|
|
'mlc_number' => $mlcNumber !== '' ? $mlcNumber : null,
|
|
'police_station' => $policeStation !== '' ? $policeStation : null,
|
|
'police_constable_details' => $policeConstable !== '' ? $policeConstable : null,
|
|
'brought_by' => $broughtBy !== '' ? $broughtBy : null,
|
|
'daycare_procedure_name' => $daycareProcedure !== '' ? $daycareProcedure : null,
|
|
];
|
|
|
|
$insertId = $this->ipdEditModel->insertIpd([
|
|
'master' => $master,
|
|
'doc_list' => $docList,
|
|
]);
|
|
|
|
if ($insertId > 0) {
|
|
$roomId = (int) ($this->request->getPost('room_list') ?? 0);
|
|
if ($roomId > 0) {
|
|
$this->ipdEditModel->bedAssign([
|
|
'ipd_id' => $insertId,
|
|
'Fdate' => date('Y-m-d H:i:s'),
|
|
'TDate' => date('Y-m-d H:i:s'),
|
|
'bed_id' => $roomId,
|
|
]);
|
|
}
|
|
|
|
$caseId = (int) ($master['case_id'] ?? 0);
|
|
if ($caseId > 0 && $this->db->tableExists('organization_case_master')) {
|
|
$this->db->table('organization_case_master')
|
|
->where('id', $caseId)
|
|
->update(['ipd_id' => $insertId]);
|
|
}
|
|
|
|
$this->enqueueIpdAdmissionSync((int) $insertId, 'ipd.admission.created');
|
|
}
|
|
|
|
return $this->response->setJSON([
|
|
'insertid' => (int) $insertId,
|
|
]);
|
|
}
|
|
|
|
public function convertToIpd()
|
|
{
|
|
$permission = $this->requireAnyPermission([
|
|
'billing.access',
|
|
'billing.ipd.invoice',
|
|
'ipd_nursing.view',
|
|
]);
|
|
if ($permission) {
|
|
return $permission;
|
|
}
|
|
|
|
$ipdId = (int) ($this->request->getPost('ipd_id') ?? 0);
|
|
$targetBedId = (int) ($this->request->getPost('target_bed_id') ?? 0);
|
|
$doctorId = (int) ($this->request->getPost('doctor_id') ?? 0);
|
|
$reason = trim((string) ($this->request->getPost('conversion_reason') ?? ''));
|
|
|
|
$sbarSituation = trim((string) ($this->request->getPost('sbar_situation') ?? ''));
|
|
$sbarBackground = trim((string) ($this->request->getPost('sbar_background') ?? ''));
|
|
$sbarAssessment = trim((string) ($this->request->getPost('sbar_assessment') ?? ''));
|
|
$sbarRecommendation = trim((string) ($this->request->getPost('sbar_recommendation') ?? ''));
|
|
|
|
$sbarNote = "S (Situation): " . $sbarSituation . "\n"
|
|
. "B (Background): " . $sbarBackground . "\n"
|
|
. "A (Assessment): " . $sbarAssessment . "\n"
|
|
. "R (Recommendation): " . $sbarRecommendation;
|
|
|
|
$result = $this->ipdEditModel->convertToIpd($ipdId, [
|
|
'target_bed_id' => $targetBedId,
|
|
'doctor_id' => $doctorId,
|
|
'conversion_reason' => $reason,
|
|
'sbar_handover' => trim($sbarNote),
|
|
]);
|
|
|
|
if (! empty($result['status'])) {
|
|
$this->enqueueIpdAdmissionSync($ipdId, 'ipd.admission.escalated');
|
|
}
|
|
|
|
return $this->response->setJSON($result);
|
|
}
|
|
|
|
public function getAvailableIpdBeds()
|
|
{
|
|
$permission = $this->requireAnyPermission([
|
|
'billing.access',
|
|
'billing.ipd.invoice',
|
|
'ipd_nursing.view',
|
|
]);
|
|
if ($permission) {
|
|
return $permission;
|
|
}
|
|
|
|
$beds = $this->db->table('bed_master b')
|
|
->select('b.id, b.bed_number, b.bed_code, w.ward_name, w.ward_type, w.floor_number')
|
|
->join('ward_master w', 'w.id = b.ward_id', 'left')
|
|
->where('b.bed_status', 'available')
|
|
->where('b.status', 'active')
|
|
->where('b.current_ipd_id IS NULL')
|
|
->orderBy('w.ward_type', 'ASC')
|
|
->orderBy('w.ward_name', 'ASC')
|
|
->orderBy('b.bed_number', 'ASC')
|
|
->get()
|
|
->getResultArray();
|
|
|
|
$doctors = $this->db->table('doctor_master')
|
|
->select("id, concat_ws(' ', 'Dr.', p_fname, p_mname, p_lname) as doc_name")
|
|
->where('status', '1')
|
|
->orderBy('p_fname', 'ASC')
|
|
->get()
|
|
->getResultArray();
|
|
|
|
return $this->response->setJSON([
|
|
'status' => true,
|
|
'beds' => $beds,
|
|
'doctors' => $doctors,
|
|
]);
|
|
}
|
|
|
|
private function buildLegacyIpdRegistrationData(int $patientId): array
|
|
{
|
|
$person = $this->db->table('patient_master')
|
|
->select("*, if(gender=1,'Male','FeMale') as xgender", false)
|
|
->where('id', $patientId)
|
|
->get()
|
|
->getRow();
|
|
|
|
if ($person) {
|
|
$person->age = get_age_1(
|
|
$person->dob ?? null,
|
|
$person->age ?? '',
|
|
$person->age_in_month ?? '',
|
|
$person->estimate_dob ?? ''
|
|
);
|
|
}
|
|
|
|
$docSpecList = $this->db->query(
|
|
"select d.id, d.p_fname, group_concat(m.SpecName) as SpecName
|
|
from doctor_master d
|
|
join doc_spec s on d.id = s.doc_id
|
|
join med_spec m on s.med_spec_id = m.id
|
|
where d.active = 1
|
|
group by d.id
|
|
order by d.p_fname"
|
|
)->getResult();
|
|
|
|
$ipdBedList = [];
|
|
if ($this->db->tableExists('bed_master')) {
|
|
$ipdBedList = $this->db->query(
|
|
"select b.id,
|
|
w.ward_type,
|
|
concat('Bed No:', coalesce(b.bed_number, b.bed_code), '/', '[', coalesce(w.ward_name, ''), ']', if(w.ward_type is not null and w.ward_type != '', concat(' (', w.ward_type, ')'), '')) as Bed_Desc
|
|
from bed_master b
|
|
left join ward_master w on w.id = b.ward_id
|
|
where b.bed_status = 'available'
|
|
order by w.ward_type, b.bed_number, b.id"
|
|
)->getResult();
|
|
} elseif ($this->db->tableExists('hc_bed_master')) {
|
|
$ipdBedList = $this->db->query(
|
|
"select id, concat('Bed No:', coalesce(bed_no, bed_number), '/', '[', coalesce(room_name, ward_name), ']') as Bed_Desc
|
|
from hc_bed_master
|
|
where ifnull(bed_used_p_id,0)=0"
|
|
)->getResult();
|
|
}
|
|
|
|
$caseMaster = [];
|
|
if ($this->db->tableExists('organization_case_master')) {
|
|
$caseMaster = $this->db->table('organization_case_master')
|
|
->where('status', 0)
|
|
->where('case_type', 1)
|
|
->where('ipd_id', 0)
|
|
->where('p_id', $patientId)
|
|
->get()
|
|
->getResult();
|
|
}
|
|
|
|
$referMaster = [];
|
|
if ($this->db->tableExists('refer_master')) {
|
|
$referMaster = $this->db->table('refer_master')
|
|
->where('active', 1)
|
|
->orderBy('f_name', 'ASC')
|
|
->get()
|
|
->getResult();
|
|
}
|
|
|
|
$hcDepartment = [];
|
|
if ($this->db->tableExists('hc_department')) {
|
|
$hcDepartment = $this->db->table('hc_department')
|
|
->orderBy('vName', 'ASC')
|
|
->get()
|
|
->getResult();
|
|
}
|
|
|
|
return [
|
|
'person_info' => $person,
|
|
'doc_spec_l' => $docSpecList,
|
|
'ipd_bed_list' => $ipdBedList,
|
|
'case_master' => $caseMaster,
|
|
'refer_master' => $referMaster,
|
|
'hc_department' => $hcDepartment,
|
|
];
|
|
}
|
|
|
|
public function panelTab(int $ipdId, string $tab)
|
|
{
|
|
$permission = $this->requireAnyPermission([
|
|
'billing.access',
|
|
'billing.ipd.invoice',
|
|
]);
|
|
if ($permission) {
|
|
return $permission;
|
|
}
|
|
|
|
try {
|
|
$panelData = $this->ipdModel->getIpdPanelInfo($ipdId);
|
|
} catch (\Throwable $e) {
|
|
log_message('error', 'IPD panel tab load failed for IPD #' . $ipdId . ' tab ' . $tab . ': ' . $e->getMessage());
|
|
if ($tab === 'ipd-charges') {
|
|
return '<div class="alert alert-danger m-2">Unable to load IPD Charges right now. Please contact admin and check server log for IPD #' . (int) $ipdId . '.</div>';
|
|
}
|
|
|
|
return $this->response->setStatusCode(500)->setBody('Unable to load IPD tab');
|
|
}
|
|
if (empty($panelData)) {
|
|
return $this->response->setStatusCode(404)->setBody('IPD not found');
|
|
}
|
|
|
|
if ($tab === 'admission') {
|
|
return view('billing/ipd/panel_admission', $this->buildAdmissionTabData($panelData, $ipdId));
|
|
}
|
|
|
|
if ($tab === 'bed-assign') {
|
|
$panelData['bed_assignments'] = $this->bedAssignmentModel->getByIpd($ipdId);
|
|
|
|
return view('billing/ipd/panel_bed_assign', $panelData);
|
|
}
|
|
|
|
if ($tab === 'ipd-charges') {
|
|
$panelData['ipd_charges_grouped'] = $panelData['ipd_charges_grouped'] ?? [];
|
|
$panelData['ipd_charges_total'] = $panelData['ipd_charges_total'] ?? 0;
|
|
$panelData['ipd_packages'] = $panelData['ipd_packages'] ?? [];
|
|
$panelData['bedside_items_by_category'] = $panelData['bedside_items_by_category'] ?? [];
|
|
$panelData['ipd_insurance_id'] = $panelData['ipd_insurance_id'] ?? 0;
|
|
$panelData['doc_list'] = $panelData['doc_list'] ?? [];
|
|
$panelData['doctor_visit_fee_types'] = $panelData['doctor_visit_fee_types'] ?? [];
|
|
$panelData['doctor_visit_fee_map'] = $panelData['doctor_visit_fee_map'] ?? [];
|
|
$panelData['item_types'] = $panelData['item_types'] ?? [];
|
|
$panelData['item_lists'] = $panelData['item_lists'] ?? [];
|
|
|
|
$insCompId = 1;
|
|
|
|
try {
|
|
$this->syncNursingChargesToInvoice($ipdId);
|
|
} catch (\Throwable $e) {
|
|
log_message('error', 'IPD Charges sync failed for IPD #' . $ipdId . ': ' . $e->getMessage());
|
|
}
|
|
|
|
try {
|
|
$panelData['ipd_charges_grouped'] = $this->ipdModel->getIpdChargesGrouped($ipdId);
|
|
$panelData['ipd_charges_total'] = $this->ipdModel->getIpdChargesTotal($ipdId);
|
|
$panelData['ipd_packages'] = $this->ipdModel->getIpdPackages($ipdId);
|
|
} catch (\Throwable $e) {
|
|
log_message('error', 'IPD Charges core list failed for IPD #' . $ipdId . ': ' . $e->getMessage());
|
|
}
|
|
|
|
try {
|
|
$caseMeta = $this->ipdModel->getIpdCaseMeta($ipdId);
|
|
$insCompId = (int) ($caseMeta['insurance_id'] ?? 0);
|
|
if ($insCompId <= 0) {
|
|
$insCompId = 1;
|
|
}
|
|
$panelData['ipd_insurance_id'] = $insCompId;
|
|
} catch (\Throwable $e) {
|
|
log_message('error', 'IPD Charges insurance meta failed for IPD #' . $ipdId . ': ' . $e->getMessage());
|
|
$panelData['ipd_insurance_id'] = 1;
|
|
}
|
|
|
|
try {
|
|
$panelData['bedside_items_by_category'] = $this->nursingBedsideItemModel->getBillableGroupedByCategory($insCompId);
|
|
} catch (\Throwable $e) {
|
|
log_message('error', 'IPD bedside item load failed for IPD #' . $ipdId . ': ' . $e->getMessage());
|
|
}
|
|
|
|
try {
|
|
$panelData['doc_list'] = $this->ipdModel->getIpdDoctorList();
|
|
} catch (\Throwable $e) {
|
|
log_message('error', 'IPD doctor list load failed for IPD #' . $ipdId . ': ' . $e->getMessage());
|
|
$panelData['doc_list'] = [];
|
|
}
|
|
|
|
$doctorIds = array_map(static fn ($doc) => (int) ($doc->id ?? 0), $panelData['doc_list']);
|
|
$doctorIds = array_values(array_filter($doctorIds, static fn ($id) => $id > 0));
|
|
|
|
try {
|
|
$panelData['doctor_visit_fee_types'] = $this->ipdModel->getDoctorVisitFeeTypes();
|
|
$panelData['doctor_visit_fee_map'] = $this->ipdModel->getDoctorVisitFeeMap($doctorIds);
|
|
} catch (\Throwable $e) {
|
|
log_message('error', 'IPD doctor fee load failed for IPD #' . $ipdId . ': ' . $e->getMessage());
|
|
$panelData['doctor_visit_fee_types'] = [];
|
|
$panelData['doctor_visit_fee_map'] = [];
|
|
}
|
|
|
|
try {
|
|
$panelData['item_types'] = $this->itemIpdModel->getItemTypes();
|
|
} catch (\Throwable $e) {
|
|
log_message('error', 'IPD item type load failed for IPD #' . $ipdId . ': ' . $e->getMessage());
|
|
$panelData['item_types'] = [];
|
|
}
|
|
|
|
$itemLists = [];
|
|
foreach ($panelData['item_types'] as $type) {
|
|
$typeId = (int) ($type->itype_id ?? 0);
|
|
if ($typeId <= 0) {
|
|
continue;
|
|
}
|
|
try {
|
|
$itemLists[$typeId] = $this->itemIpdModel->getItemsByTypeWithInsurance($typeId, $insCompId);
|
|
} catch (\Throwable $e) {
|
|
log_message('error', 'IPD item list load failed for type ' . $typeId . ' (IPD #' . $ipdId . '): ' . $e->getMessage());
|
|
$itemLists[$typeId] = [];
|
|
}
|
|
}
|
|
$panelData['item_lists'] = $itemLists;
|
|
|
|
try {
|
|
return view('billing/ipd/panel_ipd_charges', $panelData);
|
|
} catch (\Throwable $e) {
|
|
log_message('error', 'IPD Charges view render failed for IPD #' . $ipdId . ': ' . $e->getMessage());
|
|
|
|
return '<div class="alert alert-danger m-2">Unable to load IPD Charges right now. Please contact admin and check server log for IPD #' . (int) $ipdId . '.</div>';
|
|
}
|
|
}
|
|
|
|
if ($tab === 'package') {
|
|
$panelData['ipd_packages'] = $this->ipdModel->getIpdPackages($ipdId);
|
|
$panelData['medical_bills'] = $this->ipdModel->getMedicalBills($ipdId);
|
|
$caseMeta = $this->ipdModel->getIpdCaseMeta($ipdId);
|
|
$insCompId = (int) ($caseMeta['insurance_id'] ?? 0);
|
|
if ($insCompId <= 0) {
|
|
$insCompId = 0;
|
|
}
|
|
$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);
|
|
}
|
|
|
|
if ($tab === 'diagnosis-charges') {
|
|
$panelData['diagnosis_charges'] = $this->ipdModel->getDiagnosisCharges($ipdId);
|
|
|
|
return view('billing/ipd/panel_diagnosis', $panelData);
|
|
}
|
|
|
|
if ($tab === 'payments') {
|
|
$panelData['payments'] = $this->ipdModel->getPayments($ipdId);
|
|
$panelData['medical_bills'] = $this->ipdModel->getMedicalBills($ipdId);
|
|
|
|
return view('billing/ipd/panel_payments', $panelData);
|
|
}
|
|
|
|
if ($tab === 'medical-bills') {
|
|
$panelData['medical_bills'] = $this->ipdModel->getMedicalBills($ipdId);
|
|
|
|
return view('billing/ipd/panel_medical_bills', $panelData);
|
|
}
|
|
|
|
if ($tab === 'bill-details') {
|
|
$this->recalculateIpdBillingSafely($ipdId, 'panel.tab.bill-details');
|
|
$panelData = $this->ipdModel->getIpdPanelInfo($ipdId);
|
|
return view('billing/ipd/panel_bill_details', $this->buildBillDetailsTabData($panelData, $ipdId));
|
|
}
|
|
|
|
if ($tab === 'discharge-process') {
|
|
$panelData['discharge_info'] = $this->ipdModel->getDischargeInfo($ipdId);
|
|
$user = auth()->user();
|
|
$panelData['can_edit_discharge'] = $user !== null && $user->can('billing.ipd.discharge.edit');
|
|
|
|
return view('billing/ipd/panel_discharge', $panelData);
|
|
}
|
|
|
|
if ($tab === 'documents') {
|
|
return view('billing/ipd/panel_documents', [
|
|
'ipd_id' => $ipdId,
|
|
'ipd_info' => $panelData['ipd_info'] ?? null,
|
|
'documents' => $this->getAvailableIpdDocuments(),
|
|
]);
|
|
}
|
|
|
|
return view('billing/ipd/panel_placeholder', [
|
|
'title' => ucwords(str_replace('-', ' ', $tab)),
|
|
]);
|
|
}
|
|
|
|
public function updateAdmission(int $ipdId)
|
|
{
|
|
$permission = $this->requireAnyPermission([
|
|
'billing.ipd.admission.edit',
|
|
]);
|
|
if ($permission) {
|
|
return $permission;
|
|
}
|
|
|
|
if (! $this->request->isAJAX()) {
|
|
return $this->response->setStatusCode(400)->setJSON(['update' => 0, 'message' => 'Invalid request']);
|
|
}
|
|
|
|
$panelData = $this->ipdModel->getIpdPanelInfo($ipdId);
|
|
if (empty($panelData)) {
|
|
return $this->response->setStatusCode(404)->setJSON(['update' => 0, 'message' => 'IPD not found']);
|
|
}
|
|
|
|
$registerDate = $this->normalizeAdmissionDate((string) ($this->request->getPost('register_date') ?? ''));
|
|
if ($registerDate === null) {
|
|
return $this->response->setStatusCode(422)->setJSON(['update' => 0, 'message' => 'Enter a valid admission date']);
|
|
}
|
|
|
|
$regTime = $this->normalizeAdmissionTime((string) ($this->request->getPost('reg_time') ?? ''));
|
|
if ($regTime === null) {
|
|
return $this->response->setStatusCode(422)->setJSON(['update' => 0, 'message' => 'Enter a valid admission time']);
|
|
}
|
|
|
|
$doctorIds = $this->request->getPost('doc_id');
|
|
$doctorIds = is_array($doctorIds)
|
|
? array_values(array_unique(array_filter(array_map('intval', $doctorIds), static fn ($id) => $id > 0)))
|
|
: [];
|
|
|
|
$ipdFields = $this->db->getFieldNames('ipd_master') ?? [];
|
|
$fieldMap = array_flip($ipdFields);
|
|
|
|
$primaryDoctorId = $doctorIds[0] ?? 0;
|
|
$primaryDoctorName = $primaryDoctorId > 0 ? $this->ipdModel->getDoctorNameById($primaryDoctorId) : '';
|
|
|
|
$masterUpdate = [];
|
|
if (isset($fieldMap['register_date'])) {
|
|
$masterUpdate['register_date'] = $registerDate;
|
|
}
|
|
if (isset($fieldMap['reg_time'])) {
|
|
$masterUpdate['reg_time'] = $regTime;
|
|
}
|
|
if (isset($fieldMap['reg_time_bak'])) {
|
|
$masterUpdate['reg_time_bak'] = $regTime;
|
|
}
|
|
if (isset($fieldMap['case_type'])) {
|
|
$masterUpdate['case_type'] = (int) ($this->request->getPost('case_type') ?? 0) === 1 ? 1 : 0;
|
|
}
|
|
if (isset($fieldMap['dept_id'])) {
|
|
$masterUpdate['dept_id'] = (int) ($this->request->getPost('dept_id') ?? 0);
|
|
}
|
|
if (isset($fieldMap['refer_by'])) {
|
|
$masterUpdate['refer_by'] = (int) ($this->request->getPost('refer_by') ?? 0);
|
|
}
|
|
if (isset($fieldMap['problem'])) {
|
|
$masterUpdate['problem'] = substr((string) ($this->request->getPost('problem') ?? ''), 0, 200);
|
|
}
|
|
if (isset($fieldMap['remark'])) {
|
|
$masterUpdate['remark'] = (string) ($this->request->getPost('remark') ?? '');
|
|
}
|
|
if (isset($fieldMap['contact_person_Name'])) {
|
|
$masterUpdate['contact_person_Name'] = substr((string) ($this->request->getPost('contact_person_Name') ?? ''), 0, 50);
|
|
}
|
|
if (isset($fieldMap['P_mobile1'])) {
|
|
$masterUpdate['P_mobile1'] = substr((string) ($this->request->getPost('P_mobile1') ?? ''), 0, 50);
|
|
}
|
|
if (isset($fieldMap['P_mobile2'])) {
|
|
$masterUpdate['P_mobile2'] = substr((string) ($this->request->getPost('P_mobile2') ?? ''), 0, 50);
|
|
}
|
|
if (isset($fieldMap['relation'])) {
|
|
$masterUpdate['relation'] = substr((string) ($this->request->getPost('relation') ?? ''), 0, 50);
|
|
}
|
|
if (isset($fieldMap['r_doc_id'])) {
|
|
$masterUpdate['r_doc_id'] = $primaryDoctorId;
|
|
}
|
|
if (isset($fieldMap['r_doc_name'])) {
|
|
$masterUpdate['r_doc_name'] = $primaryDoctorName;
|
|
}
|
|
|
|
$this->db->transStart();
|
|
if (! empty($masterUpdate)) {
|
|
$this->ipdEditModel->updateIpd($masterUpdate, $ipdId);
|
|
}
|
|
$this->ipdEditModel->replaceIpdDoctors($ipdId, $doctorIds);
|
|
$this->db->transComplete();
|
|
|
|
if (! $this->db->transStatus()) {
|
|
return $this->response->setStatusCode(500)->setJSON(['update' => 0, 'message' => 'Unable to update admission details']);
|
|
}
|
|
|
|
$this->enqueueIpdAdmissionSync($ipdId, 'ipd.admission.updated');
|
|
|
|
$freshPanelData = $this->ipdModel->getIpdPanelInfo($ipdId);
|
|
$admissionData = $this->buildAdmissionTabData($freshPanelData, $ipdId);
|
|
|
|
return $this->response->setJSON([
|
|
'update' => 1,
|
|
'message' => 'Admission details updated.',
|
|
'html' => view('billing/ipd/panel_admission', $admissionData),
|
|
'header' => $this->buildAdmissionHeaderPayload($freshPanelData),
|
|
]);
|
|
}
|
|
|
|
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([
|
|
'billing.ipd.discharge.edit',
|
|
]);
|
|
if ($permission) {
|
|
return $permission;
|
|
}
|
|
|
|
if (! $this->request->isAJAX()) {
|
|
return $this->response->setStatusCode(400)->setJSON(['update' => 0, 'message' => 'Invalid request']);
|
|
}
|
|
|
|
if (! in_array($slot, [1, 2, 3], true)) {
|
|
return $this->response->setStatusCode(422)->setJSON(['update' => 0, 'message' => 'Invalid discount slot']);
|
|
}
|
|
|
|
$fieldMap = $this->getIpdFieldMap();
|
|
$amount = max(0, (float) ($this->request->getPost('amount') ?? 0));
|
|
$remark = trim((string) ($this->request->getPost('remark') ?? ''));
|
|
|
|
$amountKey = $slot === 1 ? 'Discount' : 'Discount' . $slot;
|
|
$remarkKey = $slot === 1 ? 'Discount_Remark' : 'Discount_Remark' . $slot;
|
|
|
|
$update = [];
|
|
if (isset($fieldMap[$amountKey])) {
|
|
$update[$amountKey] = $amount;
|
|
}
|
|
if (isset($fieldMap[$remarkKey])) {
|
|
$update[$remarkKey] = substr($remark, 0, 200);
|
|
}
|
|
|
|
if (empty($update)) {
|
|
return $this->response->setStatusCode(422)->setJSON(['update' => 0, 'message' => 'Discount fields are not available in schema']);
|
|
}
|
|
|
|
$this->ipdEditModel->updateIpd($update, $ipdId);
|
|
$this->ipdEditModel->calculateIPD($ipdId);
|
|
|
|
return $this->buildDischargeProcessResponse($ipdId, 'Discount updated.');
|
|
}
|
|
|
|
public function updateDischargeCharge(int $ipdId, int $slot)
|
|
{
|
|
$permission = $this->requireAnyPermission([
|
|
'billing.ipd.discharge.edit',
|
|
]);
|
|
if ($permission) {
|
|
return $permission;
|
|
}
|
|
|
|
if (! $this->request->isAJAX()) {
|
|
return $this->response->setStatusCode(400)->setJSON(['update' => 0, 'message' => 'Invalid request']);
|
|
}
|
|
|
|
if (! in_array($slot, [1, 2], true)) {
|
|
return $this->response->setStatusCode(422)->setJSON(['update' => 0, 'message' => 'Invalid additional charge slot']);
|
|
}
|
|
|
|
$fieldMap = $this->getIpdFieldMap();
|
|
$amount = max(0, (float) ($this->request->getPost('amount') ?? 0));
|
|
$remark = trim((string) ($this->request->getPost('remark') ?? ''));
|
|
|
|
$remarkKey = 'charge' . $slot;
|
|
$amountKey = 'chargeamount' . $slot;
|
|
$byKey = 'charge_by' . $slot;
|
|
|
|
$update = [];
|
|
if (isset($fieldMap[$remarkKey])) {
|
|
$update[$remarkKey] = substr($remark, 0, 200);
|
|
}
|
|
if (isset($fieldMap[$amountKey])) {
|
|
$update[$amountKey] = $amount;
|
|
}
|
|
if (isset($fieldMap[$byKey])) {
|
|
$user = auth()->user();
|
|
$update[$byKey] = (string) ($user->username ?? $user->email ?? 'system');
|
|
}
|
|
|
|
if (empty($update)) {
|
|
return $this->response->setStatusCode(422)->setJSON(['update' => 0, 'message' => 'Charge fields are not available in schema']);
|
|
}
|
|
|
|
$this->ipdEditModel->updateIpd($update, $ipdId);
|
|
$this->ipdEditModel->calculateIPD($ipdId);
|
|
|
|
return $this->buildDischargeProcessResponse($ipdId, 'Additional charge updated.');
|
|
}
|
|
|
|
public function updateDischargeProcess(int $ipdId)
|
|
{
|
|
$permission = $this->requireAnyPermission([
|
|
'billing.ipd.discharge.edit',
|
|
]);
|
|
if ($permission) {
|
|
return $permission;
|
|
}
|
|
|
|
if (! $this->request->isAJAX()) {
|
|
return $this->response->setStatusCode(400)->setJSON(['update' => 0, 'message' => 'Invalid request']);
|
|
}
|
|
|
|
$fieldMap = $this->getIpdFieldMap();
|
|
|
|
$dischargeDate = $this->normalizeAdmissionDate((string) ($this->request->getPost('discharge_date') ?? ''));
|
|
if ($dischargeDate === null) {
|
|
return $this->response->setStatusCode(422)->setJSON(['update' => 0, 'message' => 'Enter a valid discharge date']);
|
|
}
|
|
|
|
$dischargeTime = $this->normalizeAdmissionTime((string) ($this->request->getPost('discharge_time') ?? ''));
|
|
if ($dischargeTime === null) {
|
|
return $this->response->setStatusCode(422)->setJSON(['update' => 0, 'message' => 'Enter a valid discharge time']);
|
|
}
|
|
|
|
$status = (int) ($this->request->getPost('discharge_status') ?? 0);
|
|
if ($status < 1 || $status > 7) {
|
|
return $this->response->setStatusCode(422)->setJSON(['update' => 0, 'message' => 'Select valid discharge status']);
|
|
}
|
|
|
|
$update = [];
|
|
if (isset($fieldMap['discarge_patient_status'])) {
|
|
$update['discarge_patient_status'] = $status;
|
|
}
|
|
if (isset($fieldMap['discharge_date'])) {
|
|
$update['discharge_date'] = $dischargeDate;
|
|
}
|
|
if (isset($fieldMap['discharge_time'])) {
|
|
$update['discharge_time'] = $dischargeTime;
|
|
}
|
|
if (isset($fieldMap['discharge_time_bak'])) {
|
|
$update['discharge_time_bak'] = $dischargeTime;
|
|
}
|
|
if (isset($fieldMap['discharge_remark'])) {
|
|
$update['discharge_remark'] = (string) ($this->request->getPost('discharge_remark') ?? '');
|
|
}
|
|
if (isset($fieldMap['discharge_balance_user'])) {
|
|
$update['discharge_balance_user'] = substr((string) ($this->request->getPost('balance_user') ?? ''), 0, 200);
|
|
}
|
|
if (isset($fieldMap['discharge_balance_remark'])) {
|
|
$update['discharge_balance_remark'] = substr((string) ($this->request->getPost('balance_remark') ?? ''), 0, 200);
|
|
}
|
|
if (isset($fieldMap['discharge_by'])) {
|
|
$user = auth()->user();
|
|
$update['discharge_by'] = (string) ($user->username ?? $user->email ?? 'system');
|
|
}
|
|
if (isset($fieldMap['ipd_status'])) {
|
|
$update['ipd_status'] = 1;
|
|
}
|
|
|
|
if (empty($update)) {
|
|
return $this->response->setStatusCode(422)->setJSON(['update' => 0, 'message' => 'Discharge fields are not available in schema']);
|
|
}
|
|
|
|
$this->ipdEditModel->updateIpd($update, $ipdId);
|
|
$this->ipdEditModel->calculateIPD($ipdId);
|
|
|
|
// Release bed and close org case when discharging patient
|
|
if (isset($update['ipd_status']) && $update['ipd_status'] == 1) {
|
|
$this->ipdEditModel->releaseBed($ipdId);
|
|
|
|
$currentIpd = $this->ipdEditModel->find($ipdId);
|
|
$caseId = (int) ($currentIpd['case_id'] ?? 0);
|
|
if ($caseId > 0) {
|
|
\Config\Database::connect()->table('organization_case_master')
|
|
->where('id', $caseId)
|
|
->update(['status' => 1]);
|
|
}
|
|
}
|
|
|
|
return $this->buildDischargeProcessResponse($ipdId, 'Discharge status updated.');
|
|
}
|
|
|
|
public function updateDischargeToggleStatus(int $ipdId)
|
|
{
|
|
$permission = $this->requireAnyPermission([
|
|
'billing.ipd.status.manage',
|
|
]);
|
|
if ($permission) {
|
|
return $permission;
|
|
}
|
|
|
|
if (! $this->request->isAJAX()) {
|
|
return $this->response->setStatusCode(400)->setJSON(['update' => 0, 'message' => 'Invalid request']);
|
|
}
|
|
|
|
$fieldMap = $this->getIpdFieldMap();
|
|
if (! isset($fieldMap['ipd_status'])) {
|
|
return $this->response->setStatusCode(422)->setJSON([
|
|
'update' => 0,
|
|
'message' => 'ipd_status field not available in schema'
|
|
]);
|
|
}
|
|
|
|
$newStatus = (int) ($this->request->getPost('ipd_status') ?? -1);
|
|
if ($newStatus !== 0 && $newStatus !== 1) {
|
|
return $this->response->setStatusCode(422)->setJSON([
|
|
'update' => 0,
|
|
'message' => 'Invalid status value (must be 0 or 1)'
|
|
]);
|
|
}
|
|
|
|
// Get current status
|
|
$currentIpd = $this->ipdEditModel->find($ipdId);
|
|
if (! $currentIpd) {
|
|
return $this->response->setStatusCode(404)->setJSON(['update' => 0, 'message' => 'IPD not found']);
|
|
}
|
|
|
|
$currentStatus = (int) ($currentIpd['ipd_status'] ?? 0);
|
|
|
|
// Update status
|
|
$update = ['ipd_status' => $newStatus];
|
|
$this->ipdEditModel->updateIpd($update, $ipdId);
|
|
|
|
// Release bed and close org case when setting to discharged (status = 1)
|
|
if ($newStatus === 1 && $currentStatus === 0) {
|
|
$this->ipdEditModel->releaseBed($ipdId);
|
|
|
|
$caseId = (int) ($currentIpd['case_id'] ?? 0);
|
|
if ($caseId > 0) {
|
|
\Config\Database::connect()->table('organization_case_master')
|
|
->where('id', $caseId)
|
|
->update(['status' => 1]);
|
|
}
|
|
}
|
|
|
|
$statusText = $newStatus === 0 ? 'Active (Bills unlocked)' : 'Discharged (Bills locked)';
|
|
|
|
return $this->buildDischargeProcessResponse($ipdId, 'IPD status set to: ' . $statusText);
|
|
}
|
|
|
|
public function billPrint(int $ipdId, int $mode = 1)
|
|
{
|
|
$permission = $this->requireAnyPermission([
|
|
'billing.ipd.bill.print',
|
|
'billing.ipd.invoice',
|
|
]);
|
|
if ($permission) {
|
|
return $permission;
|
|
}
|
|
|
|
$showHeader = $this->request->getGet('header') !== '0';
|
|
$pdfBinary = $this->generateIpdBillPdfBinary($ipdId, $mode, $showHeader);
|
|
if ($pdfBinary === null) {
|
|
return $this->response->setStatusCode(404)->setBody('IPD not found');
|
|
}
|
|
|
|
$panelData = $this->ipdModel->getIpdPanelInfo($ipdId);
|
|
$ipdCode = (string) ($panelData['ipd_info']->ipd_code ?? ('IPD-' . $ipdId));
|
|
$fileName = 'IPD_Bill_' . preg_replace('/[^A-Za-z0-9_\-]/', '_', $ipdCode) . '_M' . $mode . '.pdf';
|
|
|
|
return $this->response
|
|
->setHeader('Content-Type', 'application/pdf')
|
|
->setHeader('Content-Disposition', 'inline; filename="' . $fileName . '"')
|
|
->setHeader('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0')
|
|
->setHeader('Pragma', 'no-cache')
|
|
->setHeader('Expires', '0')
|
|
->setBody($pdfBinary);
|
|
}
|
|
|
|
public function generateIpdBillPdfBinary(int $ipdId, int $mode = 1, bool $showHeader = true): ?string
|
|
{
|
|
if ($ipdId <= 0) {
|
|
return null;
|
|
}
|
|
|
|
$panelData = $this->ipdModel->getIpdPanelInfo($ipdId);
|
|
if (empty($panelData)) {
|
|
return null;
|
|
}
|
|
|
|
// Recalculate before print so PDF always reflects latest bill math.
|
|
$this->recalculateIpdBillingSafely($ipdId, 'bill.print');
|
|
$panelData = $this->ipdModel->getIpdPanelInfo($ipdId);
|
|
|
|
$mode = max(1, min(6, $mode));
|
|
|
|
$data = $this->buildBillDetailsTabData($panelData, $ipdId);
|
|
$data['show_print_actions'] = false;
|
|
$data['show_payment_details'] = in_array($mode, [1, 6], true);
|
|
$data['letterhead_mode'] = in_array($mode, [5, 6], true);
|
|
$data['print_mode'] = $mode;
|
|
$data['show_header'] = $showHeader;
|
|
|
|
$html = view('billing/ipd/bill_print', $data);
|
|
|
|
$mpdfTempDir = WRITEPATH . 'cache' . DIRECTORY_SEPARATOR . 'mpdf';
|
|
if (! is_dir($mpdfTempDir)) {
|
|
mkdir($mpdfTempDir, 0755, true);
|
|
}
|
|
|
|
// Adjust margins based on header visibility
|
|
// When header is hidden (letterhead mode), use larger top margin
|
|
$marginTop = $showHeader ? 8 : 50; // 50mm = ~5cm for letterhead
|
|
$marginBottom = $showHeader ? 8 : 20; // 20mm = 2cm
|
|
|
|
$mpdf = new Mpdf([
|
|
'tempDir' => $mpdfTempDir,
|
|
'format' => 'A4',
|
|
'margin_top' => $marginTop,
|
|
'margin_bottom' => $marginBottom,
|
|
'margin_left' => 8,
|
|
'margin_right' => 8,
|
|
]);
|
|
|
|
$mpdf->WriteHTML($html);
|
|
|
|
$ipdCode = (string) ($data['ipd_info']->ipd_code ?? ('IPD-' . $ipdId));
|
|
$fileName = 'IPD_Bill_' . preg_replace('/[^A-Za-z0-9_\-]/', '_', $ipdCode) . '_M' . $mode . '.pdf';
|
|
|
|
$pdfBinary = $mpdf->Output($fileName, 'S');
|
|
if (is_string($pdfBinary) && str_starts_with($pdfBinary, '%PDF-')) {
|
|
$this->cacheAbdmIpdBillPdf($ipdId, $pdfBinary);
|
|
return $pdfBinary;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private function cacheAbdmIpdBillPdf(int $ipdId, string $pdfBinary): void
|
|
{
|
|
if ($ipdId <= 0 || $pdfBinary === '' || ! str_starts_with($pdfBinary, '%PDF-')) {
|
|
return;
|
|
}
|
|
|
|
$directory = WRITEPATH . 'uploads' . DIRECTORY_SEPARATOR . 'abdm' . DIRECTORY_SEPARATOR . 'ipd' . DIRECTORY_SEPARATOR . $ipdId;
|
|
if (! is_dir($directory) && ! mkdir($directory, 0755, true) && ! is_dir($directory)) {
|
|
return;
|
|
}
|
|
|
|
file_put_contents($directory . DIRECTORY_SEPARATOR . 'ipd-bill.pdf', $pdfBinary, LOCK_EX);
|
|
}
|
|
|
|
public function showIpdForm(int $ipdId, int $formNo = 1)
|
|
{
|
|
$permission = $this->requireAnyPermission([
|
|
'billing.ipd.bill.print',
|
|
'billing.ipd.invoice',
|
|
]);
|
|
if ($permission) {
|
|
return $permission;
|
|
}
|
|
|
|
$data = $this->buildIpdPrintableFormData($ipdId, $formNo);
|
|
if ($data === null) {
|
|
return $this->response->setStatusCode(404)->setBody('IPD not found');
|
|
}
|
|
|
|
$html = view('billing/ipd/ipd_form_print', $data);
|
|
$customTemplate = $this->getActiveIpdDocumentTemplate($formNo);
|
|
$templatePageSize = '';
|
|
$templateCustomWidth = 210;
|
|
$templateCustomHeight = 297;
|
|
$templateMarginTop = 0.8;
|
|
$templateMarginBottom = 0.8;
|
|
$templateMarginLeft = 0.8;
|
|
$templateMarginRight = 0.8;
|
|
$templateMarginHeader = 0.5;
|
|
$templateMarginFooter = 0.5;
|
|
if ($customTemplate !== null) {
|
|
$html = $this->renderIpdDocumentTemplateHtml($customTemplate, $data);
|
|
$templatePageSize = strtoupper(trim((string) ($customTemplate['page_size'] ?? '')));
|
|
$templateCustomWidth = (int) ($customTemplate['custom_width_mm'] ?? 210);
|
|
$templateCustomHeight = (int) ($customTemplate['custom_height_mm'] ?? 297);
|
|
$templateMarginTop = (float) ($customTemplate['page_margin_top_cm'] ?? 0.8);
|
|
$templateMarginBottom = (float) ($customTemplate['page_margin_bottom_cm'] ?? 0.8);
|
|
$templateMarginLeft = (float) ($customTemplate['page_margin_left_cm'] ?? 0.8);
|
|
$templateMarginRight = (float) ($customTemplate['page_margin_right_cm'] ?? 0.8);
|
|
$templateMarginHeader = (float) ($customTemplate['margin_header_cm'] ?? 0.5);
|
|
$templateMarginFooter = (float) ($customTemplate['margin_footer_cm'] ?? 0.5);
|
|
}
|
|
|
|
$mpdfTempDir = WRITEPATH . 'cache' . DIRECTORY_SEPARATOR . 'mpdf';
|
|
if (! is_dir($mpdfTempDir)) {
|
|
mkdir($mpdfTempDir, 0755, true);
|
|
}
|
|
|
|
$mpdfConfig = [
|
|
'tempDir' => $mpdfTempDir,
|
|
'margin_top' => 8,
|
|
'margin_bottom' => 8,
|
|
'margin_left' => 8,
|
|
'margin_right' => 8,
|
|
'margin_header' => 5,
|
|
'margin_footer' => 5,
|
|
'default_font' => 'freeserif',
|
|
'autoScriptToLang' => true,
|
|
'autoLangToFont' => true,
|
|
];
|
|
|
|
if ($customTemplate !== null && $templatePageSize !== '') {
|
|
if ($templatePageSize === 'CUSTOM') {
|
|
$templateCustomWidth = max(20, min(600, $templateCustomWidth));
|
|
$templateCustomHeight = max(20, min(1000, $templateCustomHeight));
|
|
$mpdfConfig['format'] = [$templateCustomWidth, $templateCustomHeight];
|
|
} elseif ($templatePageSize === 'A4-L') {
|
|
$mpdfConfig['format'] = 'A4';
|
|
$mpdfConfig['orientation'] = 'L';
|
|
} else {
|
|
$allowedFormats = ['A4', 'A5', 'A6', 'LETTER', 'LEGAL'];
|
|
$mpdfConfig['format'] = in_array($templatePageSize, $allowedFormats, true) ? $templatePageSize : 'A4';
|
|
}
|
|
|
|
$mpdfConfig['margin_top'] = max(0, min(250, (int) round($templateMarginTop * 10)));
|
|
$mpdfConfig['margin_bottom'] = max(0, min(250, (int) round($templateMarginBottom * 10)));
|
|
$mpdfConfig['margin_left'] = max(0, min(250, (int) round($templateMarginLeft * 10)));
|
|
$mpdfConfig['margin_right'] = max(0, min(250, (int) round($templateMarginRight * 10)));
|
|
$mpdfConfig['margin_header'] = max(0, min(250, (int) round($templateMarginHeader * 10)));
|
|
$mpdfConfig['margin_footer'] = max(0, min(250, (int) round($templateMarginFooter * 10)));
|
|
} elseif (in_array($formNo, [10, 11], true)) {
|
|
$mpdfConfig['format'] = $formNo === 10 ? [50.8, 152.4] : [50.8, 203.2];
|
|
$mpdfConfig['margin_top'] = 2;
|
|
$mpdfConfig['margin_bottom'] = 2;
|
|
$mpdfConfig['margin_left'] = 2;
|
|
$mpdfConfig['margin_right'] = 2;
|
|
} else {
|
|
$mpdfConfig['format'] = 'A4';
|
|
}
|
|
|
|
$mpdf = new Mpdf($mpdfConfig);
|
|
$mpdf->WriteHTML($html);
|
|
|
|
$ipdCode = (string) ($data['ipd_info']->ipd_code ?? ('IPD-' . $ipdId));
|
|
$fileName = 'IPD_Form_' . (int) $formNo . '_' . preg_replace('/[^A-Za-z0-9_\-]/', '_', $ipdCode) . '.pdf';
|
|
|
|
return $this->response
|
|
->setHeader('Content-Type', 'application/pdf')
|
|
->setHeader('Content-Disposition', 'inline; filename="' . $fileName . '"')
|
|
->setBody($mpdf->Output($fileName, 'S'));
|
|
}
|
|
|
|
public function addIpdCharge(int $ipdId)
|
|
{
|
|
$permission = $this->requireAnyPermission([
|
|
'billing.access',
|
|
'billing.ipd.invoice',
|
|
]);
|
|
if ($permission) {
|
|
return $permission;
|
|
}
|
|
|
|
if (! $this->request->isAJAX()) {
|
|
return $this->response->setStatusCode(400)->setJSON(['error' => 'Invalid request']);
|
|
}
|
|
|
|
$itemType = (int) ($this->request->getPost('item_type') ?? 0);
|
|
$itemId = (int) ($this->request->getPost('item_id') ?? 0);
|
|
$rate = (float) ($this->request->getPost('item_rate') ?? 0);
|
|
$qty = (float) ($this->request->getPost('item_qty') ?? 1);
|
|
$itemDate = (string) ($this->request->getPost('item_date') ?? date('Y-m-d'));
|
|
$comment = (string) ($this->request->getPost('comment') ?? '');
|
|
$docId = (int) ($this->request->getPost('doc_id') ?? 0);
|
|
$referName = (string) ($this->request->getPost('refer_name') ?? '');
|
|
|
|
if ($itemType <= 0 || $itemId <= 0) {
|
|
return $this->response->setStatusCode(400)->setJSON(['error' => 'Invalid item selection']);
|
|
}
|
|
|
|
$itemRow = $this->itemIpdModel->getItemById($itemId);
|
|
if (empty($itemRow)) {
|
|
return $this->response->setStatusCode(404)->setJSON(['error' => 'Item not found']);
|
|
}
|
|
|
|
$item = $itemRow[0];
|
|
if ($rate <= 0) {
|
|
$rate = (float) ($item->amount ?? 0);
|
|
}
|
|
|
|
if ($qty <= 0) {
|
|
$qty = 1;
|
|
}
|
|
|
|
$caseMeta = $this->ipdModel->getIpdCaseMeta($ipdId);
|
|
$insId = (int) ($caseMeta['insurance_id'] ?? 0);
|
|
$orgCode = (string) ($caseMeta['org_code'] ?? '');
|
|
|
|
$docName = '';
|
|
if ($docId > 0) {
|
|
$docName = $this->ipdModel->getDoctorNameById($docId);
|
|
} else {
|
|
$docName = $referName;
|
|
}
|
|
|
|
$amount = $rate * $qty;
|
|
|
|
$insertData = [
|
|
'ipd_id' => $ipdId,
|
|
'item_type' => $itemType,
|
|
'item_id' => $itemId,
|
|
'item_name' => (string) ($item->idesc ?? ''),
|
|
'item_rate' => $rate,
|
|
'item_added_date' => date('Y-m-d H:i:s'),
|
|
'item_qty' => $qty,
|
|
'item_amount' => $amount,
|
|
'doc_id' => $docId,
|
|
'doc_name' => $docName,
|
|
'date_item' => $itemDate,
|
|
'comment' => $comment,
|
|
'ins_id' => $insId,
|
|
'org_code' => $orgCode,
|
|
];
|
|
|
|
$insertId = $this->ipdEditModel->insertIpdItem($insertData);
|
|
|
|
return $this->response->setJSON([
|
|
'ok' => $insertId > 0,
|
|
'id' => $insertId,
|
|
]);
|
|
}
|
|
|
|
public function updateIpdCharge(int $itemId)
|
|
{
|
|
$permission = $this->requireAnyPermission([
|
|
'billing.access',
|
|
'billing.ipd.invoice',
|
|
]);
|
|
if ($permission) {
|
|
return $permission;
|
|
}
|
|
|
|
if (! $this->request->isAJAX()) {
|
|
return $this->response->setStatusCode(400)->setJSON(['error' => 'Invalid request']);
|
|
}
|
|
|
|
$qty = (float) ($this->request->getPost('item_qty') ?? 1);
|
|
$rate = (float) ($this->request->getPost('item_rate') ?? 0);
|
|
if ($qty <= 0) {
|
|
$qty = 1;
|
|
}
|
|
|
|
$amount = $rate * $qty;
|
|
|
|
$this->ipdEditModel->updateIpdItem([
|
|
'item_qty' => $qty,
|
|
'item_amount' => $amount,
|
|
], $itemId);
|
|
|
|
return $this->response->setJSON(['ok' => true]);
|
|
}
|
|
|
|
public function addBedsideCharge(int $ipdId)
|
|
{
|
|
$permission = $this->requireAnyPermission([
|
|
'ipd_nursing.charge.manage',
|
|
'billing.access',
|
|
'billing.ipd.invoice',
|
|
'billing.ipd.current-admission',
|
|
]);
|
|
if ($permission) {
|
|
return $permission;
|
|
}
|
|
|
|
if (! $this->request->isAJAX()) {
|
|
return $this->response->setStatusCode(400)->setJSON(['error' => 'Invalid request']);
|
|
}
|
|
|
|
$bedsideItemId = (int) ($this->request->getPost('bedside_item_id') ?? 0);
|
|
$qty = (float) ($this->request->getPost('item_qty') ?? 1);
|
|
$itemDate = (string) ($this->request->getPost('item_date') ?? date('Y-m-d'));
|
|
$visitTime = (string) ($this->request->getPost('visit_time') ?? '');
|
|
$comment = (string) ($this->request->getPost('comment') ?? '');
|
|
$docId = (int) ($this->request->getPost('doc_id') ?? 0);
|
|
$referName = (string) ($this->request->getPost('refer_name') ?? '');
|
|
|
|
if ($bedsideItemId <= 0) {
|
|
return $this->response->setStatusCode(400)->setJSON(['error' => 'Select a bedside item']);
|
|
}
|
|
|
|
$caseMeta = $this->ipdModel->getIpdCaseMeta($ipdId);
|
|
$insId = (int) ($caseMeta['insurance_id'] ?? 0);
|
|
|
|
$item = $this->nursingBedsideItemModel->getBillableItemForInsurance($bedsideItemId, $insId);
|
|
if (! is_array($item) || empty($item)) {
|
|
return $this->response->setStatusCode(404)->setJSON(['error' => 'Bedside item not found or not billable']);
|
|
}
|
|
|
|
if ($qty <= 0) {
|
|
$qty = 1;
|
|
}
|
|
|
|
$rateInput = $this->request->getPost('item_rate');
|
|
$insuranceRate = $item['insurance_rate'] ?? null;
|
|
$rate = ($rateInput !== null && $rateInput !== '')
|
|
? (float) $rateInput
|
|
: ($insuranceRate !== null ? (float) $insuranceRate : (float) ($item['default_rate'] ?? 0));
|
|
if ($rate < 0) {
|
|
$rate = 0;
|
|
}
|
|
|
|
$orgCode = trim((string) ($item['insurance_code'] ?? ''));
|
|
|
|
$docName = '';
|
|
if ($docId > 0) {
|
|
$docName = $this->ipdModel->getDoctorNameById($docId);
|
|
} else {
|
|
$docName = $referName;
|
|
}
|
|
|
|
$typeId = $this->resolveBedsideItemTypeId((string) ($item['category'] ?? 'Bedside'));
|
|
$amount = $rate * $qty;
|
|
|
|
$nursingChargeId = $this->insertNursingIpdChargeRow([
|
|
'ipd_id' => $ipdId,
|
|
'item_id' => $bedsideItemId,
|
|
'item_name' => (string) ($item['item_name'] ?? ''),
|
|
'item_type' => (string) ($item['item_type'] ?? 'Bedside'),
|
|
'doctor_id' => $docId > 0 ? $docId : null,
|
|
'doctor_name' => $docName !== '' ? $docName : null,
|
|
'visit_date' => $itemDate,
|
|
'visit_time' => $visitTime !== '' ? $visitTime : null,
|
|
'consultation_type' => null,
|
|
'duration' => null,
|
|
'specialization' => null,
|
|
'hospital_clinic' => null,
|
|
'is_outside_doctor' => 0,
|
|
'rate' => $rate,
|
|
'qty' => (int) $qty,
|
|
'amount' => $amount,
|
|
'performed_by' => null,
|
|
'performed_date' => $itemDate,
|
|
'performed_time' => $visitTime !== '' ? $visitTime : null,
|
|
'remarks' => $comment,
|
|
'include_in_bill' => 1,
|
|
'updated_by' => null,
|
|
]);
|
|
|
|
$invoiceComment = $comment;
|
|
if ($nursingChargeId > 0) {
|
|
$marker = '[NURSING_CHARGE_ID:' . $nursingChargeId . ']';
|
|
$invoiceComment = trim($invoiceComment . ' ' . $marker);
|
|
}
|
|
|
|
$insertData = [
|
|
'ipd_id' => $ipdId,
|
|
'item_type' => $typeId,
|
|
'item_id' => $bedsideItemId,
|
|
'item_name' => (string) ($item['item_name'] ?? ''),
|
|
'item_rate' => $rate,
|
|
'item_added_date' => date('Y-m-d H:i:s'),
|
|
'item_qty' => $qty,
|
|
'item_amount' => $amount,
|
|
'doc_id' => $docId,
|
|
'doc_name' => $docName,
|
|
'date_item' => $itemDate,
|
|
'comment' => $invoiceComment,
|
|
'ins_id' => $insId,
|
|
'org_code' => $orgCode,
|
|
];
|
|
|
|
$insertId = $this->ipdEditModel->insertIpdItem($insertData);
|
|
|
|
return $this->response->setJSON([
|
|
'ok' => $insertId > 0,
|
|
'id' => $insertId,
|
|
]);
|
|
}
|
|
|
|
public function addDoctorVisitCharge(int $ipdId)
|
|
{
|
|
$permission = $this->requireAnyPermission([
|
|
'ipd_nursing.charge.manage',
|
|
'billing.access',
|
|
'billing.ipd.invoice',
|
|
'billing.ipd.current-admission',
|
|
]);
|
|
if ($permission) {
|
|
return $permission;
|
|
}
|
|
|
|
if (! $this->request->isAJAX()) {
|
|
return $this->response->setStatusCode(400)->setJSON(['error' => 'Invalid request']);
|
|
}
|
|
|
|
$docId = (int) ($this->request->getPost('doc_id') ?? 0);
|
|
$feeTypeId = (int) ($this->request->getPost('fee_type_id') ?? 0);
|
|
$qty = (float) ($this->request->getPost('item_qty') ?? 1);
|
|
$itemDate = (string) ($this->request->getPost('item_date') ?? date('Y-m-d'));
|
|
$visitTime = (string) ($this->request->getPost('visit_time') ?? '');
|
|
$duration = (int) ($this->request->getPost('duration') ?? 0);
|
|
$hospitalClinic = trim((string) ($this->request->getPost('hospital_clinic') ?? ''));
|
|
$isOutsideDoctor = (int) ($this->request->getPost('is_outside_doctor') ?? 0) === 1 ? 1 : 0;
|
|
$comment = trim((string) ($this->request->getPost('comment') ?? ''));
|
|
|
|
if ($docId <= 0) {
|
|
return $this->response->setStatusCode(400)->setJSON(['error' => 'Select doctor']);
|
|
}
|
|
|
|
if ($qty <= 0) {
|
|
$qty = 1;
|
|
}
|
|
|
|
$feeDetail = $this->ipdModel->getDoctorVisitFeeDetail($docId, $feeTypeId);
|
|
|
|
$rateInput = $this->request->getPost('item_rate');
|
|
$rate = ($rateInput !== null && $rateInput !== '')
|
|
? (float) $rateInput
|
|
: (float) ($feeDetail['amount'] ?? 0);
|
|
if ($rate < 0) {
|
|
$rate = 0;
|
|
}
|
|
|
|
$docName = $this->ipdModel->getDoctorNameById($docId);
|
|
if ($docName === '') {
|
|
return $this->response->setStatusCode(404)->setJSON(['error' => 'Doctor not found']);
|
|
}
|
|
|
|
$visitLabel = trim((string) ($feeDetail['fee_desc'] ?? 'Doctor Visit'));
|
|
if ($visitLabel === '') {
|
|
$visitLabel = 'Doctor Visit';
|
|
}
|
|
|
|
$itemName = 'Doctor Visit - ' . $visitLabel;
|
|
$typeId = $this->resolveDoctorVisitItemTypeId();
|
|
$amount = $rate * $qty;
|
|
|
|
$caseMeta = $this->ipdModel->getIpdCaseMeta($ipdId);
|
|
$insId = (int) ($caseMeta['insurance_id'] ?? 0);
|
|
|
|
$nursingChargeId = $this->insertNursingIpdChargeRow([
|
|
'ipd_id' => $ipdId,
|
|
'item_id' => (int) ($feeDetail['id'] ?? $docId),
|
|
'item_name' => $itemName,
|
|
'item_type' => 'Doctor Visit',
|
|
'doctor_id' => $docId,
|
|
'doctor_name' => $docName,
|
|
'visit_date' => $itemDate,
|
|
'visit_time' => $visitTime !== '' ? $visitTime : null,
|
|
'consultation_type' => (string) ($feeDetail['fee_type'] ?? ''),
|
|
'duration' => $duration > 0 ? $duration : null,
|
|
'specialization' => null,
|
|
'hospital_clinic' => $hospitalClinic !== '' ? $hospitalClinic : null,
|
|
'is_outside_doctor' => $isOutsideDoctor,
|
|
'rate' => $rate,
|
|
'qty' => (int) $qty,
|
|
'amount' => $amount,
|
|
'performed_by' => null,
|
|
'performed_date' => $itemDate,
|
|
'performed_time' => $visitTime !== '' ? $visitTime : null,
|
|
'remarks' => $comment,
|
|
'include_in_bill' => 1,
|
|
'updated_by' => null,
|
|
]);
|
|
|
|
$invoiceComment = $comment;
|
|
if ($nursingChargeId > 0) {
|
|
$marker = '[NURSING_CHARGE_ID:' . $nursingChargeId . ']';
|
|
$invoiceComment = trim($invoiceComment . ' ' . $marker);
|
|
}
|
|
|
|
$insertData = [
|
|
'ipd_id' => $ipdId,
|
|
'item_type' => $typeId,
|
|
'item_id' => (int) ($feeDetail['id'] ?? $docId),
|
|
'item_name' => $itemName,
|
|
'item_rate' => $rate,
|
|
'item_added_date' => date('Y-m-d H:i:s'),
|
|
'item_qty' => $qty,
|
|
'item_amount' => $amount,
|
|
'doc_id' => $docId,
|
|
'doc_name' => $docName,
|
|
'date_item' => $itemDate,
|
|
'comment' => $invoiceComment,
|
|
'ins_id' => $insId,
|
|
'org_code' => '',
|
|
];
|
|
|
|
$insertId = $this->ipdEditModel->insertIpdItem($insertData);
|
|
|
|
return $this->response->setJSON([
|
|
'ok' => $insertId > 0,
|
|
'id' => $insertId,
|
|
]);
|
|
}
|
|
|
|
public function deleteIpdCharge(int $itemId)
|
|
{
|
|
$permission = $this->requireAnyPermission([
|
|
'billing.access',
|
|
'billing.ipd.invoice',
|
|
]);
|
|
if ($permission) {
|
|
return $permission;
|
|
}
|
|
|
|
if (! $this->request->isAJAX()) {
|
|
return $this->response->setStatusCode(400)->setJSON(['error' => 'Invalid request']);
|
|
}
|
|
|
|
$result = $this->ipdEditModel->deleteIpdInvoiceItem($itemId);
|
|
if ($result !== 1) {
|
|
return $this->response->setJSON([
|
|
'ok' => false,
|
|
'error' => 'Unable to remove charge item. Please refresh and try again.',
|
|
]);
|
|
}
|
|
|
|
return $this->response->setJSON([
|
|
'ok' => true,
|
|
'deleted_id' => $itemId,
|
|
]);
|
|
}
|
|
|
|
public function updateNursingCharge(int $ipdId, int $chargeId)
|
|
{
|
|
$permission = $this->requireAnyPermission([
|
|
'ipd_nursing.charge.manage',
|
|
'billing.access',
|
|
'billing.ipd.invoice',
|
|
'billing.ipd.current-admission',
|
|
]);
|
|
if ($permission) {
|
|
return $permission;
|
|
}
|
|
|
|
if (! $this->request->isAJAX()) {
|
|
return $this->response->setStatusCode(400)->setJSON(['error' => 'Invalid request']);
|
|
}
|
|
|
|
if ($chargeId <= 0 || $ipdId <= 0 || ! $this->db->tableExists('nursing_ipd_charges')) {
|
|
return $this->response->setStatusCode(400)->setJSON(['error' => 'Invalid charge reference']);
|
|
}
|
|
|
|
$row = $this->db->table('nursing_ipd_charges')
|
|
->where('charge_id', $chargeId)
|
|
->where('ipd_id', $ipdId)
|
|
->get()
|
|
->getRowArray();
|
|
|
|
if (! is_array($row) || empty($row)) {
|
|
return $this->response->setStatusCode(404)->setJSON(['error' => 'Charge history row not found']);
|
|
}
|
|
|
|
$rate = (float) ($this->request->getPost('rate') ?? ($row['rate'] ?? 0));
|
|
$qty = (float) ($this->request->getPost('qty') ?? ($row['qty'] ?? 1));
|
|
$comment = trim((string) ($this->request->getPost('comment') ?? ($row['remarks'] ?? '')));
|
|
$visitDate = (string) ($this->request->getPost('visit_date') ?? ($row['visit_date'] ?? date('Y-m-d')));
|
|
$visitTime = (string) ($this->request->getPost('visit_time') ?? ($row['visit_time'] ?? ''));
|
|
|
|
if ($qty <= 0) {
|
|
$qty = 1;
|
|
}
|
|
if ($rate < 0) {
|
|
$rate = 0;
|
|
}
|
|
|
|
$amount = $rate * $qty;
|
|
$user = auth()->user();
|
|
$updatedBy = ($user && isset($user->username))
|
|
? (string) $user->username
|
|
: (($user && isset($user->email)) ? (string) $user->email : 'system');
|
|
|
|
$this->db->table('nursing_ipd_charges')
|
|
->where('charge_id', $chargeId)
|
|
->where('ipd_id', $ipdId)
|
|
->update([
|
|
'rate' => $rate,
|
|
'qty' => $qty,
|
|
'amount' => $amount,
|
|
'remarks' => $comment,
|
|
'visit_date' => $visitDate,
|
|
'visit_time' => $visitTime,
|
|
'updated_by' => $updatedBy . ' [' . date('Y-m-d H:i:s') . ']',
|
|
]);
|
|
|
|
$marker = '[NURSING_CHARGE_ID:' . $chargeId . ']';
|
|
$invoiceRows = $this->db->table('ipd_invoice_item')
|
|
->select('id')
|
|
->where('ipd_id', $ipdId)
|
|
->like('comment', $marker)
|
|
->get()
|
|
->getResultArray();
|
|
|
|
$invoiceComment = trim($comment . ' ' . $marker);
|
|
foreach ($invoiceRows as $invoiceRow) {
|
|
$invoiceId = (int) ($invoiceRow['id'] ?? 0);
|
|
if ($invoiceId <= 0) {
|
|
continue;
|
|
}
|
|
$this->ipdEditModel->updateIpdItem([
|
|
'item_rate' => $rate,
|
|
'item_qty' => $qty,
|
|
'item_amount' => $amount,
|
|
'date_item' => $visitDate,
|
|
'comment' => $invoiceComment,
|
|
], $invoiceId);
|
|
}
|
|
|
|
return $this->response->setJSON(['ok' => true]);
|
|
}
|
|
|
|
public function deleteNursingCharge(int $ipdId, int $chargeId)
|
|
{
|
|
$permission = $this->requireAnyPermission([
|
|
'ipd_nursing.charge.manage',
|
|
'billing.access',
|
|
'billing.ipd.invoice',
|
|
'billing.ipd.current-admission',
|
|
]);
|
|
if ($permission) {
|
|
return $permission;
|
|
}
|
|
|
|
if (! $this->request->isAJAX()) {
|
|
return $this->response->setStatusCode(400)->setJSON(['error' => 'Invalid request']);
|
|
}
|
|
|
|
if ($chargeId <= 0 || $ipdId <= 0 || ! $this->db->tableExists('nursing_ipd_charges')) {
|
|
return $this->response->setStatusCode(400)->setJSON(['error' => 'Invalid charge reference']);
|
|
}
|
|
|
|
$row = $this->db->table('nursing_ipd_charges')
|
|
->where('charge_id', $chargeId)
|
|
->where('ipd_id', $ipdId)
|
|
->get()
|
|
->getRowArray();
|
|
|
|
if (! is_array($row) || empty($row)) {
|
|
return $this->response->setStatusCode(404)->setJSON(['error' => 'Charge history row not found']);
|
|
}
|
|
|
|
$user = auth()->user();
|
|
$updatedBy = ($user && isset($user->username))
|
|
? (string) $user->username
|
|
: (($user && isset($user->email)) ? (string) $user->email : 'system');
|
|
|
|
$this->db->table('nursing_ipd_charges')
|
|
->where('charge_id', $chargeId)
|
|
->where('ipd_id', $ipdId)
|
|
->update([
|
|
'include_in_bill' => 0,
|
|
'updated_by' => $updatedBy . ' [' . date('Y-m-d H:i:s') . ']',
|
|
]);
|
|
|
|
$marker = '[NURSING_CHARGE_ID:' . $chargeId . ']';
|
|
$invoiceRows = $this->db->table('ipd_invoice_item')
|
|
->select('id')
|
|
->where('ipd_id', $ipdId)
|
|
->like('comment', $marker)
|
|
->get()
|
|
->getResultArray();
|
|
|
|
foreach ($invoiceRows as $invoiceRow) {
|
|
$invoiceId = (int) ($invoiceRow['id'] ?? 0);
|
|
if ($invoiceId <= 0) {
|
|
continue;
|
|
}
|
|
$this->ipdEditModel->deleteIpdInvoiceItem($invoiceId);
|
|
}
|
|
|
|
return $this->response->setJSON(['ok' => true]);
|
|
}
|
|
|
|
public function updateIpdChargePackage(int $itemId)
|
|
{
|
|
$permission = $this->requireAnyPermission([
|
|
'billing.access',
|
|
'billing.ipd.invoice',
|
|
]);
|
|
if ($permission) {
|
|
return $permission;
|
|
}
|
|
|
|
if (! $this->request->isAJAX()) {
|
|
return $this->response->setStatusCode(400)->setJSON(['error' => 'Invalid request']);
|
|
}
|
|
|
|
$packageId = (int) ($this->request->getPost('package_id') ?? 0);
|
|
|
|
$this->ipdEditModel->updateIpdItem([
|
|
'package_id' => $packageId,
|
|
], $itemId);
|
|
|
|
return $this->response->setJSON(['ok' => true]);
|
|
}
|
|
|
|
public function addIpdPackage(int $ipdId)
|
|
{
|
|
$permission = $this->requireAnyPermission([
|
|
'billing.access',
|
|
'billing.ipd.invoice',
|
|
]);
|
|
if ($permission) {
|
|
return $permission;
|
|
}
|
|
|
|
if (! $this->request->isAJAX()) {
|
|
return $this->response->setStatusCode(400)->setJSON(['error' => 'Invalid request']);
|
|
}
|
|
|
|
$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);
|
|
|
|
$caseMeta = $this->ipdModel->getIpdCaseMeta($ipdId);
|
|
$insCompId = (int) ($caseMeta['insurance_id'] ?? 0);
|
|
if ($insCompId <= 0) {
|
|
$insCompId = 0;
|
|
}
|
|
|
|
$packageDesc = $comment;
|
|
$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) {
|
|
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);
|
|
}
|
|
}
|
|
|
|
if ($inputAmount > 0) {
|
|
$amount = $inputAmount;
|
|
}
|
|
|
|
$userId = 0;
|
|
$userName = 'system';
|
|
if (function_exists('auth')) {
|
|
$user = auth()->user();
|
|
if ($user) {
|
|
$userId = (int) ($user->id ?? 0);
|
|
$userName = (string) ($user->username ?? $user->email ?? 'user');
|
|
}
|
|
}
|
|
$updateBy = $userName . '[' . $userId . '] D:' . date('d-m-Y h:i:s');
|
|
|
|
$insertData = [
|
|
'ipd_id' => $ipdId,
|
|
'package_id' => $packageId,
|
|
'package_name' => $packageName,
|
|
'package_desc' => $packageDesc,
|
|
'package_Amount' => $amount,
|
|
'comment' => $comment,
|
|
'update_by' => $updateBy,
|
|
'ins_id' => $insCompId,
|
|
'org_code' => $orgCode,
|
|
];
|
|
|
|
$insertId = $this->ipdEditModel->insertPackage($insertData);
|
|
|
|
return $this->response->setJSON([
|
|
'ok' => $insertId > 0,
|
|
'id' => $insertId,
|
|
]);
|
|
}
|
|
|
|
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'),
|
|
'output' => $output,
|
|
];
|
|
|
|
$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([
|
|
'billing.access',
|
|
'billing.ipd.invoice',
|
|
]);
|
|
if ($permission) {
|
|
return $permission;
|
|
}
|
|
|
|
if (! $this->request->isAJAX()) {
|
|
return $this->response->setStatusCode(400)->setJSON(['error' => 'Invalid request']);
|
|
}
|
|
|
|
$amount = (float) ($this->request->getPost('input_amount') ?? 0);
|
|
|
|
$this->ipdEditModel->updatePackage([
|
|
'package_Amount' => $amount,
|
|
], $packageItemId);
|
|
|
|
return $this->response->setJSON(['ok' => true]);
|
|
}
|
|
|
|
public function deleteIpdPackage(int $packageItemId)
|
|
{
|
|
$permission = $this->requireAnyPermission([
|
|
'billing.access',
|
|
'billing.ipd.invoice',
|
|
]);
|
|
if ($permission) {
|
|
return $permission;
|
|
}
|
|
|
|
if (! $this->request->isAJAX()) {
|
|
return $this->response->setStatusCode(400)->setJSON(['error' => 'Invalid request']);
|
|
}
|
|
|
|
$result = $this->ipdEditModel->deletePackage($packageItemId);
|
|
|
|
return $this->response->setJSON(['ok' => $result === 1]);
|
|
}
|
|
|
|
public function medicalBillDetails(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)->setBody('Invalid request');
|
|
}
|
|
|
|
$details = $this->ipdModel->getMedicalBillDetails($ipdId, $invoiceId);
|
|
if (empty($details)) {
|
|
return $this->response->setStatusCode(404)->setBody('Medical bill not found for this admission.');
|
|
}
|
|
|
|
return view('billing/ipd/panel_medical_bill_details', $details);
|
|
}
|
|
|
|
public function updateMedicalBillCreditType(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 ($invoiceId <= 0 || ! $this->db->tableExists('invoice_med_master')) {
|
|
return $this->response->setStatusCode(404)->setJSON(['ok' => false, 'error' => 'Medical bill not found']);
|
|
}
|
|
|
|
$fields = $this->db->getFieldNames('invoice_med_master') ?? [];
|
|
if (! in_array('ipd_credit_type', $fields, true)) {
|
|
return $this->response->setStatusCode(422)->setJSON(['ok' => false, 'error' => 'Medical bill credit type column not available']);
|
|
}
|
|
|
|
$ipdId = (int) ($this->request->getPost('ipd_id') ?? 0);
|
|
if ($ipdId <= 0) {
|
|
return $this->response->setStatusCode(422)->setJSON(['ok' => false, 'error' => 'IPD admission is required']);
|
|
}
|
|
|
|
$invoice = $this->db->table('invoice_med_master')
|
|
->select('id,ipd_id,ipd_credit')
|
|
->where('id', $invoiceId)
|
|
->where('ipd_id', $ipdId)
|
|
->where('ipd_credit', 1)
|
|
->get(1)
|
|
->getRowArray();
|
|
|
|
if (empty($invoice)) {
|
|
return $this->response->setStatusCode(404)->setJSON(['ok' => false, 'error' => 'IPD credit bill not found for this admission']);
|
|
}
|
|
|
|
$creditType = (int) ($this->request->getPost('ipd_credit_type') ?? 0) === 1 ? 1 : 0;
|
|
$update = [
|
|
'ipd_credit_type' => $creditType,
|
|
];
|
|
|
|
if (in_array('ipd_credit_type_update', $fields, true)) {
|
|
$user = function_exists('auth') ? auth()->user() : null;
|
|
$userId = (int) ($user->id ?? 0);
|
|
$userName = (string) ($user->username ?? $user->email ?? 'user');
|
|
$typeLabel = $creditType === 1 ? 'Include in Bill' : 'In Package';
|
|
$update['ipd_credit_type_update'] = $typeLabel . ' by ' . $userName . '[' . $userId . '] D:' . date('d-m-Y H:i:s');
|
|
}
|
|
|
|
$updated = $this->db->table('invoice_med_master')
|
|
->where('id', $invoiceId)
|
|
->where('ipd_id', $ipdId)
|
|
->where('ipd_credit', 1)
|
|
->update($update);
|
|
|
|
if (! $updated) {
|
|
return $this->response->setStatusCode(500)->setJSON(['ok' => false, 'error' => 'Unable to update medical bill type']);
|
|
}
|
|
|
|
$this->recalculateIpdBillingSafely($ipdId, 'medical-bill.credit-type');
|
|
|
|
return $this->response->setJSON([
|
|
'ok' => true,
|
|
'invoice_id' => $invoiceId,
|
|
'ipd_id' => $ipdId,
|
|
'ipd_credit_type' => $creditType,
|
|
]);
|
|
}
|
|
|
|
public function loadPaymentModal(int $ipdId)
|
|
{
|
|
$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');
|
|
}
|
|
|
|
$panelData['bank_data'] = $this->ipdModel->getBankPaymentSources();
|
|
|
|
return view('billing/ipd/ipd_model_payment_box', $panelData);
|
|
}
|
|
|
|
public function loadDeductionModal(int $ipdId)
|
|
{
|
|
$permission = $this->requireAnyPermission([
|
|
'billing.access',
|
|
'billing.ipd.invoice',
|
|
]);
|
|
if ($permission) {
|
|
return $permission;
|
|
}
|
|
|
|
return view('billing/ipd/ipd_panel_deduction_payment', [
|
|
'ipd_id' => $ipdId,
|
|
]);
|
|
}
|
|
|
|
public function loadTpaPaymentModal(int $ipdId)
|
|
{
|
|
$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');
|
|
}
|
|
|
|
$panelData['ipd_id'] = $ipdId;
|
|
|
|
return view('billing/ipd/ipd_panel_tpa_payment', $panelData);
|
|
}
|
|
|
|
public function confirmPayment()
|
|
{
|
|
$permission = $this->requireAnyPermission([
|
|
'billing.access',
|
|
'billing.ipd.invoice',
|
|
]);
|
|
if ($permission) {
|
|
return $permission;
|
|
}
|
|
|
|
if (! $this->request->isAJAX()) {
|
|
return $this->response->setStatusCode(400)->setJSON(['update' => 0, 'error_text' => 'Invalid request']);
|
|
}
|
|
|
|
$ipdId = (int) ($this->request->getPost('ipd_id') ?? 0);
|
|
$amount = (float) ($this->request->getPost('amount') ?? 0);
|
|
$mode = (int) ($this->request->getPost('mode') ?? 0);
|
|
$paymentDate = (string) ($this->request->getPost('date_payment') ?? '');
|
|
|
|
if ($ipdId <= 0 || $amount <= 0) {
|
|
return $this->response->setStatusCode(400)->setJSON(['update' => 0, 'error_text' => 'Amount should be greater than zero.']);
|
|
}
|
|
|
|
$ipdRow = $this->db->table('ipd_master')
|
|
->select('id, ipd_code')
|
|
->where('id', $ipdId)
|
|
->get()
|
|
->getRow();
|
|
|
|
if (! $ipdRow) {
|
|
return $this->response->setStatusCode(404)->setJSON(['update' => 0, 'error_text' => 'IPD not found']);
|
|
}
|
|
|
|
$userId = 0;
|
|
$userName = 'system';
|
|
if (function_exists('auth')) {
|
|
$user = auth()->user();
|
|
if ($user) {
|
|
$userId = (int) ($user->id ?? 0);
|
|
$userName = (string) ($user->username ?? $user->email ?? 'user');
|
|
}
|
|
}
|
|
|
|
$payDate = $paymentDate !== '' ? $paymentDate : date('Y-m-d');
|
|
$payDatetime = $payDate . ' ' . date('H:i:s');
|
|
$remark = (string) ($this->request->getPost('cash_remark') ?? '');
|
|
|
|
$paymentModel = new PaymentModel();
|
|
|
|
if ($mode === 1) {
|
|
$insertId = $paymentModel->insertPayment([
|
|
'payment_mode' => 1,
|
|
'payof_type' => 4,
|
|
'payof_id' => $ipdId,
|
|
'payof_code' => $ipdRow->ipd_code ?? '',
|
|
'credit_debit' => 0,
|
|
'amount' => $amount,
|
|
'payment_date' => $payDatetime,
|
|
'remark' => $remark,
|
|
'update_by' => $userName . '[' . $userId . ']',
|
|
'update_by_id' => $userId,
|
|
]);
|
|
|
|
$this->ipdEditModel->calculateIPD($ipdId);
|
|
|
|
return $this->response->setJSON([
|
|
'update' => 1,
|
|
'ipd_id' => $ipdId,
|
|
'payid' => $insertId,
|
|
'pay_date' => $payDatetime,
|
|
]);
|
|
}
|
|
|
|
if ($mode === 2) {
|
|
$cardTran = (string) ($this->request->getPost('input_card_tran') ?? '');
|
|
if ($cardTran === '') {
|
|
return $this->response->setJSON(['update' => 0, 'error_text' => 'Card transaction ID is required.']);
|
|
}
|
|
|
|
$insertId = $paymentModel->insertPayment([
|
|
'payment_mode' => 2,
|
|
'payof_type' => 4,
|
|
'payof_id' => $ipdId,
|
|
'payof_code' => $ipdRow->ipd_code ?? '',
|
|
'credit_debit' => 0,
|
|
'amount' => $amount,
|
|
'payment_date' => $payDatetime,
|
|
'remark' => $remark,
|
|
'update_by' => $userName . '[' . $userId . ']',
|
|
'pay_bank_id' => (int) ($this->request->getPost('cbo_pay_type') ?? 0),
|
|
'card_tran_id' => $cardTran,
|
|
'update_by_id' => $userId,
|
|
]);
|
|
|
|
$this->ipdEditModel->calculateIPD($ipdId);
|
|
|
|
return $this->response->setJSON([
|
|
'update' => 1,
|
|
'ipd_id' => $ipdId,
|
|
'payid' => $insertId,
|
|
]);
|
|
}
|
|
|
|
return $this->response->setJSON(['update' => 0, 'error_text' => 'Invalid payment mode.']);
|
|
}
|
|
|
|
public function deductPayment()
|
|
{
|
|
$permission = $this->requireAnyPermission([
|
|
'billing.access',
|
|
'billing.ipd.invoice',
|
|
]);
|
|
if ($permission) {
|
|
return $permission;
|
|
}
|
|
|
|
if (! $this->request->isAJAX()) {
|
|
return $this->response->setStatusCode(400)->setJSON(['update' => 0, 'error_text' => 'Invalid request']);
|
|
}
|
|
|
|
$ipdId = (int) ($this->request->getPost('ipd_id') ?? 0);
|
|
$amount = (float) ($this->request->getPost('amount') ?? 0);
|
|
$mode = (int) ($this->request->getPost('mode') ?? 0);
|
|
|
|
if ($ipdId <= 0 || $amount <= 0) {
|
|
return $this->response->setStatusCode(400)->setJSON(['update' => 0, 'error_text' => 'Amount should be greater than zero.']);
|
|
}
|
|
|
|
$ipdRow = $this->db->table('ipd_master')
|
|
->select('id, ipd_code')
|
|
->where('id', $ipdId)
|
|
->get()
|
|
->getRow();
|
|
|
|
if (! $ipdRow) {
|
|
return $this->response->setStatusCode(404)->setJSON(['update' => 0, 'error_text' => 'IPD not found']);
|
|
}
|
|
|
|
$userId = 0;
|
|
$userName = 'system';
|
|
if (function_exists('auth')) {
|
|
$user = auth()->user();
|
|
if ($user) {
|
|
$userId = (int) ($user->id ?? 0);
|
|
$userName = (string) ($user->username ?? $user->email ?? 'user');
|
|
}
|
|
}
|
|
|
|
$remark = (string) ($this->request->getPost('cash_remark') ?? '');
|
|
$paymentModel = new PaymentModel();
|
|
|
|
if ($mode === 3 || $mode === 4) {
|
|
$insertId = $paymentModel->insertPayment([
|
|
'payment_mode' => $mode === 3 ? 1 : 2,
|
|
'payof_type' => 4,
|
|
'payof_id' => $ipdId,
|
|
'payof_code' => $ipdRow->ipd_code ?? '',
|
|
'credit_debit' => 1,
|
|
'amount' => $amount,
|
|
'payment_date' => date('Y-m-d H:i:s'),
|
|
'remark' => $remark,
|
|
'update_by' => $userName . '[' . $userId . ']',
|
|
'update_by_id' => $userId,
|
|
]);
|
|
|
|
$this->ipdEditModel->calculateIPD($ipdId);
|
|
|
|
return $this->response->setJSON([
|
|
'update' => 1,
|
|
'ipd_id' => $ipdId,
|
|
'payid' => $insertId,
|
|
]);
|
|
}
|
|
|
|
return $this->response->setJSON(['update' => 0, 'error_text' => 'Invalid payment mode.']);
|
|
}
|
|
|
|
public function tpaPayment()
|
|
{
|
|
$permission = $this->requireAnyPermission([
|
|
'billing.access',
|
|
'billing.ipd.invoice',
|
|
]);
|
|
if ($permission) {
|
|
return $permission;
|
|
}
|
|
|
|
if (! $this->request->isAJAX()) {
|
|
return $this->response->setStatusCode(400)->setJSON(['update' => 0, 'show_text' => 'Invalid request']);
|
|
}
|
|
|
|
$ipdId = (int) ($this->request->getPost('hid_ipd_id') ?? 0);
|
|
if ($ipdId <= 0) {
|
|
return $this->response->setStatusCode(400)->setJSON(['update' => 0, 'show_text' => 'Invalid IPD']);
|
|
}
|
|
|
|
$payData = [
|
|
'payable_by_tpa' => (float) ($this->request->getPost('input_payable_by_tpa') ?? 0),
|
|
'discount_for_tpa' => (float) ($this->request->getPost('input_discount_for_tpa') ?? 0),
|
|
'discount_by_hospital' => (float) ($this->request->getPost('input_discount_by_hospital') ?? 0),
|
|
'discount_by_hospital_2' => (float) ($this->request->getPost('input_discount_by_hospital_2') ?? 0),
|
|
'discount_by_hospital_remark' => (string) ($this->request->getPost('input_discount_by_hospital_remark') ?? ''),
|
|
'discount_by_hospital_2_remark' => (string) ($this->request->getPost('input_discount_by_hospital_2_remark') ?? ''),
|
|
];
|
|
|
|
$this->ipdEditModel->updateIpd($payData, $ipdId);
|
|
$this->ipdEditModel->calculateIPD($ipdId);
|
|
|
|
return $this->response->setJSON([
|
|
'update' => 1,
|
|
'ipd_id' => $ipdId,
|
|
]);
|
|
}
|
|
|
|
public function ipdCashPrintPdf(int $ipdId, int $payId)
|
|
{
|
|
$permission = $this->requireAnyPermission([
|
|
'billing.access',
|
|
'billing.ipd.invoice',
|
|
]);
|
|
if ($permission) {
|
|
return $permission;
|
|
}
|
|
|
|
$payment = $this->db->table('payment_history p')
|
|
->select("p.*,date_format(p.payment_date,'%d-%m-%Y %h:%i %p') as payment_date_str,if(p.credit_debit=0,'','Return') as Amount_str,s.pay_type,b.bank_name", false)
|
|
->join('hospital_bank_payment_source s', 's.id = p.pay_bank_id', 'left')
|
|
->join('hospital_bank b', 'b.id = s.bank_id', 'left')
|
|
->where('p.payof_type', 4)
|
|
->where('p.payof_id', $ipdId)
|
|
->where('p.id', $payId)
|
|
->get()
|
|
->getResult();
|
|
|
|
$ipdMaster = $this->db->table('ipd_master')
|
|
->where('id', $ipdId)
|
|
->get()
|
|
->getResult();
|
|
|
|
if (empty($ipdMaster)) {
|
|
return $this->response->setStatusCode(404)->setBody('IPD not found');
|
|
}
|
|
|
|
$patient = $this->db->table('patient_master')
|
|
->select("*,if(gender=1,'Male','Female') as xgender", false)
|
|
->where('id', $ipdMaster[0]->p_id)
|
|
->get()
|
|
->getResult();
|
|
|
|
return view('billing/ipd/ipd_payment_receipt_print', [
|
|
'ipd_payment' => $payment,
|
|
'ipd_master' => $ipdMaster,
|
|
'patient_master' => $patient,
|
|
'ipd_id' => $ipdId,
|
|
'payid' => $payId,
|
|
]);
|
|
}
|
|
|
|
public function pdfPaymentReceipt(int $ipdId, int $payId)
|
|
{
|
|
$permission = $this->requireAnyPermission([
|
|
'billing.access',
|
|
'billing.ipd.invoice',
|
|
]);
|
|
if ($permission) {
|
|
return $permission;
|
|
}
|
|
|
|
$payment = $this->db->table('payment_history p')
|
|
->select("p.*,date_format(p.payment_date,'%d-%m-%Y %h:%i %p') as payment_date_str,if(p.credit_debit=0,'','Return') as Amount_str,s.pay_type,b.bank_name", false)
|
|
->join('hospital_bank_payment_source s', 's.id = p.pay_bank_id', 'left')
|
|
->join('hospital_bank b', 'b.id = s.bank_id', 'left')
|
|
->where('p.payof_type', 4)
|
|
->where('p.payof_id', $ipdId)
|
|
->where('p.id', $payId)
|
|
->get()
|
|
->getResult();
|
|
|
|
$ipdMaster = $this->db->table('ipd_master')
|
|
->where('id', $ipdId)
|
|
->get()
|
|
->getResult();
|
|
|
|
if (empty($ipdMaster)) {
|
|
return $this->response->setStatusCode(404)->setBody('IPD not found');
|
|
}
|
|
|
|
$patient = $this->db->table('patient_master')
|
|
->select("*,if(gender=1,'Male','Female') as xgender", false)
|
|
->where('id', $ipdMaster[0]->p_id)
|
|
->get()
|
|
->getResult();
|
|
|
|
$html = view('billing/ipd/ipd_payment_receipt_pdf', [
|
|
'ipd_payment' => $payment,
|
|
'ipd_master' => $ipdMaster,
|
|
'patient_master' => $patient,
|
|
'ipd_id' => $ipdId,
|
|
'payid' => $payId,
|
|
]);
|
|
|
|
$mpdfTempDir = WRITEPATH . 'cache' . DIRECTORY_SEPARATOR . 'mpdf';
|
|
if (! is_dir($mpdfTempDir)) {
|
|
mkdir($mpdfTempDir, 0755, true);
|
|
}
|
|
|
|
$mpdf = new Mpdf([
|
|
'tempDir' => $mpdfTempDir,
|
|
'format' => 'A4',
|
|
]);
|
|
|
|
$hospitalName = defined('H_Name') ? (string) constant('H_Name') : '';
|
|
if ($hospitalName !== '') {
|
|
$mpdf->SetWatermarkText($hospitalName);
|
|
$mpdf->showWatermarkText = false;
|
|
}
|
|
|
|
// Strip BOM and sanitize to valid UTF-8 before passing to mPDF
|
|
$html = ltrim($html, "\xEF\xBB\xBF");
|
|
$html = mb_convert_encoding($html, 'UTF-8', 'UTF-8');
|
|
|
|
$mpdf->WriteHTML($html);
|
|
|
|
$fileName = 'IPD_Payment_Receipt_' . $payId . '_' . date('YmdHis') . '.pdf';
|
|
|
|
return $this->response
|
|
->setHeader('Content-Type', 'application/pdf')
|
|
->setHeader('Content-Disposition', 'inline; filename="' . $fileName . '"')
|
|
->setBody($mpdf->Output($fileName, 'S'));
|
|
}
|
|
|
|
private function parseDateRange(string $range): array
|
|
{
|
|
$start = date('Y-m-d');
|
|
$end = $start;
|
|
|
|
$range = trim(urldecode($range));
|
|
if ($range === '') {
|
|
return [$start, $end];
|
|
}
|
|
|
|
// Preferred format from UI: YYYY-MM-DDSYYYY-MM-DD
|
|
if (strpos($range, 'S') !== false) {
|
|
$parts = explode('S', $range, 2);
|
|
$startCandidate = trim((string) ($parts[0] ?? ''));
|
|
$endCandidate = trim((string) ($parts[1] ?? ''));
|
|
if (preg_match('/^\d{4}-\d{2}-\d{2}$/', $startCandidate) === 1
|
|
&& preg_match('/^\d{4}-\d{2}-\d{2}$/', $endCandidate) === 1
|
|
) {
|
|
return [$startCandidate, $endCandidate];
|
|
}
|
|
}
|
|
|
|
// Fallback: extract first two ISO dates from any incoming range string.
|
|
$dates = [];
|
|
if (preg_match_all('/\d{4}-\d{2}-\d{2}/', $range, $matches) > 0) {
|
|
$dates = $matches[0] ?? [];
|
|
}
|
|
|
|
if (count($dates) >= 2) {
|
|
return [(string) $dates[0], (string) $dates[1]];
|
|
}
|
|
if (count($dates) === 1) {
|
|
return [(string) $dates[0], (string) $dates[0]];
|
|
}
|
|
|
|
return [$start, $end];
|
|
}
|
|
|
|
private function getIpdFormMeta(int $formNo): array
|
|
{
|
|
$meta = [
|
|
1 => ['title' => 'Face Form'],
|
|
3 => ['title' => 'Self Declaration Form'],
|
|
5 => ['title' => 'Admission Assessment Form'],
|
|
8 => ['title' => 'Progress Notes and Doctor Order'],
|
|
9 => ['title' => 'Fluid In / Out'],
|
|
10 => ['title' => 'Sticker [2 x 6]'],
|
|
11 => ['title' => 'Sticker [2 x 8]'],
|
|
6 => ['title' => 'Treatment Chart'],
|
|
7 => ['title' => 'Vitals Chart'],
|
|
];
|
|
|
|
if (isset($meta[$formNo])) {
|
|
return $meta[$formNo];
|
|
}
|
|
|
|
$template = $this->getActiveIpdDocumentTemplate($formNo);
|
|
if (is_array($template) && ! empty($template['template_name'])) {
|
|
return ['title' => (string) $template['template_name']];
|
|
}
|
|
|
|
return ['title' => 'IPD Form #' . $formNo];
|
|
}
|
|
|
|
private function getAvailableIpdDocuments(): array
|
|
{
|
|
$default = [
|
|
['id' => 1, 'label' => 'Print Face Form'],
|
|
['id' => 5, 'label' => 'Admission Form'],
|
|
['id' => 10, 'label' => 'Sticker [2 x 6]'],
|
|
['id' => 11, 'label' => 'Sticker [2 x 8]'],
|
|
['id' => 8, 'label' => 'Progress Notes'],
|
|
['id' => 9, 'label' => 'Fluid In / Out'],
|
|
['id' => 3, 'label' => 'Self Declaration Form'],
|
|
['id' => 7, 'label' => 'Vitals Chart'],
|
|
['id' => 6, 'label' => 'Treatment Chart'],
|
|
];
|
|
|
|
if (! $this->db->tableExists('ipd_document_templates')) {
|
|
return $default;
|
|
}
|
|
|
|
$rows = $this->db->table('ipd_document_templates')
|
|
->select('form_no, template_name, id')
|
|
->where('status', 1)
|
|
->orderBy('form_no', 'ASC')
|
|
->orderBy('id', 'DESC')
|
|
->get()
|
|
->getResultArray();
|
|
|
|
if (empty($rows)) {
|
|
return $default;
|
|
}
|
|
|
|
$documents = [];
|
|
foreach ($rows as $row) {
|
|
$id = (int) ($row['form_no'] ?? 0);
|
|
if ($id <= 0 || isset($documents[$id])) {
|
|
continue;
|
|
}
|
|
|
|
$label = trim((string) ($row['template_name'] ?? ''));
|
|
if ($label === '') {
|
|
$label = (string) ($this->getIpdFormMeta($id)['title'] ?? ('IPD Form #' . $id));
|
|
}
|
|
|
|
$documents[$id] = [
|
|
'id' => $id,
|
|
'label' => $label,
|
|
];
|
|
}
|
|
|
|
if (empty($documents)) {
|
|
return $default;
|
|
}
|
|
|
|
ksort($documents);
|
|
|
|
return array_values($documents);
|
|
}
|
|
|
|
private function buildIpdPrintableFormData(int $ipdId, int $formNo): ?array
|
|
{
|
|
$panelData = $this->ipdModel->getIpdPanelInfo($ipdId);
|
|
if (empty($panelData)) {
|
|
return null;
|
|
}
|
|
|
|
$ipdInfo = $panelData['ipd_info'] ?? null;
|
|
$personInfo = $panelData['person_info'] ?? null;
|
|
if ($ipdInfo === null || $personInfo === null) {
|
|
return null;
|
|
}
|
|
|
|
$personInfo->age = get_age_1(
|
|
$personInfo->dob ?? null,
|
|
$personInfo->age ?? '',
|
|
$personInfo->age_in_month ?? '',
|
|
$personInfo->estimate_dob ?? ''
|
|
);
|
|
|
|
$doctorRows = [];
|
|
if ($this->db->tableExists('ipd_master_doc_list') && $this->db->tableExists('doctor_master')) {
|
|
$doctorRows = $this->db->table('ipd_master_doc_list l')
|
|
->select("concat_ws(' ', 'Dr.', d.p_fname, d.p_mname, d.p_lname) as doc_name", false)
|
|
->join('doctor_master d', 'd.id = l.doc_id', 'inner')
|
|
->where('l.ipd_id', $ipdId)
|
|
->groupBy('d.id, d.p_fname, d.p_mname, d.p_lname')
|
|
->orderBy('d.p_fname', 'ASC')
|
|
->get()
|
|
->getResultArray();
|
|
}
|
|
|
|
$doctorNames = array_values(array_filter(array_map(
|
|
static fn ($row) => trim((string) ($row['doc_name'] ?? '')),
|
|
$doctorRows
|
|
)));
|
|
|
|
if (empty($doctorNames)) {
|
|
$fallbackDoctor = trim((string) ($ipdInfo->r_doc_name ?? ''));
|
|
if ($fallbackDoctor !== '') {
|
|
$doctorNames[] = $fallbackDoctor;
|
|
}
|
|
}
|
|
|
|
$insuranceName = trim((string) ($ipdInfo->ins_company_name ?? ''));
|
|
if ($insuranceName === '' && $this->db->tableExists('organization_case_master')) {
|
|
$insuranceRow = $this->db->table('organization_case_master o')
|
|
->select('h.ins_company_name')
|
|
->join('hc_insurance h', 'h.id = o.insurance_id', 'left')
|
|
->where('o.id', (int) ($ipdInfo->case_id ?? 0))
|
|
->get()
|
|
->getRowArray();
|
|
$insuranceName = trim((string) ($insuranceRow['ins_company_name'] ?? ''));
|
|
}
|
|
|
|
$hospitalName = defined('H_Name') ? (string) constant('H_Name') : 'Hospital';
|
|
$hospitalAddress1 = defined('H_address_1') ? (string) constant('H_address_1') : '';
|
|
$hospitalAddress2 = defined('H_address_2') ? (string) constant('H_address_2') : '';
|
|
$hospitalAddress = trim($hospitalAddress1 . ', ' . $hospitalAddress2, ', ');
|
|
$hospitalPhone = defined('H_phone_No') ? (string) constant('H_phone_No') : '';
|
|
$hospitalEmail = defined('H_Email') ? (string) constant('H_Email') : '';
|
|
$hospitalLogo = defined('H_logo') ? (string) constant('H_logo') : '';
|
|
|
|
return [
|
|
'form_no' => $formNo,
|
|
'form_meta' => $this->getIpdFormMeta($formNo),
|
|
'ipd_info' => $ipdInfo,
|
|
'person_info' => $personInfo,
|
|
'doctor_names' => $doctorNames,
|
|
'insurance_name' => $insuranceName,
|
|
'hospital_name' => $hospitalName,
|
|
'hospital_address' => $hospitalAddress,
|
|
'hospital_address_1' => $hospitalAddress1,
|
|
'hospital_address_2' => $hospitalAddress2,
|
|
'hospital_phone' => $hospitalPhone,
|
|
'hospital_email' => $hospitalEmail,
|
|
'hospital_logo' => $hospitalLogo,
|
|
'generated_at' => date('d-m-Y h:i A'),
|
|
];
|
|
}
|
|
|
|
private function getActiveIpdDocumentTemplate(int $formNo): ?array
|
|
{
|
|
if (! $this->db->tableExists('ipd_document_templates')) {
|
|
return null;
|
|
}
|
|
|
|
$row = $this->db->table('ipd_document_templates')
|
|
->select('id, form_no, template_name, page_size, custom_width_mm, custom_height_mm, page_margin_top_cm, page_margin_bottom_cm, page_margin_left_cm, page_margin_right_cm, margin_header_cm, margin_footer_cm, header_html, footer_html, template_css, template_html')
|
|
->where('form_no', $formNo)
|
|
->where('status', 1)
|
|
->orderBy('id', 'DESC')
|
|
->get(1)
|
|
->getRowArray();
|
|
|
|
if (! is_array($row) || empty($row)) {
|
|
return null;
|
|
}
|
|
|
|
return $row;
|
|
}
|
|
|
|
private function renderIpdDocumentTemplateHtml(array $templateRow, array $data): string
|
|
{
|
|
$templateHtml = (string) ($templateRow['template_html'] ?? '');
|
|
if (trim($templateHtml) === '') {
|
|
return view('billing/ipd/ipd_form_print', $data);
|
|
}
|
|
|
|
$formMeta = $data['form_meta'] ?? [];
|
|
$ipd = $data['ipd_info'] ?? null;
|
|
$person = $data['person_info'] ?? null;
|
|
$doctorNames = is_array($data['doctor_names'] ?? null) ? $data['doctor_names'] : [];
|
|
|
|
$patientName = trim((string) (($person->title ?? '') . ' ' . ($person->p_fname ?? '')));
|
|
if ($patientName === '') {
|
|
$patientName = '-';
|
|
}
|
|
|
|
$age = (string) ($person->age ?? '-');
|
|
$gender = (string) ($person->xgender ?? '-');
|
|
$ageGender = trim($age . ' / ' . $gender);
|
|
if ($ageGender === '/') {
|
|
$ageGender = '-';
|
|
}
|
|
|
|
$qrContent = trim(
|
|
(string) ($person->p_code ?? '')
|
|
. ':' . (string) ($ipd->ipd_code ?? '')
|
|
. ':P-' . date('Y-m-d H:i:s')
|
|
. ':C-' . (string) ($person->p_fname ?? '')
|
|
, ':');
|
|
$hospitalLogo = trim((string) ($data['hospital_logo'] ?? ''));
|
|
$hospitalLogoAbs = $hospitalLogo !== '' ? (FCPATH . 'assets/images/' . $hospitalLogo) : '';
|
|
$hospitalLogoHtml = $hospitalLogoAbs !== ''
|
|
? '<img style="width: 75px;vertical-align: top;" src="' . esc($hospitalLogoAbs, 'attr') . '" />'
|
|
: '';
|
|
$qrCodeHtml = $qrContent !== ''
|
|
? '<barcode code="' . esc($qrContent, 'attr') . '" size="0.8" type="QR" error="M" class="barcode" />'
|
|
: '';
|
|
|
|
$replacements = [
|
|
'{{FORM_TITLE}}' => esc((string) ($formMeta['title'] ?? 'IPD Form')),
|
|
'{{HOSPITAL_NAME}}' => esc((string) ($data['hospital_name'] ?? 'Hospital')),
|
|
'{{HOSPITAL_ADDRESS}}' => esc((string) ($data['hospital_address'] ?? '')),
|
|
'{{H_Name}}' => esc((string) ($data['hospital_name'] ?? 'Hospital')),
|
|
'{{H_address_1}}' => esc((string) ($data['hospital_address_1'] ?? '')),
|
|
'{{H_address_2}}' => esc((string) ($data['hospital_address_2'] ?? '')),
|
|
'{{H_phone_No}}' => esc((string) ($data['hospital_phone'] ?? '')),
|
|
'{{H_Email}}' => esc((string) ($data['hospital_email'] ?? '')),
|
|
'{{H_logo}}' => esc($hospitalLogo),
|
|
'{{H_logo_abs}}' => esc($hospitalLogoAbs),
|
|
'{{hospital_logo_html}}' => $hospitalLogoHtml,
|
|
'{{HOSPITAL_LOGO_HTML}}' => $hospitalLogoHtml,
|
|
'{{PATIENT_NAME}}' => esc($patientName),
|
|
'{{pName}}' => esc($patientName),
|
|
'{{AGE}}' => esc($age),
|
|
'{{GENDER}}' => esc($gender),
|
|
'{{AGE_GENDER}}' => esc($ageGender),
|
|
'{{age_sex}}' => esc($ageGender),
|
|
'{{UHID}}' => esc((string) ($person->p_code ?? '-')),
|
|
'{{IPD_CODE}}' => esc((string) ($ipd->ipd_code ?? '-')),
|
|
'{{ADMIT_DATE}}' => esc((string) ($ipd->str_register_date ?? ($ipd->register_date ?? '-'))),
|
|
'{{DOCTORS}}' => esc(! empty($doctorNames) ? implode(', ', $doctorNames) : '-'),
|
|
'{{doctor_name}}' => esc(! empty($doctorNames) ? implode(', ', $doctorNames) : '-'),
|
|
'{{doctor_sign_html}}' => '',
|
|
'{{phoneno}}' => esc((string) ($person->mphone1 ?? '-')),
|
|
'{{INSURANCE_NAME}}' => esc((string) ($data['insurance_name'] ?? 'Direct')),
|
|
'{{CURRENT_DATE}}' => esc(date('d-m-Y')),
|
|
'{{CURRENT_DATETIME}}' => esc((string) ($data['generated_at'] ?? date('d-m-Y h:i A'))),
|
|
'{{print_time}}' => esc((string) ($data['generated_at'] ?? date('d-m-Y h:i A'))),
|
|
'{{qr_content}}' => esc($qrContent),
|
|
'{{QR_CONTENT}}' => esc($qrContent),
|
|
'{{qr_code_html}}' => $qrCodeHtml,
|
|
'{{QR_CODE_HTML}}' => $qrCodeHtml,
|
|
];
|
|
|
|
$headerHtml = trim((string) ($templateRow['header_html'] ?? ''));
|
|
$footerHtml = trim((string) ($templateRow['footer_html'] ?? ''));
|
|
$templateCss = trim((string) ($templateRow['template_css'] ?? ''));
|
|
|
|
$bodyHtml = $this->applyIpdTemplateTokens($templateHtml, $replacements);
|
|
$renderedHeader = $headerHtml !== '' ? $this->applyIpdTemplateTokens($headerHtml, $replacements) : '';
|
|
$renderedFooter = $footerHtml !== '' ? $this->applyIpdTemplateTokens($footerHtml, $replacements) : '';
|
|
|
|
$pageTop = (float) ($templateRow['page_margin_top_cm'] ?? 0.8);
|
|
$pageBottom = (float) ($templateRow['page_margin_bottom_cm'] ?? 0.8);
|
|
$pageLeft = (float) ($templateRow['page_margin_left_cm'] ?? 0.8);
|
|
$pageRight = (float) ($templateRow['page_margin_right_cm'] ?? 0.8);
|
|
$marginHeader = (float) ($templateRow['margin_header_cm'] ?? 0.5);
|
|
$marginFooter = (float) ($templateRow['margin_footer_cm'] ?? 0.5);
|
|
|
|
$pageStyle = '<style>@page {'
|
|
. 'margin-top:' . max(0, min(25, $pageTop)) . 'cm;'
|
|
. 'margin-bottom:' . max(0, min(25, $pageBottom)) . 'cm;'
|
|
. 'margin-left:' . max(0, min(25, $pageLeft)) . 'cm;'
|
|
. 'margin-right:' . max(0, min(25, $pageRight)) . 'cm;'
|
|
. 'margin-header:' . max(0, min(25, $marginHeader)) . 'cm;'
|
|
. 'margin-footer:' . max(0, min(25, $marginFooter)) . 'cm;'
|
|
. ($renderedHeader !== '' ? 'header: html_myHeader;' : '')
|
|
. ($renderedFooter !== '' ? 'footer: html_myFooter;' : '')
|
|
. '}</style>';
|
|
|
|
$userStyle = $templateCss !== '' ? ('<style>' . $templateCss . '</style>') : '';
|
|
|
|
$headerBlock = '';
|
|
if ($renderedHeader !== '') {
|
|
$headerBlock = preg_match('/<\s*htmlpageheader\b/i', $renderedHeader)
|
|
? $renderedHeader
|
|
: ('<htmlpageheader name="myHeader">' . $renderedHeader . '</htmlpageheader>');
|
|
}
|
|
|
|
$footerBlock = '';
|
|
if ($renderedFooter !== '') {
|
|
$footerBlock = preg_match('/<\s*htmlpagefooter\b/i', $renderedFooter)
|
|
? $renderedFooter
|
|
: ('<htmlpagefooter name="myFooter">' . $renderedFooter . '</htmlpagefooter>');
|
|
}
|
|
|
|
$html = $pageStyle . $userStyle . $headerBlock . $footerBlock . $bodyHtml;
|
|
|
|
// Force a Unicode-capable fallback stack so Devanagari text renders in mPDF.
|
|
$fontStyle = '<style>body,table,td,th,p,span,div{font-family:freeserif,dejavusans,sans-serif !important;}</style>';
|
|
|
|
return $fontStyle . $html;
|
|
}
|
|
|
|
/**
|
|
* @param array<string, string> $replacements
|
|
*/
|
|
private function applyIpdTemplateTokens(string $html, array $replacements): string
|
|
{
|
|
if ($html === '') {
|
|
return '';
|
|
}
|
|
|
|
$tokenMap = [];
|
|
foreach ($replacements as $token => $value) {
|
|
$cleanToken = trim($token, '{} ');
|
|
if ($cleanToken === '') {
|
|
continue;
|
|
}
|
|
|
|
$tokenMap[$cleanToken] = $value;
|
|
$lower = strtolower($cleanToken);
|
|
if (! array_key_exists($lower, $tokenMap)) {
|
|
$tokenMap[$lower] = $value;
|
|
}
|
|
$upper = strtoupper($cleanToken);
|
|
if (! array_key_exists($upper, $tokenMap)) {
|
|
$tokenMap[$upper] = $value;
|
|
}
|
|
}
|
|
|
|
return (string) preg_replace_callback('/\{\{\s*([a-zA-Z0-9_]+)\s*\}\}/', static function (array $match) use ($tokenMap) {
|
|
$name = (string) ($match[1] ?? '');
|
|
if ($name === '') {
|
|
return (string) ($match[0] ?? '');
|
|
}
|
|
|
|
if (array_key_exists($name, $tokenMap)) {
|
|
return $tokenMap[$name];
|
|
}
|
|
|
|
$lower = strtolower($name);
|
|
if (array_key_exists($lower, $tokenMap)) {
|
|
return $tokenMap[$lower];
|
|
}
|
|
|
|
$upper = strtoupper($name);
|
|
if (array_key_exists($upper, $tokenMap)) {
|
|
return $tokenMap[$upper];
|
|
}
|
|
|
|
return (string) ($match[0] ?? '');
|
|
}, $html);
|
|
}
|
|
|
|
private function calculateCashBalanceTotals(array $rows): array
|
|
{
|
|
$totalPaid = 0.0;
|
|
$totalBalance = 0.0;
|
|
|
|
foreach ($rows as $row) {
|
|
$totalPaid += (float) ($row->sum_of_paid ?? 0);
|
|
$totalBalance += (float) ($row->balance_amount ?? 0);
|
|
}
|
|
|
|
return [
|
|
'total_paid' => $totalPaid,
|
|
'total_balance' => $totalBalance,
|
|
];
|
|
}
|
|
|
|
private function recalculateIpdBillingSafely(int $ipdId, string $context): void
|
|
{
|
|
try {
|
|
$this->ipdEditModel->calculateIPD($ipdId);
|
|
} catch (\Throwable $e) {
|
|
// Do not break UI render if recalculation fails; log for diagnostics.
|
|
log_message('error', 'IPD calculateIPD failed for IPD #' . $ipdId . ' [' . $context . ']: ' . $e->getMessage());
|
|
}
|
|
}
|
|
|
|
private function buildBillDetailsTabData(array $panelData, int $ipdId): array
|
|
{
|
|
$panelData['ipd_packages'] = $this->ipdModel->getIpdPackages($ipdId);
|
|
$panelData['ipd_invoice_items'] = $this->ipdModel->getIpdInvoiceItems(
|
|
$ipdId,
|
|
! empty($panelData['ipd_packages'])
|
|
);
|
|
$panelData['showinvoice'] = $this->ipdModel->getIpdInvoiceCharges($ipdId);
|
|
$panelData['inv_med_list'] = $this->ipdModel->getIpdMedicalCredits($ipdId);
|
|
$panelData['ipd_payment'] = $this->ipdModel->getPayments($ipdId);
|
|
$panelData['insurance'] = $this->ipdModel->getIpdInsurance($ipdId);
|
|
|
|
$grossTotal = 0.0;
|
|
foreach ($panelData['ipd_packages'] as $row) {
|
|
$grossTotal += (float) ($row->package_Amount ?? 0);
|
|
}
|
|
foreach ($panelData['ipd_invoice_items'] as $row) {
|
|
$grossTotal += (float) ($row->item_amount ?? 0);
|
|
}
|
|
foreach ($panelData['showinvoice'] as $row) {
|
|
$grossTotal += (float) ($row->amount ?? 0);
|
|
}
|
|
foreach ($panelData['inv_med_list'] as $row) {
|
|
$grossTotal += (float) ($row->net_amount ?? 0);
|
|
}
|
|
|
|
$ipd = $panelData['ipd_info'] ?? null;
|
|
$discountTotal = (float) ($ipd->Discount ?? 0) + (float) ($ipd->Discount2 ?? 0) + (float) ($ipd->Discount3 ?? 0);
|
|
$extraCharge = (float) ($ipd->chargeamount1 ?? 0) + (float) ($ipd->chargeamount2 ?? 0);
|
|
$netTotal = ($grossTotal + $extraCharge) - $discountTotal;
|
|
|
|
$panelData['bill_totals'] = [
|
|
'gross' => $grossTotal,
|
|
'net' => $netTotal,
|
|
];
|
|
|
|
$totalPaid = 0.0;
|
|
foreach ($panelData['ipd_payment'] as $row) {
|
|
$amount = (float) ($row->amount ?? 0);
|
|
$creditDebit = (int) ($row->credit_debit ?? 0);
|
|
$totalPaid += $creditDebit === 0 ? $amount : $amount * -1;
|
|
}
|
|
$panelData['bill_totals']['paid'] = $totalPaid;
|
|
$panelData['bill_totals']['balance'] = $netTotal - $totalPaid;
|
|
|
|
$user = auth()->user();
|
|
$panelData['can_print_bill'] = $user !== null && $user->can('billing.ipd.bill.print');
|
|
$panelData['show_print_actions'] = true;
|
|
$panelData['show_payment_details'] = true;
|
|
|
|
return $panelData;
|
|
}
|
|
|
|
private function buildDischargeProcessResponse(int $ipdId, string $message)
|
|
{
|
|
$panelData = $this->ipdModel->getIpdPanelInfo($ipdId);
|
|
if (empty($panelData)) {
|
|
return $this->response->setStatusCode(404)->setJSON(['update' => 0, 'message' => 'IPD not found']);
|
|
}
|
|
|
|
$panelData['discharge_info'] = $this->ipdModel->getDischargeInfo($ipdId);
|
|
$user = auth()->user();
|
|
$panelData['can_edit_discharge'] = $user !== null && $user->can('billing.ipd.discharge.edit');
|
|
|
|
return $this->response->setJSON([
|
|
'update' => 1,
|
|
'message' => $message,
|
|
'csrf_hash' => csrf_hash(),
|
|
'html' => view('billing/ipd/panel_discharge', $panelData),
|
|
]);
|
|
}
|
|
|
|
private function getIpdFieldMap(): array
|
|
{
|
|
$ipdFields = $this->db->getFieldNames('ipd_master') ?? [];
|
|
|
|
return array_flip($ipdFields);
|
|
}
|
|
|
|
private function buildAdmissionTabData(array $panelData, int $ipdId): array
|
|
{
|
|
$ipd = $panelData['ipd_info'] ?? null;
|
|
$panelData['admission_doctors'] = $this->getAdmissionDoctors($ipdId);
|
|
$panelData['selected_doctor_ids'] = array_values(array_map(
|
|
static fn ($doctor) => (int) ($doctor->id ?? 0),
|
|
$panelData['admission_doctors']
|
|
));
|
|
$panelData['available_doctors'] = $this->ipdModel->getIpdDoctorList();
|
|
$panelData['departments'] = $this->getAdmissionDepartments();
|
|
$panelData['refer_master'] = $this->getAdmissionReferMasters();
|
|
$panelData['register_date_input'] = $this->formatHtmlDateInput((string) ($ipd->register_date ?? ''));
|
|
$panelData['reg_time_input'] = $this->formatHtmlTimeInput((string) ($ipd->reg_time ?? ''));
|
|
$user = auth()->user();
|
|
$panelData['can_edit_admission'] = $user !== null && $user->can('billing.ipd.admission.edit');
|
|
|
|
return $panelData;
|
|
}
|
|
|
|
private function getAdmissionDoctors(int $ipdId): array
|
|
{
|
|
if (! $this->db->tableExists('ipd_master_doc_list') || ! $this->db->tableExists('doctor_master')) {
|
|
return [];
|
|
}
|
|
|
|
return $this->db->table('ipd_master_doc_list l')
|
|
->select("d.id, concat_ws(' ', 'Dr.', d.p_fname, d.p_mname, d.p_lname) as doctor_name", false)
|
|
->join('doctor_master d', 'd.id = l.doc_id', 'inner')
|
|
->where('l.ipd_id', $ipdId)
|
|
->groupBy('d.id, d.p_fname, d.p_mname, d.p_lname')
|
|
->orderBy('d.p_fname', 'ASC')
|
|
->get()
|
|
->getResult();
|
|
}
|
|
|
|
private function getAdmissionDepartments(): array
|
|
{
|
|
if (! $this->db->tableExists('hc_department')) {
|
|
return [];
|
|
}
|
|
|
|
return $this->db->table('hc_department')
|
|
->orderBy('vName', 'ASC')
|
|
->get()
|
|
->getResult();
|
|
}
|
|
|
|
private function getAdmissionReferMasters(): array
|
|
{
|
|
if (! $this->db->tableExists('refer_master')) {
|
|
return [];
|
|
}
|
|
|
|
return $this->db->table('refer_master')
|
|
->where('active', 1)
|
|
->orderBy('f_name', 'ASC')
|
|
->get()
|
|
->getResult();
|
|
}
|
|
|
|
private function buildAdmissionHeaderPayload(array $panelData): array
|
|
{
|
|
$ipd = $panelData['ipd_info'] ?? null;
|
|
|
|
return [
|
|
'admit_date' => (string) ($ipd->str_register_date ?? ''),
|
|
'discharge_date' => (string) ($ipd->str_discharge_date ?? ''),
|
|
'no_days' => (string) ($ipd->no_days ?? ''),
|
|
];
|
|
}
|
|
|
|
private function formatHtmlDateInput(string $dateValue): string
|
|
{
|
|
$dateValue = trim($dateValue);
|
|
if ($dateValue === '') {
|
|
return date('Y-m-d');
|
|
}
|
|
|
|
if (preg_match('/^\d{4}-\d{2}-\d{2}$/', $dateValue) === 1) {
|
|
return $dateValue;
|
|
}
|
|
|
|
$timestamp = strtotime($dateValue);
|
|
|
|
return $timestamp !== false ? date('Y-m-d', $timestamp) : date('Y-m-d');
|
|
}
|
|
|
|
private function formatHtmlTimeInput(string $timeValue): string
|
|
{
|
|
$timeValue = trim($timeValue);
|
|
if ($timeValue === '') {
|
|
return date('H:i');
|
|
}
|
|
|
|
if (preg_match('/^\d{2}:\d{2}(:\d{2})?$/', $timeValue) === 1) {
|
|
return substr($timeValue, 0, 5);
|
|
}
|
|
|
|
$timestamp = strtotime($timeValue);
|
|
|
|
return $timestamp !== false ? date('H:i', $timestamp) : date('H:i');
|
|
}
|
|
|
|
private function normalizeAdmissionDate(string $dateValue): ?string
|
|
{
|
|
$dateValue = trim($dateValue);
|
|
if ($dateValue === '') {
|
|
return null;
|
|
}
|
|
|
|
if (preg_match('/^\d{4}-\d{2}-\d{2}$/', $dateValue) === 1) {
|
|
return $dateValue;
|
|
}
|
|
|
|
$timestamp = strtotime($dateValue);
|
|
|
|
return $timestamp !== false ? date('Y-m-d', $timestamp) : null;
|
|
}
|
|
|
|
private function normalizeAdmissionTime(string $timeValue): ?string
|
|
{
|
|
$timeValue = trim($timeValue);
|
|
if ($timeValue === '') {
|
|
return null;
|
|
}
|
|
|
|
if (preg_match('/^\d{2}:\d{2}(:\d{2})?$/', $timeValue) === 1) {
|
|
return substr($timeValue, 0, 5);
|
|
}
|
|
|
|
$timestamp = strtotime($timeValue);
|
|
|
|
return $timestamp !== false ? date('H:i', $timestamp) : null;
|
|
}
|
|
|
|
private function requireAnyPermission(array $permissions)
|
|
{
|
|
if (! function_exists('auth')) {
|
|
return null;
|
|
}
|
|
|
|
$user = auth()->user();
|
|
if (! $user || ! method_exists($user, 'can')) {
|
|
return $this->response->setStatusCode(403)->setBody('Access denied');
|
|
}
|
|
|
|
foreach ($permissions as $permission) {
|
|
if ($user->can($permission)) {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
return $this->response->setStatusCode(403)->setBody('Access denied');
|
|
}
|
|
|
|
private function resolveBedsideItemTypeId(string $category): int
|
|
{
|
|
$groupDesc = 'Bedside - ' . trim($category);
|
|
if ($groupDesc === 'Bedside -') {
|
|
$groupDesc = 'Bedside - General';
|
|
}
|
|
|
|
$row = $this->db->table('ipd_item_type')
|
|
->select('itype_id')
|
|
->where('group_desc', $groupDesc)
|
|
->get()
|
|
->getRow();
|
|
|
|
if ($row) {
|
|
return (int) ($row->itype_id ?? 0);
|
|
}
|
|
|
|
$this->db->table('ipd_item_type')->insert([
|
|
'group_desc' => $groupDesc,
|
|
]);
|
|
|
|
return (int) $this->db->insertID();
|
|
}
|
|
|
|
private function resolveDoctorVisitItemTypeId(): int
|
|
{
|
|
$groupDesc = 'Doctor Visit';
|
|
|
|
$row = $this->db->table('ipd_item_type')
|
|
->select('itype_id')
|
|
->where('group_desc', $groupDesc)
|
|
->get()
|
|
->getRow();
|
|
|
|
if ($row) {
|
|
return (int) ($row->itype_id ?? 0);
|
|
}
|
|
|
|
$this->db->table('ipd_item_type')->insert([
|
|
'group_desc' => $groupDesc,
|
|
]);
|
|
|
|
return (int) $this->db->insertID();
|
|
}
|
|
|
|
private function insertNursingIpdChargeRow(array $data): int
|
|
{
|
|
if (! $this->db->tableExists('nursing_ipd_charges')) {
|
|
return 0;
|
|
}
|
|
|
|
$this->db->table('nursing_ipd_charges')->insert($data);
|
|
|
|
return (int) $this->db->insertID();
|
|
}
|
|
|
|
private function enqueueIpdAdmissionSync(int $ipdId, string $eventType): void
|
|
{
|
|
if ($ipdId <= 0 || ! $this->db->tableExists('ipd_master')) {
|
|
return;
|
|
}
|
|
|
|
$ipdRow = $this->db->table('ipd_master')->where('id', $ipdId)->get(1)->getRowArray();
|
|
if (empty($ipdRow)) {
|
|
return;
|
|
}
|
|
|
|
$patientId = (int) ($ipdRow['p_id'] ?? 0);
|
|
$patientRow = [];
|
|
if ($patientId > 0 && $this->db->tableExists('patient_master')) {
|
|
$patientRow = $this->db->table('patient_master')->where('id', $patientId)->get(1)->getRowArray() ?? [];
|
|
}
|
|
|
|
$abhaId = '';
|
|
foreach (['abha_id', 'abha_no', 'abha_address', 'abha'] as $field) {
|
|
if (trim((string) ($patientRow[$field] ?? '')) !== '') {
|
|
$abhaId = trim((string) ($patientRow[$field] ?? ''));
|
|
break;
|
|
}
|
|
}
|
|
|
|
$doctorIds = [];
|
|
if ($this->db->tableExists('ipd_doctor')) {
|
|
$doctorRows = $this->db->table('ipd_doctor')
|
|
->select('doc_id')
|
|
->where('ipd_id', $ipdId)
|
|
->orderBy('id', 'ASC')
|
|
->get()
|
|
->getResultArray();
|
|
foreach ($doctorRows as $doctorRow) {
|
|
$docId = (int) ($doctorRow['doc_id'] ?? 0);
|
|
if ($docId > 0) {
|
|
$doctorIds[$docId] = $docId;
|
|
}
|
|
}
|
|
}
|
|
|
|
$payload = [
|
|
'ipd_id' => $ipdId,
|
|
'ipd_code' => trim((string) ($ipdRow['ipd_code'] ?? '')),
|
|
'patient_id' => $patientId,
|
|
'patient_name' => trim((string) ($patientRow['p_fname'] ?? $ipdRow['P_name'] ?? '')),
|
|
'abha_id' => $abhaId,
|
|
'register_date' => trim((string) ($ipdRow['register_date'] ?? '')),
|
|
'reg_time' => trim((string) ($ipdRow['reg_time'] ?? '')),
|
|
'discharge_date' => trim((string) ($ipdRow['discharge_date'] ?? '')),
|
|
'discharge_time' => trim((string) ($ipdRow['discharge_time'] ?? '')),
|
|
'dept_id' => (int) ($ipdRow['dept_id'] ?? 0),
|
|
'doctor_ids' => array_values($doctorIds),
|
|
'updated_at' => date('Y-m-d H:i:s'),
|
|
];
|
|
|
|
try {
|
|
$bridgeSync = new BridgeSyncService();
|
|
$bridgeSync->enqueue($eventType, $payload, 'ipd', (string) $ipdId);
|
|
} catch (\Throwable $e) {
|
|
// Do not block IPD admission workflows if queue service is unavailable.
|
|
}
|
|
|
|
if ($eventType === 'ipd.admission.created' && preg_match('/^\d{14}$/', $abhaId) === 1) {
|
|
$taskService = new AbdmWorkTaskService();
|
|
$taskService->createOrRefreshTask(
|
|
'ipd_admission_publish',
|
|
'ipd',
|
|
'ipd',
|
|
(string) $ipdId,
|
|
$patientId,
|
|
trim((string) ($patientRow['p_fname'] ?? $ipdRow['P_name'] ?? '')),
|
|
$abhaId,
|
|
'submit',
|
|
[
|
|
'ipd_id' => $ipdId,
|
|
'trigger' => 'ipd.admission.created',
|
|
]
|
|
);
|
|
}
|
|
}
|
|
|
|
private function syncNursingChargesToInvoice(int $ipdId): void
|
|
{
|
|
if (! $this->db->tableExists('nursing_ipd_charges')) {
|
|
return;
|
|
}
|
|
|
|
$rows = $this->db->table('nursing_ipd_charges')
|
|
->where('ipd_id', $ipdId)
|
|
->where('include_in_bill', 1)
|
|
->orderBy('charge_id', 'ASC')
|
|
->get()
|
|
->getResultArray();
|
|
|
|
if (empty($rows)) {
|
|
return;
|
|
}
|
|
|
|
$caseMeta = $this->ipdModel->getIpdCaseMeta($ipdId);
|
|
$insId = (int) ($caseMeta['insurance_id'] ?? 0);
|
|
|
|
foreach ($rows as $row) {
|
|
$chargeId = (int) ($row['charge_id'] ?? 0);
|
|
if ($chargeId <= 0) {
|
|
continue;
|
|
}
|
|
|
|
$marker = '[NURSING_CHARGE_ID:' . $chargeId . ']';
|
|
$existsByMarker = $this->db->table('ipd_invoice_item')
|
|
->where('ipd_id', $ipdId)
|
|
->like('comment', $marker)
|
|
->countAllResults();
|
|
if ($existsByMarker > 0) {
|
|
continue;
|
|
}
|
|
|
|
$itemName = trim((string) ($row['item_name'] ?? ''));
|
|
$dateItem = (string) ($row['visit_date'] ?? date('Y-m-d'));
|
|
$qty = (float) ($row['qty'] ?? 1);
|
|
$rate = (float) ($row['rate'] ?? 0);
|
|
$amount = (float) ($row['amount'] ?? ($rate * $qty));
|
|
$docId = (int) ($row['doctor_id'] ?? 0);
|
|
|
|
$existsByValue = $this->db->table('ipd_invoice_item')
|
|
->where('ipd_id', $ipdId)
|
|
->where('item_name', $itemName)
|
|
->where('date_item', $dateItem)
|
|
->where('item_qty', $qty)
|
|
->where('item_amount', $amount)
|
|
->where('doc_id', $docId)
|
|
->countAllResults();
|
|
if ($existsByValue > 0) {
|
|
continue;
|
|
}
|
|
|
|
$rawType = trim((string) ($row['item_type'] ?? ''));
|
|
$typeId = stripos($rawType, 'doctor') !== false
|
|
? $this->resolveDoctorVisitItemTypeId()
|
|
: $this->resolveBedsideItemTypeId($rawType !== '' ? $rawType : 'General');
|
|
|
|
$remarks = trim((string) ($row['remarks'] ?? ''));
|
|
$comment = trim($remarks . ' ' . $marker);
|
|
|
|
$this->ipdEditModel->insertIpdItem([
|
|
'ipd_id' => $ipdId,
|
|
'item_type' => $typeId,
|
|
'item_id' => (int) ($row['item_id'] ?? $chargeId),
|
|
'item_name' => $itemName,
|
|
'item_rate' => $rate,
|
|
'item_added_date' => date('Y-m-d H:i:s'),
|
|
'item_qty' => $qty,
|
|
'item_amount' => $amount,
|
|
'doc_id' => $docId,
|
|
'doc_name' => (string) ($row['doctor_name'] ?? ''),
|
|
'date_item' => $dateItem,
|
|
'comment' => $comment,
|
|
'ins_id' => $insId,
|
|
'org_code' => '',
|
|
]);
|
|
}
|
|
}
|
|
}
|