fix(billing & pharmacy): fix IPD medical bill package classification, default empty qty, and doctor ajax selection
- IPD Billing: replace ambiguous 'In Package' switch with explicit 'Include in IPD Bill (Normal)' / 'In Package (Covered)' dropdown selector in Medical Bills and Package tabs - IPD Billing: return recalculated summary metrics from credit-type endpoint and update dashboard cards in real-time - Pharmacy: classify IPD bills using ipd_credit_type instead of group_invoice_id to correctly show Package: No for credit bills and add to Total Credit - Pharmacy Billing: default item quantity to blank instead of 0 to allow direct typing without backspacing, with validation against 0/empty submissions - Pharmacy Invoice: add outside doctor/consulting doctor selection and auto-save via AJAX
This commit is contained in:
parent
6cc34057b5
commit
51c06304d0
|
|
@ -2318,12 +2318,22 @@ class Ipd extends BaseController
|
|||
}
|
||||
|
||||
$this->recalculateIpdBillingSafely($ipdId, 'medical-bill.credit-type');
|
||||
$updatedIpd = $this->db->table('ipd_master')->where('id', $ipdId)->get()->getRow();
|
||||
|
||||
return $this->response->setJSON([
|
||||
'ok' => true,
|
||||
'invoice_id' => $invoiceId,
|
||||
'ipd_id' => $ipdId,
|
||||
'ipd_credit_type' => $creditType,
|
||||
'summary' => [
|
||||
'charge_amount' => number_format((float) ($updatedIpd->charge_amount ?? 0), 2),
|
||||
'med_amount' => number_format((float) ($updatedIpd->med_amount ?? 0), 2),
|
||||
'net_amount' => number_format((float) ($updatedIpd->net_amount ?? 0), 2),
|
||||
'total_paid_amount' => number_format((float) ($updatedIpd->total_paid_amount ?? 0), 2),
|
||||
'balance_amount' => number_format((float) ($updatedIpd->balance_amount ?? 0), 2),
|
||||
'cash_med_amount' => number_format((float) ($updatedIpd->cash_med_amount ?? 0), 2),
|
||||
'med_paid' => number_format((float) ($updatedIpd->med_paid ?? 0), 2),
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1590,11 +1590,26 @@ class Medical extends BaseController
|
|||
return $this->invoiceUpdateResponse(false, 'Not Done');
|
||||
}
|
||||
|
||||
if (! $this->db->tableExists('invoice_med_master')) {
|
||||
return $this->invoiceUpdateResponse(false, 'Invoice table missing');
|
||||
}
|
||||
|
||||
$invoice = $this->db->table('invoice_med_master')->where('id', $invoiceId)->get()->getRow();
|
||||
if (! $invoice) {
|
||||
return $this->invoiceUpdateResponse(false, 'Invoice not found');
|
||||
}
|
||||
|
||||
if ($this->isInvoiceFinalized($invoice)) {
|
||||
return $this->invoiceUpdateResponse(false, 'Invoice is finalized');
|
||||
}
|
||||
|
||||
if ($docId > 0 && $this->db->tableExists('doctor_master')) {
|
||||
$doc = $this->db->table('doctor_master')->where('id', $docId)->get()->getRow();
|
||||
if ($doc) {
|
||||
$docName = (string) ($doc->p_fname ?? $docName);
|
||||
$docName = trim((string) ($doc->p_fname ?? $docName));
|
||||
}
|
||||
} elseif ($docId < 0) {
|
||||
$docId = 0;
|
||||
}
|
||||
|
||||
$ok = $this->updateInvoiceMasterByFields($invoiceId, [
|
||||
|
|
@ -2329,8 +2344,8 @@ class Medical extends BaseController
|
|||
$legacyBalanceTotal += $balance;
|
||||
|
||||
$ipdCredit = (int) ($row->ipd_credit ?? 0);
|
||||
$groupInvoiceId = (int) ($row->group_invoice_id ?? 0);
|
||||
if ($ipdCredit > 0 && $groupInvoiceId > 0) {
|
||||
$ipdCreditType = (int) ($row->ipd_credit_type ?? 1);
|
||||
if ($ipdCredit > 0 && $ipdCreditType === 0) {
|
||||
$packageTotal += $net;
|
||||
} elseif ($ipdCredit > 0) {
|
||||
$creditTotal += $net;
|
||||
|
|
@ -4075,6 +4090,33 @@ class Medical extends BaseController
|
|||
}
|
||||
}
|
||||
|
||||
$docList = [];
|
||||
if ($this->db->tableExists('doctor_master')) {
|
||||
$docBuilder = $this->db->table('doctor_master');
|
||||
$docFields = $this->db->getFieldNames('doctor_master') ?? [];
|
||||
if (in_array('active', $docFields, true)) {
|
||||
$docBuilder->where('active', 1);
|
||||
}
|
||||
$docList = $docBuilder->orderBy('p_fname', 'ASC')->get()->getResult();
|
||||
|
||||
$invDocId = (int) ($invoice->doc_id ?? 0);
|
||||
if ($invDocId > 0) {
|
||||
$found = false;
|
||||
foreach ($docList as $d) {
|
||||
if ((int) ($d->id ?? 0) === $invDocId) {
|
||||
$found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (! $found) {
|
||||
$docRow = $this->db->table('doctor_master')->where('id', $invDocId)->get()->getRow();
|
||||
if ($docRow) {
|
||||
$docList[] = $docRow;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'invoice' => $invoice,
|
||||
'items' => $items,
|
||||
|
|
@ -4083,7 +4125,8 @@ class Medical extends BaseController
|
|||
'totals' => $totals,
|
||||
'oldInvoices' => $oldInvoices,
|
||||
'isFinalized' => $this->isInvoiceFinalized($invoice),
|
||||
'message' => (string) $this->request->getGet('msg'),
|
||||
'message' => (string) ($this->request ? $this->request->getGet('msg') : ''),
|
||||
'docList' => $docList,
|
||||
];
|
||||
}
|
||||
|
||||
|
|
@ -10131,11 +10174,11 @@ class Medical extends BaseController
|
|||
ELSE 0
|
||||
END) AS ipd_cash_amount,
|
||||
SUM(CASE
|
||||
WHEN IFNULL(m.ipd_id,0)>0 AND IFNULL(m.ipd_credit,0)=1 AND IFNULL(m.group_invoice_id,0)=0 THEN " . $netExpr . "
|
||||
WHEN IFNULL(m.ipd_id,0)>0 AND IFNULL(m.ipd_credit,0)=1 AND IFNULL(m.ipd_credit_type,1)=1 THEN " . $netExpr . "
|
||||
ELSE 0
|
||||
END) AS ipd_credit_amount,
|
||||
SUM(CASE
|
||||
WHEN IFNULL(m.ipd_id,0)>0 AND IFNULL(m.ipd_credit,0)=1 AND IFNULL(m.group_invoice_id,0)>0 THEN " . $netExpr . "
|
||||
WHEN IFNULL(m.ipd_id,0)>0 AND IFNULL(m.ipd_credit,0)=1 AND IFNULL(m.ipd_credit_type,1)=0 THEN " . $netExpr . "
|
||||
ELSE 0
|
||||
END) AS ipd_package_amount
|
||||
FROM invoice_med_master m
|
||||
|
|
@ -10173,10 +10216,10 @@ class Medical extends BaseController
|
|||
$billCondition = ' AND IFNULL(m.ipd_id,0)>0 AND IFNULL(m.ipd_credit,0)=0';
|
||||
break;
|
||||
case 4:
|
||||
$billCondition = ' AND IFNULL(m.ipd_id,0)>0 AND IFNULL(m.ipd_credit,0)=1 AND IFNULL(m.group_invoice_id,0)=0';
|
||||
$billCondition = ' AND IFNULL(m.ipd_id,0)>0 AND IFNULL(m.ipd_credit,0)=1 AND IFNULL(m.ipd_credit_type,1)=1';
|
||||
break;
|
||||
case 5:
|
||||
$billCondition = ' AND IFNULL(m.ipd_id,0)>0 AND IFNULL(m.ipd_credit,0)=1 AND IFNULL(m.group_invoice_id,0)>0';
|
||||
$billCondition = ' AND IFNULL(m.ipd_id,0)>0 AND IFNULL(m.ipd_credit,0)=1 AND IFNULL(m.ipd_credit_type,1)=0';
|
||||
break;
|
||||
case 6:
|
||||
$billCondition = ' AND IFNULL(m.sale_return,0)=1';
|
||||
|
|
@ -10220,8 +10263,8 @@ class Medical extends BaseController
|
|||
WHEN IFNULL(m.ipd_id,0)=0 AND IFNULL(m.case_id,0)=0 AND IFNULL(m.case_credit,0)=0 THEN 'OPD CASH'
|
||||
WHEN IFNULL(m.ipd_id,0)=0 AND IFNULL(m.case_id,0)>0 THEN 'OPD ORG'
|
||||
WHEN IFNULL(m.ipd_id,0)>0 AND IFNULL(m.ipd_credit,0)=0 THEN 'IPD CASH'
|
||||
WHEN IFNULL(m.ipd_id,0)>0 AND IFNULL(m.ipd_credit,0)=1 AND IFNULL(m.group_invoice_id,0)=0 THEN 'IPD CREDIT'
|
||||
WHEN IFNULL(m.ipd_id,0)>0 AND IFNULL(m.ipd_credit,0)=1 AND IFNULL(m.group_invoice_id,0)>0 THEN 'IPD PACKAGE'
|
||||
WHEN IFNULL(m.ipd_id,0)>0 AND IFNULL(m.ipd_credit,0)=1 AND IFNULL(m.ipd_credit_type,1)=1 THEN 'IPD CREDIT'
|
||||
WHEN IFNULL(m.ipd_id,0)>0 AND IFNULL(m.ipd_credit,0)=1 AND IFNULL(m.ipd_credit_type,1)=0 THEN 'IPD PACKAGE'
|
||||
ELSE 'OTHER'
|
||||
END AS bill_type,
|
||||
" . $orgNameExpr . " AS org_name
|
||||
|
|
|
|||
|
|
@ -26,18 +26,19 @@ $csrfHash = csrf_hash();
|
|||
<td><?= esc($row->inv_date ?? '') ?></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"
|
||||
<?php $includeInBill = (int) ($row->ipd_credit_type ?? 1) === 1; ?>
|
||||
<div class="d-flex align-items-center gap-2">
|
||||
<select
|
||||
class="form-select form-select-sm"
|
||||
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 ?>">
|
||||
style="width: auto; min-width: 210px; font-weight: 600;"
|
||||
onchange="ipdMedicalBillsChangeType(this, <?= (int) $row->id ?>)">
|
||||
<option value="1" <?= $includeInBill ? 'selected' : '' ?>>✓ Include in IPD Bill (Normal)</option>
|
||||
<option value="0" <?= ! $includeInBill ? 'selected' : '' ?>>📦 In Package (Covered)</option>
|
||||
</select>
|
||||
<span id="badge-bill-type-<?= (int) $row->id ?>" class="badge <?= $includeInBill ? 'bg-success-subtle text-success border border-success' : 'bg-warning-subtle text-warning border border-warning' ?>">
|
||||
<?= $includeInBill ? 'Include in Bill' : 'In Package' ?>
|
||||
</label>
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
|
|
@ -65,33 +66,104 @@ $csrfHash = csrf_hash();
|
|||
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 + '"]');
|
||||
function ipdMedicalBillsChangeType(select, invoiceId) {
|
||||
var val = parseInt(select.value, 10);
|
||||
var badge = document.getElementById('badge-bill-type-' + invoiceId);
|
||||
var payload = {
|
||||
ipd_id: ipdMedicalBillsIpdId,
|
||||
ipd_credit_type: checkbox.checked ? 1 : 0
|
||||
ipd_credit_type: val
|
||||
};
|
||||
payload[ipdMedicalBillsCsrfName] = ipdMedicalBillsCsrfHash;
|
||||
checkbox.disabled = true;
|
||||
select.disabled = true;
|
||||
|
||||
if (badge) {
|
||||
badge.className = 'badge bg-secondary-subtle text-secondary border border-secondary';
|
||||
badge.textContent = 'Updating...';
|
||||
}
|
||||
|
||||
$.post(ipdMedicalBillsUpdateUrl + '/' + invoiceId, payload, function(resp) {
|
||||
if (!resp || !resp.ok) {
|
||||
checkbox.checked = !checkbox.checked;
|
||||
select.value = val === 1 ? '0' : '1';
|
||||
alert((resp && resp.error) ? resp.error : 'Unable to update medical bill type.');
|
||||
return;
|
||||
}
|
||||
label.textContent = checkbox.checked ? 'Include in Bill' : 'In Package';
|
||||
|
||||
var isIncluded = val === 1;
|
||||
if (badge) {
|
||||
badge.className = isIncluded
|
||||
? 'badge bg-success-subtle text-success border border-success'
|
||||
: 'badge bg-warning-subtle text-warning border border-warning';
|
||||
badge.textContent = isIncluded ? 'Include in Bill' : 'In Package';
|
||||
}
|
||||
|
||||
// Real-time update top dashboard summary numbers
|
||||
if (resp.summary) {
|
||||
ipdMedicalBillsUpdateDashboardCards(resp.summary);
|
||||
}
|
||||
|
||||
// Invalidate Bill Details tab so it loads fresh calculated data when clicked
|
||||
var billPanel = document.getElementById('tab_bill_content');
|
||||
if (billPanel) {
|
||||
billPanel.innerHTML = 'Loading...';
|
||||
}
|
||||
|
||||
if (typeof window.notify === 'function') {
|
||||
window.notify('success', 'Medical Bill', isIncluded ? 'Bill marked as Include in IPD Bill' : 'Bill marked as In Package');
|
||||
}
|
||||
}).fail(function(xhr) {
|
||||
checkbox.checked = !checkbox.checked;
|
||||
select.value = val === 1 ? '0' : '1';
|
||||
var message = xhr.responseJSON && xhr.responseJSON.error
|
||||
? xhr.responseJSON.error
|
||||
: 'Unable to update medical bill type.';
|
||||
alert(message);
|
||||
}).always(function() {
|
||||
checkbox.disabled = false;
|
||||
select.disabled = false;
|
||||
});
|
||||
}
|
||||
|
||||
function ipdMedicalBillsUpdateDashboardCards(s) {
|
||||
var orangeCard = document.querySelector('.ipd-summary-card.orange');
|
||||
if (orangeCard) {
|
||||
var spans = orangeCard.querySelectorAll('span');
|
||||
spans.forEach(function(sp) {
|
||||
var txt = sp.textContent || '';
|
||||
if (txt.indexOf('Charges') >= 0 && s.charge_amount !== undefined) {
|
||||
sp.textContent = 'Charges : ' + s.charge_amount;
|
||||
} else if (txt.indexOf('Pharmacy Cr. IPD') >= 0 && s.med_amount !== undefined) {
|
||||
sp.textContent = 'Pharmacy Cr. IPD : ' + s.med_amount;
|
||||
} else if (txt.indexOf('Net Amount') >= 0 && s.net_amount !== undefined) {
|
||||
sp.textContent = 'Net Amount : ' + s.net_amount;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
var greenCard = document.querySelector('.ipd-summary-card.green');
|
||||
if (greenCard) {
|
||||
var gSpans = greenCard.querySelectorAll('span');
|
||||
gSpans.forEach(function(sp) {
|
||||
var txt = sp.textContent || '';
|
||||
if (txt.indexOf('Total Paid') >= 0 && s.total_paid_amount !== undefined) {
|
||||
sp.textContent = 'Total Paid : ' + s.total_paid_amount;
|
||||
} else if (txt.indexOf('Balance') >= 0 && s.balance_amount !== undefined) {
|
||||
sp.textContent = 'Balance : ' + s.balance_amount;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
var blueCard = document.querySelector('.ipd-summary-card.blue');
|
||||
if (blueCard) {
|
||||
var bSpans = blueCard.querySelectorAll('span');
|
||||
bSpans.forEach(function(sp) {
|
||||
var txt = sp.textContent || '';
|
||||
if (txt.indexOf('Pharmacy Bill') >= 0 && s.cash_med_amount !== undefined) {
|
||||
sp.textContent = 'Pharmacy Bill : ' + s.cash_med_amount;
|
||||
} else if (txt.indexOf('Paid Amount') >= 0 && s.med_paid !== undefined) {
|
||||
sp.textContent = 'Paid Amount : ' + s.med_paid;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function ipdMedicalBillsShowInvoice(button, invoiceId) {
|
||||
var details = $('#ipd-medical-bill-details');
|
||||
button.disabled = true;
|
||||
|
|
|
|||
|
|
@ -324,7 +324,7 @@ $ayushmanChecklistItems = $ayushman_checklist_items ?? [];
|
|||
<div class="card border-top border-3 border-info mt-3">
|
||||
<div class="card-header d-flex justify-content-between align-items-center">
|
||||
<strong>Pharmacy Bills Package Mapping</strong>
|
||||
<span class="small text-muted">Unchecked = In Package, Checked = Include in Bill</span>
|
||||
<span class="small text-muted">Select whether pharmacy bill is covered In Package or billed to IPD</span>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="table-responsive">
|
||||
|
|
@ -335,14 +335,14 @@ $ayushmanChecklistItems = $ayushman_checklist_items ?? [];
|
|||
<th>Invoice Code</th>
|
||||
<th>Date</th>
|
||||
<th class="text-end">Amount</th>
|
||||
<th class="text-center">Include in Bill</th>
|
||||
<th class="text-center">Bill Type</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php if (! empty($medical_bills)) : ?>
|
||||
<?php $medicalSrNo = 1; ?>
|
||||
<?php foreach ($medical_bills as $medicalBill) : ?>
|
||||
<?php $includeInBillChecked = (int) ($medicalBill->ipd_credit_type ?? 1) === 1 ? 'checked' : ''; ?>
|
||||
<?php $includeInBill = (int) ($medicalBill->ipd_credit_type ?? 1) === 1; ?>
|
||||
<tr>
|
||||
<td><?= $medicalSrNo++ ?></td>
|
||||
<td>
|
||||
|
|
@ -356,14 +356,10 @@ $ayushmanChecklistItems = $ayushman_checklist_items ?? [];
|
|||
<td><?= esc((string) ($medicalBill->inv_date ?? '')) ?></td>
|
||||
<td class="text-end"><?= esc(number_format((float) ($medicalBill->net_amount ?? 0), 2)) ?></td>
|
||||
<td class="text-center">
|
||||
<div class="form-check d-inline-flex justify-content-center mb-0">
|
||||
<input
|
||||
class="form-check-input"
|
||||
type="checkbox"
|
||||
onchange="ipdMedicalBillToggleType(this, <?= (int) ($medicalBill->id ?? 0) ?>)"
|
||||
<?= $includeInBillChecked ?>
|
||||
/>
|
||||
</div>
|
||||
<select class="form-select form-select-sm d-inline-block" style="width: auto; min-width: 210px; font-weight: 600;" onchange="ipdPackageMedicalBillChangeType(this, <?= (int) ($medicalBill->id ?? 0) ?>)">
|
||||
<option value="1" <?= $includeInBill ? 'selected' : '' ?>>✓ Include in IPD Bill (Normal)</option>
|
||||
<option value="0" <?= ! $includeInBill ? 'selected' : '' ?>>📦 In Package (Covered)</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
|
|
@ -539,23 +535,31 @@ $ayushmanChecklistItems = $ayushman_checklist_items ?? [];
|
|||
});
|
||||
}
|
||||
|
||||
function ipdMedicalBillToggleType(checkbox, invoiceId) {
|
||||
function ipdPackageMedicalBillChangeType(select, invoiceId) {
|
||||
var val = parseInt(select.value, 10);
|
||||
var payload = {};
|
||||
payload[ipdPackageCsrfName] = ipdPackageCsrfHash;
|
||||
payload.ipd_id = ipdPackageIpdId;
|
||||
payload.ipd_credit_type = checkbox.checked ? 1 : 0;
|
||||
payload.ipd_credit_type = val;
|
||||
select.disabled = true;
|
||||
|
||||
$.post(ipdMedicalBillCreditTypeUrlBase + '/' + invoiceId, payload, function(resp) {
|
||||
if (!resp || !resp.ok) {
|
||||
checkbox.checked = !checkbox.checked;
|
||||
select.value = val === 1 ? '0' : '1';
|
||||
alert((resp && resp.error) ? resp.error : 'Unable to update pharmacy bill option.');
|
||||
return;
|
||||
}
|
||||
// Update dashboard summary cards if function exists
|
||||
if (resp.summary && typeof ipdMedicalBillsUpdateDashboardCards === 'function') {
|
||||
ipdMedicalBillsUpdateDashboardCards(resp.summary);
|
||||
}
|
||||
ipdPackageReload();
|
||||
}).fail(function(xhr) {
|
||||
checkbox.checked = !checkbox.checked;
|
||||
select.value = val === 1 ? '0' : '1';
|
||||
var msg = (xhr && xhr.responseJSON && xhr.responseJSON.error) ? xhr.responseJSON.error : 'Unable to update pharmacy bill option.';
|
||||
alert(msg);
|
||||
}).always(function() {
|
||||
select.disabled = false;
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -36,18 +36,66 @@
|
|||
</div>
|
||||
|
||||
<?php if (empty($isFinalized)): ?>
|
||||
<form method="post" action="<?= base_url('Medical/go_final') ?>" class="row g-2 mb-3">
|
||||
<?= csrf_field() ?>
|
||||
<input type="hidden" name="inv_id" value="<?= (int)($invoice->id ?? 0) ?>">
|
||||
<div class="col-md-2">
|
||||
<label class="form-label form-label-sm mb-1">Doctor ID</label>
|
||||
<input type="number" name="doc_id" value="<?= esc($invoice->doc_id ?? 0) ?>" class="form-control form-control-sm" min="0" step="1">
|
||||
<?php
|
||||
$currentDocId = (int) ($invoice->doc_id ?? 0);
|
||||
$currentDocName = trim((string) ($invoice->doc_name ?? ''));
|
||||
|
||||
$docFound = false;
|
||||
if (!empty($docList) && $currentDocId > 0) {
|
||||
foreach ($docList as $d) {
|
||||
if ((int) ($d->id ?? 0) === $currentDocId) {
|
||||
$docFound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// If not found in hospital doctors list but doctor name exists, treat as outside doctor
|
||||
$isOtherDoc = (!$docFound && ($currentDocId === 0 || $currentDocId === 999999) && $currentDocName !== '');
|
||||
?>
|
||||
<div class="row g-2 mb-3 align-items-end" id="invoice-doctor-panel">
|
||||
<input type="hidden" name="doc_id" id="hidden_doc_id" value="<?= $currentDocId ?>">
|
||||
<input type="hidden" name="doc_name" id="hidden_doc_name" value="<?= esc($currentDocName) ?>">
|
||||
|
||||
<div class="col-md-4 col-sm-6">
|
||||
<label class="form-label form-label-sm mb-1 fw-bold text-secondary">
|
||||
<i class="bi bi-person-badge"></i> Doctor
|
||||
</label>
|
||||
<select id="invoice_doc_select" class="form-select form-select-sm">
|
||||
<option value="">-- Select Doctor --</option>
|
||||
<?php foreach (($docList ?? []) as $doc): ?>
|
||||
<?php
|
||||
$dId = (int) ($doc->id ?? 0);
|
||||
$dName = trim((string) ($doc->p_fname ?? ('Doctor #' . $dId)));
|
||||
$isSelected = (!$isOtherDoc && $currentDocId === $dId);
|
||||
?>
|
||||
<option value="<?= $dId ?>" data-name="<?= esc($dName) ?>" <?= $isSelected ? 'selected' : '' ?>>
|
||||
<?= esc($dName) ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
<option value="other" <?= $isOtherDoc ? 'selected' : '' ?>>Outside Doctor / Other</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label class="form-label form-label-sm mb-1">Doctor Name</label>
|
||||
<input type="text" name="doc_name" value="<?= esc($invoice->doc_name ?? '') ?>" class="form-control form-control-sm">
|
||||
|
||||
<div class="col-md-4 col-sm-6" id="wrap_doc_other" style="<?= $isOtherDoc ? '' : 'display: none;' ?>">
|
||||
<label class="form-label form-label-sm mb-1 fw-bold text-secondary">
|
||||
<i class="bi bi-pencil-square"></i> Outside Doctor Name
|
||||
</label>
|
||||
<div class="input-group input-group-sm">
|
||||
<input type="text" id="input_doc_other" class="form-control form-control-sm" placeholder="Enter Doctor Name" value="<?= $isOtherDoc ? esc($currentDocName) : '' ?>">
|
||||
<button type="button" id="btn_save_doc_other" class="btn btn-outline-primary btn-sm" title="Save Doctor">Save</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div class="col-auto">
|
||||
<span id="doc_save_indicator" class="badge bg-success-subtle text-success border border-success-subtle py-1 px-2 d-none">
|
||||
<i class="bi bi-check-circle-fill"></i> Saved
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<div class="small mb-2 text-muted">
|
||||
<strong>Doctor :</strong> <span class="badge bg-light text-dark border"><?= esc($invoice->doc_name ?: 'Not Specified') ?></span>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div id="show_item_list" class="table-responsive mb-3">
|
||||
|
|
@ -210,11 +258,11 @@
|
|||
</div>
|
||||
<div class="col-md-4">
|
||||
<label class="form-label form-label-sm mb-1">Qty</label>
|
||||
<input class="form-control form-control-sm" name="qty" id="input_product_qty" placeholder="Qty Like No. of Tab." type="number" min="0" step="1" value="0" autocomplete="off">
|
||||
<input class="form-control form-control-sm" name="qty" id="input_product_qty" placeholder="Qty" type="number" min="1" step="1" value="" autocomplete="off" required>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label class="form-label form-label-sm mb-1">Disc %</label>
|
||||
<input class="form-control form-control-sm" name="disc_per" id="input_disc" placeholder="Discount %" type="number" min="0" max="100" $11" value="0" autocomplete="off">
|
||||
<input class="form-control form-control-sm" name="disc_per" id="input_disc" placeholder="Discount %" type="number" min="0" max="100" value="0" autocomplete="off">
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<button class="btn btn-sm btn-primary w-100" type="submit" id="additem" disabled>Add</button>
|
||||
|
|
@ -250,7 +298,7 @@
|
|||
<input type="hidden" name="inv_id" value="<?= (int)($invoice->id ?? 0) ?>">
|
||||
<input type="hidden" name="stock_id" value="<?= (int)($row->stock_id ?? 0) ?>">
|
||||
<input type="number" name="qty" value="1" min="1" step="1" class="form-control form-control-sm" style="width:80px;">
|
||||
<input type="number" name="disc_per" value="0" min="0" max="100" $11" class="form-control form-control-sm" style="width:90px;" title="Disc %">
|
||||
<input type="number" name="disc_per" value="0" min="0" max="100" class="form-control form-control-sm" style="width:90px;" title="Disc %">
|
||||
<button class="btn btn-sm btn-primary" type="submit">Add</button>
|
||||
</form>
|
||||
</td>
|
||||
|
|
@ -545,6 +593,7 @@
|
|||
addBtn.disabled = !item.l_ss_no;
|
||||
}
|
||||
if (qtyInput) {
|
||||
qtyInput.value = '';
|
||||
qtyInput.focus();
|
||||
}
|
||||
}
|
||||
|
|
@ -737,11 +786,13 @@
|
|||
}
|
||||
|
||||
var qtyInput = document.getElementById('input_product_qty');
|
||||
var qtyValue = parseFloat((qtyInput && qtyInput.value) ? qtyInput.value : '0');
|
||||
if (!qtyValue || qtyValue <= 0) {
|
||||
var rawQty = (qtyInput && qtyInput.value) ? qtyInput.value.trim() : '';
|
||||
var qtyValue = parseFloat(rawQty || '0');
|
||||
if (!rawQty || isNaN(qtyValue) || qtyValue <= 0) {
|
||||
event.preventDefault();
|
||||
alert('Enter valid Qty greater than 0.');
|
||||
alert('Please enter valid Qty greater than 0.');
|
||||
if (qtyInput) {
|
||||
qtyInput.value = '';
|
||||
qtyInput.focus();
|
||||
}
|
||||
return;
|
||||
|
|
@ -774,5 +825,129 @@
|
|||
if (addBtn) {
|
||||
addBtn.accessKey = 'i';
|
||||
}
|
||||
|
||||
// Doctor Select & Outside Doctor AJAX save handler
|
||||
(function () {
|
||||
var docSelect = document.getElementById('invoice_doc_select');
|
||||
var wrapOther = document.getElementById('wrap_doc_other');
|
||||
var inputOther = document.getElementById('input_doc_other');
|
||||
var btnSaveOther = document.getElementById('btn_save_doc_other');
|
||||
var hiddenDocId = document.getElementById('hidden_doc_id');
|
||||
var hiddenDocName = document.getElementById('hidden_doc_name');
|
||||
var saveIndicator = document.getElementById('doc_save_indicator');
|
||||
var invId = <?= (int) ($invoice->id ?? 0) ?>;
|
||||
|
||||
if (!docSelect || !invId) {
|
||||
return;
|
||||
}
|
||||
|
||||
function showSaveStatus(text, isError) {
|
||||
if (!saveIndicator) return;
|
||||
saveIndicator.classList.remove('d-none', 'bg-success-subtle', 'text-success', 'border-success-subtle', 'bg-danger-subtle', 'text-danger', 'border-danger-subtle');
|
||||
if (isError) {
|
||||
saveIndicator.classList.add('bg-danger-subtle', 'text-danger', 'border-danger-subtle');
|
||||
saveIndicator.innerHTML = '<i class="bi bi-exclamation-circle-fill"></i> ' + (text || 'Error');
|
||||
} else {
|
||||
saveIndicator.classList.add('bg-success-subtle', 'text-success', 'border-success-subtle');
|
||||
saveIndicator.innerHTML = '<i class="bi bi-check-circle-fill"></i> ' + (text || 'Saved');
|
||||
}
|
||||
setTimeout(function () {
|
||||
if (saveIndicator) saveIndicator.classList.add('d-none');
|
||||
}, 2500);
|
||||
}
|
||||
|
||||
function updateDoctorAjax(docId, docName, onSuccess) {
|
||||
var csrfInput = document.querySelector('input[name="<?= csrf_token() ?>"]');
|
||||
var csrfHash = csrfInput ? csrfInput.value : '';
|
||||
|
||||
var body = new URLSearchParams();
|
||||
body.append('<?= csrf_token() ?>', csrfHash);
|
||||
body.append('med_invoice_id', String(invId));
|
||||
body.append('doc_id', String(docId));
|
||||
body.append('doc_name', String(docName || ''));
|
||||
|
||||
fetch('<?= base_url('Medical/update_doctor') ?>', {
|
||||
method: 'POST',
|
||||
body: body,
|
||||
headers: {
|
||||
'X-Requested-With': 'XMLHttpRequest',
|
||||
'Content-Type': 'application/x-www-form-urlencoded'
|
||||
}
|
||||
}).then(function (res) {
|
||||
return res.json();
|
||||
}).then(function (res) {
|
||||
if (res && res.csrfHash) {
|
||||
document.querySelectorAll('input[name="<?= csrf_token() ?>"]').forEach(function (inp) {
|
||||
inp.value = res.csrfHash;
|
||||
});
|
||||
}
|
||||
if (res && res.status) {
|
||||
if (hiddenDocId) hiddenDocId.value = docId;
|
||||
if (hiddenDocName) hiddenDocName.value = docName;
|
||||
showSaveStatus(docId === 0 ? 'Outside Doctor Saved' : 'Doctor Updated', false);
|
||||
if (typeof onSuccess === 'function') onSuccess();
|
||||
} else {
|
||||
showSaveStatus((res && res.remark) ? res.remark : 'Save Failed', true);
|
||||
}
|
||||
}).catch(function () {
|
||||
showSaveStatus('Network Error', true);
|
||||
});
|
||||
}
|
||||
|
||||
docSelect.addEventListener('change', function () {
|
||||
var val = docSelect.value;
|
||||
if (val === 'other') {
|
||||
if (wrapOther) {
|
||||
wrapOther.style.display = '';
|
||||
}
|
||||
if (inputOther) {
|
||||
inputOther.focus();
|
||||
if (inputOther.value.trim() !== '') {
|
||||
updateDoctorAjax(0, inputOther.value.trim());
|
||||
}
|
||||
}
|
||||
} else if (val === '') {
|
||||
if (wrapOther) {
|
||||
wrapOther.style.display = 'none';
|
||||
}
|
||||
updateDoctorAjax(0, '');
|
||||
} else {
|
||||
if (wrapOther) {
|
||||
wrapOther.style.display = 'none';
|
||||
}
|
||||
var opt = docSelect.options[docSelect.selectedIndex];
|
||||
var docName = opt ? (opt.getAttribute('data-name') || opt.text.trim()) : '';
|
||||
updateDoctorAjax(parseInt(val, 10), docName);
|
||||
}
|
||||
});
|
||||
|
||||
function saveOtherDoctor() {
|
||||
if (!inputOther) return;
|
||||
var otherName = inputOther.value.trim();
|
||||
if (!otherName) {
|
||||
inputOther.focus();
|
||||
showSaveStatus('Enter Doctor Name', true);
|
||||
return;
|
||||
}
|
||||
updateDoctorAjax(0, otherName);
|
||||
}
|
||||
|
||||
if (btnSaveOther) {
|
||||
btnSaveOther.addEventListener('click', saveOtherDoctor);
|
||||
}
|
||||
if (inputOther) {
|
||||
inputOther.addEventListener('keydown', function (e) {
|
||||
if (e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
saveOtherDoctor();
|
||||
}
|
||||
});
|
||||
inputOther.addEventListener('blur', function () {
|
||||
if (docSelect.value === 'other' && inputOther.value.trim() !== '') {
|
||||
saveOtherDoctor();
|
||||
}
|
||||
});
|
||||
}
|
||||
})();
|
||||
})();
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -164,23 +164,51 @@
|
|||
</div>
|
||||
|
||||
<div class="card mb-0">
|
||||
<div class="card-body d-flex flex-wrap gap-2">
|
||||
<div class="flex-grow-1">
|
||||
<label class="form-label">Doctor</label>
|
||||
<select class="form-select" id="doc-select">
|
||||
<option value="">Select</option>
|
||||
<?php foreach (($docList ?? []) as $doc): ?>
|
||||
<option value="<?= esc($doc->id ?? '') ?>" data-name="<?= esc($doc->p_fname ?? '') ?>" <?= ((int)($invoice->doc_id ?? 0) === (int)($doc->id ?? 0)) ? 'selected' : '' ?>>
|
||||
<?= esc($doc->p_fname ?? ('Doctor #' . ($doc->id ?? ''))) ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
<input type="hidden" name="doc_id" id="doc_id" value="<?= (int)($invoice->doc_id ?? 0) ?>">
|
||||
<input type="hidden" name="doc_name" id="doc_name" value="<?= esc($invoice->doc_name ?? '') ?>">
|
||||
</div>
|
||||
<div class="d-flex align-items-end gap-2">
|
||||
<button type="submit" class="btn btn-success">Update Invoice Header</button>
|
||||
<a class="btn btn-outline-secondary" href="javascript:load_form_div('<?= base_url('Medical/invoice_edit/' . (int)($invoice->id ?? 0)) ?>','medical-main');">Cancel</a>
|
||||
<div class="card-body">
|
||||
<?php
|
||||
$masterDocId = (int) ($invoice->doc_id ?? 0);
|
||||
$masterDocName = trim((string) ($invoice->doc_name ?? ''));
|
||||
$masterDocFound = false;
|
||||
if (!empty($docList) && $masterDocId > 0) {
|
||||
foreach ($docList as $d) {
|
||||
if ((int) ($d->id ?? 0) === $masterDocId) {
|
||||
$masterDocFound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
$masterIsOtherDoc = (!$masterDocFound && ($masterDocId === 0 || $masterDocId === 999999) && $masterDocName !== '');
|
||||
?>
|
||||
<div class="row g-2 align-items-end">
|
||||
<div class="col-md-4">
|
||||
<label class="form-label">Doctor</label>
|
||||
<select class="form-select" id="doc-select">
|
||||
<option value="">-- Select Doctor --</option>
|
||||
<?php foreach (($docList ?? []) as $doc): ?>
|
||||
<?php
|
||||
$dId = (int) ($doc->id ?? 0);
|
||||
$dName = trim((string) ($doc->p_fname ?? ('Doctor #' . $dId)));
|
||||
$isSelected = (!$masterIsOtherDoc && $masterDocId === $dId);
|
||||
?>
|
||||
<option value="<?= $dId ?>" data-name="<?= esc($dName) ?>" <?= $isSelected ? 'selected' : '' ?>>
|
||||
<?= esc($dName) ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
<option value="other" <?= $masterIsOtherDoc ? 'selected' : '' ?>>Outside Doctor / Other</option>
|
||||
</select>
|
||||
<input type="hidden" name="doc_id" id="doc_id" value="<?= $masterDocId ?>">
|
||||
<input type="hidden" name="doc_name" id="doc_name" value="<?= esc($masterDocName) ?>">
|
||||
</div>
|
||||
|
||||
<div class="col-md-4" id="wrap_master_doc_other" style="<?= $masterIsOtherDoc ? '' : 'display: none;' ?>">
|
||||
<label class="form-label">Outside Doctor Name</label>
|
||||
<input type="text" class="form-control" id="master_doc_other_input" placeholder="Enter Doctor Name" value="<?= $masterIsOtherDoc ? esc($masterDocName) : '' ?>">
|
||||
</div>
|
||||
|
||||
<div class="col-md-4 d-flex align-items-end gap-2">
|
||||
<button type="submit" class="btn btn-success">Update Invoice Header</button>
|
||||
<a class="btn btn-outline-secondary" href="javascript:load_form_div('<?= base_url('Medical/invoice_edit/' . (int)($invoice->id ?? 0)) ?>','medical-main');">Cancel</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -419,20 +447,37 @@
|
|||
|
||||
(function () {
|
||||
var select = document.getElementById('doc-select');
|
||||
var wrapOther = document.getElementById('wrap_master_doc_other');
|
||||
var otherInput = document.getElementById('master_doc_other_input');
|
||||
var docIdInput = document.getElementById('doc_id');
|
||||
var docNameInput = document.getElementById('doc_name');
|
||||
if (!select) {
|
||||
return;
|
||||
}
|
||||
|
||||
select.addEventListener('change', function () {
|
||||
var option = select.options[select.selectedIndex];
|
||||
var docIdInput = document.getElementById('doc_id');
|
||||
var docNameInput = document.getElementById('doc_name');
|
||||
if (docIdInput) {
|
||||
docIdInput.value = option ? (option.value || 0) : 0;
|
||||
function syncDoctor() {
|
||||
var val = select.value;
|
||||
if (val === 'other') {
|
||||
if (wrapOther) wrapOther.style.display = '';
|
||||
if (docIdInput) docIdInput.value = 0;
|
||||
if (docNameInput && otherInput) docNameInput.value = otherInput.value.trim();
|
||||
if (otherInput) otherInput.focus();
|
||||
} else {
|
||||
if (wrapOther) wrapOther.style.display = 'none';
|
||||
var option = select.options[select.selectedIndex];
|
||||
if (docIdInput) docIdInput.value = option ? (option.value || 0) : 0;
|
||||
if (docNameInput) docNameInput.value = option ? (option.getAttribute('data-name') || '') : '';
|
||||
}
|
||||
if (docNameInput) {
|
||||
docNameInput.value = option ? (option.getAttribute('data-name') || '') : '';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
select.addEventListener('change', syncDoctor);
|
||||
if (otherInput) {
|
||||
otherInput.addEventListener('input', function () {
|
||||
if (select.value === 'other' && docNameInput) {
|
||||
docNameInput.value = otherInput.value.trim();
|
||||
if (docIdInput) docIdInput.value = 0;
|
||||
}
|
||||
});
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -96,9 +96,9 @@
|
|||
<?php
|
||||
$invoiceId = (int) ($row->id ?? 0);
|
||||
$ipdCredit = (int)($row->ipd_credit ?? 0);
|
||||
$groupInvoiceId = (int)($row->group_invoice_id ?? 0);
|
||||
$ipdCreditType = (int)($row->ipd_credit_type ?? 1);
|
||||
$creditText = ($ipdCredit > 0) ? 'Credit' : 'CASH';
|
||||
$packageText = ($ipdCredit > 0 && $groupInvoiceId > 0) ? 'Yes' : 'No';
|
||||
$packageText = ($ipdCredit > 0 && $ipdCreditType === 0) ? 'Yes' : 'No';
|
||||
$editMeta = $invoiceEditMeta[$invoiceId] ?? ['can_edit' => true, 'reason' => ''];
|
||||
$canEditRow = !empty($editMeta['can_edit']);
|
||||
$editReason = (string) ($editMeta['reason'] ?? 'Invoice is view-only.');
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user