Add IPD medical bill type controls
Filter IPD pharmacy credit bills, add package/include toggles with admission validation, and provide an inline invoice detail view.
This commit is contained in:
parent
561c0d9fc3
commit
ee87802af1
|
|
@ -459,6 +459,7 @@ $routes->group('billing', function($routes) {
|
|||
$routes->post('ipd/nursing-charge/delete/(:num)/(:num)', 'Billing\\Ipd::deleteNursingCharge/$1/$2');
|
||||
$routes->post('ipd/charge/package/(:num)', 'Billing\\Ipd::updateIpdChargePackage/$1');
|
||||
$routes->post('ipd/package/add/(:num)', 'Billing\\Ipd::addIpdPackage/$1');
|
||||
$routes->get('ipd/medical-bill/(:num)/(:num)', 'Billing\\Ipd::medicalBillDetails/$1/$2');
|
||||
$routes->post('ipd/medical-bill/credit-type/(:num)', 'Billing\\Ipd::updateMedicalBillCreditType/$1');
|
||||
$routes->get('ipd/panel/(:num)/ayushman/search', 'Billing\Ipd::searchAyushmanPackages/$1');
|
||||
$routes->post('ipd/panel/(:num)/ayushman/map/(:num)', 'Billing\Ipd::saveAyushmanPackageMapping/$1/$2');
|
||||
|
|
|
|||
|
|
@ -1976,6 +1976,28 @@ class Ipd extends BaseController
|
|||
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([
|
||||
|
|
@ -1999,14 +2021,21 @@ class Ipd extends BaseController
|
|||
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')
|
||||
->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' => 'Medical bill not found']);
|
||||
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;
|
||||
|
|
@ -2014,25 +2043,30 @@ class Ipd extends BaseController
|
|||
'ipd_credit_type' => $creditType,
|
||||
];
|
||||
|
||||
if (in_array('ipd_credit', $fields, true)) {
|
||||
$update['ipd_credit'] = 1;
|
||||
}
|
||||
|
||||
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');
|
||||
$update['ipd_credit_type_update'] = $userName . '[' . $userId . '] D:' . date('d-m-Y h:i:s');
|
||||
$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');
|
||||
}
|
||||
|
||||
$this->db->table('invoice_med_master')
|
||||
$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' => (int) ($invoice['ipd_id'] ?? 0),
|
||||
'ipd_id' => $ipdId,
|
||||
'ipd_credit_type' => $creditType,
|
||||
]);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -677,12 +677,37 @@ class IpdBillingModel extends Model
|
|||
public function getMedicalBills(int $ipdId): array
|
||||
{
|
||||
return $this->db->table('invoice_med_master')
|
||||
->where('ipd_id >', 0)
|
||||
->where('ipd_id', $ipdId)
|
||||
->where('ipd_credit', 1)
|
||||
->orderBy('id', 'DESC')
|
||||
->get()
|
||||
->getResult();
|
||||
}
|
||||
|
||||
public function getMedicalBillDetails(int $ipdId, int $invoiceId): array
|
||||
{
|
||||
$invoice = $this->db->table('invoice_med_master')
|
||||
->where('id', $invoiceId)
|
||||
->where('ipd_id', $ipdId)
|
||||
->where('ipd_credit', 1)
|
||||
->get(1)
|
||||
->getRow();
|
||||
|
||||
if ($invoice === null) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return [
|
||||
'invoice' => $invoice,
|
||||
'items' => $this->db->table('inv_med_item')
|
||||
->where('inv_med_id', $invoiceId)
|
||||
->orderBy('id', 'ASC')
|
||||
->get()
|
||||
->getResult(),
|
||||
];
|
||||
}
|
||||
|
||||
public function getBillDetails(int $ipdId): array
|
||||
{
|
||||
return $this->db->table('ipd_master')
|
||||
|
|
|
|||
56
app/Views/billing/ipd/panel_medical_bill_details.php
Normal file
56
app/Views/billing/ipd/panel_medical_bill_details.php
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
<?php
|
||||
/** @var object $invoice */
|
||||
/** @var array<int, object> $items */
|
||||
$invoiceCode = (string) ($invoice->inv_med_code ?? $invoice->id ?? '');
|
||||
?>
|
||||
|
||||
<div class="card mb-0">
|
||||
<div class="card-header d-flex align-items-center justify-content-between">
|
||||
<strong>Invoice ID: <?= esc($invoiceCode) ?></strong>
|
||||
<a href="<?= site_url('Medical/invoice_print/' . (int) $invoice->id) ?>" target="_blank" class="btn btn-sm btn-outline-secondary">
|
||||
<i class="bi bi-printer"></i> Print
|
||||
</a>
|
||||
</div>
|
||||
<div class="card-body p-0">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped mb-0 align-middle">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>#</th>
|
||||
<th>Item Name</th>
|
||||
<th class="text-end">Qty.</th>
|
||||
<th class="text-end">Price</th>
|
||||
<th class="text-end">Amount</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php if (! empty($items)) : ?>
|
||||
<?php foreach ($items as $index => $item) : ?>
|
||||
<?php
|
||||
$itemName = trim((string) ($item->item_Name ?? $item->item_name ?? ''));
|
||||
$formulation = trim((string) ($item->formulation ?? ''));
|
||||
?>
|
||||
<tr>
|
||||
<td><?= $index + 1 ?></td>
|
||||
<td><?= esc($itemName . ($formulation !== '' ? ' [' . $formulation . ']' : '')) ?></td>
|
||||
<td class="text-end"><?= esc($item->qty ?? '') ?></td>
|
||||
<td class="text-end"><?= number_format((float) ($item->price ?? 0), 2) ?></td>
|
||||
<td class="text-end"><?= number_format((float) ($item->tamount ?? 0), 2) ?></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php else : ?>
|
||||
<tr>
|
||||
<td colspan="5" class="text-center text-muted">No invoice items found.</td>
|
||||
</tr>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr>
|
||||
<th colspan="4" class="text-end">Total</th>
|
||||
<th class="text-end"><?= number_format((float) ($invoice->net_amount ?? 0), 2) ?></th>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -1,3 +1,9 @@
|
|||
<?php
|
||||
$ipdId = (int) ($ipd_info->id ?? 0);
|
||||
$csrfName = csrf_token();
|
||||
$csrfHash = csrf_hash();
|
||||
?>
|
||||
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped table-hover align-middle">
|
||||
<thead>
|
||||
|
|
@ -6,7 +12,8 @@
|
|||
<th>Invoice Code</th>
|
||||
<th>Date</th>
|
||||
<th class="text-end">Amount</th>
|
||||
<th>Status</th>
|
||||
<th>Bill Type</th>
|
||||
<th>Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
|
@ -17,15 +24,85 @@
|
|||
<td><?= $srNo++ ?></td>
|
||||
<td><?= esc($row->inv_med_code ?? $row->id ?? '') ?></td>
|
||||
<td><?= esc($row->inv_date ?? '') ?></td>
|
||||
<td class="text-end"><?= esc($row->net_amount ?? '') ?></td>
|
||||
<td><?= ((int) ($row->payment_status ?? 0) > 0) ? 'Paid' : 'Pending' ?></td>
|
||||
<td class="text-end"><?= number_format((float) ($row->net_amount ?? 0), 2) ?></td>
|
||||
<td>
|
||||
<?php $includeInBill = (int) ($row->ipd_credit_type ?? 0) === 1; ?>
|
||||
<div class="form-check form-switch mb-0">
|
||||
<input
|
||||
class="form-check-input"
|
||||
type="checkbox"
|
||||
role="switch"
|
||||
id="medical-bill-type-<?= (int) $row->id ?>"
|
||||
<?= $includeInBill ? 'checked' : '' ?>
|
||||
onchange="ipdMedicalBillsToggleType(this, <?= (int) $row->id ?>)">
|
||||
<label class="form-check-label" for="medical-bill-type-<?= (int) $row->id ?>">
|
||||
<?= $includeInBill ? 'Include in Bill' : 'In Package' ?>
|
||||
</label>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<button type="button" class="btn btn-sm btn-primary" onclick="ipdMedicalBillsShowInvoice(this, <?= (int) $row->id ?>)">
|
||||
Show Invoice
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php else : ?>
|
||||
<tr>
|
||||
<td colspan="5" class="text-center text-muted">No medical bills found.</td>
|
||||
<td colspan="6" class="text-center text-muted">No IPD credit medical bills found.</td>
|
||||
</tr>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div id="ipd-medical-bill-details" class="mt-3"></div>
|
||||
|
||||
<script>
|
||||
var ipdMedicalBillsIpdId = <?= $ipdId ?>;
|
||||
var ipdMedicalBillsCsrfName = '<?= esc($csrfName) ?>';
|
||||
var ipdMedicalBillsCsrfHash = '<?= esc($csrfHash) ?>';
|
||||
var ipdMedicalBillsUpdateUrl = '<?= site_url('billing/ipd/medical-bill/credit-type') ?>';
|
||||
var ipdMedicalBillsDetailsUrl = '<?= site_url('billing/ipd/medical-bill') ?>';
|
||||
|
||||
function ipdMedicalBillsToggleType(checkbox, invoiceId) {
|
||||
var label = document.querySelector('label[for="' + checkbox.id + '"]');
|
||||
var payload = {
|
||||
ipd_id: ipdMedicalBillsIpdId,
|
||||
ipd_credit_type: checkbox.checked ? 1 : 0
|
||||
};
|
||||
payload[ipdMedicalBillsCsrfName] = ipdMedicalBillsCsrfHash;
|
||||
checkbox.disabled = true;
|
||||
|
||||
$.post(ipdMedicalBillsUpdateUrl + '/' + invoiceId, payload, function(resp) {
|
||||
if (!resp || !resp.ok) {
|
||||
checkbox.checked = !checkbox.checked;
|
||||
alert((resp && resp.error) ? resp.error : 'Unable to update medical bill type.');
|
||||
return;
|
||||
}
|
||||
label.textContent = checkbox.checked ? 'Include in Bill' : 'In Package';
|
||||
}).fail(function(xhr) {
|
||||
checkbox.checked = !checkbox.checked;
|
||||
var message = xhr.responseJSON && xhr.responseJSON.error
|
||||
? xhr.responseJSON.error
|
||||
: 'Unable to update medical bill type.';
|
||||
alert(message);
|
||||
}).always(function() {
|
||||
checkbox.disabled = false;
|
||||
});
|
||||
}
|
||||
|
||||
function ipdMedicalBillsShowInvoice(button, invoiceId) {
|
||||
var details = $('#ipd-medical-bill-details');
|
||||
button.disabled = true;
|
||||
details.html('<div class="text-muted p-3">Loading invoice...</div>');
|
||||
|
||||
$.get(ipdMedicalBillsDetailsUrl + '/' + ipdMedicalBillsIpdId + '/' + invoiceId, function(html) {
|
||||
details.html(html);
|
||||
}).fail(function(xhr) {
|
||||
details.html('<div class="alert alert-danger">' + (xhr.responseText || 'Unable to load invoice details.') + '</div>');
|
||||
}).always(function() {
|
||||
button.disabled = false;
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -542,6 +542,7 @@ $ayushmanChecklistItems = $ayushman_checklist_items ?? [];
|
|||
function ipdMedicalBillToggleType(checkbox, invoiceId) {
|
||||
var payload = {};
|
||||
payload[ipdPackageCsrfName] = ipdPackageCsrfHash;
|
||||
payload.ipd_id = ipdPackageIpdId;
|
||||
payload.ipd_credit_type = checkbox.checked ? 1 : 0;
|
||||
|
||||
$.post(ipdMedicalBillCreditTypeUrlBase + '/' + invoiceId, payload, function(resp) {
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user