feat(ipd-charges): group delete, sort order, Up/Down buttons, foldable billing panels

- Safe delete for empty IPD charge groups with CSRF-safe AJAX
- Migration: add sort_order to ipd_item_type (backfilled)
- Sort order UI on create/edit/list views; persist in controller/model
- Up/Down swap buttons on group list (AJAX swap endpoint + CSRF refresh)
- Fix 500 on edit screen: restore missing getItemTypeById() in ItemIpdModel
- IpdBillingModel: propagate sort_order ordering to ipd-charges and bill-details tabs
- Billing IPD Charges tab: Bedside Clinical/Nursing Charge and Doctor Visit Charge panels now collapsible, start collapsed
This commit is contained in:
DevSoftTech Server 2026-04-18 06:56:38 +05:30
parent 058debb593
commit fbbecee3b4
9 changed files with 513 additions and 133 deletions

View File

@ -1071,6 +1071,8 @@ $routes->group('item-ipd', static function($routes) {
$routes->post('update', 'ItemIpd::updateRecord');
$routes->post('create-type', 'ItemIpd::createItemTypeRecord');
$routes->post('update-type', 'ItemIpd::updateItemTypeRecord');
$routes->post('delete-type', 'ItemIpd::deleteItemTypeRecord');
$routes->post('change-sort-type', 'ItemIpd::changeItemTypeSort');
$routes->post('insurance/add', 'ItemIpd::addInsuranceItemRecord');
$routes->post('insurance/remove', 'ItemIpd::removeInsuranceItem');
});
@ -1143,6 +1145,7 @@ $routes->group('Item_IPD', static function($routes) {
$routes->post('UpdateRecord', 'ItemIpd::updateRecord');
$routes->post('CreateItemTypeRecord', 'ItemIpd::createItemTypeRecord');
$routes->post('UpdateItemTypeRecord', 'ItemIpd::updateItemTypeRecord');
$routes->post('change_sort_itemtype', 'ItemIpd::changeItemTypeSort');
$routes->post('AddInsuranceItemRecord', 'ItemIpd::addInsuranceItemRecord');
$routes->post('remove_record_item', 'ItemIpd::removeInsuranceItem');
});

View File

@ -7,12 +7,20 @@ use App\Models\ItemIpdModel;
class ItemIpd extends BaseController
{
private function getPostedItemTypeSortOrder(): int
{
$sortOrder = $this->request->getPost('sort_order');
return max(0, (int) $sortOrder);
}
public function searchItemType(): string
{
$model = new ItemIpdModel();
return view('Setting/Charges/IPD_Charges/item_type_search_v', [
'data' => $model->getItemTypes(),
'supportsSortOrder' => $model->supportsItemTypeSortOrder(),
]);
}
@ -222,6 +230,25 @@ class ItemIpd extends BaseController
]);
}
public function deleteItemTypeRecord()
{
if (! $this->request->isAJAX()) {
return $this->response->setJSON(['deleted' => 0, 'error_text' => 'Invalid request']);
}
$model = new ItemIpdModel();
$id = (int) $this->request->getPost('itype_id');
$deleted = $model->deleteItemType($id);
return $this->response->setJSON([
'deleted' => $deleted ? 1 : 0,
'error_text' => $deleted ? '' : 'Cannot delete: group has charges or does not exist.',
'csrfName' => csrf_token(),
'csrfHash' => csrf_hash(),
]);
}
public function createItemTypeRecord()
{
if (! $this->request->isAJAX()) {
@ -233,6 +260,10 @@ class ItemIpd extends BaseController
'group_desc' => $this->request->getPost('input_Item_type'),
];
if ($model->supportsItemTypeSortOrder()) {
$data['sort_order'] = $this->getPostedItemTypeSortOrder();
}
$insertId = $model->insertItemType($data);
return $this->response->setJSON([
@ -256,6 +287,10 @@ class ItemIpd extends BaseController
'group_desc' => $this->request->getPost('input_Item_type'),
];
if ($model->supportsItemTypeSortOrder()) {
$data['sort_order'] = $this->getPostedItemTypeSortOrder();
}
$updated = $model->updateItemType($data, $itemTypeId);
return $this->response->setJSON([
@ -267,4 +302,36 @@ class ItemIpd extends BaseController
]);
}
public function changeItemTypeSort()
{
if (! $this->request->isAJAX()) {
return $this->response->setJSON(['moved' => 0, 'error_text' => 'Invalid request']);
}
$model = new ItemIpdModel();
if (! $model->supportsItemTypeSortOrder()) {
return $this->response->setJSON([
'moved' => 0,
'error_text' => 'Sort order is not available in this environment.',
'csrfName' => csrf_token(),
'csrfHash' => csrf_hash(),
]);
}
$currentId = (int) $this->request->getPost('current_id');
$currentSort = max(0, (int) $this->request->getPost('current_sort'));
$targetId = (int) $this->request->getPost('target_id');
$targetSort = max(0, (int) $this->request->getPost('target_sort'));
$moved = $model->swapItemTypeSortOrder($currentId, $currentSort, $targetId, $targetSort);
return $this->response->setJSON([
'moved' => $moved ? 1 : 0,
'error_text' => $moved ? '' : 'Could not update sort order.',
'csrfName' => csrf_token(),
'csrfHash' => csrf_hash(),
]);
}
}

View File

@ -0,0 +1,58 @@
<?php
namespace App\Database\Migrations;
use CodeIgniter\Database\Migration;
class AddSortOrderToIpdItemType extends Migration
{
private function tableExists(string $table): bool
{
return $this->db->query('SHOW TABLES LIKE ' . $this->db->escape($table))->getNumRows() > 0;
}
private function fieldExists(string $table, string $field): bool
{
return $this->db->query('SHOW COLUMNS FROM `' . $table . '` LIKE ' . $this->db->escape($field))->getNumRows() > 0;
}
public function up()
{
if (! $this->tableExists('ipd_item_type')) {
return;
}
if (! $this->fieldExists('ipd_item_type', 'sort_order')) {
$this->forge->addColumn('ipd_item_type', [
'sort_order' => [
'type' => 'INT',
'constraint' => 11,
'default' => 0,
'null' => false,
'after' => 'group_desc',
],
]);
}
$rows = $this->db->table('ipd_item_type')
->select('itype_id')
->orderBy('group_desc', 'ASC')
->get()
->getResult();
$sortOrder = 1;
foreach ($rows as $row) {
$this->db->table('ipd_item_type')
->where('itype_id', (int) ($row->itype_id ?? 0))
->update(['sort_order' => $sortOrder]);
$sortOrder++;
}
}
public function down()
{
if ($this->tableExists('ipd_item_type') && $this->fieldExists('ipd_item_type', 'sort_order')) {
$this->forge->dropColumn('ipd_item_type', 'sort_order');
}
}
}

View File

@ -8,6 +8,17 @@ class IpdBillingModel extends Model
{
protected $table = 'v_ipd_list';
private ?bool $ipdItemTypeHasSortOrder = null;
private function ipdItemTypeHasSortOrder(): bool
{
if ($this->ipdItemTypeHasSortOrder === null) {
$this->ipdItemTypeHasSortOrder = $this->db->fieldExists('sort_order', 'ipd_item_type');
}
return $this->ipdItemTypeHasSortOrder;
}
public function getCurrentAdmissions(): array
{
$builder = $this->baseIpdListQuery();
@ -172,10 +183,16 @@ class IpdBillingModel extends Model
public function getIpdCharges(int $ipdId): array
{
return $this->db->table('ipd_invoice_item i')
$builder = $this->db->table('ipd_invoice_item i')
->select('i.*, t.group_desc')
->join('ipd_item_type t', 'i.item_type = t.itype_id', 'left')
->where('i.ipd_id', $ipdId)
->where('i.ipd_id', $ipdId);
if ($this->ipdItemTypeHasSortOrder()) {
$builder->orderBy('COALESCE(t.sort_order, 0)', 'ASC', false);
}
return $builder
->orderBy('i.id', 'DESC')
->get()
->getResult();
@ -183,11 +200,17 @@ class IpdBillingModel extends Model
public function getIpdChargesGrouped(int $ipdId): array
{
return $this->db->table('ipd_invoice_item i')
$builder = $this->db->table('ipd_invoice_item i')
->select('t.group_desc, i.*, sum(i.item_amount) as xAmount')
->join('ipd_item_type t', 'i.item_type = t.itype_id', 'left')
->where('i.ipd_id', $ipdId)
->groupBy('i.item_type,i.id')
->groupBy('i.item_type,i.id');
if ($this->ipdItemTypeHasSortOrder()) {
$builder->orderBy('COALESCE(t.sort_order, 0)', 'ASC', false);
}
return $builder
->orderBy('i.item_type', 'ASC')
->orderBy('i.id', 'ASC')
->get()
@ -365,6 +388,10 @@ class IpdBillingModel extends Model
$builder->where('i.package_id', 0);
}
if ($this->ipdItemTypeHasSortOrder()) {
$builder->orderBy('COALESCE(t.sort_order, 0)', 'ASC', false);
}
return $builder
->orderBy('t.group_desc', 'ASC')
->orderBy('i.id', 'ASC')

View File

@ -7,34 +7,89 @@ use Config\Database;
class ItemIpdModel extends Model
{
/**
* @var array<string, bool>
*/
private array $fieldExistsCache = [];
public function __construct()
{
parent::__construct();
$this->db = Database::connect();
}
public function supportsItemTypeSortOrder(): bool
{
return $this->fieldExists('ipd_item_type', 'sort_order');
}
private function fieldExists(string $table, string $field): bool
{
$cacheKey = $table . '.' . $field;
if (! array_key_exists($cacheKey, $this->fieldExistsCache)) {
$this->fieldExistsCache[$cacheKey] = $this->db->fieldExists($field, $table);
}
return $this->fieldExistsCache[$cacheKey];
}
private function applyItemTypeOrdering($builder, string $alias = '')
{
$prefix = $alias !== '' ? $alias . '.' : '';
if ($this->supportsItemTypeSortOrder()) {
$builder->orderBy('COALESCE(' . $prefix . 'sort_order, 0)', 'ASC', false);
}
return $builder->orderBy($prefix . 'group_desc', 'ASC');
}
private function filterItemTypeData(array $data): array
{
if (! $this->supportsItemTypeSortOrder()) {
unset($data['sort_order']);
}
return $data;
}
public function getItemTypes(): array
{
return $this->db->table('ipd_item_type')
->orderBy('group_desc', 'ASC')
$select = ['t.itype_id', 't.group_desc', 'COUNT(m.id) AS item_count'];
$groupBy = ['t.itype_id', 't.group_desc'];
if ($this->supportsItemTypeSortOrder()) {
$select[] = 'COALESCE(t.sort_order, 0) AS sort_order';
$groupBy[] = 't.sort_order';
}
$builder = $this->db->table('ipd_item_type t')
->select(implode(', ', $select), false)
->join('ipd_items m', 'm.itype = t.itype_id', 'left')
->groupBy(implode(', ', $groupBy), false);
return $this->applyItemTypeOrdering($builder, 't')
->get()
->getResult();
}
public function getItemTypesList(): array
{
return $this->db->table('ipd_item_type')
->orderBy('group_desc', 'ASC')
$builder = $this->db->table('ipd_item_type');
return $this->applyItemTypeOrdering($builder)
->get()
->getResult();
}
public function getItemTypeById(int $id): array
{
return $this->db->table('ipd_item_type')
->where('itype_id', $id)
->get()
->getResult();
$builder = $this->db->table('ipd_item_type');
$builder->where('itype_id', $id);
$this->applyItemTypeOrdering($builder);
return $builder->get()->getResult();
}
public function getItemsByType(int $typeId): array
@ -68,7 +123,7 @@ class ItemIpdModel extends Model
't.group_desc',
]);
$builder->join('ipd_item_type t', 'm.itype = t.itype_id', 'inner');
$builder->orderBy('t.group_desc', 'ASC');
$this->applyItemTypeOrdering($builder, 't');
return $builder->get()->getResult();
}
@ -171,7 +226,7 @@ class ItemIpdModel extends Model
public function insertItemType(array $data): int
{
$this->db->table('ipd_item_type')->insert($data);
$this->db->table('ipd_item_type')->insert($this->filterItemTypeData($data));
return (int) $this->db->insertID();
}
@ -180,7 +235,48 @@ class ItemIpdModel extends Model
{
return (bool) $this->db->table('ipd_item_type')
->where('itype_id', $id)
->update($data);
->update($this->filterItemTypeData($data));
}
public function swapItemTypeSortOrder(int $currentId, int $currentSort, int $targetId, int $targetSort): bool
{
if (! $this->supportsItemTypeSortOrder() || $currentId <= 0 || $targetId <= 0 || $currentId === $targetId) {
return false;
}
$this->db->transStart();
$this->db->table('ipd_item_type')
->where('itype_id', $currentId)
->update(['sort_order' => 0]);
$this->db->table('ipd_item_type')
->where('itype_id', $targetId)
->update(['sort_order' => $currentSort]);
$this->db->table('ipd_item_type')
->where('itype_id', $currentId)
->update(['sort_order' => $targetSort]);
$this->db->transComplete();
return $this->db->transStatus();
}
public function deleteItemType(int $id): bool
{
// Only delete if no items are linked
$count = (int) $this->db->table('ipd_items')
->where('itype', $id)
->countAllResults();
if ($count > 0) {
return false;
}
return (bool) $this->db->table('ipd_item_type')
->where('itype_id', $id)
->delete();
}
public function insertItemInsurance(array $data): int

View File

@ -17,6 +17,10 @@
<label class="form-label">Charges Group Name</label>
<input class="form-control" name="input_Item_type" placeholder="Item Name" type="text" autocomplete="off">
</div>
<div class="col-md-3">
<label class="form-label">Sort Order</label>
<input class="form-control" name="sort_order" placeholder="0" value="0" type="number" min="0" step="1" autocomplete="off">
</div>
<div class="col-md-4 d-flex align-items-end">
<button type="submit" class="btn btn-primary" id="btn_update">Add Record</button>
</div>

View File

@ -19,6 +19,10 @@
<label class="form-label">Charges Group Name</label>
<input class="form-control" name="input_Item_type" placeholder="Item Name" value="<?= esc($data_item[0]->group_desc ?? '') ?>" type="text" autocomplete="off">
</div>
<div class="col-md-3">
<label class="form-label">Sort Order</label>
<input class="form-control" name="sort_order" placeholder="0" value="<?= esc((string) ((int) ($data_item[0]->sort_order ?? 0))) ?>" type="number" min="0" step="1" autocomplete="off">
</div>
<div class="col-md-4 d-flex align-items-end">
<button type="button" class="btn btn-primary" id="btn_update">Update Record</button>
</div>

View File

@ -7,19 +7,39 @@
</div>
</div>
<div class="card-body">
<input type="hidden" id="csrf_name" value="<?= csrf_token() ?>">
<input type="hidden" id="csrf_val" value="<?= csrf_hash() ?>">
<table id="example1" class="table table-bordered table-striped">
<thead>
<tr>
<th>Charges Group Name</th>
<th></th>
<th style="width: 120px;">Sort Order</th>
<th style="width: 280px;">Action</th>
</tr>
</thead>
<tbody>
<?php foreach ($data as $row) : ?>
<?php
$rows = is_array($data) ? array_values($data) : [];
foreach ($rows as $index => $row) :
$currentId = (int) ($row->itype_id ?? 0);
$currentSort = (int) ($row->sort_order ?? 0);
$prevRow = $rows[$index - 1] ?? null;
$nextRow = $rows[$index + 1] ?? null;
?>
<tr>
<td><?= esc($row->group_desc ?? '') ?></td>
<td><?= esc((string) ((int) ($row->sort_order ?? 0))) ?></td>
<td>
<button onclick="load_form_div('<?= base_url('item-ipd/itemtype-record') ?>/' + <?= (int) ($row->itype_id ?? 0) ?>, 'maindiv', 'IPD Charge Master');" type="button" class="btn btn-primary btn-sm">Edit</button>
<?php if ((int)($row->item_count ?? 0) === 0): ?>
<button onclick="deleteItemTypeGroup(<?= (int)($row->itype_id ?? 0) ?>, this);" type="button" class="btn btn-danger btn-sm ms-1">Delete</button>
<?php endif ?>
<?php if (($supportsSortOrder ?? false) && $nextRow !== null): ?>
<button onclick="moveItemTypeGroup(<?= $currentId ?>, <?= $currentSort ?>, <?= (int) ($nextRow->itype_id ?? 0) ?>, <?= (int) ($nextRow->sort_order ?? 0) ?>, this);" type="button" class="btn btn-outline-primary btn-sm ms-1">Down</button>
<?php endif ?>
<?php if (($supportsSortOrder ?? false) && $prevRow !== null): ?>
<button onclick="moveItemTypeGroup(<?= $currentId ?>, <?= $currentSort ?>, <?= (int) ($prevRow->itype_id ?? 0) ?>, <?= (int) ($prevRow->sort_order ?? 0) ?>, this);" type="button" class="btn btn-outline-primary btn-sm ms-1">Up</button>
<?php endif ?>
</td>
</tr>
<?php endforeach ?>
@ -27,7 +47,8 @@
<tfoot>
<tr>
<th>Charges Group Name</th>
<th></th>
<th>Sort Order</th>
<th>Action</th>
</tr>
</tfoot>
</table>
@ -43,4 +64,68 @@
new simpleDatatables.DataTable(table);
})();
function deleteItemTypeGroup(itypeId, btn) {
if (!confirm('Delete this empty charges group? This cannot be undone.')) {
return;
}
btn.disabled = true;
var csrfName = $('#csrf_name').val();
var csrfVal = $('#csrf_val').val();
var payload = {itype_id: itypeId};
payload[csrfName] = csrfVal;
$.post('<?= base_url('item-ipd/delete-type') ?>', payload, function(res) {
if (res.deleted) {
$(btn).closest('tr').remove();
} else {
alert(res.error_text || 'Could not delete group.');
btn.disabled = false;
}
if (res.csrfName && res.csrfHash) {
$('#csrf_name').val(res.csrfName);
$('#csrf_val').val(res.csrfHash);
}
}, 'json').fail(function() {
alert('Request failed.');
btn.disabled = false;
});
}
function moveItemTypeGroup(currentId, currentSort, targetId, targetSort, btn) {
btn.disabled = true;
var csrfName = $('#csrf_name').val();
var csrfVal = $('#csrf_val').val();
var payload = {
current_id: currentId,
current_sort: currentSort,
target_id: targetId,
target_sort: targetSort
};
payload[csrfName] = csrfVal;
$.post('<?= base_url('item-ipd/change-sort-type') ?>', payload, function(res) {
if (res.csrfName && res.csrfHash) {
$('#csrf_name').val(res.csrfName);
$('#csrf_val').val(res.csrfHash);
}
if (!res.moved) {
alert(res.error_text || 'Could not update sort order.');
btn.disabled = false;
return;
}
if (typeof load_form_div === 'function') {
load_form_div('<?= base_url('item-ipd/search-itemtype') ?>', 'maindiv', 'IPD Charge Master');
} else {
window.location.reload();
}
}, 'json').fail(function() {
alert('Request failed.');
btn.disabled = false;
});
}
</script>

View File

@ -32,6 +32,28 @@ $doctorVisitFeeMap = $doctor_visit_fee_map ?? [];
.ipd-charge-accordion .accordion-body {
background: #f8fafc;
}
.ipd-charge-section-toggle {
width: 100%;
border: 0;
background: transparent;
padding: 0;
text-align: left;
font-weight: 600;
display: flex;
align-items: center;
justify-content: space-between;
color: inherit;
}
.ipd-charge-section-toggle:focus {
outline: none;
box-shadow: none;
}
.ipd-charge-section-toggle .bi {
transition: transform 0.2s ease;
}
.ipd-charge-section-toggle.collapsed .bi {
transform: rotate(-90deg);
}
</style>
<div class="row g-3">
@ -158,128 +180,142 @@ $doctorVisitFeeMap = $doctor_visit_fee_map ?? [];
</div>
<div class="card-body">
<div class="card mb-3 border-info">
<div class="card-header py-2"><strong>Bedside Clinical / Nursing Charge</strong></div>
<div class="card-body">
<div class="row g-2">
<div class="col-md-3">
<label class="form-label">Item Type</label>
<select class="form-select form-select-sm" id="bedside_item_type">
<option value="">All</option>
</select>
</div>
<div class="col-md-4">
<label class="form-label">Category</label>
<select class="form-select form-select-sm" id="bedside_category">
<option value="">Select</option>
<?php foreach (array_keys($bedsideItemsByCategory) as $categoryName) : ?>
<option value="<?= esc($categoryName) ?>"><?= esc($categoryName) ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="col-md-5">
<label class="form-label">Item</label>
<select class="form-select form-select-sm" id="bedside_item_id">
<option value="">Select</option>
</select>
</div>
<div class="col-md-4">
<label class="form-label">Quick Search</label>
<input type="text" class="form-control form-control-sm" id="bedside_item_search" placeholder="Search by code or name">
</div>
<div class="col-md-2">
<label class="form-label">Rate</label>
<input class="form-control form-control-sm" id="bedside_item_rate" value="0" type="number" $11" />
</div>
<div class="col-md-2">
<label class="form-label">Qty</label>
<input class="form-control form-control-sm" id="bedside_item_qty" value="1" type="number" $11" />
</div>
<div class="col-md-3">
<label class="form-label">Date</label>
<input class="form-control form-control-sm" id="bedside_item_date" value="<?= esc($today) ?>" type="date" />
</div>
<div class="col-md-3">
<label class="form-label">Doctor</label>
<select class="form-select form-select-sm" id="bedside_doc_id">
<option value="0">NONE</option>
<?php foreach ($doc_list ?? [] as $doc) : ?>
<option value="<?= esc($doc->id ?? '') ?>"><?= esc($doc->DocSpecName ?? '') ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="col-md-6">
<label class="form-label">Comment</label>
<input class="form-control form-control-sm" id="bedside_comment" value="" type="text" />
</div>
<div class="col-md-12">
<button type="button" class="btn btn-outline-primary btn-sm" onclick="ipdBedsideChargeAdd()">Add Bedside Charge</button>
<div class="card-header py-2">
<button type="button" class="ipd-charge-section-toggle" data-bs-toggle="collapse" data-bs-target="#bedsideChargeCollapse" aria-expanded="false" aria-controls="bedsideChargeCollapse">
<span>Bedside Clinical / Nursing Charge</span>
<i class="bi bi-chevron-down" aria-hidden="true"></i>
</button>
</div>
<div id="bedsideChargeCollapse" class="collapse">
<div class="card-body">
<div class="row g-2">
<div class="col-md-3">
<label class="form-label">Item Type</label>
<select class="form-select form-select-sm" id="bedside_item_type">
<option value="">All</option>
</select>
</div>
<div class="col-md-4">
<label class="form-label">Category</label>
<select class="form-select form-select-sm" id="bedside_category">
<option value="">Select</option>
<?php foreach (array_keys($bedsideItemsByCategory) as $categoryName) : ?>
<option value="<?= esc($categoryName) ?>"><?= esc($categoryName) ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="col-md-5">
<label class="form-label">Item</label>
<select class="form-select form-select-sm" id="bedside_item_id">
<option value="">Select</option>
</select>
</div>
<div class="col-md-4">
<label class="form-label">Quick Search</label>
<input type="text" class="form-control form-control-sm" id="bedside_item_search" placeholder="Search by code or name">
</div>
<div class="col-md-2">
<label class="form-label">Rate</label>
<input class="form-control form-control-sm" id="bedside_item_rate" value="0" type="number" $11" />
</div>
<div class="col-md-2">
<label class="form-label">Qty</label>
<input class="form-control form-control-sm" id="bedside_item_qty" value="1" type="number" $11" />
</div>
<div class="col-md-3">
<label class="form-label">Date</label>
<input class="form-control form-control-sm" id="bedside_item_date" value="<?= esc($today) ?>" type="date" />
</div>
<div class="col-md-3">
<label class="form-label">Doctor</label>
<select class="form-select form-select-sm" id="bedside_doc_id">
<option value="0">NONE</option>
<?php foreach ($doc_list ?? [] as $doc) : ?>
<option value="<?= esc($doc->id ?? '') ?>"><?= esc($doc->DocSpecName ?? '') ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="col-md-6">
<label class="form-label">Comment</label>
<input class="form-control form-control-sm" id="bedside_comment" value="" type="text" />
</div>
<div class="col-md-12">
<button type="button" class="btn btn-outline-primary btn-sm" onclick="ipdBedsideChargeAdd()">Add Bedside Charge</button>
</div>
</div>
</div>
</div>
</div>
<div class="card mb-3 border-primary">
<div class="card-header py-2"><strong>Doctor Visit Charge</strong></div>
<div class="card-body">
<div class="row g-2">
<div class="col-md-5">
<label class="form-label">Doctor</label>
<select class="form-select form-select-sm" id="doctor_visit_doc_id">
<option value="">Select</option>
<?php foreach ($doc_list ?? [] as $doc) : ?>
<option value="<?= esc($doc->id ?? '') ?>"><?= esc($doc->DocSpecName ?? '') ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="col-md-4">
<label class="form-label">Visit Type</label>
<select class="form-select form-select-sm" id="doctor_visit_fee_type">
<option value="">Select</option>
<?php foreach ($doctorVisitFeeTypes as $row) : ?>
<option value="<?= esc($row['id'] ?? 0) ?>"><?= esc($row['fee_type'] ?? '') ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="col-md-3">
<label class="form-label">Rate</label>
<input class="form-control form-control-sm" id="doctor_visit_rate" value="0.00" type="number" $11" />
</div>
<div class="col-md-5">
<label class="form-label">Description</label>
<input class="form-control form-control-sm" id="doctor_visit_fee_desc" value="" type="text" readonly />
</div>
<div class="col-md-2">
<label class="form-label">Qty</label>
<input class="form-control form-control-sm" id="doctor_visit_qty" value="1" type="number" $11" />
</div>
<div class="col-md-3">
<label class="form-label">Date</label>
<input class="form-control form-control-sm" id="doctor_visit_date" value="<?= esc($today) ?>" type="date" />
</div>
<div class="col-md-2">
<label class="form-label">Time</label>
<input class="form-control form-control-sm" id="doctor_visit_time" type="time" />
</div>
<div class="col-md-2">
<label class="form-label">Duration (min)</label>
<input class="form-control form-control-sm" id="doctor_visit_duration" value="0" type="number" min="0" step="1" />
</div>
<div class="col-md-3">
<label class="form-label">Hospital/Clinic</label>
<input class="form-control form-control-sm" id="doctor_visit_hospital" value="" type="text" />
</div>
<div class="col-md-3 d-flex align-items-end">
<div class="form-check mb-1">
<input class="form-check-input" type="checkbox" id="doctor_visit_outside" value="1">
<label class="form-check-label" for="doctor_visit_outside">Outside Doctor</label>
<div class="card-header py-2">
<button type="button" class="ipd-charge-section-toggle" data-bs-toggle="collapse" data-bs-target="#doctorVisitChargeCollapse" aria-expanded="false" aria-controls="doctorVisitChargeCollapse">
<span>Doctor Visit Charge</span>
<i class="bi bi-chevron-down" aria-hidden="true"></i>
</button>
</div>
<div id="doctorVisitChargeCollapse" class="collapse">
<div class="card-body">
<div class="row g-2">
<div class="col-md-5">
<label class="form-label">Doctor</label>
<select class="form-select form-select-sm" id="doctor_visit_doc_id">
<option value="">Select</option>
<?php foreach ($doc_list ?? [] as $doc) : ?>
<option value="<?= esc($doc->id ?? '') ?>"><?= esc($doc->DocSpecName ?? '') ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="col-md-4">
<label class="form-label">Visit Type</label>
<select class="form-select form-select-sm" id="doctor_visit_fee_type">
<option value="">Select</option>
<?php foreach ($doctorVisitFeeTypes as $row) : ?>
<option value="<?= esc($row['id'] ?? 0) ?>"><?= esc($row['fee_type'] ?? '') ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="col-md-3">
<label class="form-label">Rate</label>
<input class="form-control form-control-sm" id="doctor_visit_rate" value="0.00" type="number" $11" />
</div>
<div class="col-md-5">
<label class="form-label">Description</label>
<input class="form-control form-control-sm" id="doctor_visit_fee_desc" value="" type="text" readonly />
</div>
<div class="col-md-2">
<label class="form-label">Qty</label>
<input class="form-control form-control-sm" id="doctor_visit_qty" value="1" type="number" $11" />
</div>
<div class="col-md-3">
<label class="form-label">Date</label>
<input class="form-control form-control-sm" id="doctor_visit_date" value="<?= esc($today) ?>" type="date" />
</div>
<div class="col-md-2">
<label class="form-label">Time</label>
<input class="form-control form-control-sm" id="doctor_visit_time" type="time" />
</div>
<div class="col-md-2">
<label class="form-label">Duration (min)</label>
<input class="form-control form-control-sm" id="doctor_visit_duration" value="0" type="number" min="0" step="1" />
</div>
<div class="col-md-3">
<label class="form-label">Hospital/Clinic</label>
<input class="form-control form-control-sm" id="doctor_visit_hospital" value="" type="text" />
</div>
<div class="col-md-3 d-flex align-items-end">
<div class="form-check mb-1">
<input class="form-check-input" type="checkbox" id="doctor_visit_outside" value="1">
<label class="form-check-label" for="doctor_visit_outside">Outside Doctor</label>
</div>
</div>
<div class="col-md-6">
<label class="form-label">Comment</label>
<input class="form-control form-control-sm" id="doctor_visit_comment" value="" type="text" />
</div>
<div class="col-md-12">
<button type="button" class="btn btn-outline-primary btn-sm" onclick="ipdDoctorVisitChargeAdd()">Add Doctor Visit</button>
</div>
</div>
<div class="col-md-6">
<label class="form-label">Comment</label>
<input class="form-control form-control-sm" id="doctor_visit_comment" value="" type="text" />
</div>
<div class="col-md-12">
<button type="button" class="btn btn-outline-primary btn-sm" onclick="ipdDoctorVisitChargeAdd()">Add Doctor Visit</button>
</div>
</div>
</div>