Finance: separate OPD Consult and Doctor Charges payout screens; compact dashboard cards; add IPD payout tracking migration
This commit is contained in:
parent
54c8543902
commit
7ab37ad80d
|
|
@ -242,6 +242,7 @@ $routes->match(['get', 'post'], 'Medical/credit_payout_pool_datatable', 'Medical
|
|||
$routes->get('Medical/credit_payout_request', 'Medical::credit_payout_request');
|
||||
$routes->post('Medical/credit_payout_request_create', 'Medical::credit_payout_request_create');
|
||||
$routes->get('Medical/credit_payout_requests', 'Medical::credit_payout_requests');
|
||||
$routes->match(['get', 'post'], 'Medical/credit_payout_requests_datatable', 'Medical::credit_payout_requests_datatable');
|
||||
$routes->get('Medical/credit_payout_request_detail/(:num)', 'Medical::credit_payout_request_detail/$1');
|
||||
|
||||
$routes->get('Payment_Medical', 'Payment_Medical::index');
|
||||
|
|
@ -276,6 +277,12 @@ $routes->get('Finance/payout/opd-consult-drafts-table', 'Finance::payoutOpdConsu
|
|||
$routes->post('Finance/payout/opd-consult-draft-create', 'Finance::payoutOpdConsultDraftCreate');
|
||||
$routes->post('Finance/payout/opd-consult-draft-update', 'Finance::payoutOpdConsultDraftUpdate');
|
||||
$routes->post('Finance/payout/opd-consult-draft-delete', 'Finance::payoutOpdConsultDraftDelete');
|
||||
$routes->get('Finance/payout/doctor-charges', 'Finance::doctorChargesPayout');
|
||||
$routes->get('Finance/payout/doctor-charges-summary', 'Finance::doctorChargesPayoutSummary');
|
||||
$routes->get('Finance/payout/doctor-charges-drafts-table', 'Finance::doctorChargesPayoutDraftsTable');
|
||||
$routes->post('Finance/payout/doctor-charges-draft-create', 'Finance::doctorChargesPayoutDraftCreate');
|
||||
$routes->post('Finance/payout/doctor-charges-draft-update', 'Finance::doctorChargesPayoutDraftUpdate');
|
||||
$routes->post('Finance/payout/doctor-charges-draft-delete', 'Finance::doctorChargesPayoutDraftDelete');
|
||||
$routes->get('Finance/bank_audit_direct_payments_table', 'Finance::bankAuditDirectPaymentsTable');
|
||||
$routes->get('Finance/bank_audit_outgoing_payments_table', 'Finance::bankAuditOutgoingPaymentsTable');
|
||||
$routes->get('Finance/bank_statement_entries_table', 'Finance::bankStatementEntriesTable');
|
||||
|
|
|
|||
|
|
@ -1858,6 +1858,48 @@ class Finance extends BaseController
|
|||
]);
|
||||
}
|
||||
|
||||
public function doctorChargesPayout()
|
||||
{
|
||||
if (! $this->canFinanceAny([
|
||||
'finance.workflow.view',
|
||||
'finance.cash.billing.submit',
|
||||
'finance.cash.accounts.accept',
|
||||
'finance.cash.accounts.verify',
|
||||
'finance.bank.deposit.create',
|
||||
'finance.bank.audit',
|
||||
'finance.bank.statement.update',
|
||||
])) {
|
||||
return $this->response->setStatusCode(403)->setBody('Access denied');
|
||||
}
|
||||
|
||||
$doctorModel = new DoctorModel();
|
||||
$doctorRows = $doctorModel->getDoctors();
|
||||
usort($doctorRows, static function ($left, $right): int {
|
||||
$leftName = trim((string) (($left->p_title ?? '') . ' ' . ($left->p_fname ?? '')));
|
||||
$rightName = trim((string) (($right->p_title ?? '') . ' ' . ($right->p_fname ?? '')));
|
||||
|
||||
return strcasecmp($leftName, $rightName);
|
||||
});
|
||||
|
||||
$doctorOptions = [];
|
||||
foreach ($doctorRows as $row) {
|
||||
$isActive = property_exists($row, 'active') ? (int) ($row->active ?? 0) : 1;
|
||||
if ($isActive !== 1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$doctorOptions[] = [
|
||||
'id' => (int) ($row->id ?? 0),
|
||||
'name' => trim((string) (($row->p_title ?? '') . ' ' . ($row->p_fname ?? ''))),
|
||||
];
|
||||
}
|
||||
|
||||
return view('finance/doctor_charges_payout', [
|
||||
'doctor_options' => $doctorOptions,
|
||||
'state_unit_options' => $this->fetchOpdStateUnitOptions(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function payoutOpdConsultSummary()
|
||||
{
|
||||
if (! $this->canFinanceAny([
|
||||
|
|
@ -1876,6 +1918,7 @@ class Finance extends BaseController
|
|||
$toDate = $this->normalizeDateInput((string) ($this->request->getGet('to_date') ?? ''));
|
||||
$doctorId = (int) ($this->request->getGet('doctor_id') ?? 0);
|
||||
$stateUnit = trim((string) ($this->request->getGet('state_unit') ?? ''));
|
||||
$payoutSource = $this->normalizePayoutSource((string) ($this->request->getGet('payout_source') ?? 'consultation'));
|
||||
|
||||
if ($fromDate === '') {
|
||||
$fromDate = date('Y-m-01');
|
||||
|
|
@ -1887,7 +1930,40 @@ class Finance extends BaseController
|
|||
[$fromDate, $toDate] = [$toDate, $fromDate];
|
||||
}
|
||||
|
||||
return view('finance/partials/payout_opd_consult_summary', $this->buildOpdConsultPayoutSummaryData($fromDate, $toDate, $doctorId, $stateUnit));
|
||||
return view('finance/partials/payout_opd_consult_summary', $this->buildPayoutSummaryDataForSource($payoutSource, $fromDate, $toDate, $doctorId, $stateUnit));
|
||||
}
|
||||
|
||||
public function doctorChargesPayoutSummary()
|
||||
{
|
||||
if (! $this->canFinanceAny([
|
||||
'finance.workflow.view',
|
||||
'finance.cash.billing.submit',
|
||||
'finance.cash.accounts.accept',
|
||||
'finance.cash.accounts.verify',
|
||||
'finance.bank.deposit.create',
|
||||
'finance.bank.audit',
|
||||
'finance.bank.statement.update',
|
||||
])) {
|
||||
return $this->response->setStatusCode(403)->setBody('Access denied');
|
||||
}
|
||||
|
||||
$fromDate = $this->normalizeDateInput((string) ($this->request->getGet('from_date') ?? ''));
|
||||
$toDate = $this->normalizeDateInput((string) ($this->request->getGet('to_date') ?? ''));
|
||||
$doctorId = (int) ($this->request->getGet('doctor_id') ?? 0);
|
||||
$stateUnit = trim((string) ($this->request->getGet('state_unit') ?? ''));
|
||||
$payoutSource = $this->normalizePayoutSource((string) ($this->request->getGet('payout_source') ?? 'pathology'));
|
||||
|
||||
if ($fromDate === '') {
|
||||
$fromDate = date('Y-m-01');
|
||||
}
|
||||
if ($toDate === '') {
|
||||
$toDate = date('Y-m-d');
|
||||
}
|
||||
if ($fromDate > $toDate) {
|
||||
[$fromDate, $toDate] = [$toDate, $fromDate];
|
||||
}
|
||||
|
||||
return view('finance/partials/doctor_charges_payout_summary', $this->buildPayoutSummaryDataForSource($payoutSource, $fromDate, $toDate, $doctorId, $stateUnit));
|
||||
}
|
||||
|
||||
public function payoutOpdConsultDraftsTable()
|
||||
|
|
@ -1909,6 +1985,8 @@ class Finance extends BaseController
|
|||
$toDate = $this->normalizeDateInput((string) ($this->request->getGet('to_date') ?? ''));
|
||||
$doctorId = (int) ($this->request->getGet('doctor_id') ?? 0);
|
||||
$stateUnit = trim((string) ($this->request->getGet('state_unit') ?? ''));
|
||||
$payoutSource = $this->normalizePayoutSource((string) ($this->request->getGet('payout_source') ?? 'consultation'));
|
||||
$payoutType = $this->payoutTypeFromSource($payoutSource);
|
||||
|
||||
if ($fromDate === '') {
|
||||
$fromDate = date('Y-m-01');
|
||||
|
|
@ -1923,7 +2001,7 @@ class Finance extends BaseController
|
|||
$builder = $this->doctorPayoutModel
|
||||
->select('finance_doctor_payouts.*, finance_doctor_agreements.doctor_code, finance_doctor_agreements.doctor_name')
|
||||
->join('finance_doctor_agreements', 'finance_doctor_agreements.id = finance_doctor_payouts.doctor_id', 'left')
|
||||
->where('finance_doctor_payouts.payout_type', 'consultation')
|
||||
->where('finance_doctor_payouts.payout_type', $payoutType)
|
||||
->where('finance_doctor_payouts.payout_date >=', $fromDate)
|
||||
->where('finance_doctor_payouts.payout_date <=', $toDate)
|
||||
->orderBy('finance_doctor_payouts.id', 'DESC');
|
||||
|
|
@ -1943,6 +2021,68 @@ class Finance extends BaseController
|
|||
|
||||
return view('finance/partials/payout_opd_consult_drafts_table', [
|
||||
'rows' => $builder->findAll(30),
|
||||
'source_type' => $payoutSource,
|
||||
'source_label' => $this->payoutSourceLabel($payoutSource),
|
||||
]);
|
||||
}
|
||||
|
||||
public function doctorChargesPayoutDraftsTable()
|
||||
{
|
||||
if (! $this->canFinanceAny([
|
||||
'finance.workflow.view',
|
||||
'finance.cash.billing.submit',
|
||||
'finance.cash.accounts.accept',
|
||||
'finance.cash.accounts.verify',
|
||||
'finance.bank.deposit.create',
|
||||
'finance.bank.audit',
|
||||
'finance.bank.statement.update',
|
||||
'finance.doctor_payout.manage',
|
||||
])) {
|
||||
return $this->response->setStatusCode(403)->setBody('Access denied');
|
||||
}
|
||||
|
||||
$fromDate = $this->normalizeDateInput((string) ($this->request->getGet('from_date') ?? ''));
|
||||
$toDate = $this->normalizeDateInput((string) ($this->request->getGet('to_date') ?? ''));
|
||||
$doctorId = (int) ($this->request->getGet('doctor_id') ?? 0);
|
||||
$stateUnit = trim((string) ($this->request->getGet('state_unit') ?? ''));
|
||||
$payoutSource = $this->normalizePayoutSource((string) ($this->request->getGet('payout_source') ?? 'pathology'));
|
||||
$payoutType = $this->payoutTypeFromSource($payoutSource);
|
||||
|
||||
if ($fromDate === '') {
|
||||
$fromDate = date('Y-m-01');
|
||||
}
|
||||
if ($toDate === '') {
|
||||
$toDate = date('Y-m-d');
|
||||
}
|
||||
if ($fromDate > $toDate) {
|
||||
[$fromDate, $toDate] = [$toDate, $fromDate];
|
||||
}
|
||||
|
||||
$builder = $this->doctorPayoutModel
|
||||
->select('finance_doctor_payouts.*, finance_doctor_agreements.doctor_code, finance_doctor_agreements.doctor_name')
|
||||
->join('finance_doctor_agreements', 'finance_doctor_agreements.id = finance_doctor_payouts.doctor_id', 'left')
|
||||
->where('finance_doctor_payouts.payout_type', $payoutType)
|
||||
->where('finance_doctor_payouts.payout_date >=', $fromDate)
|
||||
->where('finance_doctor_payouts.payout_date <=', $toDate)
|
||||
->orderBy('finance_doctor_payouts.id', 'DESC');
|
||||
|
||||
if ($doctorId > 0) {
|
||||
$agreementIds = $this->findDoctorAgreementIdsFromMaster($doctorId);
|
||||
if (! empty($agreementIds)) {
|
||||
$builder->whereIn('finance_doctor_payouts.doctor_id', $agreementIds);
|
||||
} else {
|
||||
$builder->where('1 = 0', null, false);
|
||||
}
|
||||
}
|
||||
|
||||
if ($stateUnit !== '') {
|
||||
$builder->like('finance_doctor_payouts.case_reference', '-U' . $this->normalizeStateUnitToken($stateUnit), 'both');
|
||||
}
|
||||
|
||||
return view('finance/partials/doctor_charges_payout_drafts_table', [
|
||||
'rows' => $builder->findAll(30),
|
||||
'source_type' => $payoutSource,
|
||||
'source_label' => $this->payoutSourceLabel($payoutSource),
|
||||
]);
|
||||
}
|
||||
|
||||
|
|
@ -1969,12 +2109,28 @@ class Finance extends BaseController
|
|||
$fromDate = $this->normalizeDateInput((string) ($this->request->getPost('from_date') ?? ''));
|
||||
$toDate = $this->normalizeDateInput((string) ($this->request->getPost('to_date') ?? ''));
|
||||
$stateUnit = trim((string) ($this->request->getPost('state_unit') ?? ''));
|
||||
$payoutSource = $this->normalizePayoutSource((string) ($this->request->getPost('payout_source') ?? 'consultation'));
|
||||
$baseAmount = (float) ($this->request->getPost('base_amount') ?? 0);
|
||||
$doctorShare = (float) ($this->request->getPost('doctor_share') ?? 75);
|
||||
$hospitalShare = (float) ($this->request->getPost('hospital_share') ?? 25);
|
||||
$deductions = (float) ($this->request->getPost('deductions') ?? 0);
|
||||
$adjustments = (float) ($this->request->getPost('adjustments') ?? 0);
|
||||
|
||||
if ($payoutSource !== 'consultation') {
|
||||
return $this->createChargeInvoicePayoutDraft(
|
||||
$payoutSource,
|
||||
$doctorId,
|
||||
$fromDate,
|
||||
$toDate,
|
||||
$stateUnit,
|
||||
$baseAmount,
|
||||
$doctorShare,
|
||||
$hospitalShare,
|
||||
$deductions,
|
||||
$adjustments
|
||||
);
|
||||
}
|
||||
|
||||
if ($doctorId <= 0) {
|
||||
return $this->response->setStatusCode(422)->setJSON(['status' => 0, 'message' => 'Please select a doctor before creating payout draft.']);
|
||||
}
|
||||
|
|
@ -2019,7 +2175,7 @@ class Finance extends BaseController
|
|||
$caseReference .= '-U' . $this->normalizeStateUnitToken($stateUnit);
|
||||
}
|
||||
|
||||
$remarks = 'OPD payout draft from OPD Consult Payout | Completed=' . $completedOpd
|
||||
$remarks = 'OPD payout draft from Hospital Doctor Payout | Completed=' . $completedOpd
|
||||
. ', Base=' . number_format($baseAmount, 2, '.', '')
|
||||
. ', Doc%=' . number_format($doctorShare, 2, '.', '')
|
||||
. ', Hosp%=' . number_format($hospitalShare, 2, '.', '')
|
||||
|
|
@ -2077,6 +2233,11 @@ class Finance extends BaseController
|
|||
]);
|
||||
}
|
||||
|
||||
public function doctorChargesPayoutDraftCreate()
|
||||
{
|
||||
return $this->payoutOpdConsultDraftCreate();
|
||||
}
|
||||
|
||||
public function payoutOpdConsultDraftUpdate()
|
||||
{
|
||||
if (! $this->canFinanceAny([
|
||||
|
|
@ -2133,6 +2294,11 @@ class Finance extends BaseController
|
|||
]);
|
||||
}
|
||||
|
||||
public function doctorChargesPayoutDraftUpdate()
|
||||
{
|
||||
return $this->payoutOpdConsultDraftUpdate();
|
||||
}
|
||||
|
||||
public function payoutOpdConsultDraftDelete()
|
||||
{
|
||||
if (! $this->canFinanceAny([
|
||||
|
|
@ -2164,20 +2330,39 @@ class Finance extends BaseController
|
|||
|
||||
$this->db->transStart();
|
||||
|
||||
if (method_exists($this->db, 'tableExists') && $this->db->tableExists('opd_master') && $this->tableHasField('opd_master', 'payout_draft_id')) {
|
||||
$clearData = [
|
||||
'payout_draft_id' => null,
|
||||
];
|
||||
if ($this->tableHasField('opd_master', 'payout_calculated_at')) {
|
||||
$clearData['payout_calculated_at'] = null;
|
||||
}
|
||||
if ($this->tableHasField('opd_master', 'payout_calculated_by')) {
|
||||
$clearData['payout_calculated_by'] = null;
|
||||
}
|
||||
$payoutType = strtolower(trim((string) ($row['payout_type'] ?? 'consultation')));
|
||||
if ($payoutType === 'consultation') {
|
||||
if (method_exists($this->db, 'tableExists') && $this->db->tableExists('opd_master') && $this->tableHasField('opd_master', 'payout_draft_id')) {
|
||||
$clearData = [
|
||||
'payout_draft_id' => null,
|
||||
];
|
||||
if ($this->tableHasField('opd_master', 'payout_calculated_at')) {
|
||||
$clearData['payout_calculated_at'] = null;
|
||||
}
|
||||
if ($this->tableHasField('opd_master', 'payout_calculated_by')) {
|
||||
$clearData['payout_calculated_by'] = null;
|
||||
}
|
||||
|
||||
$this->db->table('opd_master')
|
||||
->where('payout_draft_id', $payoutId)
|
||||
->update($clearData);
|
||||
$this->db->table('opd_master')
|
||||
->where('payout_draft_id', $payoutId)
|
||||
->update($clearData);
|
||||
}
|
||||
} else {
|
||||
if (method_exists($this->db, 'tableExists') && $this->db->tableExists('invoice_item') && $this->tableHasField('invoice_item', 'payout_draft_id')) {
|
||||
$clearData = [
|
||||
'payout_draft_id' => null,
|
||||
];
|
||||
if ($this->tableHasField('invoice_item', 'payout_calculated_at')) {
|
||||
$clearData['payout_calculated_at'] = null;
|
||||
}
|
||||
if ($this->tableHasField('invoice_item', 'payout_calculated_by')) {
|
||||
$clearData['payout_calculated_by'] = null;
|
||||
}
|
||||
|
||||
$this->db->table('invoice_item')
|
||||
->where('payout_draft_id', $payoutId)
|
||||
->update($clearData);
|
||||
}
|
||||
}
|
||||
|
||||
$this->doctorPayoutModel->delete($payoutId);
|
||||
|
|
@ -2190,10 +2375,15 @@ class Finance extends BaseController
|
|||
|
||||
return $this->response->setJSON([
|
||||
'status' => 1,
|
||||
'message' => 'Payout draft deleted and linked OPD records unlocked for recalculation.',
|
||||
'message' => 'Payout draft deleted and linked records unlocked for recalculation.',
|
||||
]);
|
||||
}
|
||||
|
||||
public function doctorChargesPayoutDraftDelete()
|
||||
{
|
||||
return $this->payoutOpdConsultDraftDelete();
|
||||
}
|
||||
|
||||
public function phase2()
|
||||
{
|
||||
if (! $this->canFinance('finance.access')) {
|
||||
|
|
@ -4517,6 +4707,567 @@ class Finance extends BaseController
|
|||
];
|
||||
}
|
||||
|
||||
private function normalizePayoutSource(string $source): string
|
||||
{
|
||||
$source = strtolower(trim($source));
|
||||
$allowed = ['consultation', 'pathology', 'radiology', 'other_items'];
|
||||
|
||||
return in_array($source, $allowed, true) ? $source : 'consultation';
|
||||
}
|
||||
|
||||
private function payoutTypeFromSource(string $source): string
|
||||
{
|
||||
return match ($this->normalizePayoutSource($source)) {
|
||||
'pathology' => 'pathology',
|
||||
'radiology' => 'radiology',
|
||||
'other_items' => 'other_items',
|
||||
default => 'consultation',
|
||||
};
|
||||
}
|
||||
|
||||
private function payoutSourceLabel(string $source): string
|
||||
{
|
||||
return match ($this->normalizePayoutSource($source)) {
|
||||
'pathology' => 'Pathology Charges',
|
||||
'radiology' => 'Radiology Charges',
|
||||
'other_items' => 'Other Charge Items',
|
||||
default => 'OPD Consultation',
|
||||
};
|
||||
}
|
||||
|
||||
private function buildPayoutSummaryDataForSource(string $source, string $fromDate, string $toDate, int $doctorId = 0, string $stateUnit = ''): array
|
||||
{
|
||||
$source = $this->normalizePayoutSource($source);
|
||||
if ($source === 'consultation') {
|
||||
$payload = $this->buildOpdConsultPayoutSummaryData($fromDate, $toDate, $doctorId, $stateUnit);
|
||||
$payload['source_type'] = $source;
|
||||
$payload['source_label'] = $this->payoutSourceLabel($source);
|
||||
$payload['scope_label'] = 'Completed OPDs';
|
||||
$payload['unit_label'] = 'OPDs';
|
||||
|
||||
return $payload;
|
||||
}
|
||||
|
||||
$payload = $this->buildChargeInvoicePayoutSummaryData($source, $fromDate, $toDate, $doctorId, $stateUnit);
|
||||
$payload['source_type'] = $source;
|
||||
$payload['source_label'] = $this->payoutSourceLabel($source);
|
||||
$payload['scope_label'] = 'Eligible Charge Invoices';
|
||||
$payload['unit_label'] = 'Invoices';
|
||||
|
||||
return $payload;
|
||||
}
|
||||
|
||||
private function resolveInvoiceDoctorColumn(): ?string
|
||||
{
|
||||
$candidates = ['doctor_id', 'doc_id', 'dr_id'];
|
||||
foreach ($candidates as $column) {
|
||||
if ($this->tableHasField('invoice_master', $column)) {
|
||||
return $column;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private function resolveInvoiceStateUnitColumn(): ?string
|
||||
{
|
||||
$candidates = ['unit_name', 'unit', 'state_name', 'state', 'branch_name', 'branch', 'center_name', 'location', 'city'];
|
||||
foreach ($candidates as $column) {
|
||||
if ($this->tableHasField('invoice_master', $column)) {
|
||||
return $column;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private function applyChargeCategoryFilter($builder, string $source, string $itemTypeColumn): void
|
||||
{
|
||||
$source = $this->normalizePayoutSource($source);
|
||||
if (! $this->tableHasField('invoice_item', 'item_type')) {
|
||||
if ($source !== 'other_items') {
|
||||
$builder->where('1 = 0', null, false);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ($source === 'pathology') {
|
||||
$builder->whereIn($itemTypeColumn, [5, 30]);
|
||||
return;
|
||||
}
|
||||
|
||||
if ($source === 'radiology') {
|
||||
$builder->whereIn($itemTypeColumn, [1, 2, 3, 4, 6]);
|
||||
return;
|
||||
}
|
||||
|
||||
if ($source === 'other_items') {
|
||||
$builder->groupStart()
|
||||
->where($itemTypeColumn . ' IS NULL', null, false)
|
||||
->orWhereNotIn($itemTypeColumn, [1, 2, 3, 4, 5, 6, 30])
|
||||
->groupEnd();
|
||||
}
|
||||
}
|
||||
|
||||
private function fetchEligibleChargeItemsForPayout(string $source, string $fromDate, string $toDate, int $doctorId, string $stateUnit = ''): array
|
||||
{
|
||||
if (! method_exists($this->db, 'tableExists') || ! $this->db->tableExists('invoice_master') || ! $this->db->tableExists('invoice_item')) {
|
||||
return ['item_ids' => [], 'invoice_ids' => []];
|
||||
}
|
||||
|
||||
$doctorColumn = $this->resolveInvoiceDoctorColumn();
|
||||
if ($doctorColumn === null) {
|
||||
return ['item_ids' => [], 'invoice_ids' => []];
|
||||
}
|
||||
|
||||
$stateColumn = $this->resolveInvoiceStateUnitColumn();
|
||||
$itemAmountExpr = $this->tableHasField('invoice_item', 'item_amount')
|
||||
? 'COALESCE(ii.item_amount, 0)'
|
||||
: 'COALESCE(ii.item_rate,0) * COALESCE(ii.item_qty,0)';
|
||||
|
||||
$builder = $this->db->table('invoice_item ii')
|
||||
->select('ii.id AS item_id, ii.inv_master_id AS invoice_id')
|
||||
->join('invoice_master inv', 'inv.id = ii.inv_master_id', 'inner')
|
||||
->where('DATE(inv.inv_date) >=', $fromDate)
|
||||
->where('DATE(inv.inv_date) <=', $toDate)
|
||||
->where($itemAmountExpr . ' > 0', null, false);
|
||||
|
||||
if ($doctorId > 0) {
|
||||
$builder->where('inv.' . $doctorColumn, $doctorId);
|
||||
}
|
||||
|
||||
if ($stateUnit !== '' && $stateColumn !== null) {
|
||||
$builder->where('inv.' . $stateColumn, $stateUnit);
|
||||
}
|
||||
|
||||
$this->applyChargeCategoryFilter($builder, $source, 'ii.item_type');
|
||||
|
||||
if ($this->tableHasField('invoice_item', 'payout_draft_id')) {
|
||||
$builder->groupStart()
|
||||
->where('ii.payout_draft_id IS NULL', null, false)
|
||||
->orWhere('ii.payout_draft_id', 0)
|
||||
->groupEnd();
|
||||
}
|
||||
|
||||
$rows = $builder->get()->getResultArray();
|
||||
|
||||
$itemIds = [];
|
||||
$invoiceIds = [];
|
||||
foreach ($rows as $row) {
|
||||
$itemId = (int) ($row['item_id'] ?? 0);
|
||||
$invoiceId = (int) ($row['invoice_id'] ?? 0);
|
||||
if ($itemId > 0) {
|
||||
$itemIds[] = $itemId;
|
||||
}
|
||||
if ($invoiceId > 0) {
|
||||
$invoiceIds[] = $invoiceId;
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'item_ids' => array_values(array_unique($itemIds)),
|
||||
'invoice_ids' => array_values(array_unique($invoiceIds)),
|
||||
];
|
||||
}
|
||||
|
||||
private function buildChargeInvoicePayoutSummaryData(string $source, string $fromDate, string $toDate, int $doctorId = 0, string $stateUnit = ''): array
|
||||
{
|
||||
$db = \Config\Database::connect();
|
||||
$source = $this->normalizePayoutSource($source);
|
||||
$doctorColumn = $this->resolveInvoiceDoctorColumn();
|
||||
$stateColumn = $this->resolveInvoiceStateUnitColumn();
|
||||
|
||||
$summary = [
|
||||
'completed_opd' => 0,
|
||||
'routine_opd' => 0,
|
||||
'emergency_opd' => 0,
|
||||
'running_opd' => 0,
|
||||
'new_opd' => 0,
|
||||
'calculated_opd' => 0,
|
||||
'gross_amount' => 0.0,
|
||||
'org_credit_amount' => 0.0,
|
||||
'doctor_count' => 0,
|
||||
'approved_consents' => 0,
|
||||
'payout_count' => 0,
|
||||
'payout_amount' => 0.0,
|
||||
'cash_received' => 0.0,
|
||||
'bank_received' => 0.0,
|
||||
'total_received' => 0.0,
|
||||
'credit_amount' => 0.0,
|
||||
];
|
||||
|
||||
$doctorBreakdown = [];
|
||||
$doctorName = 'All Doctors';
|
||||
if ($doctorId > 0 && $db->tableExists('doctor_master')) {
|
||||
$doctorRow = $db->table('doctor_master')->select('p_title, p_fname')->where('id', $doctorId)->get()->getRow();
|
||||
if ($doctorRow) {
|
||||
$doctorName = trim((string) (($doctorRow->p_title ?? '') . ' ' . ($doctorRow->p_fname ?? '')));
|
||||
}
|
||||
}
|
||||
|
||||
if ($doctorColumn === null || ! $db->tableExists('invoice_master') || ! $db->tableExists('invoice_item')) {
|
||||
return [
|
||||
'summary' => $summary,
|
||||
'doctor_breakdown' => [],
|
||||
'from_date' => $fromDate,
|
||||
'to_date' => $toDate,
|
||||
'doctor_name' => $doctorName,
|
||||
'state_unit' => $stateUnit,
|
||||
'state_unit_label' => $stateColumn !== null ? ucwords(str_replace('_', ' ', $stateColumn)) : 'State/Unit',
|
||||
];
|
||||
}
|
||||
|
||||
$itemAmountExpr = $this->tableHasField('invoice_item', 'item_amount')
|
||||
? 'COALESCE(ii.item_amount, 0)'
|
||||
: 'COALESCE(ii.item_rate,0) * COALESCE(ii.item_qty,0)';
|
||||
|
||||
$breakdownBuilder = $db->table('invoice_item ii')
|
||||
->select('inv.' . $doctorColumn . ' AS doc_id', false)
|
||||
->select("COALESCE(NULLIF(TRIM(CONCAT(COALESCE(dm.p_title,''), ' ', COALESCE(dm.p_fname,''))), ''), CONCAT('Doctor #', inv." . $doctorColumn . ")) AS doctor_name", false)
|
||||
->select('COUNT(DISTINCT inv.id) AS completed_opd', false)
|
||||
->select('SUM(' . $itemAmountExpr . ') AS gross_amount', false)
|
||||
->join('invoice_master inv', 'inv.id = ii.inv_master_id', 'inner')
|
||||
->join('doctor_master dm', 'dm.id = inv.' . $doctorColumn, 'left')
|
||||
->where('DATE(inv.inv_date) >=', $fromDate)
|
||||
->where('DATE(inv.inv_date) <=', $toDate)
|
||||
->where($itemAmountExpr . ' > 0', null, false);
|
||||
|
||||
if ($doctorId > 0) {
|
||||
$breakdownBuilder->where('inv.' . $doctorColumn, $doctorId);
|
||||
}
|
||||
if ($stateUnit !== '' && $stateColumn !== null) {
|
||||
$breakdownBuilder->where('inv.' . $stateColumn, $stateUnit);
|
||||
}
|
||||
|
||||
$this->applyChargeCategoryFilter($breakdownBuilder, $source, 'ii.item_type');
|
||||
|
||||
if ($this->tableHasField('invoice_item', 'payout_draft_id')) {
|
||||
$breakdownBuilder->groupStart()
|
||||
->where('ii.payout_draft_id IS NULL', null, false)
|
||||
->orWhere('ii.payout_draft_id', 0)
|
||||
->groupEnd();
|
||||
}
|
||||
|
||||
$doctorBreakdown = $breakdownBuilder
|
||||
->groupBy('inv.' . $doctorColumn)
|
||||
->orderBy('doctor_name', 'ASC')
|
||||
->get()
|
||||
->getResultArray();
|
||||
|
||||
foreach ($doctorBreakdown as &$row) {
|
||||
$row['completed_opd'] = (int) ($row['completed_opd'] ?? 0);
|
||||
$row['routine_opd'] = 0;
|
||||
$row['emergency_opd'] = 0;
|
||||
$row['running_opd'] = 0;
|
||||
$row['new_opd'] = 0;
|
||||
$row['gross_amount'] = (float) ($row['gross_amount'] ?? 0);
|
||||
$row['org_credit_amount'] = 0.0;
|
||||
$row['cash_received'] = 0.0;
|
||||
$row['bank_received'] = 0.0;
|
||||
$row['total_received'] = 0.0;
|
||||
$row['credit_amount'] = 0.0;
|
||||
$row['payout_amount'] = 0.0;
|
||||
|
||||
$summary['completed_opd'] += $row['completed_opd'];
|
||||
$summary['gross_amount'] += $row['gross_amount'];
|
||||
}
|
||||
unset($row);
|
||||
|
||||
$summary['doctor_count'] = count($doctorBreakdown);
|
||||
|
||||
if ($this->tableHasField('invoice_item', 'payout_draft_id')) {
|
||||
$calcBuilder = $db->table('invoice_item ii')
|
||||
->select('COUNT(DISTINCT inv.id) AS calculated_opd', false)
|
||||
->join('invoice_master inv', 'inv.id = ii.inv_master_id', 'inner')
|
||||
->where('DATE(inv.inv_date) >=', $fromDate)
|
||||
->where('DATE(inv.inv_date) <=', $toDate)
|
||||
->where($itemAmountExpr . ' > 0', null, false)
|
||||
->where('ii.payout_draft_id >', 0);
|
||||
|
||||
if ($doctorId > 0) {
|
||||
$calcBuilder->where('inv.' . $doctorColumn, $doctorId);
|
||||
}
|
||||
if ($stateUnit !== '' && $stateColumn !== null) {
|
||||
$calcBuilder->where('inv.' . $stateColumn, $stateUnit);
|
||||
}
|
||||
$this->applyChargeCategoryFilter($calcBuilder, $source, 'ii.item_type');
|
||||
|
||||
$calcRow = $calcBuilder->get()->getRowArray();
|
||||
$summary['calculated_opd'] = (int) ($calcRow['calculated_opd'] ?? 0);
|
||||
}
|
||||
|
||||
$eligible = $this->fetchEligibleChargeItemsForPayout($source, $fromDate, $toDate, $doctorId, $stateUnit);
|
||||
if (! empty($eligible['invoice_ids'])) {
|
||||
$invoiceIds = $eligible['invoice_ids'];
|
||||
|
||||
$categoryRows = $db->table('invoice_item ii')
|
||||
->select('ii.inv_master_id AS invoice_id, SUM(' . $itemAmountExpr . ') AS category_total', false)
|
||||
->whereIn('ii.inv_master_id', $invoiceIds)
|
||||
->where($itemAmountExpr . ' > 0', null, false);
|
||||
$this->applyChargeCategoryFilter($categoryRows, $source, 'ii.item_type');
|
||||
$categoryRows = $categoryRows->groupBy('ii.inv_master_id')->get()->getResultArray();
|
||||
|
||||
$invoiceTotals = $db->table('invoice_item ii')
|
||||
->select('ii.inv_master_id AS invoice_id, SUM(' . $itemAmountExpr . ') AS invoice_total', false)
|
||||
->whereIn('ii.inv_master_id', $invoiceIds)
|
||||
->where($itemAmountExpr . ' > 0', null, false)
|
||||
->groupBy('ii.inv_master_id')
|
||||
->get()->getResultArray();
|
||||
|
||||
$payments = $db->table('payment_history p')
|
||||
->select('p.payof_id AS invoice_id', false)
|
||||
->select('SUM(CASE WHEN p.credit_debit = 0 THEN COALESCE(p.amount,0) ELSE 0 END) AS total_received', false)
|
||||
->select('SUM(CASE WHEN p.credit_debit = 0 AND p.payment_mode = 1 THEN COALESCE(p.amount,0) ELSE 0 END) AS cash_received', false)
|
||||
->select('SUM(CASE WHEN p.credit_debit = 0 AND p.payment_mode = 2 THEN COALESCE(p.amount,0) ELSE 0 END) AS bank_received', false)
|
||||
->where('p.payof_type', 2)
|
||||
->whereIn('p.payof_id', $invoiceIds)
|
||||
->where('DATE(p.payment_date) >=', $fromDate)
|
||||
->where('DATE(p.payment_date) <=', $toDate)
|
||||
->groupBy('p.payof_id')
|
||||
->get()->getResultArray();
|
||||
|
||||
$invoiceToDocRows = $db->table('invoice_master inv')
|
||||
->select('inv.id AS invoice_id, inv.' . $doctorColumn . ' AS doc_id', false)
|
||||
->whereIn('inv.id', $invoiceIds)
|
||||
->get()->getResultArray();
|
||||
|
||||
$categoryMap = [];
|
||||
foreach ($categoryRows as $row) {
|
||||
$categoryMap[(int) ($row['invoice_id'] ?? 0)] = (float) ($row['category_total'] ?? 0);
|
||||
}
|
||||
|
||||
$invoiceTotalMap = [];
|
||||
foreach ($invoiceTotals as $row) {
|
||||
$invoiceTotalMap[(int) ($row['invoice_id'] ?? 0)] = (float) ($row['invoice_total'] ?? 0);
|
||||
}
|
||||
|
||||
$paymentMap = [];
|
||||
foreach ($payments as $row) {
|
||||
$paymentMap[(int) ($row['invoice_id'] ?? 0)] = [
|
||||
'total_received' => (float) ($row['total_received'] ?? 0),
|
||||
'cash_received' => (float) ($row['cash_received'] ?? 0),
|
||||
'bank_received' => (float) ($row['bank_received'] ?? 0),
|
||||
];
|
||||
}
|
||||
|
||||
$invoiceDocMap = [];
|
||||
foreach ($invoiceToDocRows as $row) {
|
||||
$invoiceDocMap[(int) ($row['invoice_id'] ?? 0)] = (int) ($row['doc_id'] ?? 0);
|
||||
}
|
||||
|
||||
$perDoctorReceived = [];
|
||||
foreach ($invoiceIds as $invoiceId) {
|
||||
$invoiceId = (int) $invoiceId;
|
||||
$docId = (int) ($invoiceDocMap[$invoiceId] ?? 0);
|
||||
if ($docId <= 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$categoryTotal = (float) ($categoryMap[$invoiceId] ?? 0);
|
||||
$invoiceTotal = (float) ($invoiceTotalMap[$invoiceId] ?? 0);
|
||||
if ($categoryTotal <= 0 || $invoiceTotal <= 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$ratio = $categoryTotal / $invoiceTotal;
|
||||
$recv = $paymentMap[$invoiceId] ?? ['total_received' => 0.0, 'cash_received' => 0.0, 'bank_received' => 0.0];
|
||||
|
||||
$perDoctorReceived[$docId]['total_received'] = (float) ($perDoctorReceived[$docId]['total_received'] ?? 0) + ($recv['total_received'] * $ratio);
|
||||
$perDoctorReceived[$docId]['cash_received'] = (float) ($perDoctorReceived[$docId]['cash_received'] ?? 0) + ($recv['cash_received'] * $ratio);
|
||||
$perDoctorReceived[$docId]['bank_received'] = (float) ($perDoctorReceived[$docId]['bank_received'] ?? 0) + ($recv['bank_received'] * $ratio);
|
||||
}
|
||||
|
||||
foreach ($doctorBreakdown as &$row) {
|
||||
$docId = (int) ($row['doc_id'] ?? 0);
|
||||
$recv = $perDoctorReceived[$docId] ?? ['total_received' => 0.0, 'cash_received' => 0.0, 'bank_received' => 0.0];
|
||||
|
||||
$row['total_received'] = round((float) ($recv['total_received'] ?? 0), 2);
|
||||
$row['cash_received'] = round((float) ($recv['cash_received'] ?? 0), 2);
|
||||
$row['bank_received'] = round((float) ($recv['bank_received'] ?? 0), 2);
|
||||
$row['credit_amount'] = max(0, round((float) $row['gross_amount'] - (float) $row['total_received'], 2));
|
||||
|
||||
$summary['cash_received'] += (float) $row['cash_received'];
|
||||
$summary['bank_received'] += (float) $row['bank_received'];
|
||||
}
|
||||
unset($row);
|
||||
}
|
||||
|
||||
$summary['total_received'] = round((float) ($summary['cash_received'] + $summary['bank_received']), 2);
|
||||
$summary['credit_amount'] = max(0, round((float) $summary['gross_amount'] - (float) $summary['total_received'], 2));
|
||||
|
||||
if ($db->tableExists('finance_doctor_payouts')) {
|
||||
$payoutBuilder = $db->table('finance_doctor_payouts p')
|
||||
->select('COUNT(*) AS payout_count, SUM(COALESCE(p.approved_amount, p.calculated_amount, 0)) AS payout_amount', false)
|
||||
->where('p.payout_type', $this->payoutTypeFromSource($source))
|
||||
->where('p.payout_date >=', $fromDate)
|
||||
->where('p.payout_date <=', $toDate);
|
||||
|
||||
if ($doctorId > 0) {
|
||||
$agreementIds = $this->findDoctorAgreementIdsFromMaster($doctorId);
|
||||
if (! empty($agreementIds)) {
|
||||
$payoutBuilder->whereIn('p.doctor_id', $agreementIds);
|
||||
} else {
|
||||
$payoutBuilder->where('1 = 0', null, false);
|
||||
}
|
||||
}
|
||||
|
||||
$payoutRow = $payoutBuilder->get()->getRowArray();
|
||||
$summary['payout_count'] = (int) ($payoutRow['payout_count'] ?? 0);
|
||||
$summary['payout_amount'] = (float) ($payoutRow['payout_amount'] ?? 0);
|
||||
}
|
||||
|
||||
foreach ($doctorBreakdown as &$row) {
|
||||
$row['payout_amount'] = $summary['doctor_count'] === 1 ? $summary['payout_amount'] : 0.0;
|
||||
}
|
||||
unset($row);
|
||||
|
||||
return [
|
||||
'summary' => $summary,
|
||||
'doctor_breakdown' => $doctorBreakdown,
|
||||
'from_date' => $fromDate,
|
||||
'to_date' => $toDate,
|
||||
'doctor_name' => $doctorName,
|
||||
'state_unit' => $stateUnit,
|
||||
'state_unit_label' => $stateColumn !== null ? ucwords(str_replace('_', ' ', $stateColumn)) : 'State/Unit',
|
||||
];
|
||||
}
|
||||
|
||||
private function createChargeInvoicePayoutDraft(
|
||||
string $source,
|
||||
int $doctorId,
|
||||
string $fromDate,
|
||||
string $toDate,
|
||||
string $stateUnit,
|
||||
float $baseAmount,
|
||||
float $doctorShare,
|
||||
float $hospitalShare,
|
||||
float $deductions,
|
||||
float $adjustments
|
||||
) {
|
||||
if ($doctorId <= 0) {
|
||||
return $this->response->setStatusCode(422)->setJSON(['status' => 0, 'message' => 'Please select a doctor before creating payout draft.']);
|
||||
}
|
||||
|
||||
if ($fromDate === '') {
|
||||
$fromDate = date('Y-m-01');
|
||||
}
|
||||
if ($toDate === '') {
|
||||
$toDate = date('Y-m-d');
|
||||
}
|
||||
if ($fromDate > $toDate) {
|
||||
[$fromDate, $toDate] = [$toDate, $fromDate];
|
||||
}
|
||||
|
||||
$source = $this->normalizePayoutSource($source);
|
||||
$summaryData = $this->buildChargeInvoicePayoutSummaryData($source, $fromDate, $toDate, $doctorId, $stateUnit);
|
||||
$summary = (array) ($summaryData['summary'] ?? []);
|
||||
$eligible = $this->fetchEligibleChargeItemsForPayout($source, $fromDate, $toDate, $doctorId, $stateUnit);
|
||||
$eligibleItemIds = $eligible['item_ids'] ?? [];
|
||||
$eligibleInvoiceIds = $eligible['invoice_ids'] ?? [];
|
||||
$eligibleUnits = count($eligibleInvoiceIds);
|
||||
|
||||
if ($eligibleUnits <= 0) {
|
||||
return $this->response->setStatusCode(422)->setJSON([
|
||||
'status' => 0,
|
||||
'message' => 'No eligible ' . strtolower($this->payoutSourceLabel($source)) . ' found for selected filters.',
|
||||
]);
|
||||
}
|
||||
|
||||
if ($baseAmount <= 0) {
|
||||
$baseAmount = (float) ($summary['total_received'] ?? 0);
|
||||
if ($baseAmount <= 0) {
|
||||
$baseAmount = (float) ($summary['gross_amount'] ?? 0);
|
||||
}
|
||||
}
|
||||
|
||||
$doctorGross = round(($baseAmount * $doctorShare) / 100, 2);
|
||||
$hospitalGross = round(($baseAmount * $hospitalShare) / 100, 2);
|
||||
$netPayable = round($doctorGross - $deductions + $adjustments, 2);
|
||||
if ($netPayable < 0) {
|
||||
$netPayable = 0.0;
|
||||
}
|
||||
|
||||
$agreementId = $this->resolveOrCreateDoctorAgreementIdFromMaster($doctorId);
|
||||
if ($agreementId <= 0) {
|
||||
return $this->response->setStatusCode(422)->setJSON(['status' => 0, 'message' => 'Unable to map selected doctor to payout agreement.']);
|
||||
}
|
||||
|
||||
$rate = round($netPayable / max(1, $eligibleUnits), 2);
|
||||
$token = match ($source) {
|
||||
'pathology' => 'PATH',
|
||||
'radiology' => 'RAD',
|
||||
'other_items' => 'OTH',
|
||||
default => 'CHG',
|
||||
};
|
||||
$caseReference = 'CHG-' . $token . '-' . str_replace('-', '', $fromDate) . '-' . str_replace('-', '', $toDate) . '-D' . $doctorId;
|
||||
if ($stateUnit !== '') {
|
||||
$caseReference .= '-U' . $this->normalizeStateUnitToken($stateUnit);
|
||||
}
|
||||
|
||||
$remarks = $this->payoutSourceLabel($source) . ' payout draft | Invoices=' . $eligibleUnits
|
||||
. ', Items=' . count($eligibleItemIds)
|
||||
. ', Base=' . number_format($baseAmount, 2, '.', '')
|
||||
. ', Doc%=' . number_format($doctorShare, 2, '.', '')
|
||||
. ', Hosp%=' . number_format($hospitalShare, 2, '.', '')
|
||||
. ', Deductions=' . number_format($deductions, 2, '.', '')
|
||||
. ', Adjustments=' . number_format($adjustments, 2, '.', '')
|
||||
. ', DoctorGross=' . number_format($doctorGross, 2, '.', '')
|
||||
. ', HospitalShare=' . number_format($hospitalGross, 2, '.', '')
|
||||
. ', Net=' . number_format($netPayable, 2, '.', '');
|
||||
|
||||
$this->db->transStart();
|
||||
|
||||
$this->doctorPayoutModel->insert([
|
||||
'payout_date' => $toDate,
|
||||
'doctor_id' => $agreementId,
|
||||
'case_reference' => $caseReference,
|
||||
'payout_type' => $this->payoutTypeFromSource($source),
|
||||
'units' => $eligibleUnits,
|
||||
'rate' => $rate,
|
||||
'calculated_amount' => $netPayable,
|
||||
'approved_amount' => $netPayable,
|
||||
'status' => 'draft',
|
||||
'remarks' => $remarks,
|
||||
'hr_submitted_by' => $this->currentUserName(),
|
||||
]);
|
||||
|
||||
$insertId = (int) ($this->doctorPayoutModel->getInsertID() ?? 0);
|
||||
|
||||
if ($insertId > 0 && ! empty($eligibleItemIds) && $this->tableHasField('invoice_item', 'payout_draft_id')) {
|
||||
$itemUpdate = [
|
||||
'payout_draft_id' => $insertId,
|
||||
];
|
||||
if ($this->tableHasField('invoice_item', 'payout_calculated_at')) {
|
||||
$itemUpdate['payout_calculated_at'] = date('Y-m-d H:i:s');
|
||||
}
|
||||
if ($this->tableHasField('invoice_item', 'payout_calculated_by')) {
|
||||
$itemUpdate['payout_calculated_by'] = $this->currentUserName();
|
||||
}
|
||||
if ($this->tableHasField('invoice_item', 'updated_at')) {
|
||||
$itemUpdate['updated_at'] = date('Y-m-d H:i:s');
|
||||
}
|
||||
|
||||
$this->db->table('invoice_item')
|
||||
->whereIn('id', $eligibleItemIds)
|
||||
->update($itemUpdate);
|
||||
}
|
||||
|
||||
$this->db->transComplete();
|
||||
|
||||
if (! $this->db->transStatus() || $insertId <= 0) {
|
||||
return $this->response->setStatusCode(500)->setJSON(['status' => 0, 'message' => 'Failed to create payout draft.']);
|
||||
}
|
||||
|
||||
return $this->response->setJSON([
|
||||
'status' => 1,
|
||||
'message' => 'Payout draft created successfully.',
|
||||
'payout_id' => $insertId,
|
||||
'case_reference' => $caseReference,
|
||||
'net_payable' => $netPayable,
|
||||
]);
|
||||
}
|
||||
|
||||
private function fetchOpdStateUnitOptions(): array
|
||||
{
|
||||
$column = $this->resolveOpdStateUnitColumn();
|
||||
|
|
|
|||
|
|
@ -11477,6 +11477,165 @@ class Medical extends BaseController
|
|||
]);
|
||||
}
|
||||
|
||||
public function credit_payout_requests_datatable()
|
||||
{
|
||||
if ($deny = $this->ensurePharmacyAccess()) {
|
||||
return $deny;
|
||||
}
|
||||
|
||||
$draw = (int) ($this->request->getPost('draw') ?? $this->request->getGet('draw') ?? 1);
|
||||
$start = max(0, (int) ($this->request->getPost('start') ?? $this->request->getGet('start') ?? 0));
|
||||
$length = (int) ($this->request->getPost('length') ?? $this->request->getGet('length') ?? 25);
|
||||
if ($length <= 0) {
|
||||
$length = 25;
|
||||
}
|
||||
if ($length > 200) {
|
||||
$length = 200;
|
||||
}
|
||||
|
||||
if (! $this->db->tableExists('finance_payout_requests')) {
|
||||
return $this->response->setJSON([
|
||||
'draw' => $draw,
|
||||
'recordsTotal' => 0,
|
||||
'recordsFiltered' => 0,
|
||||
'data' => [],
|
||||
'status_summary' => [
|
||||
'total' => 0,
|
||||
'submitted' => 0,
|
||||
'finance_review' => 0,
|
||||
'approved' => 0,
|
||||
'partially_paid' => 0,
|
||||
'paid' => 0,
|
||||
'rejected' => 0,
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
$searchRaw = $this->request->getPost('search');
|
||||
if ($searchRaw === null) {
|
||||
$searchRaw = $this->request->getGet('search');
|
||||
}
|
||||
$searchValue = '';
|
||||
if (is_array($searchRaw)) {
|
||||
$searchValue = trim((string) ($searchRaw['value'] ?? ''));
|
||||
} elseif (is_string($searchRaw)) {
|
||||
$searchValue = trim($searchRaw);
|
||||
}
|
||||
|
||||
$orderRaw = $this->request->getPost('order');
|
||||
if ($orderRaw === null) {
|
||||
$orderRaw = $this->request->getGet('order');
|
||||
}
|
||||
$order = is_array($orderRaw) ? $orderRaw : [];
|
||||
|
||||
$columnsRaw = $this->request->getPost('columns');
|
||||
if ($columnsRaw === null) {
|
||||
$columnsRaw = $this->request->getGet('columns');
|
||||
}
|
||||
$columns = is_array($columnsRaw) ? $columnsRaw : [];
|
||||
|
||||
$builder = $this->db->table('finance_payout_requests pr')
|
||||
->where('pr.request_type', 'medical_store_credit');
|
||||
|
||||
$recordsTotal = (int) $builder->countAllResults(false);
|
||||
|
||||
if ($searchValue !== '') {
|
||||
$builder
|
||||
->groupStart()
|
||||
->like('pr.request_no', $searchValue)
|
||||
->orLike('pr.status', $searchValue)
|
||||
->orLike('pr.request_date', $searchValue)
|
||||
->orLike('pr.created_by', $searchValue)
|
||||
->groupEnd();
|
||||
}
|
||||
|
||||
$recordsFiltered = (int) $builder->countAllResults(false);
|
||||
|
||||
$orderableMap = [
|
||||
'request_no' => 'pr.request_no',
|
||||
'request_date' => 'pr.request_date',
|
||||
'status' => 'pr.status',
|
||||
'requested_amount' => 'pr.requested_amount',
|
||||
'paid_amount' => 'pr.paid_amount',
|
||||
'pending_amount' => 'pr.pending_amount',
|
||||
'created_by' => 'pr.created_by',
|
||||
];
|
||||
|
||||
$appliedOrder = false;
|
||||
if (! empty($order)) {
|
||||
foreach ($order as $ord) {
|
||||
$colIndex = (int) ($ord['column'] ?? -1);
|
||||
$dir = strtolower((string) ($ord['dir'] ?? 'desc')) === 'asc' ? 'ASC' : 'DESC';
|
||||
$colData = '';
|
||||
if ($colIndex >= 0 && isset($columns[$colIndex]) && is_array($columns[$colIndex])) {
|
||||
$colData = (string) ($columns[$colIndex]['data'] ?? '');
|
||||
}
|
||||
if ($colData !== '' && isset($orderableMap[$colData])) {
|
||||
$builder->orderBy($orderableMap[$colData], $dir);
|
||||
$appliedOrder = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (! $appliedOrder) {
|
||||
$builder->orderBy('pr.id', 'DESC');
|
||||
}
|
||||
|
||||
$rows = $builder
|
||||
->select('pr.id, pr.request_no, pr.request_date, pr.status, pr.requested_amount, pr.paid_amount, pr.pending_amount, pr.created_by')
|
||||
->limit($length, $start)
|
||||
->get()
|
||||
->getResultArray();
|
||||
|
||||
$data = [];
|
||||
foreach ($rows as $i => $row) {
|
||||
$data[] = [
|
||||
'row_no' => $start + $i + 1,
|
||||
'id' => (int) ($row['id'] ?? 0),
|
||||
'request_no' => (string) ($row['request_no'] ?? ''),
|
||||
'request_date' => (string) ($row['request_date'] ?? ''),
|
||||
'status' => (string) ($row['status'] ?? ''),
|
||||
'requested_amount' => round((float) ($row['requested_amount'] ?? 0), 2),
|
||||
'paid_amount' => round((float) ($row['paid_amount'] ?? 0), 2),
|
||||
'pending_amount' => round((float) ($row['pending_amount'] ?? 0), 2),
|
||||
'created_by' => (string) ($row['created_by'] ?? ''),
|
||||
];
|
||||
}
|
||||
|
||||
$summaryRows = $this->db->table('finance_payout_requests')
|
||||
->select('LOWER(status) AS status_key, COUNT(1) AS cnt', false)
|
||||
->where('request_type', 'medical_store_credit')
|
||||
->groupBy('LOWER(status)')
|
||||
->get()
|
||||
->getResultArray();
|
||||
|
||||
$statusSummary = [
|
||||
'total' => 0,
|
||||
'submitted' => 0,
|
||||
'finance_review' => 0,
|
||||
'approved' => 0,
|
||||
'partially_paid' => 0,
|
||||
'paid' => 0,
|
||||
'rejected' => 0,
|
||||
];
|
||||
foreach ($summaryRows as $sr) {
|
||||
$key = strtolower(trim((string) ($sr['status_key'] ?? '')));
|
||||
$cnt = (int) ($sr['cnt'] ?? 0);
|
||||
$statusSummary['total'] += $cnt;
|
||||
if (array_key_exists($key, $statusSummary)) {
|
||||
$statusSummary[$key] += $cnt;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->response->setJSON([
|
||||
'draw' => $draw,
|
||||
'recordsTotal' => $recordsTotal,
|
||||
'recordsFiltered' => $recordsFiltered,
|
||||
'data' => $data,
|
||||
'status_summary' => $statusSummary,
|
||||
]);
|
||||
}
|
||||
|
||||
public function credit_payout_request_detail($requestId = 0)
|
||||
{
|
||||
if ($deny = $this->ensurePharmacyAccess()) {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,92 @@
|
|||
<?php
|
||||
|
||||
namespace App\Database\Migrations;
|
||||
|
||||
use CodeIgniter\Database\Migration;
|
||||
|
||||
class AddInvoiceItemPayoutTrackingFields extends Migration
|
||||
{
|
||||
private function hasTable(string $table): bool
|
||||
{
|
||||
$row = $this->db->query('SHOW TABLES LIKE ' . $this->db->escape($table))->getRowArray();
|
||||
|
||||
return ! empty($row);
|
||||
}
|
||||
|
||||
private function getTableFields(string $table): array
|
||||
{
|
||||
if (! $this->hasTable($table)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
try {
|
||||
$rows = $this->db->query('SHOW COLUMNS FROM `' . $table . '`')->getResultArray();
|
||||
return array_values(array_filter(array_map(static function (array $row): string {
|
||||
return (string) ($row['Field'] ?? '');
|
||||
}, $rows), static fn(string $field): bool => $field !== ''));
|
||||
} catch (\Throwable $e) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
public function up()
|
||||
{
|
||||
if (! $this->hasTable('invoice_item')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$fields = $this->getTableFields('invoice_item');
|
||||
|
||||
if (! in_array('payout_draft_id', $fields, true)) {
|
||||
$this->forge->addColumn('invoice_item', [
|
||||
'payout_draft_id' => [
|
||||
'type' => 'INT',
|
||||
'constraint' => 11,
|
||||
'unsigned' => true,
|
||||
'null' => true,
|
||||
'after' => 'inv_master_id',
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
if (! in_array('payout_calculated_at', $fields, true)) {
|
||||
$this->forge->addColumn('invoice_item', [
|
||||
'payout_calculated_at' => [
|
||||
'type' => 'DATETIME',
|
||||
'null' => true,
|
||||
'after' => 'payout_draft_id',
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
if (! in_array('payout_calculated_by', $fields, true)) {
|
||||
$this->forge->addColumn('invoice_item', [
|
||||
'payout_calculated_by' => [
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 120,
|
||||
'null' => true,
|
||||
'after' => 'payout_calculated_at',
|
||||
],
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
public function down()
|
||||
{
|
||||
if (! $this->hasTable('invoice_item')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$fields = $this->getTableFields('invoice_item');
|
||||
|
||||
if (in_array('payout_calculated_by', $fields, true)) {
|
||||
$this->forge->dropColumn('invoice_item', 'payout_calculated_by');
|
||||
}
|
||||
if (in_array('payout_calculated_at', $fields, true)) {
|
||||
$this->forge->dropColumn('invoice_item', 'payout_calculated_at');
|
||||
}
|
||||
if (in_array('payout_draft_id', $fields, true)) {
|
||||
$this->forge->dropColumn('invoice_item', 'payout_draft_id');
|
||||
}
|
||||
}
|
||||
}
|
||||
208
app/Views/finance/doctor_charges_payout.php
Normal file
208
app/Views/finance/doctor_charges_payout.php
Normal file
|
|
@ -0,0 +1,208 @@
|
|||
<section class="content finance-doctor-charges-payout">
|
||||
<div class="mb-3">
|
||||
<h2 class="mb-1">Doctor Charges Payout</h2>
|
||||
<p class="text-muted mb-0">Calculate and manage doctor payout from Pathology, Radiology, and Other charge invoices.</p>
|
||||
</div>
|
||||
|
||||
<div id="dc_payout_alert"></div>
|
||||
|
||||
<div class="card border-0 shadow-sm">
|
||||
<div class="card-body">
|
||||
<div class="row g-3">
|
||||
<div class="col-md-3 col-6">
|
||||
<label class="form-label form-label-sm">From Date</label>
|
||||
<input type="date" class="form-control form-control-sm" id="dc_payout_from_date" value="<?= date('Y-m-01') ?>">
|
||||
</div>
|
||||
<div class="col-md-3 col-6">
|
||||
<label class="form-label form-label-sm">To Date</label>
|
||||
<input type="date" class="form-control form-control-sm" id="dc_payout_to_date" value="<?= date('Y-m-d') ?>">
|
||||
</div>
|
||||
<div class="col-md-3 col-6">
|
||||
<label class="form-label form-label-sm">Doctor</label>
|
||||
<select class="form-select form-select-sm" id="dc_payout_doctor_id" name="doctor_id">
|
||||
<option value="">Select Doctor</option>
|
||||
<?php foreach (($doctor_options ?? []) as $doctor): ?>
|
||||
<option value="<?= (int) ($doctor['id'] ?? 0) ?>"><?= esc((string) ($doctor['name'] ?? '')) ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-3 col-6">
|
||||
<label class="form-label form-label-sm">State/Unit</label>
|
||||
<select class="form-select form-select-sm" id="dc_payout_state_unit" name="state_unit">
|
||||
<option value="">All State/Unit</option>
|
||||
<?php foreach (($state_unit_options ?? []) as $opt): ?>
|
||||
<option value="<?= esc((string) $opt) ?>"><?= esc((string) $opt) ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-3 col-6">
|
||||
<label class="form-label form-label-sm">Charge Source</label>
|
||||
<select class="form-select form-select-sm" id="dc_payout_source" name="payout_source">
|
||||
<option value="pathology">Pathology Charges</option>
|
||||
<option value="radiology">Radiology Charges</option>
|
||||
<option value="other_items">Other Charge Items</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-12 d-flex justify-content-end">
|
||||
<button type="button" class="btn btn-primary btn-sm" onclick="refreshDcPayoutSummary()">
|
||||
<i class="bi bi-arrow-repeat me-1"></i>Load Summary
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-3" id="dc_payout_summary_wrap">
|
||||
<div class="text-muted">Select filters and click Load Summary.</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-3" id="dc_payout_drafts_wrap">
|
||||
<div class="text-muted">Loading payout drafts...</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="modal fade" id="dcPayoutEditModal" tabindex="-1" aria-labelledby="dcPayoutEditModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="dcPayoutEditModalLabel">Edit Payout Draft</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<input type="hidden" id="dc_edit_payout_id" value="0">
|
||||
<div class="mb-2">
|
||||
<label class="form-label form-label-sm">Payout Date</label>
|
||||
<input type="date" class="form-control form-control-sm" id="dc_edit_payout_date">
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<label class="form-label form-label-sm">Approved Amount</label>
|
||||
<input type="number" class="form-control form-control-sm" id="dc_edit_approved_amount" min="0" step="0.01">
|
||||
</div>
|
||||
<div class="mb-0">
|
||||
<label class="form-label form-label-sm">Remarks</label>
|
||||
<textarea class="form-control form-control-sm" id="dc_edit_remarks" rows="2"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary btn-sm" data-bs-dismiss="modal">Cancel</button>
|
||||
<button type="button" class="btn btn-primary btn-sm" onclick="submitDcPayoutDraftEdit()">Save Changes</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
(function () {
|
||||
window.showDcPayoutAlert = function (message, ok) {
|
||||
var box = document.getElementById('dc_payout_alert');
|
||||
if (!box) return;
|
||||
box.innerHTML = '<div class="alert ' + (ok ? 'alert-success' : 'alert-danger') + ' alert-dismissible fade show" role="alert">'
|
||||
+ message
|
||||
+ '<button type="button" class="btn-close" data-bs-dismiss="alert"></button>'
|
||||
+ '</div>';
|
||||
};
|
||||
|
||||
window.refreshDcPayoutSummary = function () {
|
||||
var fromDate = document.getElementById('dc_payout_from_date')?.value || '';
|
||||
var toDate = document.getElementById('dc_payout_to_date')?.value || '';
|
||||
var doctorId = document.getElementById('dc_payout_doctor_id')?.value || '';
|
||||
var stateUnit = document.getElementById('dc_payout_state_unit')?.value || '';
|
||||
var payoutSource = document.getElementById('dc_payout_source')?.value || 'pathology';
|
||||
|
||||
load_form_div(
|
||||
'<?= base_url('Finance/payout/doctor-charges-summary') ?>?from_date=' + encodeURIComponent(fromDate)
|
||||
+ '&to_date=' + encodeURIComponent(toDate)
|
||||
+ '&doctor_id=' + encodeURIComponent(doctorId)
|
||||
+ '&state_unit=' + encodeURIComponent(stateUnit)
|
||||
+ '&payout_source=' + encodeURIComponent(payoutSource),
|
||||
'dc_payout_summary_wrap'
|
||||
);
|
||||
|
||||
load_form_div(
|
||||
'<?= base_url('Finance/payout/doctor-charges-drafts-table') ?>?from_date=' + encodeURIComponent(fromDate)
|
||||
+ '&to_date=' + encodeURIComponent(toDate)
|
||||
+ '&doctor_id=' + encodeURIComponent(doctorId)
|
||||
+ '&state_unit=' + encodeURIComponent(stateUnit)
|
||||
+ '&payout_source=' + encodeURIComponent(payoutSource),
|
||||
'dc_payout_drafts_wrap'
|
||||
);
|
||||
};
|
||||
|
||||
window.editDcPayoutDraft = function (payoutId, payoutDate, approvedAmount, remarks) {
|
||||
document.getElementById('dc_edit_payout_id').value = String(payoutId || 0);
|
||||
document.getElementById('dc_edit_payout_date').value = String(payoutDate || '');
|
||||
document.getElementById('dc_edit_approved_amount').value = String(approvedAmount || '0');
|
||||
document.getElementById('dc_edit_remarks').value = String(remarks || '');
|
||||
|
||||
var modalEl = document.getElementById('dcPayoutEditModal');
|
||||
if (modalEl && window.bootstrap && window.bootstrap.Modal) {
|
||||
window.bootstrap.Modal.getOrCreateInstance(modalEl).show();
|
||||
}
|
||||
};
|
||||
|
||||
window.submitDcPayoutDraftEdit = function () {
|
||||
var payoutId = document.getElementById('dc_edit_payout_id')?.value || '0';
|
||||
var newDate = document.getElementById('dc_edit_payout_date')?.value || '';
|
||||
var newAmount = document.getElementById('dc_edit_approved_amount')?.value || '0';
|
||||
var newRemarks = document.getElementById('dc_edit_remarks')?.value || '';
|
||||
|
||||
if (!payoutId || payoutId === '0') {
|
||||
window.showDcPayoutAlert('Invalid payout draft selected.', false);
|
||||
return;
|
||||
}
|
||||
|
||||
var fd = new window.FormData();
|
||||
fd.append('payout_id', String(payoutId || '0'));
|
||||
fd.append('payout_date', String(newDate || ''));
|
||||
fd.append('approved_amount', String(newAmount || '0'));
|
||||
fd.append('remarks', String(newRemarks || ''));
|
||||
|
||||
fetch('<?= base_url('Finance/payout/doctor-charges-draft-update') ?>', {
|
||||
method: 'POST',
|
||||
headers: {'X-Requested-With': 'XMLHttpRequest'},
|
||||
body: fd,
|
||||
}).then(function (res) { return res.json().then(function (data) { return {ok: res.ok, data: data}; }); })
|
||||
.then(function (result) {
|
||||
var ok = result.ok && result.data && result.data.status === 1;
|
||||
window.showDcPayoutAlert((result.data && result.data.message) ? result.data.message : (ok ? 'Updated.' : 'Failed.'), ok);
|
||||
if (ok) {
|
||||
var modalEl = document.getElementById('dcPayoutEditModal');
|
||||
if (modalEl && window.bootstrap && window.bootstrap.Modal) {
|
||||
window.bootstrap.Modal.getOrCreateInstance(modalEl).hide();
|
||||
}
|
||||
window.refreshDcPayoutSummary();
|
||||
}
|
||||
}).catch(function () {
|
||||
window.showDcPayoutAlert('Network or server error while updating draft.', false);
|
||||
});
|
||||
};
|
||||
|
||||
window.deleteDcPayoutDraft = function (payoutId) {
|
||||
if (!window.confirm('Delete this payout draft? Linked charge records will be unlocked for recalculation.')) {
|
||||
return;
|
||||
}
|
||||
|
||||
var fd = new window.FormData();
|
||||
fd.append('payout_id', String(payoutId));
|
||||
|
||||
fetch('<?= base_url('Finance/payout/doctor-charges-draft-delete') ?>', {
|
||||
method: 'POST',
|
||||
headers: {'X-Requested-With': 'XMLHttpRequest'},
|
||||
body: fd,
|
||||
}).then(function (res) { return res.json().then(function (data) { return {ok: res.ok, data: data}; }); })
|
||||
.then(function (result) {
|
||||
var ok = result.ok && result.data && result.data.status === 1;
|
||||
window.showDcPayoutAlert((result.data && result.data.message) ? result.data.message : (ok ? 'Deleted.' : 'Failed.'), ok);
|
||||
if (ok) {
|
||||
window.refreshDcPayoutSummary();
|
||||
}
|
||||
}).catch(function () {
|
||||
window.showDcPayoutAlert('Network or server error while deleting draft.', false);
|
||||
});
|
||||
};
|
||||
|
||||
document.getElementById('dc_payout_source')?.addEventListener('change', refreshDcPayoutSummary);
|
||||
|
||||
refreshDcPayoutSummary();
|
||||
})();
|
||||
</script>
|
||||
|
|
@ -14,64 +14,8 @@
|
|||
</div>
|
||||
|
||||
<div class="row g-3">
|
||||
<div class="col-xl-4">
|
||||
<div class="card h-100">
|
||||
<div class="card-header"><strong>Request Actions</strong></div>
|
||||
<div class="card-body">
|
||||
<form id="ms_request_action_form" class="row g-2">
|
||||
<div class="col-12">
|
||||
<label class="form-label mb-1">Request ID</label>
|
||||
<input type="number" class="form-control" name="request_id" id="ms_request_id" min="1" required>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<label class="form-label mb-1">Remark</label>
|
||||
<textarea class="form-control" name="remark" id="ms_action_remark" rows="2" placeholder="Optional review/approval remark"></textarea>
|
||||
</div>
|
||||
<div class="col-12 d-flex flex-wrap gap-2">
|
||||
<button type="button" class="btn btn-outline-primary btn-sm" id="btn_ms_review">Mark Finance Review</button>
|
||||
<button type="button" class="btn btn-success btn-sm" id="btn_ms_approve">Approve</button>
|
||||
<button type="button" class="btn btn-danger btn-sm" id="btn_ms_reject">Reject</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<hr>
|
||||
|
||||
<form id="ms_payment_form" class="row g-2">
|
||||
<div class="col-12"><strong>Settlement Payment</strong></div>
|
||||
<div class="col-12">
|
||||
<label class="form-label mb-1">Request ID</label>
|
||||
<input type="number" class="form-control" name="request_id" id="ms_payment_request_id" min="1" required>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label mb-1">Amount</label>
|
||||
<input type="number" class="form-control" name="amount" step="0.01" min="0.01" required>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label mb-1">Mode</label>
|
||||
<select class="form-select" name="payment_mode">
|
||||
<option value="1">Cash</option>
|
||||
<option value="2">Bank</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label mb-1">Bank Source ID</label>
|
||||
<input type="number" class="form-control" name="insert_code" placeholder="Optional">
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label mb-1">Bank ID</label>
|
||||
<input type="number" class="form-control" name="pay_bank_id" placeholder="Optional">
|
||||
</div>
|
||||
<div class="col-md-6"><input type="text" class="form-control" name="card_tran_id" placeholder="Transaction ID"></div>
|
||||
<div class="col-md-6"><input type="text" class="form-control" name="card_bank" placeholder="Bank/Card Name"></div>
|
||||
<div class="col-12"><textarea class="form-control" name="remark" rows="2" placeholder="Payment remark"></textarea></div>
|
||||
<div class="col-12"><button type="submit" class="btn btn-primary btn-sm">Post Payment</button></div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-xl-8">
|
||||
<div class="card h-100">
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="card-header d-flex justify-content-between align-items-center">
|
||||
<strong>Request List</strong>
|
||||
<div class="d-flex gap-2">
|
||||
|
|
@ -95,15 +39,119 @@
|
|||
</div>
|
||||
</section>
|
||||
|
||||
<div class="modal fade" id="msRequestDetailModal" tabindex="-1" aria-labelledby="msRequestDetailModalLabel">
|
||||
<div class="modal fade" id="msRequestActionModal" tabindex="-1" aria-labelledby="msRequestActionModalLabel">
|
||||
<div class="modal-dialog modal-xl modal-dialog-scrollable">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="msRequestDetailModalLabel">Request Details</h5>
|
||||
<div>
|
||||
<h5 class="modal-title" id="msRequestActionModalLabel">Request Action</h5>
|
||||
<div class="small text-muted" id="ms_request_action_modal_subtitle">Review request details and update its workflow status.</div>
|
||||
</div>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
<div class="modal-body p-0" id="ms_request_detail_modal_body">
|
||||
<div class="p-3 text-muted">Loading...</div>
|
||||
<div class="modal-body">
|
||||
<div class="row g-3">
|
||||
<div class="col-lg-4">
|
||||
<form id="ms_request_action_form" class="row g-2">
|
||||
<div class="col-12">
|
||||
<label class="form-label mb-1">Request ID</label>
|
||||
<input type="number" class="form-control" name="request_id" id="ms_request_id" min="1" required readonly>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label mb-1">Request No</label>
|
||||
<input type="text" class="form-control" id="ms_request_no" readonly>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label mb-1">Status</label>
|
||||
<input type="text" class="form-control" id="ms_request_status" readonly>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label mb-1">Requested</label>
|
||||
<input type="text" class="form-control" id="ms_request_requested_amount" readonly>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label mb-1">Pending</label>
|
||||
<input type="text" class="form-control" id="ms_request_pending_amount" readonly>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<label class="form-label mb-1">Remark</label>
|
||||
<textarea class="form-control" name="remark" id="ms_action_remark" rows="3" placeholder="Optional review/approval remark"></textarea>
|
||||
</div>
|
||||
<div class="col-12 d-flex flex-wrap gap-2">
|
||||
<button type="button" class="btn btn-outline-primary btn-sm" id="btn_ms_review">Mark Finance Review</button>
|
||||
<button type="button" class="btn btn-success btn-sm" id="btn_ms_approve">Approve</button>
|
||||
<button type="button" class="btn btn-danger btn-sm" id="btn_ms_reject">Reject</button>
|
||||
<button type="button" class="btn btn-primary btn-sm d-none" id="btn_ms_to_settlement">Settlement Payment</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="col-lg-8">
|
||||
<div id="ms_request_action_detail_wrap" class="border rounded overflow-hidden">
|
||||
<div class="p-3 text-muted">Loading...</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal fade" id="msSettlementModal" tabindex="-1" aria-labelledby="msSettlementModalLabel">
|
||||
<div class="modal-dialog modal-lg modal-dialog-scrollable">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<div>
|
||||
<h5 class="modal-title" id="msSettlementModalLabel">Settlement Payment</h5>
|
||||
<div class="small text-muted" id="ms_settlement_modal_subtitle">Post settlement for the approved medical store credit request.</div>
|
||||
</div>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form id="ms_payment_form" class="row g-2">
|
||||
<div class="col-md-6">
|
||||
<label class="form-label mb-1">Request ID</label>
|
||||
<input type="number" class="form-control" name="request_id" id="ms_payment_request_id" min="1" required readonly>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label mb-1">Request No</label>
|
||||
<input type="text" class="form-control" id="ms_payment_request_no" readonly>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label mb-1">Amount</label>
|
||||
<input type="number" class="form-control" name="amount" id="ms_payment_amount" step="0.01" min="0.01" required>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label mb-1">Mode</label>
|
||||
<select class="form-select" name="payment_mode" id="ms_payment_mode">
|
||||
<option value="1">Cash</option>
|
||||
<option value="2">Bank</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label mb-1">Bank Source ID</label>
|
||||
<input type="number" class="form-control" name="insert_code" placeholder="Optional">
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label mb-1">Bank ID</label>
|
||||
<input type="number" class="form-control" name="pay_bank_id" placeholder="Optional">
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label mb-1">Transaction ID</label>
|
||||
<input type="text" class="form-control" name="card_tran_id" placeholder="Transaction ID">
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label mb-1">Bank/Card Name</label>
|
||||
<input type="text" class="form-control" name="card_bank" placeholder="Bank/Card Name">
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<label class="form-label mb-1">Payment remark</label>
|
||||
<textarea class="form-control" name="remark" rows="3" placeholder="Payment remark"></textarea>
|
||||
</div>
|
||||
<div class="col-12 d-flex justify-content-between align-items-center pt-2">
|
||||
<div class="small text-muted" id="ms_payment_pending_hint">Pending amount will be loaded from the selected request.</div>
|
||||
<button type="submit" class="btn btn-primary btn-sm">Post Payment</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -112,6 +160,29 @@
|
|||
<script>
|
||||
(function () {
|
||||
var BASE = '<?= base_url() ?>';
|
||||
var currentRequest = null;
|
||||
|
||||
function formatAmount(value) {
|
||||
return Number(value || 0).toFixed(2);
|
||||
}
|
||||
|
||||
function getActionModal() {
|
||||
var modalEl = document.getElementById('msRequestActionModal');
|
||||
if (!modalEl || !window.bootstrap || !window.bootstrap.Modal) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return window.bootstrap.Modal.getOrCreateInstance(modalEl);
|
||||
}
|
||||
|
||||
function getSettlementModal() {
|
||||
var modalEl = document.getElementById('msSettlementModal');
|
||||
if (!modalEl || !window.bootstrap || !window.bootstrap.Modal) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return window.bootstrap.Modal.getOrCreateInstance(modalEl);
|
||||
}
|
||||
|
||||
function showAlert(msg, ok) {
|
||||
var cls = ok ? 'alert-success' : 'alert-danger';
|
||||
|
|
@ -160,6 +231,98 @@
|
|||
});
|
||||
}
|
||||
|
||||
function updateActionButtons(request) {
|
||||
var status = request && request.status ? String(request.status) : '';
|
||||
var pending = Number(request && request.pending_amount ? request.pending_amount : 0);
|
||||
var btnReview = document.getElementById('btn_ms_review');
|
||||
var btnApprove = document.getElementById('btn_ms_approve');
|
||||
var btnReject = document.getElementById('btn_ms_reject');
|
||||
var btnSettlement = document.getElementById('btn_ms_to_settlement');
|
||||
|
||||
if (btnReview) btnReview.disabled = status !== 'submitted';
|
||||
if (btnApprove) btnApprove.disabled = status !== 'finance_review';
|
||||
if (btnReject) btnReject.disabled = !(status === 'submitted' || status === 'finance_review');
|
||||
if (btnSettlement) {
|
||||
var canSettle = (status === 'approved' || status === 'partially_paid') && pending > 0;
|
||||
btnSettlement.classList.toggle('d-none', !canSettle);
|
||||
btnSettlement.disabled = !canSettle;
|
||||
}
|
||||
}
|
||||
|
||||
function fillActionModal(request) {
|
||||
currentRequest = request || null;
|
||||
var requestId = document.getElementById('ms_request_id');
|
||||
var requestNo = document.getElementById('ms_request_no');
|
||||
var requestStatus = document.getElementById('ms_request_status');
|
||||
var requestedAmount = document.getElementById('ms_request_requested_amount');
|
||||
var pendingAmount = document.getElementById('ms_request_pending_amount');
|
||||
var subtitle = document.getElementById('ms_request_action_modal_subtitle');
|
||||
|
||||
if (requestId) requestId.value = request && request.id ? String(request.id) : '';
|
||||
if (requestNo) requestNo.value = request && request.request_no ? String(request.request_no) : '';
|
||||
if (requestStatus) requestStatus.value = request && request.status ? String(request.status) : '';
|
||||
if (requestedAmount) requestedAmount.value = formatAmount(request && request.requested_amount ? request.requested_amount : 0);
|
||||
if (pendingAmount) pendingAmount.value = formatAmount(request && request.pending_amount ? request.pending_amount : 0);
|
||||
if (subtitle) {
|
||||
subtitle.textContent = request
|
||||
? 'Request #' + String(request.id || '') + ' is currently in ' + String(request.status || 'draft') + ' status.'
|
||||
: 'Review request details and update its workflow status.';
|
||||
}
|
||||
|
||||
updateActionButtons(request || {});
|
||||
}
|
||||
|
||||
function fillSettlementForm(request) {
|
||||
var requestId = document.getElementById('ms_payment_request_id');
|
||||
var requestNo = document.getElementById('ms_payment_request_no');
|
||||
var amount = document.getElementById('ms_payment_amount');
|
||||
var hint = document.getElementById('ms_payment_pending_hint');
|
||||
|
||||
if (requestId) requestId.value = request && request.id ? String(request.id) : '';
|
||||
if (requestNo) requestNo.value = request && request.request_no ? String(request.request_no) : '';
|
||||
if (amount) amount.value = formatAmount(request && request.pending_amount ? request.pending_amount : 0);
|
||||
if (hint) hint.textContent = 'Pending amount: ' + formatAmount(request && request.pending_amount ? request.pending_amount : 0);
|
||||
}
|
||||
|
||||
function loadRequestActionModal(requestId) {
|
||||
var detailWrap = document.getElementById('ms_request_action_detail_wrap');
|
||||
if (detailWrap) {
|
||||
detailWrap.innerHTML = '<div class="p-3 text-muted">Loading...</div>';
|
||||
}
|
||||
|
||||
fetch(BASE + 'Finance/medical_store_request_detail/' + encodeURIComponent(String(requestId)), {
|
||||
method: 'GET',
|
||||
headers: {'X-Requested-With': 'XMLHttpRequest'}
|
||||
}).then(function (res) {
|
||||
return res.json().then(function (data) { return { ok: res.ok, data: data }; });
|
||||
}).then(function (result) {
|
||||
if (!result.ok || !result.data || result.data.status !== 1 || !result.data.request) {
|
||||
showAlert((result.data && result.data.message) ? result.data.message : 'Unable to load request.', false);
|
||||
return;
|
||||
}
|
||||
|
||||
fillActionModal(result.data.request);
|
||||
fetch(BASE + 'Finance/medical_store_request_lines_table?request_id=' + encodeURIComponent(String(requestId)), {
|
||||
method: 'GET',
|
||||
headers: {'X-Requested-With': 'XMLHttpRequest'}
|
||||
}).then(function (r) {
|
||||
return r.text().then(function (html) { return { ok: r.ok, html: html }; });
|
||||
}).then(function (htmlResult) {
|
||||
if (!detailWrap) return;
|
||||
detailWrap.innerHTML = htmlResult.ok ? htmlResult.html : '<div class="p-3 text-danger">Unable to load request details.</div>';
|
||||
}).catch(function () {
|
||||
if (detailWrap) detailWrap.innerHTML = '<div class="p-3 text-danger">Network error while loading details.</div>';
|
||||
});
|
||||
|
||||
var actionModal = getActionModal();
|
||||
if (actionModal) {
|
||||
actionModal.show();
|
||||
}
|
||||
}).catch(function () {
|
||||
showAlert('Network or server error.', false);
|
||||
});
|
||||
}
|
||||
|
||||
function doStatusAction(endpoint) {
|
||||
var requestId = document.getElementById('ms_request_id') ? document.getElementById('ms_request_id').value : '';
|
||||
var remark = document.getElementById('ms_action_remark') ? document.getElementById('ms_action_remark').value : '';
|
||||
|
|
@ -179,6 +342,19 @@
|
|||
showAlert(result.data.message || 'Updated successfully.', true);
|
||||
refreshRequestsTable();
|
||||
refreshSummary();
|
||||
|
||||
if (requestId) {
|
||||
loadRequestActionModal(requestId);
|
||||
}
|
||||
|
||||
if (endpoint === 'Finance/medical_store_request_approve' && currentRequest) {
|
||||
currentRequest.status = 'approved';
|
||||
fillSettlementForm(currentRequest);
|
||||
var actionModal = getActionModal();
|
||||
var settlementModal = getSettlementModal();
|
||||
if (actionModal) actionModal.hide();
|
||||
if (settlementModal) settlementModal.show();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -196,31 +372,23 @@
|
|||
paymentForm.reset();
|
||||
refreshRequestsTable();
|
||||
refreshSummary();
|
||||
|
||||
var settlementModal = getSettlementModal();
|
||||
if (settlementModal) settlementModal.hide();
|
||||
|
||||
if (currentRequest && currentRequest.id) {
|
||||
loadRequestActionModal(currentRequest.id);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
window.openMedicalStoreRequestDetail = function (requestId) {
|
||||
if (!requestId) return;
|
||||
var body = document.getElementById('ms_request_detail_modal_body');
|
||||
if (body) body.innerHTML = '<div class="p-3 text-muted">Loading...</div>';
|
||||
|
||||
fetch(BASE + 'Finance/medical_store_request_lines_table?request_id=' + encodeURIComponent(String(requestId)), {
|
||||
method: 'GET',
|
||||
headers: {'X-Requested-With': 'XMLHttpRequest'}
|
||||
}).then(function (r) {
|
||||
return r.text().then(function (html) { return { ok: r.ok, html: html }; });
|
||||
}).then(function (result) {
|
||||
if (!body) return;
|
||||
body.innerHTML = result.ok ? result.html : '<div class="p-3 text-danger">Unable to load request details.</div>';
|
||||
}).catch(function () {
|
||||
if (body) body.innerHTML = '<div class="p-3 text-danger">Network error while loading details.</div>';
|
||||
});
|
||||
|
||||
var modalEl = document.getElementById('msRequestDetailModal');
|
||||
if (modalEl && window.bootstrap && window.bootstrap.Modal) {
|
||||
window.bootstrap.Modal.getOrCreateInstance(modalEl).show();
|
||||
window.openMedicalStoreRequestAction = function (requestId) {
|
||||
if (!requestId) {
|
||||
return;
|
||||
}
|
||||
|
||||
loadRequestActionModal(requestId);
|
||||
};
|
||||
|
||||
var btnRefresh = document.getElementById('btn_ms_refresh');
|
||||
|
|
@ -237,5 +405,21 @@
|
|||
|
||||
var btnReject = document.getElementById('btn_ms_reject');
|
||||
if (btnReject) btnReject.addEventListener('click', function () { doStatusAction('Finance/medical_store_request_reject'); });
|
||||
|
||||
var btnSettlement = document.getElementById('btn_ms_to_settlement');
|
||||
if (btnSettlement) {
|
||||
btnSettlement.addEventListener('click', function () {
|
||||
if (!currentRequest) {
|
||||
showAlert('Select a request first.', false);
|
||||
return;
|
||||
}
|
||||
|
||||
fillSettlementForm(currentRequest);
|
||||
var settlementModal = getSettlementModal();
|
||||
if (settlementModal) {
|
||||
settlementModal.show();
|
||||
}
|
||||
});
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,78 @@
|
|||
<?php
|
||||
$rows = $rows ?? [];
|
||||
$sourceLabel = trim((string) ($source_label ?? 'Doctor Charges'));
|
||||
$sourceType = trim((string) ($source_type ?? 'pathology'));
|
||||
?>
|
||||
|
||||
<div class="card border-0 shadow-sm">
|
||||
<div class="card-header bg-white d-flex justify-content-between align-items-center">
|
||||
<strong>Recent <?= esc($sourceLabel) ?> Payout Drafts</strong>
|
||||
<span class="small text-muted">Filtered by current date/doctor/state selection</span>
|
||||
</div>
|
||||
<div class="card-body p-0">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm table-striped align-middle mb-0">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th>#</th>
|
||||
<th>Payout Date</th>
|
||||
<th>Case Ref</th>
|
||||
<th>Doctor</th>
|
||||
<th class="text-end">Invoices</th>
|
||||
<th class="text-end">Rate</th>
|
||||
<th class="text-end">Calculated</th>
|
||||
<th class="text-end">Approved</th>
|
||||
<th>Status</th>
|
||||
<th>Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php if (empty($rows)): ?>
|
||||
<tr>
|
||||
<td colspan="10" class="text-center text-muted py-3">No payout drafts found for selected filters.</td>
|
||||
</tr>
|
||||
<?php else: ?>
|
||||
<?php $sr = 1; foreach ($rows as $row): ?>
|
||||
<?php $status = strtolower(trim((string) ($row['status'] ?? 'draft'))); ?>
|
||||
<tr>
|
||||
<td><?= $sr++ ?></td>
|
||||
<td class="text-nowrap"><?= esc((string) ($row['payout_date'] ?? '')) ?></td>
|
||||
<td class="font-monospace"><?= esc((string) ($row['case_reference'] ?? '')) ?></td>
|
||||
<td><?= esc((string) ($row['doctor_name'] ?? '')) ?></td>
|
||||
<td class="text-end"><?= (int) ($row['units'] ?? 0) ?></td>
|
||||
<td class="text-end">Rs <?= number_format((float) ($row['rate'] ?? 0), 2) ?></td>
|
||||
<td class="text-end">Rs <?= number_format((float) ($row['calculated_amount'] ?? 0), 2) ?></td>
|
||||
<td class="text-end">Rs <?= number_format((float) ($row['approved_amount'] ?? 0), 2) ?></td>
|
||||
<td>
|
||||
<?php if ($status === 'paid'): ?>
|
||||
<span class="badge bg-success">Paid</span>
|
||||
<?php elseif ($status === 'ceo_approved'): ?>
|
||||
<span class="badge bg-primary">CEO Approved</span>
|
||||
<?php elseif ($status === 'finance_approved'): ?>
|
||||
<span class="badge bg-info text-dark">Finance Approved</span>
|
||||
<?php else: ?>
|
||||
<span class="badge bg-secondary">Draft</span>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td>
|
||||
<?php if ($status === 'draft'): ?>
|
||||
<button type="button" class="btn btn-outline-primary btn-sm"
|
||||
onclick="editDcPayoutDraft(<?= (int) ($row['id'] ?? 0) ?>, '<?= esc((string) ($row['payout_date'] ?? ''), 'js') ?>', '<?= esc((string) ($row['approved_amount'] ?? '0'), 'js') ?>', '<?= esc((string) ($row['remarks'] ?? ''), 'js') ?>')">
|
||||
<i class="bi bi-pencil-square me-1"></i>Edit
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-danger btn-sm mt-1"
|
||||
onclick="deleteDcPayoutDraft(<?= (int) ($row['id'] ?? 0) ?>)">
|
||||
<i class="bi bi-trash me-1"></i>Delete
|
||||
</button>
|
||||
<?php else: ?>
|
||||
<span class="text-muted small">Locked</span>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
244
app/Views/finance/partials/doctor_charges_payout_summary.php
Normal file
244
app/Views/finance/partials/doctor_charges_payout_summary.php
Normal file
|
|
@ -0,0 +1,244 @@
|
|||
<?php
|
||||
$summary = $summary ?? [];
|
||||
$doctorBreakdown = $doctor_breakdown ?? [];
|
||||
$fromDate = (string) ($from_date ?? '');
|
||||
$toDate = (string) ($to_date ?? '');
|
||||
$doctorName = trim((string) ($doctor_name ?? 'All Doctors'));
|
||||
$stateUnit = trim((string) ($state_unit ?? ''));
|
||||
$stateUnitLabel = trim((string) ($state_unit_label ?? 'State/Unit'));
|
||||
$sourceType = trim((string) ($source_type ?? 'pathology'));
|
||||
$sourceLabel = trim((string) ($source_label ?? 'Pathology Charges'));
|
||||
$scopeLabel = trim((string) ($scope_label ?? 'Eligible Invoices'));
|
||||
$unitLabel = trim((string) ($unit_label ?? 'Invoices'));
|
||||
|
||||
$formatDate = static function (string $value): string {
|
||||
$value = trim($value);
|
||||
if ($value === '') {
|
||||
return '';
|
||||
}
|
||||
|
||||
$timestamp = strtotime($value);
|
||||
return $timestamp === false ? $value : date('d-m-Y', $timestamp);
|
||||
};
|
||||
|
||||
$money = static function ($value): string {
|
||||
return 'Rs ' . number_format((float) $value, 2);
|
||||
};
|
||||
?>
|
||||
|
||||
<div class="card border-0 bg-light mb-3">
|
||||
<div class="card-body py-2 px-3 d-flex justify-content-between align-items-center flex-wrap gap-2">
|
||||
<div>
|
||||
<strong>Charge Source:</strong> <?= esc($sourceLabel) ?>
|
||||
</div>
|
||||
<div>
|
||||
<strong>Summary Range:</strong> <?= esc($formatDate($fromDate)) ?> to <?= esc($formatDate($toDate)) ?>
|
||||
</div>
|
||||
<div>
|
||||
<strong>Doctor:</strong> <?= esc($doctorName !== '' ? $doctorName : 'All Doctors') ?>
|
||||
</div>
|
||||
<div>
|
||||
<strong><?= esc($stateUnitLabel) ?>:</strong> <?= esc($stateUnit !== '' ? $stateUnit : 'All State/Unit') ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row g-2 mb-3">
|
||||
<div class="col-xl-3 col-md-4 col-6"><div class="card border-primary"><div class="card-body py-2"><div class="small text-muted"><?= esc($scopeLabel) ?></div><div class="h5 mb-0 text-primary"><?= (int) ($summary['completed_opd'] ?? 0) ?></div></div></div></div>
|
||||
<div class="col-xl-3 col-md-4 col-6"><div class="card border-secondary"><div class="card-body py-2"><div class="small text-muted">Already Calculated</div><div class="h5 mb-0"><?= (int) ($summary['calculated_opd'] ?? 0) ?></div></div></div></div>
|
||||
<div class="col-xl-3 col-md-4 col-6"><div class="card border-info"><div class="card-body py-2"><div class="small text-muted">Doctors Involved</div><div class="h5 mb-0 text-info"><?= (int) ($summary['doctor_count'] ?? 0) ?></div></div></div></div>
|
||||
<div class="col-xl-3 col-md-4 col-6"><div class="card border-secondary"><div class="card-body py-2"><div class="small text-muted">Payout Records / Amount</div><div class="h6 mb-0"><?= (int) ($summary['payout_count'] ?? 0) ?> / <?= esc($money($summary['payout_amount'] ?? 0)) ?></div></div></div></div>
|
||||
</div>
|
||||
|
||||
<div class="row g-2 mb-3">
|
||||
<div class="col-xl-3 col-md-4 col-6"><div class="card border-success"><div class="card-body py-2"><div class="small text-muted">Gross Charges Amount</div><div class="h5 mb-0 text-success"><?= esc($money($summary['gross_amount'] ?? 0)) ?></div></div></div></div>
|
||||
<div class="col-xl-3 col-md-4 col-6"><div class="card border-success"><div class="card-body py-2"><div class="small text-muted">Total Received</div><div class="h5 mb-0 text-success"><?= esc($money($summary['total_received'] ?? 0)) ?></div></div></div></div>
|
||||
<div class="col-xl-3 col-md-4 col-6"><div class="card border-warning"><div class="card-body py-2"><div class="small text-muted">Cash Received</div><div class="h5 mb-0 text-warning"><?= esc($money($summary['cash_received'] ?? 0)) ?></div></div></div></div>
|
||||
<div class="col-xl-3 col-md-4 col-6"><div class="card border-primary"><div class="card-body py-2"><div class="small text-muted">Bank Received</div><div class="h5 mb-0 text-primary"><?= esc($money($summary['bank_received'] ?? 0)) ?></div></div></div></div>
|
||||
</div>
|
||||
|
||||
<div class="card border-0 shadow-sm">
|
||||
<div class="card-header bg-white d-flex justify-content-between align-items-center flex-wrap gap-2">
|
||||
<strong>Doctor-wise <?= esc($sourceLabel) ?> Collection Summary</strong>
|
||||
<span class="small text-muted">Collection is allocated by invoice share for selected charge category.</span>
|
||||
</div>
|
||||
<div class="card-body p-0">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm table-striped align-middle mb-0">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th>#</th>
|
||||
<th>Doctor</th>
|
||||
<th class="text-end"><?= esc($unitLabel) ?></th>
|
||||
<th class="text-end">Gross Amount</th>
|
||||
<th class="text-end">Cash</th>
|
||||
<th class="text-end">Bank</th>
|
||||
<th class="text-end">Credit</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php if (empty($doctorBreakdown)): ?>
|
||||
<tr>
|
||||
<td colspan="7" class="text-center text-muted py-3">No summary records found for the selected filters.</td>
|
||||
</tr>
|
||||
<?php else: ?>
|
||||
<?php $sr = 1; foreach ($doctorBreakdown as $row): ?>
|
||||
<tr>
|
||||
<td><?= $sr++ ?></td>
|
||||
<td><?= esc((string) ($row['doctor_name'] ?? '')) ?></td>
|
||||
<td class="text-end"><?= (int) ($row['completed_opd'] ?? 0) ?></td>
|
||||
<td class="text-end"><?= esc($money($row['gross_amount'] ?? 0)) ?></td>
|
||||
<td class="text-end"><?= esc($money($row['cash_received'] ?? 0)) ?></td>
|
||||
<td class="text-end"><?= esc($money($row['bank_received'] ?? 0)) ?></td>
|
||||
<td class="text-end"><?= esc($money($row['credit_amount'] ?? 0)) ?></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php $baseForPayout = (float) (($summary['total_received'] ?? 0) > 0 ? ($summary['total_received'] ?? 0) : ($summary['gross_amount'] ?? 0)); ?>
|
||||
<div class="card border-0 shadow-sm mt-3">
|
||||
<div class="card-header bg-white">
|
||||
<strong>Payout Calculation (Preview: <?= esc($sourceLabel) ?>)</strong>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row g-2 align-items-end mb-2">
|
||||
<div class="col-lg-3 col-md-6">
|
||||
<label class="form-label form-label-sm">Base Amount</label>
|
||||
<input type="number" class="form-control form-control-sm" id="dc_payout_base_amount" value="<?= number_format($baseForPayout, 2, '.', '') ?>" step="0.01" min="0">
|
||||
</div>
|
||||
<div class="col-lg-2 col-md-6">
|
||||
<label class="form-label form-label-sm">Doctor Share %</label>
|
||||
<input type="number" class="form-control form-control-sm" id="dc_payout_doctor_share" value="75" step="0.01" min="0" max="100">
|
||||
</div>
|
||||
<div class="col-lg-2 col-md-6">
|
||||
<label class="form-label form-label-sm">Hospital Share %</label>
|
||||
<input type="number" class="form-control form-control-sm" id="dc_payout_hospital_share" value="25" step="0.01" min="0" max="100">
|
||||
</div>
|
||||
<div class="col-lg-2 col-md-6">
|
||||
<label class="form-label form-label-sm">Deductions</label>
|
||||
<input type="number" class="form-control form-control-sm" id="dc_payout_deductions" value="0" step="0.01" min="0">
|
||||
</div>
|
||||
<div class="col-lg-3 col-md-6">
|
||||
<label class="form-label form-label-sm">Adjustments (+/-)</label>
|
||||
<input type="number" class="form-control form-control-sm" id="dc_payout_adjustments" value="0" step="0.01">
|
||||
</div>
|
||||
<div class="col-12 d-flex justify-content-end">
|
||||
<button type="button" class="btn btn-success btn-sm" id="dc_payout_create_draft_btn">
|
||||
<i class="bi bi-file-earmark-plus me-1"></i>Create Payout Draft
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row g-2">
|
||||
<div class="col-md-4 col-12"><div class="card border-info"><div class="card-body py-2"><div class="small text-muted">Doctor Gross Share</div><div class="h6 mb-0 text-info" id="dc_payout_doctor_gross">Rs 0.00</div></div></div></div>
|
||||
<div class="col-md-4 col-12"><div class="card border-secondary"><div class="card-body py-2"><div class="small text-muted">Hospital Share</div><div class="h6 mb-0" id="dc_payout_hospital_gross">Rs 0.00</div></div></div></div>
|
||||
<div class="col-md-4 col-12"><div class="card border-success"><div class="card-body py-2"><div class="small text-muted">Net Payable To Doctor</div><div class="h6 mb-0 text-success" id="dc_payout_net_payable">Rs 0.00</div></div></div></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
(function () {
|
||||
function toNumber(id) {
|
||||
var el = document.getElementById(id);
|
||||
if (!el) return 0;
|
||||
var value = parseFloat(el.value || '0');
|
||||
return isNaN(value) ? 0 : value;
|
||||
}
|
||||
|
||||
function formatMoney(value) {
|
||||
var num = isNaN(value) ? 0 : value;
|
||||
return 'Rs ' + num.toLocaleString('en-IN', {minimumFractionDigits: 2, maximumFractionDigits: 2});
|
||||
}
|
||||
|
||||
function recalc() {
|
||||
var baseAmount = toNumber('dc_payout_base_amount');
|
||||
var doctorShare = toNumber('dc_payout_doctor_share');
|
||||
var hospitalShare = toNumber('dc_payout_hospital_share');
|
||||
var deductions = toNumber('dc_payout_deductions');
|
||||
var adjustments = toNumber('dc_payout_adjustments');
|
||||
|
||||
var doctorGross = (baseAmount * doctorShare) / 100;
|
||||
var hospitalGross = (baseAmount * hospitalShare) / 100;
|
||||
var netPayable = doctorGross - deductions + adjustments;
|
||||
|
||||
var doctorEl = document.getElementById('dc_payout_doctor_gross');
|
||||
var hospitalEl = document.getElementById('dc_payout_hospital_gross');
|
||||
var netEl = document.getElementById('dc_payout_net_payable');
|
||||
|
||||
if (doctorEl) doctorEl.textContent = formatMoney(doctorGross);
|
||||
if (hospitalEl) hospitalEl.textContent = formatMoney(hospitalGross);
|
||||
if (netEl) netEl.textContent = formatMoney(netPayable);
|
||||
}
|
||||
|
||||
['dc_payout_base_amount', 'dc_payout_doctor_share', 'dc_payout_hospital_share', 'dc_payout_deductions', 'dc_payout_adjustments']
|
||||
.forEach(function (id) {
|
||||
var el = document.getElementById(id);
|
||||
if (el) {
|
||||
el.addEventListener('input', recalc);
|
||||
}
|
||||
});
|
||||
|
||||
var draftBtn = document.getElementById('dc_payout_create_draft_btn');
|
||||
if (draftBtn) {
|
||||
draftBtn.addEventListener('click', function () {
|
||||
var fromDate = document.getElementById('dc_payout_from_date')?.value || '';
|
||||
var toDate = document.getElementById('dc_payout_to_date')?.value || '';
|
||||
var doctorId = document.getElementById('dc_payout_doctor_id')?.value || '';
|
||||
var stateUnit = document.getElementById('dc_payout_state_unit')?.value || '';
|
||||
var payoutSource = document.getElementById('dc_payout_source')?.value || 'pathology';
|
||||
|
||||
if (!doctorId) {
|
||||
if (typeof window.showDcPayoutAlert === 'function') {
|
||||
window.showDcPayoutAlert('Please select a doctor before creating payout draft.', false);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
var fd = new window.FormData();
|
||||
fd.append('from_date', fromDate);
|
||||
fd.append('to_date', toDate);
|
||||
fd.append('doctor_id', doctorId);
|
||||
fd.append('state_unit', stateUnit);
|
||||
fd.append('payout_source', payoutSource);
|
||||
fd.append('base_amount', String(toNumber('dc_payout_base_amount')));
|
||||
fd.append('doctor_share', String(toNumber('dc_payout_doctor_share')));
|
||||
fd.append('hospital_share', String(toNumber('dc_payout_hospital_share')));
|
||||
fd.append('deductions', String(toNumber('dc_payout_deductions')));
|
||||
fd.append('adjustments', String(toNumber('dc_payout_adjustments')));
|
||||
|
||||
fetch('<?= base_url('Finance/payout/doctor-charges-draft-create') ?>', {
|
||||
method: 'POST',
|
||||
headers: {'X-Requested-With': 'XMLHttpRequest'},
|
||||
body: fd,
|
||||
})
|
||||
.then(function (res) { return res.json().then(function (data) { return {ok: res.ok, data: data}; }); })
|
||||
.then(function (result) {
|
||||
var ok = result.ok && result.data && result.data.status === 1;
|
||||
var msg = (result.data && result.data.message) ? result.data.message : (ok ? 'Payout draft created.' : 'Unable to create payout draft.');
|
||||
if (ok && result.data.case_reference) {
|
||||
msg += ' Ref: ' + result.data.case_reference;
|
||||
}
|
||||
if (typeof window.showDcPayoutAlert === 'function') {
|
||||
window.showDcPayoutAlert(msg, ok);
|
||||
}
|
||||
if (ok && typeof window.refreshDcPayoutSummary === 'function') {
|
||||
window.refreshDcPayoutSummary();
|
||||
}
|
||||
})
|
||||
.catch(function () {
|
||||
if (typeof window.showDcPayoutAlert === 'function') {
|
||||
window.showDcPayoutAlert('Network or server error while creating payout draft.', false);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
recalc();
|
||||
})();
|
||||
</script>
|
||||
|
|
@ -18,17 +18,34 @@
|
|||
<tr><td colspan="9" class="text-center text-muted py-3">No requests found.</td></tr>
|
||||
<?php else: ?>
|
||||
<?php foreach (($rows ?? []) as $i => $row): ?>
|
||||
<?php
|
||||
$status = (string) ($row['status'] ?? '');
|
||||
$badgeClass = 'bg-secondary';
|
||||
if ($status === 'submitted') {
|
||||
$badgeClass = 'bg-primary';
|
||||
} elseif ($status === 'finance_review') {
|
||||
$badgeClass = 'bg-warning text-dark';
|
||||
} elseif ($status === 'approved') {
|
||||
$badgeClass = 'bg-success';
|
||||
} elseif ($status === 'partially_paid') {
|
||||
$badgeClass = 'bg-info text-dark';
|
||||
} elseif ($status === 'paid') {
|
||||
$badgeClass = 'bg-dark';
|
||||
} elseif ($status === 'rejected') {
|
||||
$badgeClass = 'bg-danger';
|
||||
}
|
||||
?>
|
||||
<tr>
|
||||
<td><?= (int) $i + 1 ?></td>
|
||||
<td><strong><?= esc((string) ($row['request_no'] ?? '')) ?></strong><br><small class="text-muted">ID: <?= (int) ($row['id'] ?? 0) ?></small></td>
|
||||
<td><?= esc((string) ($row['request_date'] ?? '')) ?></td>
|
||||
<td><span class="badge bg-secondary"><?= esc((string) ($row['status'] ?? '')) ?></span></td>
|
||||
<td><span class="badge <?= esc($badgeClass) ?>"><?= esc($status) ?></span></td>
|
||||
<td class="text-end"><?= esc(number_format((float) ($row['requested_amount'] ?? 0), 2)) ?></td>
|
||||
<td class="text-end"><?= esc(number_format((float) ($row['paid_amount'] ?? 0), 2)) ?></td>
|
||||
<td class="text-end"><?= esc(number_format((float) ($row['pending_amount'] ?? 0), 2)) ?></td>
|
||||
<td><?= esc((string) ($row['created_by'] ?? '')) ?></td>
|
||||
<td class="text-center">
|
||||
<button type="button" class="btn btn-outline-primary btn-sm" onclick="openMedicalStoreRequestDetail(<?= (int) ($row['id'] ?? 0) ?>)">View</button>
|
||||
<button type="button" class="btn btn-outline-primary btn-sm" onclick="openMedicalStoreRequestAction(<?= (int) ($row['id'] ?? 0) ?>)">Next</button>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
|
|
|
|||
|
|
@ -1,10 +1,13 @@
|
|||
<?php
|
||||
$rows = $rows ?? [];
|
||||
$sourceLabel = trim((string) ($source_label ?? 'OPD Consultation'));
|
||||
$sourceType = trim((string) ($source_type ?? 'consultation'));
|
||||
$isConsultation = $sourceType === 'consultation';
|
||||
?>
|
||||
|
||||
<div class="card border-0 shadow-sm">
|
||||
<div class="card-header bg-white d-flex justify-content-between align-items-center">
|
||||
<strong>Recent OPD Payout Drafts</strong>
|
||||
<strong>Recent <?= esc($sourceLabel) ?> Payout Drafts</strong>
|
||||
<span class="small text-muted">Filtered by current date/doctor/state selection</span>
|
||||
</div>
|
||||
<div class="card-body p-0">
|
||||
|
|
@ -16,7 +19,7 @@ $rows = $rows ?? [];
|
|||
<th>Payout Date</th>
|
||||
<th>Case Ref</th>
|
||||
<th>Doctor</th>
|
||||
<th class="text-end">Units</th>
|
||||
<th class="text-end"><?= $isConsultation ? 'OPDs' : 'Invoices' ?></th>
|
||||
<th class="text-end">Rate</th>
|
||||
<th class="text-end">Calculated</th>
|
||||
<th class="text-end">Approved</th>
|
||||
|
|
|
|||
|
|
@ -6,6 +6,11 @@ $toDate = (string) ($to_date ?? '');
|
|||
$doctorName = trim((string) ($doctor_name ?? 'All Doctors'));
|
||||
$stateUnit = trim((string) ($state_unit ?? ''));
|
||||
$stateUnitLabel = trim((string) ($state_unit_label ?? 'State/Unit'));
|
||||
$sourceType = trim((string) ($source_type ?? 'consultation'));
|
||||
$sourceLabel = trim((string) ($source_label ?? 'OPD Consultation'));
|
||||
$scopeLabel = trim((string) ($scope_label ?? 'Completed OPDs'));
|
||||
$unitLabel = trim((string) ($unit_label ?? 'OPDs'));
|
||||
$isConsultation = $sourceType === 'consultation';
|
||||
|
||||
$formatDate = static function (string $value): string {
|
||||
$value = trim($value);
|
||||
|
|
@ -24,6 +29,9 @@ $money = static function ($value): string {
|
|||
|
||||
<div class="card border-0 bg-light mb-3">
|
||||
<div class="card-body py-2 px-3 d-flex justify-content-between align-items-center flex-wrap gap-2">
|
||||
<div>
|
||||
<strong>Source:</strong> <?= esc($sourceLabel) ?>
|
||||
</div>
|
||||
<div>
|
||||
<strong>Summary Range:</strong> <?= esc($formatDate($fromDate)) ?> to <?= esc($formatDate($toDate)) ?>
|
||||
</div>
|
||||
|
|
@ -37,18 +45,25 @@ $money = static function ($value): string {
|
|||
</div>
|
||||
|
||||
<div class="row g-2 mb-3">
|
||||
<div class="col-xl-3 col-md-4 col-6"><div class="card border-primary"><div class="card-body py-2"><div class="small text-muted">Completed OPDs</div><div class="h5 mb-0 text-primary"><?= (int) ($summary['completed_opd'] ?? 0) ?></div></div></div></div>
|
||||
<div class="col-xl-3 col-md-4 col-6"><div class="card border-primary"><div class="card-body py-2"><div class="small text-muted"><?= esc($scopeLabel) ?></div><div class="h5 mb-0 text-primary"><?= (int) ($summary['completed_opd'] ?? 0) ?></div></div></div></div>
|
||||
<?php if ($isConsultation): ?>
|
||||
<div class="col-xl-3 col-md-4 col-6"><div class="card border-secondary"><div class="card-body py-2"><div class="small text-muted">Routine OPDs</div><div class="h5 mb-0"><?= (int) ($summary['routine_opd'] ?? 0) ?></div></div></div></div>
|
||||
<div class="col-xl-3 col-md-4 col-6"><div class="card border-danger"><div class="card-body py-2"><div class="small text-muted">Emergency OPDs</div><div class="h5 mb-0 text-danger"><?= (int) ($summary['emergency_opd'] ?? 0) ?></div></div></div></div>
|
||||
<?php else: ?>
|
||||
<div class="col-xl-3 col-md-4 col-6"><div class="card border-secondary"><div class="card-body py-2"><div class="small text-muted">Calculated Invoices</div><div class="h5 mb-0"><?= (int) ($summary['calculated_opd'] ?? 0) ?></div></div></div></div>
|
||||
<div class="col-xl-3 col-md-4 col-6"><div class="card border-danger"><div class="card-body py-2"><div class="small text-muted">Locked Credit</div><div class="h5 mb-0 text-danger"><?= esc($money($summary['credit_amount'] ?? 0)) ?></div></div></div></div>
|
||||
<?php endif; ?>
|
||||
<div class="col-xl-3 col-md-4 col-6"><div class="card border-info"><div class="card-body py-2"><div class="small text-muted">Doctors Involved</div><div class="h5 mb-0 text-info"><?= (int) ($summary['doctor_count'] ?? 0) ?></div></div></div></div>
|
||||
</div>
|
||||
|
||||
<?php if ($isConsultation): ?>
|
||||
<div class="row g-2 mb-3">
|
||||
<div class="col-xl-3 col-md-4 col-6"><div class="card border-dark"><div class="card-body py-2"><div class="small text-muted">Already Calculated OPDs</div><div class="h5 mb-0\"><?= (int) ($summary['calculated_opd'] ?? 0) ?></div></div></div></div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="row g-2 mb-3">
|
||||
<div class="col-xl-3 col-md-4 col-6"><div class="card border-success"><div class="card-body py-2"><div class="small text-muted">Gross OPD Revenue</div><div class="h5 mb-0 text-success"><?= esc($money($summary['gross_amount'] ?? 0)) ?></div></div></div></div>
|
||||
<div class="col-xl-3 col-md-4 col-6"><div class="card border-success"><div class="card-body py-2"><div class="small text-muted"><?= $isConsultation ? 'Gross OPD Revenue' : 'Gross Charges Amount' ?></div><div class="h5 mb-0 text-success"><?= esc($money($summary['gross_amount'] ?? 0)) ?></div></div></div></div>
|
||||
<div class="col-xl-3 col-md-4 col-6"><div class="card border-success"><div class="card-body py-2"><div class="small text-muted">Total Received</div><div class="h5 mb-0 text-success"><?= esc($money($summary['total_received'] ?? 0)) ?></div></div></div></div>
|
||||
<div class="col-xl-3 col-md-4 col-6"><div class="card border-warning"><div class="card-body py-2"><div class="small text-muted">Cash Received</div><div class="h5 mb-0 text-warning"><?= esc($money($summary['cash_received'] ?? 0)) ?></div></div></div></div>
|
||||
<div class="col-xl-3 col-md-4 col-6"><div class="card border-primary"><div class="card-body py-2"><div class="small text-muted">Bank Received</div><div class="h5 mb-0 text-primary"><?= esc($money($summary['bank_received'] ?? 0)) ?></div></div></div></div>
|
||||
|
|
@ -57,14 +72,14 @@ $money = static function ($value): string {
|
|||
<div class="row g-2 mb-3">
|
||||
<div class="col-xl-3 col-md-4 col-6"><div class="card border-dark"><div class="card-body py-2"><div class="small text-muted">Total Credit Amount</div><div class="h5 mb-0"><?= esc($money($summary['credit_amount'] ?? 0)) ?></div></div></div></div>
|
||||
<div class="col-xl-3 col-md-4 col-6"><div class="card border-dark"><div class="card-body py-2"><div class="small text-muted">Organizational Credit</div><div class="h5 mb-0"><?= esc($money($summary['org_credit_amount'] ?? 0)) ?></div></div></div></div>
|
||||
<div class="col-xl-3 col-md-4 col-6"><div class="card border-info"><div class="card-body py-2"><div class="small text-muted">Approved Consents</div><div class="h5 mb-0 text-info"><?= (int) ($summary['approved_consents'] ?? 0) ?></div></div></div></div>
|
||||
<div class="col-xl-3 col-md-4 col-6"><div class="card border-info"><div class="card-body py-2"><div class="small text-muted"><?= $isConsultation ? 'Approved Consents' : 'Charge Group' ?></div><div class="h5 mb-0 text-info"><?= $isConsultation ? (int) ($summary['approved_consents'] ?? 0) : esc($sourceLabel) ?></div></div></div></div>
|
||||
<div class="col-xl-3 col-md-4 col-6"><div class="card border-secondary"><div class="card-body py-2"><div class="small text-muted">Payout Records / Amount</div><div class="h6 mb-0"><?= (int) ($summary['payout_count'] ?? 0) ?> / <?= esc($money($summary['payout_amount'] ?? 0)) ?></div></div></div></div>
|
||||
</div>
|
||||
|
||||
<div class="card border-0 shadow-sm">
|
||||
<div class="card-header bg-white d-flex justify-content-between align-items-center flex-wrap gap-2">
|
||||
<strong>Doctor-wise OPD and Collection Summary</strong>
|
||||
<span class="small text-muted">Running and New are shown for internal review; routine figure is non-emergency total.</span>
|
||||
<strong>Doctor-wise <?= esc($sourceLabel) ?> and Collection Summary</strong>
|
||||
<span class="small text-muted"><?= $isConsultation ? 'Running and New are shown for internal review; routine figure is non-emergency total.' : 'Collection is allocated by invoice share for selected charge category.' ?></span>
|
||||
</div>
|
||||
<div class="card-body p-0">
|
||||
<div class="table-responsive">
|
||||
|
|
@ -73,11 +88,13 @@ $money = static function ($value): string {
|
|||
<tr>
|
||||
<th>#</th>
|
||||
<th>Doctor</th>
|
||||
<th class="text-end">Completed</th>
|
||||
<th class="text-end"><?= esc($unitLabel) ?></th>
|
||||
<?php if ($isConsultation): ?>
|
||||
<th class="text-end">Routine</th>
|
||||
<th class="text-end">Emergency</th>
|
||||
<th class="text-end">Running</th>
|
||||
<th class="text-end">New</th>
|
||||
<?php endif; ?>
|
||||
<th class="text-end">Gross Amount</th>
|
||||
<th class="text-end">Cash</th>
|
||||
<th class="text-end">Bank</th>
|
||||
|
|
@ -87,7 +104,7 @@ $money = static function ($value): string {
|
|||
<tbody>
|
||||
<?php if (empty($doctorBreakdown)): ?>
|
||||
<tr>
|
||||
<td colspan="11" class="text-center text-muted py-3">No OPD summary records found for the selected filters.</td>
|
||||
<td colspan="<?= $isConsultation ? '11' : '7' ?>" class="text-center text-muted py-3">No summary records found for the selected filters.</td>
|
||||
</tr>
|
||||
<?php else: ?>
|
||||
<?php $sr = 1; foreach ($doctorBreakdown as $row): ?>
|
||||
|
|
@ -95,10 +112,12 @@ $money = static function ($value): string {
|
|||
<td><?= $sr++ ?></td>
|
||||
<td><?= esc((string) ($row['doctor_name'] ?? '')) ?></td>
|
||||
<td class="text-end"><?= (int) ($row['completed_opd'] ?? 0) ?></td>
|
||||
<?php if ($isConsultation): ?>
|
||||
<td class="text-end"><?= (int) ($row['routine_opd'] ?? 0) ?></td>
|
||||
<td class="text-end"><?= (int) ($row['emergency_opd'] ?? 0) ?></td>
|
||||
<td class="text-end"><?= (int) ($row['running_opd'] ?? 0) ?></td>
|
||||
<td class="text-end"><?= (int) ($row['new_opd'] ?? 0) ?></td>
|
||||
<?php endif; ?>
|
||||
<td class="text-end"><?= esc($money($row['gross_amount'] ?? 0)) ?></td>
|
||||
<td class="text-end"><?= esc($money($row['cash_received'] ?? 0)) ?></td>
|
||||
<td class="text-end"><?= esc($money($row['bank_received'] ?? 0)) ?></td>
|
||||
|
|
@ -112,12 +131,10 @@ $money = static function ($value): string {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<?php
|
||||
$baseForPayout = (float) ($summary['total_received'] ?? 0);
|
||||
?>
|
||||
<?php $baseForPayout = (float) (($summary['total_received'] ?? 0) > 0 ? ($summary['total_received'] ?? 0) : ($summary['gross_amount'] ?? 0)); ?>
|
||||
<div class="card border-0 shadow-sm mt-3">
|
||||
<div class="card-header bg-white">
|
||||
<strong>Payout Calculation (Preview)</strong>
|
||||
<strong>Payout Calculation (Preview: <?= esc($sourceLabel) ?>)</strong>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row g-2 align-items-end mb-2">
|
||||
|
|
@ -125,37 +142,26 @@ $baseForPayout = (float) ($summary['total_received'] ?? 0);
|
|||
<label class="form-label form-label-sm">Base Amount</label>
|
||||
<input type="number" class="form-control form-control-sm" id="opd_payout_base_amount" value="<?= number_format($baseForPayout, 2, '.', '') ?>" step="0.01" min="0">
|
||||
</div>
|
||||
<div class="col-lg-2 col-md-6">
|
||||
<label class="form-label form-label-sm">Doctor Share %</label>
|
||||
<input type="number" class="form-control form-control-sm" id="opd_payout_doctor_share" value="75" step="0.01" min="0" max="100">
|
||||
<div class="row g-1 mb-2">
|
||||
<div class="col-xl-2 col-md-3 col-6"><div class="card border-start border-primary border-2 border-top-0 border-end-0 border-bottom-0 rounded-0"><div class="card-body py-1 px-2"><div class="text-muted" style="font-size:.7rem"><?= esc($scopeLabel) ?></div><div class="fw-bold text-primary"><?= (int) ($summary['completed_opd'] ?? 0) ?></div></div></div></div>
|
||||
<?php if ($isConsultation): ?>
|
||||
<div class="col-xl-2 col-md-3 col-6"><div class="card border-start border-secondary border-2 border-top-0 border-end-0 border-bottom-0 rounded-0"><div class="card-body py-1 px-2"><div class="text-muted" style="font-size:.7rem">Routine OPDs</div><div class="fw-bold"><?= (int) ($summary['routine_opd'] ?? 0) ?></div></div></div></div>
|
||||
<div class="col-xl-2 col-md-3 col-6"><div class="card border-start border-danger border-2 border-top-0 border-end-0 border-bottom-0 rounded-0"><div class="card-body py-1 px-2"><div class="text-muted" style="font-size:.7rem">Emergency OPDs</div><div class="fw-bold text-danger"><?= (int) ($summary['emergency_opd'] ?? 0) ?></div></div></div></div>
|
||||
<div class="col-xl-2 col-md-3 col-6"><div class="card border-start border-dark border-2 border-top-0 border-end-0 border-bottom-0 rounded-0"><div class="card-body py-1 px-2"><div class="text-muted" style="font-size:.7rem">Already Calculated</div><div class="fw-bold"><?= (int) ($summary['calculated_opd'] ?? 0) ?></div></div></div></div>
|
||||
<?php else: ?>
|
||||
<div class="col-xl-2 col-md-3 col-6"><div class="card border-start border-secondary border-2 border-top-0 border-end-0 border-bottom-0 rounded-0"><div class="card-body py-1 px-2"><div class="text-muted" style="font-size:.7rem">Already Calculated</div><div class="fw-bold"><?= (int) ($summary['calculated_opd'] ?? 0) ?></div></div></div></div>
|
||||
<div class="col-xl-2 col-md-3 col-6"><div class="card border-start border-danger border-2 border-top-0 border-end-0 border-bottom-0 rounded-0"><div class="card-body py-1 px-2"><div class="text-muted" style="font-size:.7rem">Locked Credit</div><div class="fw-bold text-danger" style="font-size:.85rem"><?= esc($money($summary['credit_amount'] ?? 0)) ?></div></div></div></div>
|
||||
<?php endif; ?>
|
||||
<div class="col-xl-2 col-md-3 col-6"><div class="card border-start border-info border-2 border-top-0 border-end-0 border-bottom-0 rounded-0"><div class="card-body py-1 px-2"><div class="text-muted" style="font-size:.7rem">Doctors Involved</div><div class="fw-bold text-info"><?= (int) ($summary['doctor_count'] ?? 0) ?></div></div></div></div>
|
||||
<div class="col-xl-2 col-md-3 col-6"><div class="card border-start border-success border-2 border-top-0 border-end-0 border-bottom-0 rounded-0"><div class="card-body py-1 px-2"><div class="text-muted" style="font-size:.7rem"><?= $isConsultation ? 'Gross OPD Revenue' : 'Gross Charges' ?></div><div class="fw-bold text-success" style="font-size:.85rem"><?= esc($money($summary['gross_amount'] ?? 0)) ?></div></div></div></div>
|
||||
<div class="col-xl-2 col-md-3 col-6"><div class="card border-start border-success border-2 border-top-0 border-end-0 border-bottom-0 rounded-0"><div class="card-body py-1 px-2"><div class="text-muted" style="font-size:.7rem">Total Received</div><div class="fw-bold text-success" style="font-size:.85rem"><?= esc($money($summary['total_received'] ?? 0)) ?></div></div></div></div>
|
||||
<div class="col-xl-2 col-md-3 col-6"><div class="card border-start border-warning border-2 border-top-0 border-end-0 border-bottom-0 rounded-0"><div class="card-body py-1 px-2"><div class="text-muted" style="font-size:.7rem">Cash Received</div><div class="fw-bold text-warning" style="font-size:.85rem"><?= esc($money($summary['cash_received'] ?? 0)) ?></div></div></div></div>
|
||||
<div class="col-xl-2 col-md-3 col-6"><div class="card border-start border-primary border-2 border-top-0 border-end-0 border-bottom-0 rounded-0"><div class="card-body py-1 px-2"><div class="text-muted" style="font-size:.7rem">Bank Received</div><div class="fw-bold text-primary" style="font-size:.85rem"><?= esc($money($summary['bank_received'] ?? 0)) ?></div></div></div></div>
|
||||
<div class="col-xl-2 col-md-3 col-6"><div class="card border-start border-dark border-2 border-top-0 border-end-0 border-bottom-0 rounded-0"><div class="card-body py-1 px-2"><div class="text-muted" style="font-size:.7rem">Total Credit</div><div class="fw-bold" style="font-size:.85rem"><?= esc($money($summary['credit_amount'] ?? 0)) ?></div></div></div></div>
|
||||
<div class="col-xl-2 col-md-3 col-6"><div class="card border-start border-dark border-2 border-top-0 border-end-0 border-bottom-0 rounded-0"><div class="card-body py-1 px-2"><div class="text-muted" style="font-size:.7rem">Org. Credit</div><div class="fw-bold" style="font-size:.85rem"><?= esc($money($summary['org_credit_amount'] ?? 0)) ?></div></div></div></div>
|
||||
<div class="col-xl-2 col-md-3 col-6"><div class="card border-start border-info border-2 border-top-0 border-end-0 border-bottom-0 rounded-0"><div class="card-body py-1 px-2"><div class="text-muted" style="font-size:.7rem"><?= $isConsultation ? 'Approved Consents' : 'Charge Group' ?></div><div class="fw-bold text-info"><?= $isConsultation ? (int) ($summary['approved_consents'] ?? 0) : esc($sourceLabel) ?></div></div></div></div>
|
||||
<div class="col-xl-2 col-md-3 col-6"><div class="card border-start border-secondary border-2 border-top-0 border-end-0 border-bottom-0 rounded-0"><div class="card-body py-1 px-2"><div class="text-muted" style="font-size:.7rem">Payout Drafts / Amt</div><div class="fw-bold" style="font-size:.8rem"><?= (int) ($summary['payout_count'] ?? 0) ?> / <?= esc($money($summary['payout_amount'] ?? 0)) ?></div></div></div></div>
|
||||
</div>
|
||||
<div class="col-lg-2 col-md-6">
|
||||
<label class="form-label form-label-sm">Hospital Share %</label>
|
||||
<input type="number" class="form-control form-control-sm" id="opd_payout_hospital_share" value="25" step="0.01" min="0" max="100">
|
||||
</div>
|
||||
<div class="col-lg-2 col-md-6">
|
||||
<label class="form-label form-label-sm">Deductions</label>
|
||||
<input type="number" class="form-control form-control-sm" id="opd_payout_deductions" value="0" step="0.01" min="0">
|
||||
</div>
|
||||
<div class="col-lg-3 col-md-6">
|
||||
<label class="form-label form-label-sm">Adjustments (+/-)</label>
|
||||
<input type="number" class="form-control form-control-sm" id="opd_payout_adjustments" value="0" step="0.01">
|
||||
</div>
|
||||
<div class="col-12 d-flex justify-content-end">
|
||||
<button type="button" class="btn btn-success btn-sm" id="opd_payout_create_draft_btn">
|
||||
<i class="bi bi-file-earmark-plus me-1"></i>Create Payout Draft
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row g-2">
|
||||
<div class="col-md-4 col-12"><div class="card border-info"><div class="card-body py-2"><div class="small text-muted">Doctor Gross Share</div><div class="h6 mb-0 text-info" id="opd_payout_doctor_gross">Rs 0.00</div></div></div></div>
|
||||
<div class="col-md-4 col-12"><div class="card border-secondary"><div class="card-body py-2"><div class="small text-muted">Hospital Share</div><div class="h6 mb-0" id="opd_payout_hospital_gross">Rs 0.00</div></div></div></div>
|
||||
<div class="col-md-4 col-12"><div class="card border-success"><div class="card-body py-2"><div class="small text-muted">Net Payable To Doctor</div><div class="h6 mb-0 text-success" id="opd_payout_net_payable">Rs 0.00</div></div></div></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
(function () {
|
||||
function toNumber(id) {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<section class="content finance-opd-payout">
|
||||
<div class="mb-3">
|
||||
<h2 class="mb-1">OPD Consult Payout</h2>
|
||||
<p class="text-muted mb-0">Start payout calculation from OPD consultation and collection data. This screen is enabled for testing and will be extended in the next phase.</p>
|
||||
<h2 class="mb-1">OPD Consultation Payout</h2>
|
||||
<p class="text-muted mb-0">Calculate and manage doctor payout from OPD consultation fees.</p>
|
||||
</div>
|
||||
|
||||
<div id="opd_payout_alert"></div>
|
||||
|
|
@ -42,13 +42,6 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
|
||||
<div class="alert alert-info mb-0" role="alert">
|
||||
<strong>Phase note:</strong> Menu and page are now available under Accounts And Finance -> Payout -> OPD Consult Payout.
|
||||
Calculation grid, policy split engine, and payout approval workflow will be implemented next.
|
||||
</div>
|
||||
|
||||
<div class="mt-3" id="opd_consult_payout_summary_wrap">
|
||||
<div class="text-muted">Loading summary...</div>
|
||||
</div>
|
||||
|
|
@ -106,7 +99,6 @@
|
|||
var toDate = document.getElementById('opd_payout_to_date')?.value || '';
|
||||
var doctorId = document.getElementById('opd_payout_doctor_id')?.value || '';
|
||||
var stateUnit = document.getElementById('opd_payout_state_unit')?.value || '';
|
||||
|
||||
load_form_div(
|
||||
'<?= base_url('Finance/payout/opd-consult-summary') ?>?from_date=' + encodeURIComponent(fromDate)
|
||||
+ '&to_date=' + encodeURIComponent(toDate)
|
||||
|
|
@ -174,7 +166,7 @@
|
|||
};
|
||||
|
||||
window.deleteOpdPayoutDraft = function (payoutId) {
|
||||
if (!window.confirm('Delete this payout draft? Linked OPDs will be unlocked for recalculation.')) {
|
||||
if (!window.confirm('Delete this payout draft? Linked source records will be unlocked for recalculation.')) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -198,5 +190,7 @@
|
|||
};
|
||||
|
||||
refreshOpdConsultPayoutSummary();
|
||||
|
||||
|
||||
})();
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -10,6 +10,88 @@
|
|||
|
||||
<section class="section">
|
||||
<div id="med_credit_req_alert"></div>
|
||||
<style>
|
||||
#med_req_status_dashboard_strip {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(7, minmax(110px, 1fr));
|
||||
gap: 8px;
|
||||
}
|
||||
#med_req_status_dashboard_strip .med-stat {
|
||||
border: 1px solid #d9dee6;
|
||||
border-radius: 6px;
|
||||
padding: 8px 10px;
|
||||
background: #fff;
|
||||
min-height: 58px;
|
||||
}
|
||||
#med_req_status_dashboard_strip .med-stat-label {
|
||||
color: #6c757d;
|
||||
font-size: 12px;
|
||||
line-height: 1.1;
|
||||
}
|
||||
#med_req_status_dashboard_strip .med-stat-value {
|
||||
font-size: 28px;
|
||||
font-weight: 600;
|
||||
line-height: 1;
|
||||
margin-top: 4px;
|
||||
}
|
||||
#med_request_history_table {
|
||||
width: 100% !important;
|
||||
}
|
||||
@media (max-width: 1200px) {
|
||||
#med_req_status_dashboard_strip {
|
||||
grid-template-columns: repeat(4, minmax(110px, 1fr));
|
||||
}
|
||||
}
|
||||
@media (max-width: 768px) {
|
||||
#med_req_status_dashboard_strip {
|
||||
grid-template-columns: repeat(2, minmax(110px, 1fr));
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<?php
|
||||
$historyRows = $rows ?? [];
|
||||
$statusCounts = [
|
||||
'submitted' => 0,
|
||||
'finance_review' => 0,
|
||||
'approved' => 0,
|
||||
'partially_paid' => 0,
|
||||
'paid' => 0,
|
||||
'rejected' => 0,
|
||||
];
|
||||
foreach ($historyRows as $historyRow) {
|
||||
$st = strtolower(trim((string) ($historyRow['status'] ?? '')));
|
||||
if (array_key_exists($st, $statusCounts)) {
|
||||
$statusCounts[$st]++;
|
||||
}
|
||||
}
|
||||
?>
|
||||
|
||||
<div class="card mb-3" id="med_req_status_dashboard">
|
||||
<div class="card-body p-2">
|
||||
<div id="med_req_status_dashboard_strip">
|
||||
<div class="med-stat"><div class="med-stat-label">Total</div><div class="med-stat-value text-dark" id="med_dash_total"><?= (int) count($historyRows) ?></div></div>
|
||||
<div class="med-stat"><div class="med-stat-label">Submitted</div><div class="med-stat-value text-primary" id="med_dash_submitted"><?= (int) ($statusCounts['submitted'] ?? 0) ?></div></div>
|
||||
<div class="med-stat"><div class="med-stat-label">Finance Review</div><div class="med-stat-value text-warning" id="med_dash_finance_review"><?= (int) ($statusCounts['finance_review'] ?? 0) ?></div></div>
|
||||
<div class="med-stat"><div class="med-stat-label">Approved</div><div class="med-stat-value text-success" id="med_dash_approved"><?= (int) ($statusCounts['approved'] ?? 0) ?></div></div>
|
||||
<div class="med-stat"><div class="med-stat-label">Partial</div><div class="med-stat-value text-info" id="med_dash_partially_paid"><?= (int) ($statusCounts['partially_paid'] ?? 0) ?></div></div>
|
||||
<div class="med-stat"><div class="med-stat-label">Paid</div><div class="med-stat-value text-secondary" id="med_dash_paid"><?= (int) ($statusCounts['paid'] ?? 0) ?></div></div>
|
||||
<div class="med-stat"><div class="med-stat-label">Rejected</div><div class="med-stat-value text-danger" id="med_dash_rejected"><?= (int) ($statusCounts['rejected'] ?? 0) ?></div></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ul class="nav nav-tabs" id="medCreditTabs" role="tablist">
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link active" id="med-credit-select-tab" data-bs-toggle="tab" data-bs-target="#med-credit-select-pane" type="button" role="tab" aria-controls="med-credit-select-pane" aria-selected="true">1) Select Credit Entries</button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link" id="med-credit-history-tab" data-bs-toggle="tab" data-bs-target="#med-credit-history-pane" type="button" role="tab" aria-controls="med-credit-history-pane" aria-selected="false">Request History</button>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div class="tab-content border border-top-0 p-3 bg-white" id="medCreditTabsContent">
|
||||
<div class="tab-pane fade show active" id="med-credit-select-pane" role="tabpanel" aria-labelledby="med-credit-select-tab" tabindex="0">
|
||||
|
||||
<div class="row g-3">
|
||||
<div class="col-xl-7">
|
||||
|
|
@ -117,13 +199,16 @@
|
|||
</div>
|
||||
</form>
|
||||
<hr>
|
||||
<div class="small text-muted">Request history is shown below. Use Finance panel for approval and payment settlement.</div>
|
||||
<div class="small text-muted">Use Request History tab to track status updates. Use Finance panel for approval and payment settlement.</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card mt-3">
|
||||
</div>
|
||||
<div class="tab-pane fade" id="med-credit-history-pane" role="tabpanel" aria-labelledby="med-credit-history-tab" tabindex="0">
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header d-flex justify-content-between align-items-center">
|
||||
<strong>Request History</strong>
|
||||
<button type="button" class="btn btn-outline-secondary btn-sm" id="btn_med_history_refresh">Refresh</button>
|
||||
|
|
@ -142,33 +227,20 @@
|
|||
<th class="text-end">Pending</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php if (empty($rows ?? [])): ?>
|
||||
<tr><td colspan="7" class="text-center text-muted py-3">No payout requests yet.</td></tr>
|
||||
<?php else: ?>
|
||||
<?php foreach (($rows ?? []) as $i => $row): ?>
|
||||
<tr>
|
||||
<td><?= (int) $i + 1 ?></td>
|
||||
<td><strong><?= esc((string) ($row['request_no'] ?? '')) ?></strong><br><small class="text-muted">ID: <?= (int) ($row['id'] ?? 0) ?></small></td>
|
||||
<td><?= esc((string) ($row['request_date'] ?? '')) ?></td>
|
||||
<td><span class="badge bg-secondary"><?= esc((string) ($row['status'] ?? '')) ?></span></td>
|
||||
<td class="text-end"><?= esc(number_format((float) ($row['requested_amount'] ?? 0), 2)) ?></td>
|
||||
<td class="text-end"><?= esc(number_format((float) ($row['paid_amount'] ?? 0), 2)) ?></td>
|
||||
<td class="text-end"><?= esc(number_format((float) ($row['pending_amount'] ?? 0), 2)) ?></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
<tbody><tr><td colspan="7" class="text-center text-muted py-3">Loading...</td></tr></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<script>
|
||||
(function () {
|
||||
var BASE = '<?= base_url() ?>';
|
||||
var poolTable = null;
|
||||
var historyTable = null;
|
||||
var selectedMap = {};
|
||||
var poolRowCache = {};
|
||||
|
||||
|
|
@ -179,6 +251,46 @@
|
|||
+ msg + '<button type="button" class="btn-close" data-bs-dismiss="alert"></button></div>';
|
||||
}
|
||||
|
||||
function getStatusBadgeClass(status) {
|
||||
var s = String(status || '').toLowerCase();
|
||||
if (s === 'submitted') return 'bg-primary';
|
||||
if (s === 'finance_review') return 'bg-warning text-dark';
|
||||
if (s === 'approved') return 'bg-success';
|
||||
if (s === 'partially_paid') return 'bg-info text-dark';
|
||||
if (s === 'paid') return 'bg-dark';
|
||||
if (s === 'rejected') return 'bg-danger';
|
||||
return 'bg-secondary';
|
||||
}
|
||||
|
||||
function refreshStatusDashboard(summary) {
|
||||
var s = summary || {};
|
||||
var counts = {
|
||||
total: Number(s.total || 0),
|
||||
submitted: Number(s.submitted || 0),
|
||||
finance_review: Number(s.finance_review || 0),
|
||||
approved: Number(s.approved || 0),
|
||||
partially_paid: Number(s.partially_paid || 0),
|
||||
paid: Number(s.paid || 0),
|
||||
rejected: Number(s.rejected || 0)
|
||||
};
|
||||
|
||||
var totalEl = document.getElementById('med_dash_total');
|
||||
var submittedEl = document.getElementById('med_dash_submitted');
|
||||
var reviewEl = document.getElementById('med_dash_finance_review');
|
||||
var approvedEl = document.getElementById('med_dash_approved');
|
||||
var partialEl = document.getElementById('med_dash_partially_paid');
|
||||
var paidEl = document.getElementById('med_dash_paid');
|
||||
var rejectedEl = document.getElementById('med_dash_rejected');
|
||||
|
||||
if (totalEl) totalEl.textContent = String(counts.total);
|
||||
if (submittedEl) submittedEl.textContent = String(counts.submitted);
|
||||
if (reviewEl) reviewEl.textContent = String(counts.finance_review);
|
||||
if (approvedEl) approvedEl.textContent = String(counts.approved);
|
||||
if (partialEl) partialEl.textContent = String(counts.partially_paid);
|
||||
if (paidEl) paidEl.textContent = String(counts.paid);
|
||||
if (rejectedEl) rejectedEl.textContent = String(counts.rejected);
|
||||
}
|
||||
|
||||
function getRowKey(row) {
|
||||
return String(row.source_type || '') + ':' + String(row.source_ref_id || 0);
|
||||
}
|
||||
|
|
@ -436,37 +548,89 @@
|
|||
}
|
||||
|
||||
function loadHistory() {
|
||||
fetch(BASE + 'Medical/credit_payout_requests', {
|
||||
method: 'GET',
|
||||
headers: {'X-Requested-With': 'XMLHttpRequest'}
|
||||
}).then(function (res) {
|
||||
return res.json().then(function (data) { return { ok: res.ok, data: data }; });
|
||||
}).then(function (result) {
|
||||
var table = document.querySelector('#med_request_history_table tbody');
|
||||
if (!table) return;
|
||||
if (!result.ok || !result.data || result.data.status !== 1) {
|
||||
table.innerHTML = '<tr><td colspan="7" class="text-center text-danger py-3">Unable to load request history.</td></tr>';
|
||||
return;
|
||||
if (!historyTable) {
|
||||
return;
|
||||
}
|
||||
historyTable.ajax.reload(null, false);
|
||||
}
|
||||
|
||||
function initHistoryTable() {
|
||||
if (typeof window.jQuery === 'undefined' || typeof window.jQuery.fn.DataTable === 'undefined') {
|
||||
showAlert('DataTable dependency missing. Unable to load request history.', false);
|
||||
return;
|
||||
}
|
||||
|
||||
historyTable = window.jQuery('#med_request_history_table').DataTable({
|
||||
processing: true,
|
||||
serverSide: true,
|
||||
searching: true,
|
||||
autoWidth: false,
|
||||
lengthMenu: [[10, 25, 50, 100], [10, 25, 50, 100]],
|
||||
pageLength: 25,
|
||||
order: [[0, 'desc']],
|
||||
ajax: {
|
||||
url: BASE + 'Medical/credit_payout_requests_datatable',
|
||||
type: 'POST',
|
||||
dataSrc: function (json) {
|
||||
refreshStatusDashboard(json && json.status_summary ? json.status_summary : {});
|
||||
return (json && Array.isArray(json.data)) ? json.data : [];
|
||||
},
|
||||
error: function () {
|
||||
refreshStatusDashboard({});
|
||||
showAlert('Unable to load request history.', false);
|
||||
}
|
||||
},
|
||||
columns: [
|
||||
{ data: 'row_no', orderable: false, searchable: false },
|
||||
{
|
||||
data: null,
|
||||
render: function (data, type, row) {
|
||||
return '<strong>' + String(row.request_no || '') + '</strong><br><small class="text-muted">ID: ' + Number(row.id || 0) + '</small>';
|
||||
}
|
||||
},
|
||||
{ data: 'request_date', defaultContent: '' },
|
||||
{
|
||||
data: 'status',
|
||||
render: function (v) {
|
||||
var status = String(v || '');
|
||||
return '<span class="badge ' + getStatusBadgeClass(status) + '">' + status + '</span>';
|
||||
}
|
||||
},
|
||||
{
|
||||
data: 'requested_amount',
|
||||
className: 'text-end',
|
||||
render: function (v) {
|
||||
return Number(v || 0).toFixed(2);
|
||||
}
|
||||
},
|
||||
{
|
||||
data: 'paid_amount',
|
||||
className: 'text-end',
|
||||
render: function (v) {
|
||||
return Number(v || 0).toFixed(2);
|
||||
}
|
||||
},
|
||||
{
|
||||
data: 'pending_amount',
|
||||
className: 'text-end',
|
||||
render: function (v) {
|
||||
return Number(v || 0).toFixed(2);
|
||||
}
|
||||
}
|
||||
],
|
||||
language: {
|
||||
emptyTable: 'No payout requests yet.'
|
||||
}
|
||||
var rows = result.data.rows || [];
|
||||
if (!rows.length) {
|
||||
table.innerHTML = '<tr><td colspan="7" class="text-center text-muted py-3">No payout requests yet.</td></tr>';
|
||||
return;
|
||||
}
|
||||
var html = '';
|
||||
rows.forEach(function (row, i) {
|
||||
html += '<tr>'
|
||||
+ '<td>' + (i + 1) + '</td>'
|
||||
+ '<td><strong>' + (row.request_no || '') + '</strong><br><small class="text-muted">ID: ' + Number(row.id || 0) + '</small></td>'
|
||||
+ '<td>' + (row.request_date || '') + '</td>'
|
||||
+ '<td><span class="badge bg-secondary">' + (row.status || '') + '</span></td>'
|
||||
+ '<td class="text-end">' + Number(row.requested_amount || 0).toFixed(2) + '</td>'
|
||||
+ '<td class="text-end">' + Number(row.paid_amount || 0).toFixed(2) + '</td>'
|
||||
+ '<td class="text-end">' + Number(row.pending_amount || 0).toFixed(2) + '</td>'
|
||||
+ '</tr>';
|
||||
});
|
||||
table.innerHTML = html;
|
||||
});
|
||||
|
||||
var historyTab = document.getElementById('med-credit-history-tab');
|
||||
if (historyTab) {
|
||||
historyTab.addEventListener('shown.bs.tab', function () {
|
||||
if (historyTable && historyTable.columns) {
|
||||
historyTable.columns.adjust().draw(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
var form = document.getElementById('med_credit_request_form');
|
||||
|
|
@ -613,6 +777,7 @@
|
|||
|
||||
syncScopeWithGroupMode();
|
||||
initPoolTable();
|
||||
initHistoryTable();
|
||||
loadHistory();
|
||||
})();
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -303,6 +303,12 @@ if (! $canChargesSettings && $user && method_exists($user, 'inGroup')) {
|
|||
<span>OPD Consult Payout</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="javascript:load_form('<?= base_url('Finance/payout/doctor-charges') ?>','Doctor Charges Payout')">
|
||||
<i class="bi bi-circle"></i>
|
||||
<span>Doctor Charges Payout</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="javascript:load_form('<?= base_url('Finance/medical_store_credit_account') ?>','Medical Store Credit Account')">
|
||||
<i class="bi bi-circle"></i>
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user