Implement medical store credit payout workflow and optimize payout pool DataTable grouping
This commit is contained in:
parent
ec6ceaea98
commit
36d3fa843b
|
|
@ -237,6 +237,12 @@ $routes->get('Medical/master', 'Medical::master');
|
|||
$routes->get('Medical/master_report/med-payment-edit', 'Payment_Medical::index');
|
||||
$routes->get('Medical/master_report/(:segment)', 'Medical::master_report/$1');
|
||||
$routes->get('Medical/master_link/(:segment)', 'Medical::master_link/$1');
|
||||
$routes->get('Medical/credit_payout_pool', 'Medical::credit_payout_pool');
|
||||
$routes->match(['get', 'post'], 'Medical/credit_payout_pool_datatable', 'Medical::credit_payout_pool_datatable');
|
||||
$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->get('Medical/credit_payout_request_detail/(:num)', 'Medical::credit_payout_request_detail/$1');
|
||||
|
||||
$routes->get('Payment_Medical', 'Payment_Medical::index');
|
||||
$routes->get('Payment_Medical/payment_log', 'Payment_Medical::payment_log');
|
||||
|
|
@ -263,6 +269,7 @@ $routes->get('Finance/scroll_items_table', 'Finance::scrollItemsTable');
|
|||
$routes->get('Finance/bank_deposits', 'Finance::bankDeposits');
|
||||
$routes->get('Finance/bank_deposits_table', 'Finance::bankDepositsTable');
|
||||
$routes->get('Finance/bank_audit', 'Finance::bankAudit');
|
||||
$routes->get('Finance/medical_store_credit_account', 'Finance::medicalStoreCreditAccount');
|
||||
$routes->get('Finance/payout/opd-consult', 'Finance::payoutOpdConsult');
|
||||
$routes->get('Finance/payout/opd-consult-summary', 'Finance::payoutOpdConsultSummary');
|
||||
$routes->get('Finance/payout/opd-consult-drafts-table', 'Finance::payoutOpdConsultDraftsTable');
|
||||
|
|
@ -270,6 +277,7 @@ $routes->post('Finance/payout/opd-consult-draft-create', 'Finance::payoutOpdCons
|
|||
$routes->post('Finance/payout/opd-consult-draft-update', 'Finance::payoutOpdConsultDraftUpdate');
|
||||
$routes->post('Finance/payout/opd-consult-draft-delete', 'Finance::payoutOpdConsultDraftDelete');
|
||||
$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');
|
||||
$routes->get('Finance/bank_settlement_entries_table', 'Finance::bankSettlementEntriesTable');
|
||||
$routes->get('Finance/bank_settlement_linked_payments_table', 'Finance::bankSettlementLinkedPaymentsTable');
|
||||
|
|
@ -285,11 +293,22 @@ $routes->post('Finance/bank_deposit_status_update', 'Finance::bankDepositStatusU
|
|||
$routes->post('Finance/bank_statement_entry_create', 'Finance::bankStatementEntryCreate');
|
||||
$routes->post('Finance/bank_reconcile_match', 'Finance::bankReconcileMatch');
|
||||
$routes->post('Finance/bank_reconcile_batch_match', 'Finance::bankReconcileBatchMatch');
|
||||
$routes->post('Finance/bank_reconcile_outgoing_single_match', 'Finance::bankReconcileOutgoingSingleMatch');
|
||||
$routes->post('Finance/bank_reconcile_outgoing_batch_match', 'Finance::bankReconcileOutgoingBatchMatch');
|
||||
$routes->post('Finance/bank_settlement_create', 'Finance::bankSettlementCreate');
|
||||
$routes->post('Finance/bank_settlement_match_statement', 'Finance::bankSettlementMatchStatement');
|
||||
$routes->post('Finance/bank_reconcile_unmatch', 'Finance::bankReconcileUnmatch');
|
||||
$routes->post('Finance/bank_pos_settlement_create', 'Finance::bankPosSettlementCreate');
|
||||
$routes->post('Finance/bank_pos_settlement_accept', 'Finance::bankPosSettlementAccept');
|
||||
$routes->get('Finance/medical_store_requests', 'Finance::medicalStoreRequests');
|
||||
$routes->get('Finance/medical_store_requests_table', 'Finance::medicalStoreRequestsTable');
|
||||
$routes->get('Finance/medical_store_request_detail/(:num)', 'Finance::medicalStoreRequestDetail/$1');
|
||||
$routes->get('Finance/medical_store_request_lines_table', 'Finance::medicalStoreRequestLinesTable');
|
||||
$routes->post('Finance/medical_store_request_review', 'Finance::medicalStoreRequestReview');
|
||||
$routes->post('Finance/medical_store_request_approve', 'Finance::medicalStoreRequestApprove');
|
||||
$routes->post('Finance/medical_store_request_reject', 'Finance::medicalStoreRequestReject');
|
||||
$routes->post('Finance/medical_store_payment_create', 'Finance::medicalStorePaymentCreate');
|
||||
$routes->get('Finance/medical_store_dashboard_card', 'Finance::medicalStoreDashboardCard');
|
||||
|
||||
service('auth')->routes($routes);
|
||||
|
||||
|
|
|
|||
|
|
@ -19,6 +19,11 @@ use App\Models\FinanceScrollSubmissionItemModel;
|
|||
use App\Models\FinanceScrollSubmissionModel;
|
||||
use App\Models\FinanceVendorInvoiceModel;
|
||||
use App\Models\FinancePharmacyBillModel;
|
||||
use App\Models\FinancePayoutRequestAuditModel;
|
||||
use App\Models\FinancePayoutRequestLineModel;
|
||||
use App\Models\FinancePayoutRequestModel;
|
||||
use App\Models\FinanceOutgoingPaymentAllocationModel;
|
||||
use App\Models\FinanceOutgoingPaymentHistoryModel;
|
||||
use App\Models\FinanceVendorModel;
|
||||
|
||||
class Finance extends BaseController
|
||||
|
|
@ -40,6 +45,11 @@ class Finance extends BaseController
|
|||
private FinanceDoctorAgreementModel $doctorAgreementModel;
|
||||
private FinanceDoctorPayoutModel $doctorPayoutModel;
|
||||
private FinancePharmacyBillModel $pharmBillModel;
|
||||
private FinancePayoutRequestModel $payoutRequestModel;
|
||||
private FinancePayoutRequestLineModel $payoutRequestLineModel;
|
||||
private FinanceOutgoingPaymentHistoryModel $outgoingPaymentHistoryModel;
|
||||
private FinanceOutgoingPaymentAllocationModel $outgoingPaymentAllocationModel;
|
||||
private FinancePayoutRequestAuditModel $payoutRequestAuditModel;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
|
|
@ -60,6 +70,11 @@ class Finance extends BaseController
|
|||
$this->doctorAgreementModel = new FinanceDoctorAgreementModel();
|
||||
$this->doctorPayoutModel = new FinanceDoctorPayoutModel();
|
||||
$this->pharmBillModel = new FinancePharmacyBillModel();
|
||||
$this->payoutRequestModel = new FinancePayoutRequestModel();
|
||||
$this->payoutRequestLineModel = new FinancePayoutRequestLineModel();
|
||||
$this->outgoingPaymentHistoryModel = new FinanceOutgoingPaymentHistoryModel();
|
||||
$this->outgoingPaymentAllocationModel = new FinanceOutgoingPaymentAllocationModel();
|
||||
$this->payoutRequestAuditModel = new FinancePayoutRequestAuditModel();
|
||||
}
|
||||
|
||||
public function bankDeposits()
|
||||
|
|
@ -3567,6 +3582,607 @@ class Finance extends BaseController
|
|||
return $builder->get()->getResultArray();
|
||||
}
|
||||
|
||||
public function medicalStoreRequests()
|
||||
{
|
||||
if (! $this->canFinanceAny(['finance.workflow.view', 'finance.cash.accounts.accept', 'finance.cash.accounts.verify', 'finance.doctor_payout.manage'])) {
|
||||
return $this->response->setStatusCode(403)->setJSON(['status' => 0, 'message' => 'Access denied']);
|
||||
}
|
||||
|
||||
if (! $this->db->tableExists('finance_payout_requests')) {
|
||||
return $this->response->setJSON(['status' => 1, 'rows' => [], 'message' => 'Payout request table not available']);
|
||||
}
|
||||
|
||||
$status = trim((string) ($this->request->getGet('status') ?? ''));
|
||||
$fromDate = $this->normalizeDateInput((string) ($this->request->getGet('from_date') ?? ''));
|
||||
$toDate = $this->normalizeDateInput((string) ($this->request->getGet('to_date') ?? ''));
|
||||
|
||||
$builder = $this->payoutRequestModel->where('request_type', 'medical_store_credit')->orderBy('id', 'DESC');
|
||||
if ($status !== '') {
|
||||
$builder->where('status', $status);
|
||||
}
|
||||
if ($fromDate !== '') {
|
||||
$builder->where('request_date >=', $fromDate);
|
||||
}
|
||||
if ($toDate !== '') {
|
||||
$builder->where('request_date <=', $toDate);
|
||||
}
|
||||
|
||||
$rows = $builder->findAll(300);
|
||||
|
||||
return $this->response->setJSON([
|
||||
'status' => 1,
|
||||
'rows' => $rows,
|
||||
]);
|
||||
}
|
||||
|
||||
public function medicalStoreCreditAccount()
|
||||
{
|
||||
if (! $this->canFinanceAny(['finance.workflow.view', 'finance.cash.accounts.accept', 'finance.cash.accounts.verify', 'finance.doctor_payout.manage'])) {
|
||||
return $this->response->setStatusCode(403)->setBody('Access denied');
|
||||
}
|
||||
|
||||
$summary = [
|
||||
'total_requested' => 0.0,
|
||||
'total_paid' => 0.0,
|
||||
'total_pending' => 0.0,
|
||||
'open_requests' => 0,
|
||||
];
|
||||
$rows = [];
|
||||
|
||||
if ($this->db->tableExists('finance_payout_requests')) {
|
||||
$summaryRow = $this->db->table('finance_payout_requests')
|
||||
->select('COALESCE(SUM(requested_amount),0) AS total_requested, COALESCE(SUM(paid_amount),0) AS total_paid, COALESCE(SUM(pending_amount),0) AS total_pending', false)
|
||||
->where('request_type', 'medical_store_credit')
|
||||
->get()->getRowArray();
|
||||
|
||||
$summary['total_requested'] = (float) ($summaryRow['total_requested'] ?? 0);
|
||||
$summary['total_paid'] = (float) ($summaryRow['total_paid'] ?? 0);
|
||||
$summary['total_pending'] = (float) ($summaryRow['total_pending'] ?? 0);
|
||||
$summary['open_requests'] = $this->db->table('finance_payout_requests')
|
||||
->where('request_type', 'medical_store_credit')
|
||||
->whereIn('status', ['submitted', 'finance_review', 'approved', 'partially_paid'])
|
||||
->countAllResults();
|
||||
|
||||
$rows = $this->payoutRequestModel
|
||||
->where('request_type', 'medical_store_credit')
|
||||
->orderBy('id', 'DESC')
|
||||
->findAll(60);
|
||||
}
|
||||
|
||||
return view('finance/medical_store_credit_account', [
|
||||
'summary' => $summary,
|
||||
'rows' => $rows,
|
||||
]);
|
||||
}
|
||||
|
||||
public function medicalStoreRequestsTable()
|
||||
{
|
||||
if (! $this->canFinanceAny(['finance.workflow.view', 'finance.cash.accounts.accept', 'finance.cash.accounts.verify', 'finance.doctor_payout.manage'])) {
|
||||
return $this->response->setStatusCode(403)->setBody('Access denied');
|
||||
}
|
||||
|
||||
$status = trim((string) ($this->request->getGet('status') ?? ''));
|
||||
$rows = [];
|
||||
|
||||
if ($this->db->tableExists('finance_payout_requests')) {
|
||||
$builder = $this->payoutRequestModel
|
||||
->where('request_type', 'medical_store_credit')
|
||||
->orderBy('id', 'DESC');
|
||||
if ($status !== '') {
|
||||
$builder->where('status', $status);
|
||||
}
|
||||
$rows = $builder->findAll(200);
|
||||
}
|
||||
|
||||
return view('finance/partials/medical_store_requests_table', [
|
||||
'rows' => $rows,
|
||||
]);
|
||||
}
|
||||
|
||||
public function medicalStoreRequestLinesTable()
|
||||
{
|
||||
if (! $this->canFinanceAny(['finance.workflow.view', 'finance.cash.accounts.accept', 'finance.cash.accounts.verify', 'finance.doctor_payout.manage'])) {
|
||||
return $this->response->setStatusCode(403)->setBody('Access denied');
|
||||
}
|
||||
|
||||
$requestId = (int) ($this->request->getGet('request_id') ?? 0);
|
||||
if ($requestId <= 0) {
|
||||
return view('finance/partials/medical_store_request_lines_table', [
|
||||
'request' => null,
|
||||
'lines' => [],
|
||||
'payments' => [],
|
||||
]);
|
||||
}
|
||||
|
||||
$request = $this->payoutRequestModel->find($requestId);
|
||||
$lines = $this->db->tableExists('finance_payout_request_lines')
|
||||
? $this->payoutRequestLineModel->where('request_id', $requestId)->orderBy('line_order', 'ASC')->orderBy('id', 'ASC')->findAll()
|
||||
: [];
|
||||
$payments = $this->db->tableExists('finance_outgoing_payment_history')
|
||||
? $this->outgoingPaymentHistoryModel->where('request_id', $requestId)->orderBy('id', 'DESC')->findAll()
|
||||
: [];
|
||||
|
||||
return view('finance/partials/medical_store_request_lines_table', [
|
||||
'request' => $request,
|
||||
'lines' => $lines,
|
||||
'payments' => $payments,
|
||||
]);
|
||||
}
|
||||
|
||||
public function medicalStoreRequestDetail($requestId = 0)
|
||||
{
|
||||
if (! $this->canFinanceAny(['finance.workflow.view', 'finance.cash.accounts.accept', 'finance.cash.accounts.verify', 'finance.doctor_payout.manage'])) {
|
||||
return $this->response->setStatusCode(403)->setJSON(['status' => 0, 'message' => 'Access denied']);
|
||||
}
|
||||
|
||||
$requestId = (int) $requestId;
|
||||
if ($requestId <= 0) {
|
||||
return $this->response->setStatusCode(422)->setJSON(['status' => 0, 'message' => 'Request ID required']);
|
||||
}
|
||||
|
||||
$request = $this->payoutRequestModel->find($requestId);
|
||||
if (! is_array($request)) {
|
||||
return $this->response->setStatusCode(404)->setJSON(['status' => 0, 'message' => 'Request not found']);
|
||||
}
|
||||
|
||||
$lines = [];
|
||||
$payments = [];
|
||||
if ($this->db->tableExists('finance_payout_request_lines')) {
|
||||
$lines = $this->payoutRequestLineModel
|
||||
->where('request_id', $requestId)
|
||||
->orderBy('line_order', 'ASC')
|
||||
->orderBy('id', 'ASC')
|
||||
->findAll();
|
||||
}
|
||||
if ($this->db->tableExists('finance_outgoing_payment_history')) {
|
||||
$payments = $this->outgoingPaymentHistoryModel
|
||||
->where('request_id', $requestId)
|
||||
->orderBy('id', 'DESC')
|
||||
->findAll();
|
||||
}
|
||||
|
||||
return $this->response->setJSON([
|
||||
'status' => 1,
|
||||
'request' => $request,
|
||||
'lines' => $lines,
|
||||
'payments' => $payments,
|
||||
]);
|
||||
}
|
||||
|
||||
public function medicalStoreRequestReview()
|
||||
{
|
||||
return $this->transitionMedicalStoreRequestStatus('finance_review', ['submitted']);
|
||||
}
|
||||
|
||||
public function medicalStoreRequestApprove()
|
||||
{
|
||||
return $this->transitionMedicalStoreRequestStatus('approved', ['finance_review']);
|
||||
}
|
||||
|
||||
public function medicalStoreRequestReject()
|
||||
{
|
||||
return $this->transitionMedicalStoreRequestStatus('rejected', ['finance_review', 'submitted']);
|
||||
}
|
||||
|
||||
public function medicalStorePaymentCreate()
|
||||
{
|
||||
if (! $this->canFinanceAny(['finance.cash.accounts.accept', 'finance.cash.accounts.verify', 'finance.doctor_payout.manage'])) {
|
||||
return $this->response->setStatusCode(403)->setJSON(['status' => 0, 'message' => 'Access denied']);
|
||||
}
|
||||
|
||||
$requestId = (int) ($this->request->getPost('request_id') ?? 0);
|
||||
$amount = round((float) ($this->request->getPost('amount') ?? 0), 2);
|
||||
$paymentMode = (int) ($this->request->getPost('payment_mode') ?? 1);
|
||||
$remark = trim((string) ($this->request->getPost('remark') ?? ''));
|
||||
|
||||
if ($requestId <= 0 || $amount <= 0) {
|
||||
return $this->response->setStatusCode(422)->setJSON(['status' => 0, 'message' => 'Request and amount are required.']);
|
||||
}
|
||||
|
||||
$request = $this->payoutRequestModel->find($requestId);
|
||||
if (! is_array($request)) {
|
||||
return $this->response->setStatusCode(404)->setJSON(['status' => 0, 'message' => 'Request not found.']);
|
||||
}
|
||||
|
||||
$status = (string) ($request['status'] ?? '');
|
||||
if (! in_array($status, ['approved', 'partially_paid'], true)) {
|
||||
return $this->response->setStatusCode(422)->setJSON(['status' => 0, 'message' => 'Only approved or partially paid requests can be paid.']);
|
||||
}
|
||||
|
||||
$pending = round((float) ($request['pending_amount'] ?? 0), 2);
|
||||
if ($pending <= 0) {
|
||||
return $this->response->setStatusCode(422)->setJSON(['status' => 0, 'message' => 'Request has no pending amount.']);
|
||||
}
|
||||
if ($amount > $pending) {
|
||||
return $this->response->setStatusCode(422)->setJSON(['status' => 0, 'message' => 'Payment amount exceeds pending amount.']);
|
||||
}
|
||||
|
||||
$userId = $this->currentUserId();
|
||||
$userName = $this->currentUserName();
|
||||
$now = date('Y-m-d H:i:s');
|
||||
$paymentNo = 'MSP-' . date('YmdHis') . '-' . random_int(1000, 9999);
|
||||
|
||||
$this->db->transStart();
|
||||
|
||||
$this->outgoingPaymentHistoryModel->insert([
|
||||
'payment_no' => $paymentNo,
|
||||
'payout_type' => 'medical_store',
|
||||
'payee_label' => 'Medical Store Credit Settlement',
|
||||
'request_id' => $requestId,
|
||||
'payment_date' => $now,
|
||||
'amount' => $amount,
|
||||
'payment_mode' => $paymentMode,
|
||||
'pay_bank_id' => (int) ($this->request->getPost('pay_bank_id') ?? 0),
|
||||
'card_bank' => trim((string) ($this->request->getPost('card_bank') ?? '')),
|
||||
'card_remark' => trim((string) ($this->request->getPost('card_remark') ?? '')),
|
||||
'cust_card' => trim((string) ($this->request->getPost('cust_card') ?? '')),
|
||||
'card_tran_id' => trim((string) ($this->request->getPost('card_tran_id') ?? '')),
|
||||
'bankcard_machine' => trim((string) ($this->request->getPost('bankcard_machine') ?? '')),
|
||||
'insert_code' => trim((string) ($this->request->getPost('insert_code') ?? '')),
|
||||
'credit_debit' => 0,
|
||||
'remark' => $remark,
|
||||
'status' => 'paid',
|
||||
'created_by' => $userName,
|
||||
'created_by_id' => $userId,
|
||||
'created_at' => $now,
|
||||
'updated_at' => $now,
|
||||
]);
|
||||
|
||||
$paymentId = (int) $this->outgoingPaymentHistoryModel->getInsertID();
|
||||
|
||||
$openLines = $this->payoutRequestLineModel
|
||||
->where('request_id', $requestId)
|
||||
->where('pending_amount >', 0)
|
||||
->orderBy('line_order', 'ASC')
|
||||
->orderBy('id', 'ASC')
|
||||
->findAll();
|
||||
|
||||
$remaining = $amount;
|
||||
$allocOrder = 1;
|
||||
foreach ($openLines as $line) {
|
||||
$lineId = (int) ($line['id'] ?? 0);
|
||||
$linePending = round((float) ($line['pending_amount'] ?? 0), 2);
|
||||
if ($lineId <= 0 || $linePending <= 0 || $remaining <= 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$alloc = min($linePending, $remaining);
|
||||
$alloc = round($alloc, 2);
|
||||
|
||||
$this->outgoingPaymentAllocationModel->insert([
|
||||
'payment_id' => $paymentId,
|
||||
'request_id' => $requestId,
|
||||
'request_line_id' => $lineId,
|
||||
'allocated_amount' => $alloc,
|
||||
'allocation_order' => $allocOrder,
|
||||
'allocation_note' => 'Auto FIFO allocation',
|
||||
'created_by' => $userName,
|
||||
'created_by_id' => $userId,
|
||||
'created_at' => $now,
|
||||
]);
|
||||
|
||||
$newAllocated = round((float) ($line['allocated_amount'] ?? 0) + $alloc, 2);
|
||||
$newPending = round(max(0, (float) ($line['line_amount'] ?? 0) - $newAllocated), 2);
|
||||
$newLineStatus = $newPending <= 0 ? 'closed' : ($newAllocated > 0 ? 'partial' : 'open');
|
||||
|
||||
$this->payoutRequestLineModel->update($lineId, [
|
||||
'allocated_amount' => $newAllocated,
|
||||
'pending_amount' => $newPending,
|
||||
'line_status' => $newLineStatus,
|
||||
'updated_at' => $now,
|
||||
]);
|
||||
|
||||
$remaining = round($remaining - $alloc, 2);
|
||||
$allocOrder++;
|
||||
}
|
||||
|
||||
if ($remaining > 0) {
|
||||
$this->db->transRollback();
|
||||
return $this->response->setStatusCode(422)->setJSON(['status' => 0, 'message' => 'Payment exceeds open line capacity.']);
|
||||
}
|
||||
|
||||
$this->recalculateMedicalStoreRequestTotals($requestId, $userId, $userName);
|
||||
$this->logPayoutRequestAudit($requestId, 'payment_create', $status, null, 'Payment #' . $paymentNo . ' amount: ' . number_format($amount, 2, '.', ''));
|
||||
|
||||
$this->db->transComplete();
|
||||
|
||||
if (! $this->db->transStatus()) {
|
||||
return $this->response->setStatusCode(500)->setJSON(['status' => 0, 'message' => 'Failed to save payout payment.']);
|
||||
}
|
||||
|
||||
return $this->response->setJSON([
|
||||
'status' => 1,
|
||||
'message' => 'Payment saved and allocated successfully.',
|
||||
'payment_id' => $paymentId,
|
||||
'payment_no' => $paymentNo,
|
||||
]);
|
||||
}
|
||||
|
||||
public function medicalStoreDashboardCard()
|
||||
{
|
||||
if (! $this->canFinanceAny(['finance.workflow.view', 'finance.cash.accounts.accept', 'finance.cash.accounts.verify', 'finance.doctor_payout.manage'])) {
|
||||
return $this->response->setStatusCode(403)->setJSON(['status' => 0, 'message' => 'Access denied']);
|
||||
}
|
||||
|
||||
if (! $this->db->tableExists('finance_payout_requests')) {
|
||||
return $this->response->setJSON([
|
||||
'status' => 1,
|
||||
'summary' => [
|
||||
'total_requested' => 0,
|
||||
'total_paid' => 0,
|
||||
'total_pending' => 0,
|
||||
'open_requests' => 0,
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
$summaryRow = $this->db->table('finance_payout_requests')
|
||||
->select('COALESCE(SUM(requested_amount),0) AS total_requested, COALESCE(SUM(paid_amount),0) AS total_paid, COALESCE(SUM(pending_amount),0) AS total_pending', false)
|
||||
->where('request_type', 'medical_store_credit')
|
||||
->get()->getRowArray();
|
||||
|
||||
$openRequests = $this->db->table('finance_payout_requests')
|
||||
->where('request_type', 'medical_store_credit')
|
||||
->whereIn('status', ['submitted', 'finance_review', 'approved', 'partially_paid'])
|
||||
->countAllResults();
|
||||
|
||||
return $this->response->setJSON([
|
||||
'status' => 1,
|
||||
'summary' => [
|
||||
'total_requested' => (float) ($summaryRow['total_requested'] ?? 0),
|
||||
'total_paid' => (float) ($summaryRow['total_paid'] ?? 0),
|
||||
'total_pending' => (float) ($summaryRow['total_pending'] ?? 0),
|
||||
'open_requests' => $openRequests,
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
public function bankAuditOutgoingPaymentsTable()
|
||||
{
|
||||
if (! $this->canFinance('finance.bank.audit')) {
|
||||
return $this->response->setStatusCode(403)->setJSON(['status' => 0, 'message' => 'Access denied']);
|
||||
}
|
||||
|
||||
if (! $this->db->tableExists('finance_outgoing_payment_history')) {
|
||||
return $this->response->setJSON(['status' => 1, 'rows' => []]);
|
||||
}
|
||||
|
||||
$status = trim((string) ($this->request->getGet('status') ?? 'unmatched'));
|
||||
$fromDate = $this->normalizeDateInput((string) ($this->request->getGet('from_date') ?? ''));
|
||||
$toDate = $this->normalizeDateInput((string) ($this->request->getGet('to_date') ?? ''));
|
||||
|
||||
$q = $this->db->table('finance_outgoing_payment_history p')
|
||||
->select('p.id, p.payment_no, p.payment_date, p.amount, p.payment_mode, p.card_tran_id, p.card_remark, p.cust_card, p.remark, p.created_by, p.created_by_id, p.bank_reconcile_status, p.bank_statement_entry_id, p.bank_reconcile_batch_ref, p.bank_settlement_entry_id, p.insert_code, p.request_id, p.payout_type')
|
||||
->where('p.payment_mode', 2)
|
||||
->where('p.credit_debit', 0)
|
||||
->orderBy('p.id', 'DESC');
|
||||
|
||||
if ($status === 'unmatched') {
|
||||
$q->groupStart()->where('p.bank_reconcile_status', null)->orWhere('p.bank_reconcile_status', '')->groupEnd();
|
||||
} elseif ($status === 'matched') {
|
||||
$q->where('p.bank_reconcile_status', 'matched');
|
||||
}
|
||||
|
||||
if ($fromDate !== '') {
|
||||
$q->where('DATE(p.payment_date) >=', $fromDate);
|
||||
}
|
||||
if ($toDate !== '') {
|
||||
$q->where('DATE(p.payment_date) <=', $toDate);
|
||||
}
|
||||
|
||||
return $this->response->setJSON([
|
||||
'status' => 1,
|
||||
'rows' => $q->get()->getResultArray(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function bankReconcileOutgoingSingleMatch()
|
||||
{
|
||||
if (! $this->canFinance('finance.bank.audit')) {
|
||||
return $this->response->setStatusCode(403)->setJSON(['status' => 0, 'message' => 'Access denied']);
|
||||
}
|
||||
|
||||
$paymentId = (int) ($this->request->getPost('payment_id') ?? 0);
|
||||
if ($paymentId <= 0) {
|
||||
return $this->response->setStatusCode(422)->setJSON(['status' => 0, 'message' => 'Payment ID required.']);
|
||||
}
|
||||
|
||||
$payment = $this->outgoingPaymentHistoryModel->find($paymentId);
|
||||
if (! is_array($payment) || (int) ($payment['payment_mode'] ?? 0) !== 2 || (int) ($payment['credit_debit'] ?? 0) !== 0) {
|
||||
return $this->response->setStatusCode(404)->setJSON(['status' => 0, 'message' => 'Outgoing bank payment not found.']);
|
||||
}
|
||||
|
||||
if ((string) ($payment['bank_reconcile_status'] ?? '') === 'matched') {
|
||||
return $this->response->setStatusCode(409)->setJSON(['status' => 0, 'message' => 'Payment already matched.']);
|
||||
}
|
||||
|
||||
$this->outgoingPaymentHistoryModel->update($paymentId, [
|
||||
'bank_reconcile_status' => 'matched',
|
||||
'bank_statement_entry_id' => null,
|
||||
'bank_reconcile_batch_ref' => null,
|
||||
'bank_settlement_entry_id' => null,
|
||||
'updated_at' => date('Y-m-d H:i:s'),
|
||||
]);
|
||||
|
||||
return $this->response->setJSON(['status' => 1, 'message' => 'Outgoing payment marked as matched.']);
|
||||
}
|
||||
|
||||
public function bankReconcileOutgoingBatchMatch()
|
||||
{
|
||||
if (! $this->canFinance('finance.bank.audit')) {
|
||||
return $this->response->setStatusCode(403)->setJSON(['status' => 0, 'message' => 'Access denied']);
|
||||
}
|
||||
|
||||
$paymentIds = $this->request->getPost('payment_ids');
|
||||
if (! is_array($paymentIds)) {
|
||||
$paymentIds = [];
|
||||
}
|
||||
|
||||
$ids = [];
|
||||
foreach ($paymentIds as $id) {
|
||||
$v = (int) $id;
|
||||
if ($v > 0) {
|
||||
$ids[$v] = $v;
|
||||
}
|
||||
}
|
||||
$ids = array_values($ids);
|
||||
if ($ids === []) {
|
||||
return $this->response->setStatusCode(422)->setJSON(['status' => 0, 'message' => 'Select at least one payment.']);
|
||||
}
|
||||
|
||||
$rows = $this->outgoingPaymentHistoryModel
|
||||
->select('id, bank_reconcile_status')
|
||||
->whereIn('id', $ids)
|
||||
->where('payment_mode', 2)
|
||||
->where('credit_debit', 0)
|
||||
->findAll();
|
||||
|
||||
if ($rows === []) {
|
||||
return $this->response->setStatusCode(404)->setJSON(['status' => 0, 'message' => 'No eligible outgoing bank payments found.']);
|
||||
}
|
||||
|
||||
$batchRef = 'OBR-' . date('YmdHis') . '-' . random_int(1000, 9999);
|
||||
$matched = 0;
|
||||
$now = date('Y-m-d H:i:s');
|
||||
foreach ($rows as $row) {
|
||||
if ((string) ($row['bank_reconcile_status'] ?? '') === 'matched') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->outgoingPaymentHistoryModel->update((int) ($row['id'] ?? 0), [
|
||||
'bank_reconcile_status' => 'matched',
|
||||
'bank_statement_entry_id' => null,
|
||||
'bank_reconcile_batch_ref' => $batchRef,
|
||||
'bank_settlement_entry_id' => null,
|
||||
'updated_at' => $now,
|
||||
]);
|
||||
$matched++;
|
||||
}
|
||||
|
||||
return $this->response->setJSON([
|
||||
'status' => 1,
|
||||
'message' => 'Matched outgoing payments: ' . $matched,
|
||||
'batch_ref' => $batchRef,
|
||||
]);
|
||||
}
|
||||
|
||||
private function transitionMedicalStoreRequestStatus(string $newStatus, array $allowedFrom)
|
||||
{
|
||||
if (! $this->canFinanceAny(['finance.workflow.view', 'finance.cash.accounts.accept', 'finance.cash.accounts.verify', 'finance.doctor_payout.manage'])) {
|
||||
return $this->response->setStatusCode(403)->setJSON(['status' => 0, 'message' => 'Access denied']);
|
||||
}
|
||||
|
||||
$requestId = (int) ($this->request->getPost('request_id') ?? 0);
|
||||
$remark = trim((string) ($this->request->getPost('remark') ?? ''));
|
||||
if ($requestId <= 0) {
|
||||
return $this->response->setStatusCode(422)->setJSON(['status' => 0, 'message' => 'Request ID required.']);
|
||||
}
|
||||
|
||||
$request = $this->payoutRequestModel->find($requestId);
|
||||
if (! is_array($request)) {
|
||||
return $this->response->setStatusCode(404)->setJSON(['status' => 0, 'message' => 'Request not found.']);
|
||||
}
|
||||
|
||||
$oldStatus = (string) ($request['status'] ?? '');
|
||||
if (! in_array($oldStatus, $allowedFrom, true)) {
|
||||
return $this->response->setStatusCode(422)->setJSON(['status' => 0, 'message' => 'Invalid status transition from ' . $oldStatus . ' to ' . $newStatus . '.']);
|
||||
}
|
||||
|
||||
$now = date('Y-m-d H:i:s');
|
||||
$update = [
|
||||
'status' => $newStatus,
|
||||
'updated_at' => $now,
|
||||
'updated_by' => $this->currentUserName(),
|
||||
'updated_by_id' => $this->currentUserId(),
|
||||
];
|
||||
if ($newStatus === 'finance_review') {
|
||||
$update['finance_reviewed_at'] = $now;
|
||||
}
|
||||
if ($newStatus === 'approved') {
|
||||
$update['approved_at'] = $now;
|
||||
if ((float) ($request['approved_amount'] ?? 0) <= 0) {
|
||||
$update['approved_amount'] = (float) ($request['requested_amount'] ?? 0);
|
||||
}
|
||||
}
|
||||
|
||||
$this->payoutRequestModel->update($requestId, $update);
|
||||
$this->logPayoutRequestAudit($requestId, 'status_change', $oldStatus, $newStatus, $remark);
|
||||
|
||||
return $this->response->setJSON([
|
||||
'status' => 1,
|
||||
'message' => 'Request status updated to ' . $newStatus . '.',
|
||||
]);
|
||||
}
|
||||
|
||||
private function recalculateMedicalStoreRequestTotals(int $requestId, ?int $userId = null, ?string $userName = null): void
|
||||
{
|
||||
$sumLines = $this->db->table('finance_payout_request_lines')
|
||||
->select('COALESCE(SUM(line_amount),0) AS line_total, COALESCE(SUM(allocated_amount),0) AS allocated_total, COALESCE(SUM(pending_amount),0) AS pending_total', false)
|
||||
->where('request_id', $requestId)
|
||||
->get()->getRowArray();
|
||||
|
||||
$requested = round((float) ($sumLines['line_total'] ?? 0), 2);
|
||||
$paid = round((float) ($sumLines['allocated_total'] ?? 0), 2);
|
||||
$pending = round((float) ($sumLines['pending_total'] ?? 0), 2);
|
||||
|
||||
$newStatus = 'approved';
|
||||
if ($paid <= 0) {
|
||||
$newStatus = 'approved';
|
||||
} elseif ($pending > 0) {
|
||||
$newStatus = 'partially_paid';
|
||||
} else {
|
||||
$newStatus = 'paid';
|
||||
}
|
||||
|
||||
$update = [
|
||||
'requested_amount' => $requested,
|
||||
'paid_amount' => $paid,
|
||||
'pending_amount' => $pending,
|
||||
'status' => $newStatus,
|
||||
'updated_at' => date('Y-m-d H:i:s'),
|
||||
];
|
||||
|
||||
if ($userName !== null) {
|
||||
$update['updated_by'] = $userName;
|
||||
}
|
||||
if ($userId !== null) {
|
||||
$update['updated_by_id'] = $userId;
|
||||
}
|
||||
if ($newStatus === 'paid') {
|
||||
$update['closed_at'] = date('Y-m-d H:i:s');
|
||||
}
|
||||
|
||||
$this->payoutRequestModel->update($requestId, $update);
|
||||
}
|
||||
|
||||
private function logPayoutRequestAudit(int $requestId, string $actionType, ?string $oldStatus, ?string $newStatus, string $note = ''): void
|
||||
{
|
||||
if (! $this->db->tableExists('finance_payout_request_audit')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->payoutRequestAuditModel->insert([
|
||||
'request_id' => $requestId,
|
||||
'action_type' => $actionType,
|
||||
'old_status' => $oldStatus,
|
||||
'new_status' => $newStatus,
|
||||
'action_note' => $note !== '' ? $note : null,
|
||||
'action_by' => $this->currentUserName(),
|
||||
'action_by_id' => $this->currentUserId(),
|
||||
'action_at' => date('Y-m-d H:i:s'),
|
||||
]);
|
||||
}
|
||||
|
||||
private function currentUserId(): ?int
|
||||
{
|
||||
$user = function_exists('auth') ? auth()->user() : null;
|
||||
if (! $user || ! isset($user->id)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (int) $user->id;
|
||||
}
|
||||
|
||||
private function summarizePaymentHistoryRows(array $rows): array
|
||||
{
|
||||
$total = 0.0;
|
||||
|
|
|
|||
|
|
@ -11139,6 +11139,457 @@ class Medical extends BaseController
|
|||
];
|
||||
}
|
||||
|
||||
public function credit_payout_pool()
|
||||
{
|
||||
if ($deny = $this->ensurePharmacyAccess()) {
|
||||
return $deny;
|
||||
}
|
||||
|
||||
$scope = strtolower(trim((string) ($this->request->getGet('scope') ?? 'all')));
|
||||
if (! in_array($scope, ['all', 'ipd', 'org'], true)) {
|
||||
$scope = 'all';
|
||||
}
|
||||
$fromDate = $this->normalizeDate((string) ($this->request->getGet('from_date') ?? ''));
|
||||
$toDate = $this->normalizeDate((string) ($this->request->getGet('to_date') ?? ''));
|
||||
|
||||
$baseSql = $this->buildMedicalCreditPoolBaseSql($scope, $fromDate, $toDate);
|
||||
if ($baseSql === '') {
|
||||
return $this->response->setJSON(['status' => 1, 'rows' => []]);
|
||||
}
|
||||
|
||||
$rows = $this->db->query('SELECT * FROM (' . $baseSql . ') t ORDER BY t.inv_date DESC, t.source_ref_id DESC LIMIT 300')->getResultArray();
|
||||
$rows = array_map(function (array $row): array {
|
||||
return $this->normalizeMedicalCreditPoolRow($row);
|
||||
}, $rows);
|
||||
|
||||
return $this->response->setJSON([
|
||||
'status' => 1,
|
||||
'rows' => $rows,
|
||||
]);
|
||||
}
|
||||
|
||||
public function credit_payout_pool_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 > 500) {
|
||||
$length = 25;
|
||||
}
|
||||
|
||||
$scope = strtolower(trim((string) ($this->request->getPost('scope') ?? $this->request->getGet('scope') ?? 'all')));
|
||||
if (! in_array($scope, ['all', 'ipd', 'org'], true)) {
|
||||
$scope = 'all';
|
||||
}
|
||||
|
||||
$groupBy = strtolower(trim((string) ($this->request->getPost('group_by') ?? $this->request->getGet('group_by') ?? 'none')));
|
||||
if (! in_array($groupBy, ['none', 'ipd', 'case'], true)) {
|
||||
$groupBy = 'none';
|
||||
}
|
||||
|
||||
$fromDate = $this->normalizeDate((string) ($this->request->getPost('from_date') ?? $this->request->getGet('from_date') ?? ''));
|
||||
$toDate = $this->normalizeDate((string) ($this->request->getPost('to_date') ?? $this->request->getGet('to_date') ?? ''));
|
||||
|
||||
$search = $this->request->getPost('search');
|
||||
if (! is_array($search)) {
|
||||
$search = ['value' => (string) ($this->request->getGet('search') ?? '')];
|
||||
}
|
||||
$searchValue = trim((string) ($search['value'] ?? ''));
|
||||
|
||||
$order = $this->request->getPost('order');
|
||||
if (! is_array($order)) {
|
||||
$order = [];
|
||||
}
|
||||
$orderColIndex = isset($order[0]['column']) ? (int) $order[0]['column'] : 6;
|
||||
$orderDir = strtolower((string) ($order[0]['dir'] ?? 'desc')) === 'asc' ? 'ASC' : 'DESC';
|
||||
|
||||
$orderCols = [
|
||||
1 => 'q.source_type',
|
||||
2 => 'q.invoice_code',
|
||||
3 => 'q.ipd_code',
|
||||
4 => 'q.case_code',
|
||||
5 => 'q.credit_category',
|
||||
6 => 'q.inv_date',
|
||||
7 => 'q.line_amount',
|
||||
];
|
||||
$orderBy = $orderCols[$orderColIndex] ?? 'q.inv_date';
|
||||
|
||||
$baseSql = $this->buildMedicalCreditPoolBaseSql($scope, $fromDate, $toDate);
|
||||
if ($baseSql === '') {
|
||||
return $this->response->setJSON([
|
||||
'draw' => $draw,
|
||||
'recordsTotal' => 0,
|
||||
'recordsFiltered' => 0,
|
||||
'data' => [],
|
||||
]);
|
||||
}
|
||||
|
||||
$whereParts = ['q.line_amount > 0'];
|
||||
|
||||
if ($this->db->tableExists('finance_payout_request_lines') && $this->db->tableExists('finance_payout_requests')) {
|
||||
$whereParts[] = "NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM finance_payout_request_lines pl
|
||||
JOIN finance_payout_requests pr ON pr.id = pl.request_id
|
||||
WHERE pl.source_type = q.source_type
|
||||
AND pl.source_ref_id = q.source_ref_id
|
||||
AND pr.request_type = 'medical_store_credit'
|
||||
AND pr.status IN ('submitted','finance_review','approved','partially_paid')
|
||||
)";
|
||||
}
|
||||
|
||||
if ($searchValue !== '') {
|
||||
$esc = $this->db->escapeLikeString($searchValue);
|
||||
$like = $this->db->escape('%' . $esc . '%');
|
||||
$whereParts[] = "(
|
||||
q.source_type LIKE {$like} ESCAPE '!'
|
||||
OR CAST(q.source_ref_id AS CHAR) LIKE {$like} ESCAPE '!'
|
||||
OR q.invoice_code LIKE {$like} ESCAPE '!'
|
||||
OR q.ipd_code LIKE {$like} ESCAPE '!'
|
||||
OR q.case_code LIKE {$like} ESCAPE '!'
|
||||
OR q.credit_category LIKE {$like} ESCAPE '!'
|
||||
)";
|
||||
}
|
||||
|
||||
$whereSql = ' WHERE ' . implode(' AND ', $whereParts);
|
||||
$countTotalSql = 'SELECT COUNT(1) AS cnt FROM (' . $baseSql . ') q WHERE q.line_amount > 0';
|
||||
|
||||
$countFilteredSql = 'SELECT COUNT(1) AS cnt FROM (' . $baseSql . ') q' . $whereSql;
|
||||
$orderPrefix = '';
|
||||
if ($groupBy === 'ipd') {
|
||||
$orderPrefix = 'COALESCE(NULLIF(q.ipd_code,\'\'), \'ZZZ\') ASC, ';
|
||||
} elseif ($groupBy === 'case') {
|
||||
$orderPrefix = 'COALESCE(NULLIF(q.case_code,\'\'), \'ZZZ\') ASC, ';
|
||||
}
|
||||
|
||||
$dataSql = 'SELECT q.* FROM (' . $baseSql . ') q' . $whereSql
|
||||
. ' ORDER BY ' . $orderPrefix . $orderBy . ' ' . $orderDir . ', q.source_ref_id DESC'
|
||||
. ' LIMIT ' . $start . ', ' . $length;
|
||||
|
||||
$recordsTotal = (int) (($this->db->query($countTotalSql)->getRowArray()['cnt'] ?? 0));
|
||||
$recordsFiltered = (int) (($this->db->query($countFilteredSql)->getRowArray()['cnt'] ?? 0));
|
||||
$rows = $this->db->query($dataSql)->getResultArray();
|
||||
|
||||
$data = array_map(function (array $row): array {
|
||||
return $this->normalizeMedicalCreditPoolRow($row);
|
||||
}, $rows);
|
||||
|
||||
return $this->response->setJSON([
|
||||
'draw' => $draw,
|
||||
'recordsTotal' => $recordsTotal,
|
||||
'recordsFiltered' => $recordsFiltered,
|
||||
'data' => $data,
|
||||
]);
|
||||
}
|
||||
|
||||
public function credit_payout_request()
|
||||
{
|
||||
if ($deny = $this->ensurePharmacyAccess()) {
|
||||
return $deny;
|
||||
}
|
||||
|
||||
$rows = [];
|
||||
if ($this->db->tableExists('finance_payout_requests')) {
|
||||
$rows = $this->db->table('finance_payout_requests')
|
||||
->where('request_type', 'medical_store_credit')
|
||||
->orderBy('id', 'DESC')
|
||||
->get(80)
|
||||
->getResultArray();
|
||||
}
|
||||
|
||||
return view('medical/credit_payout_request', [
|
||||
'rows' => $rows,
|
||||
]);
|
||||
}
|
||||
|
||||
public function credit_payout_request_create()
|
||||
{
|
||||
if ($deny = $this->ensurePharmacyAccess()) {
|
||||
return $deny;
|
||||
}
|
||||
|
||||
if (! $this->db->tableExists('finance_payout_requests') || ! $this->db->tableExists('finance_payout_request_lines')) {
|
||||
return $this->response->setStatusCode(500)->setJSON(['status' => 0, 'message' => 'Finance payout tables not available.']);
|
||||
}
|
||||
|
||||
$requestDate = $this->normalizeDate((string) ($this->request->getPost('request_date') ?? date('Y-m-d')));
|
||||
$remarks = trim((string) ($this->request->getPost('remarks') ?? ''));
|
||||
$lineItemsRaw = $this->request->getPost('line_items');
|
||||
$lineItems = [];
|
||||
if (is_string($lineItemsRaw)) {
|
||||
$decoded = json_decode($lineItemsRaw, true);
|
||||
if (is_array($decoded)) {
|
||||
$lineItems = $decoded;
|
||||
}
|
||||
} elseif (is_array($lineItemsRaw)) {
|
||||
$lineItems = $lineItemsRaw;
|
||||
}
|
||||
|
||||
if ($requestDate === '' || $lineItems === []) {
|
||||
return $this->response->setStatusCode(422)->setJSON(['status' => 0, 'message' => 'Request date and line items are required.']);
|
||||
}
|
||||
|
||||
$requestNo = 'MSR-' . date('YmdHis') . '-' . random_int(1000, 9999);
|
||||
$now = date('Y-m-d H:i:s');
|
||||
$userLabel = $this->pharmacyActorLabel();
|
||||
$user = service('auth')->user();
|
||||
$userId = $user && isset($user->id) ? (int) $user->id : null;
|
||||
|
||||
$this->db->transStart();
|
||||
|
||||
$this->db->table('finance_payout_requests')->insert([
|
||||
'request_no' => $requestNo,
|
||||
'request_type' => 'medical_store_credit',
|
||||
'requester_unit' => 'medical_store',
|
||||
'beneficiary_unit' => 'hospital',
|
||||
'request_date' => $requestDate,
|
||||
'requested_amount' => 0,
|
||||
'approved_amount' => 0,
|
||||
'paid_amount' => 0,
|
||||
'pending_amount' => 0,
|
||||
'status' => 'submitted',
|
||||
'remarks' => $remarks,
|
||||
'submitted_at' => $now,
|
||||
'created_by' => $userLabel,
|
||||
'created_by_id' => $userId,
|
||||
'updated_by' => $userLabel,
|
||||
'updated_by_id' => $userId,
|
||||
'created_at' => $now,
|
||||
'updated_at' => $now,
|
||||
]);
|
||||
$requestId = (int) $this->db->insertID();
|
||||
|
||||
$lineOrder = 1;
|
||||
$total = 0.0;
|
||||
foreach ($lineItems as $line) {
|
||||
$sourceType = trim((string) ($line['source_type'] ?? ''));
|
||||
$sourceRefId = (int) ($line['source_ref_id'] ?? 0);
|
||||
$lineAmount = round((float) ($line['line_amount'] ?? 0), 2);
|
||||
if ($sourceType === '' || $sourceRefId <= 0 || $lineAmount <= 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->db->table('finance_payout_request_lines')->insert([
|
||||
'request_id' => $requestId,
|
||||
'source_domain' => 'medical_store',
|
||||
'source_type' => $sourceType,
|
||||
'source_ref_id' => $sourceRefId,
|
||||
'invoice_id' => (int) ($line['invoice_id'] ?? 0) ?: null,
|
||||
'invoice_code' => trim((string) ($line['invoice_code'] ?? '')),
|
||||
'ipd_id' => (int) ($line['ipd_id'] ?? 0) ?: null,
|
||||
'ipd_code' => trim((string) ($line['ipd_code'] ?? '')),
|
||||
'case_id' => (int) ($line['case_id'] ?? 0) ?: null,
|
||||
'case_code' => trim((string) ($line['case_code'] ?? '')),
|
||||
'credit_category' => trim((string) ($line['credit_category'] ?? 'ipd_credit')),
|
||||
'line_amount' => $lineAmount,
|
||||
'allocated_amount' => 0,
|
||||
'pending_amount' => $lineAmount,
|
||||
'line_status' => 'open',
|
||||
'line_remarks' => trim((string) ($line['line_remarks'] ?? '')),
|
||||
'line_order' => $lineOrder,
|
||||
'created_at' => $now,
|
||||
'updated_at' => $now,
|
||||
]);
|
||||
|
||||
$total += $lineAmount;
|
||||
$lineOrder++;
|
||||
}
|
||||
|
||||
if ($total <= 0) {
|
||||
$this->db->transRollback();
|
||||
return $this->response->setStatusCode(422)->setJSON(['status' => 0, 'message' => 'No valid line item found.']);
|
||||
}
|
||||
|
||||
$this->db->table('finance_payout_requests')->where('id', $requestId)->update([
|
||||
'requested_amount' => round($total, 2),
|
||||
'pending_amount' => round($total, 2),
|
||||
'updated_at' => $now,
|
||||
]);
|
||||
|
||||
if ($this->db->tableExists('finance_payout_request_audit')) {
|
||||
$this->db->table('finance_payout_request_audit')->insert([
|
||||
'request_id' => $requestId,
|
||||
'action_type' => 'request_create',
|
||||
'old_status' => null,
|
||||
'new_status' => 'submitted',
|
||||
'action_note' => $remarks !== '' ? $remarks : 'Medical store payout request submitted.',
|
||||
'action_by' => $userLabel,
|
||||
'action_by_id' => $userId,
|
||||
'action_at' => $now,
|
||||
]);
|
||||
}
|
||||
|
||||
$this->db->transComplete();
|
||||
if (! $this->db->transStatus()) {
|
||||
return $this->response->setStatusCode(500)->setJSON(['status' => 0, 'message' => 'Failed to create payout request.']);
|
||||
}
|
||||
|
||||
return $this->response->setJSON([
|
||||
'status' => 1,
|
||||
'message' => 'Payout request created successfully.',
|
||||
'request_id' => $requestId,
|
||||
'request_no' => $requestNo,
|
||||
]);
|
||||
}
|
||||
|
||||
public function credit_payout_requests()
|
||||
{
|
||||
if ($deny = $this->ensurePharmacyAccess()) {
|
||||
return $deny;
|
||||
}
|
||||
|
||||
if (! $this->db->tableExists('finance_payout_requests')) {
|
||||
return $this->response->setJSON(['status' => 1, 'rows' => []]);
|
||||
}
|
||||
|
||||
$status = trim((string) ($this->request->getGet('status') ?? ''));
|
||||
$builder = $this->db->table('finance_payout_requests')
|
||||
->where('request_type', 'medical_store_credit')
|
||||
->orderBy('id', 'DESC');
|
||||
if ($status !== '') {
|
||||
$builder->where('status', $status);
|
||||
}
|
||||
|
||||
return $this->response->setJSON([
|
||||
'status' => 1,
|
||||
'rows' => $builder->get()->getResultArray(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function credit_payout_request_detail($requestId = 0)
|
||||
{
|
||||
if ($deny = $this->ensurePharmacyAccess()) {
|
||||
return $deny;
|
||||
}
|
||||
|
||||
$requestId = (int) $requestId;
|
||||
if ($requestId <= 0) {
|
||||
return $this->response->setStatusCode(422)->setJSON(['status' => 0, 'message' => 'Request ID required']);
|
||||
}
|
||||
|
||||
$request = $this->db->table('finance_payout_requests')->where('id', $requestId)->get()->getRowArray();
|
||||
if (! is_array($request)) {
|
||||
return $this->response->setStatusCode(404)->setJSON(['status' => 0, 'message' => 'Request not found']);
|
||||
}
|
||||
|
||||
$lines = $this->db->table('finance_payout_request_lines')
|
||||
->where('request_id', $requestId)
|
||||
->orderBy('line_order', 'ASC')
|
||||
->orderBy('id', 'ASC')
|
||||
->get()->getResultArray();
|
||||
|
||||
$payments = [];
|
||||
if ($this->db->tableExists('finance_outgoing_payment_history')) {
|
||||
$payments = $this->db->table('finance_outgoing_payment_history')
|
||||
->where('request_id', $requestId)
|
||||
->orderBy('id', 'DESC')
|
||||
->get()->getResultArray();
|
||||
}
|
||||
|
||||
return $this->response->setJSON([
|
||||
'status' => 1,
|
||||
'request' => $request,
|
||||
'lines' => $lines,
|
||||
'payments' => $payments,
|
||||
]);
|
||||
}
|
||||
|
||||
private function normalizeMedicalCreditPoolRow(array $row): array
|
||||
{
|
||||
return [
|
||||
'source_type' => trim((string) ($row['source_type'] ?? '')),
|
||||
'source_ref_id' => (int) ($row['source_ref_id'] ?? 0),
|
||||
'invoice_id' => (int) ($row['invoice_id'] ?? 0),
|
||||
'invoice_code' => trim((string) ($row['invoice_code'] ?? '')),
|
||||
'ipd_id' => (int) ($row['ipd_id'] ?? 0),
|
||||
'ipd_code' => trim((string) ($row['ipd_code'] ?? '')),
|
||||
'case_id' => (int) ($row['case_id'] ?? 0),
|
||||
'case_code' => trim((string) ($row['case_code'] ?? '')),
|
||||
'credit_category' => trim((string) ($row['credit_category'] ?? 'ipd_credit')),
|
||||
'line_amount' => round((float) ($row['line_amount'] ?? 0), 2),
|
||||
'inv_date' => ! empty($row['inv_date']) ? date('Y-m-d', strtotime((string) $row['inv_date'])) : null,
|
||||
];
|
||||
}
|
||||
|
||||
private function buildMedicalCreditPoolBaseSql(string $scope, string $fromDate, string $toDate): string
|
||||
{
|
||||
$parts = [];
|
||||
|
||||
$hasInvMedGroup = $this->db->tableExists('inv_med_group');
|
||||
$hasInvMedMaster = $this->db->tableExists('invoice_med_master');
|
||||
$hasIpdMaster = $this->db->tableExists('ipd_master');
|
||||
$hasOrgCase = $this->db->tableExists('organization_case_master');
|
||||
|
||||
$dateCondIpd = '';
|
||||
$dateCondOrg = '';
|
||||
if ($fromDate !== '') {
|
||||
$dateCondIpd .= ' AND DATE(m.inv_date) >= ' . $this->db->escape($fromDate);
|
||||
$dateCondOrg .= ' AND DATE(m.inv_date) >= ' . $this->db->escape($fromDate);
|
||||
}
|
||||
if ($toDate !== '') {
|
||||
$dateCondIpd .= ' AND DATE(m.inv_date) <= ' . $this->db->escape($toDate);
|
||||
$dateCondOrg .= ' AND DATE(m.inv_date) <= ' . $this->db->escape($toDate);
|
||||
}
|
||||
|
||||
if (($scope === 'all' || $scope === 'ipd') && $hasInvMedGroup && $hasInvMedMaster && $hasIpdMaster) {
|
||||
$parts[] = "
|
||||
SELECT
|
||||
'inv_med_group' AS source_type,
|
||||
g.med_group_id AS source_ref_id,
|
||||
MAX(m.id) AS invoice_id,
|
||||
MAX(COALESCE(m.inv_med_code, '')) AS invoice_code,
|
||||
MAX(COALESCE(m.ipd_id, 0)) AS ipd_id,
|
||||
MAX(COALESCE(m.ipd_code, '')) AS ipd_code,
|
||||
MAX(COALESCE(m.case_id, 0)) AS case_id,
|
||||
'' AS case_code,
|
||||
IF(g.med_type = 3, 'ipd_package', 'ipd_credit') AS credit_category,
|
||||
DATE(MAX(m.inv_date)) AS inv_date,
|
||||
ROUND(COALESCE(NULLIF(MAX(COALESCE(g.net_amount, 0)), 0), NULLIF(SUM(COALESCE(m.net_amount, 0)), 0), SUM(COALESCE(m.gross_amount, 0))), 2) AS line_amount
|
||||
FROM inv_med_group g
|
||||
JOIN invoice_med_master m ON m.med_group_id = g.med_group_id
|
||||
JOIN ipd_master ip ON ip.id = g.ipd_id
|
||||
WHERE g.med_type IN (2, 3)
|
||||
AND IFNULL(ip.ipd_status, 0) <> 0
|
||||
AND IFNULL(m.sale_return, 0) = 0
|
||||
{$dateCondIpd}
|
||||
GROUP BY g.med_group_id, g.med_type
|
||||
";
|
||||
}
|
||||
|
||||
if (($scope === 'all' || $scope === 'org') && $hasInvMedMaster && $hasOrgCase) {
|
||||
$orgFields = $this->db->getFieldNames('organization_case_master') ?? [];
|
||||
$caseCodeExpr = in_array('case_id_code', $orgFields, true) ? "COALESCE(o.case_id_code, '')" : "''";
|
||||
|
||||
$parts[] = "
|
||||
SELECT
|
||||
'invoice_med_master' AS source_type,
|
||||
m.id AS source_ref_id,
|
||||
m.id AS invoice_id,
|
||||
COALESCE(m.inv_med_code, '') AS invoice_code,
|
||||
COALESCE(m.ipd_id, 0) AS ipd_id,
|
||||
COALESCE(m.ipd_code, '') AS ipd_code,
|
||||
COALESCE(m.case_id, 0) AS case_id,
|
||||
{$caseCodeExpr} AS case_code,
|
||||
'org_case_credit' AS credit_category,
|
||||
DATE(m.inv_date) AS inv_date,
|
||||
ROUND(COALESCE(NULLIF(m.net_amount, 0), m.gross_amount, 0), 2) AS line_amount
|
||||
FROM invoice_med_master m
|
||||
JOIN organization_case_master o ON o.id = m.case_id
|
||||
WHERE m.case_id > 0
|
||||
AND IFNULL(m.case_credit, 0) = 1
|
||||
AND IFNULL(o.status, 0) = 2
|
||||
AND IFNULL(m.sale_return, 0) = 0
|
||||
{$dateCondOrg}
|
||||
";
|
||||
}
|
||||
|
||||
return $parts === [] ? '' : implode(' UNION ALL ', $parts);
|
||||
}
|
||||
|
||||
public function master()
|
||||
{
|
||||
if ($deny = $this->ensurePharmacyAccess()) {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,518 @@
|
|||
<?php
|
||||
|
||||
namespace App\Database\Migrations;
|
||||
|
||||
use CodeIgniter\Database\Migration;
|
||||
|
||||
class CreateFinanceMedicalPayoutWorkflowTables extends Migration
|
||||
{
|
||||
private function hasTable(string $table): bool
|
||||
{
|
||||
$row = $this->db->query('SHOW TABLES LIKE ' . $this->db->escape($table))->getRowArray();
|
||||
|
||||
return ! empty($row);
|
||||
}
|
||||
|
||||
public function up()
|
||||
{
|
||||
if (! $this->hasTable('finance_payout_requests')) {
|
||||
$this->forge->addField([
|
||||
'id' => [
|
||||
'type' => 'BIGINT',
|
||||
'constraint' => 20,
|
||||
'unsigned' => true,
|
||||
'auto_increment' => true,
|
||||
],
|
||||
'request_no' => [
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 40,
|
||||
'null' => false,
|
||||
],
|
||||
'request_type' => [
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 30,
|
||||
'default' => 'medical_store_credit',
|
||||
],
|
||||
'requester_unit' => [
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 30,
|
||||
'default' => 'medical_store',
|
||||
],
|
||||
'beneficiary_unit' => [
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 30,
|
||||
'default' => 'hospital',
|
||||
],
|
||||
'request_date' => [
|
||||
'type' => 'DATE',
|
||||
'null' => false,
|
||||
],
|
||||
'requested_amount' => [
|
||||
'type' => 'DECIMAL',
|
||||
'constraint' => '12,2',
|
||||
'default' => 0,
|
||||
],
|
||||
'approved_amount' => [
|
||||
'type' => 'DECIMAL',
|
||||
'constraint' => '12,2',
|
||||
'default' => 0,
|
||||
],
|
||||
'paid_amount' => [
|
||||
'type' => 'DECIMAL',
|
||||
'constraint' => '12,2',
|
||||
'default' => 0,
|
||||
],
|
||||
'pending_amount' => [
|
||||
'type' => 'DECIMAL',
|
||||
'constraint' => '12,2',
|
||||
'default' => 0,
|
||||
],
|
||||
'status' => [
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 30,
|
||||
'default' => 'draft',
|
||||
],
|
||||
'priority' => [
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 20,
|
||||
'null' => true,
|
||||
],
|
||||
'remarks' => [
|
||||
'type' => 'TEXT',
|
||||
'null' => true,
|
||||
],
|
||||
'submitted_at' => [
|
||||
'type' => 'DATETIME',
|
||||
'null' => true,
|
||||
],
|
||||
'finance_reviewed_at' => [
|
||||
'type' => 'DATETIME',
|
||||
'null' => true,
|
||||
],
|
||||
'approved_at' => [
|
||||
'type' => 'DATETIME',
|
||||
'null' => true,
|
||||
],
|
||||
'closed_at' => [
|
||||
'type' => 'DATETIME',
|
||||
'null' => true,
|
||||
],
|
||||
'created_by' => [
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 120,
|
||||
'null' => true,
|
||||
],
|
||||
'created_by_id' => [
|
||||
'type' => 'INT',
|
||||
'constraint' => 11,
|
||||
'null' => true,
|
||||
],
|
||||
'updated_by' => [
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 120,
|
||||
'null' => true,
|
||||
],
|
||||
'updated_by_id' => [
|
||||
'type' => 'INT',
|
||||
'constraint' => 11,
|
||||
'null' => true,
|
||||
],
|
||||
'created_at' => [
|
||||
'type' => 'DATETIME',
|
||||
'null' => true,
|
||||
],
|
||||
'updated_at' => [
|
||||
'type' => 'DATETIME',
|
||||
'null' => true,
|
||||
],
|
||||
]);
|
||||
$this->forge->addKey('id', true);
|
||||
$this->forge->addUniqueKey('request_no');
|
||||
$this->forge->addKey(['status', 'request_date']);
|
||||
$this->forge->addKey(['request_type', 'status']);
|
||||
$this->forge->createTable('finance_payout_requests', true);
|
||||
}
|
||||
|
||||
if (! $this->hasTable('finance_payout_request_lines')) {
|
||||
$this->forge->addField([
|
||||
'id' => [
|
||||
'type' => 'BIGINT',
|
||||
'constraint' => 20,
|
||||
'unsigned' => true,
|
||||
'auto_increment' => true,
|
||||
],
|
||||
'request_id' => [
|
||||
'type' => 'BIGINT',
|
||||
'constraint' => 20,
|
||||
'unsigned' => true,
|
||||
'null' => false,
|
||||
],
|
||||
'source_domain' => [
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 30,
|
||||
'default' => 'medical_store',
|
||||
],
|
||||
'source_type' => [
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 30,
|
||||
'null' => false,
|
||||
],
|
||||
'source_ref_id' => [
|
||||
'type' => 'BIGINT',
|
||||
'constraint' => 20,
|
||||
'unsigned' => true,
|
||||
'null' => false,
|
||||
],
|
||||
'invoice_id' => [
|
||||
'type' => 'BIGINT',
|
||||
'constraint' => 20,
|
||||
'unsigned' => true,
|
||||
'null' => true,
|
||||
],
|
||||
'invoice_code' => [
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 50,
|
||||
'null' => true,
|
||||
],
|
||||
'ipd_id' => [
|
||||
'type' => 'BIGINT',
|
||||
'constraint' => 20,
|
||||
'unsigned' => true,
|
||||
'null' => true,
|
||||
],
|
||||
'ipd_code' => [
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 50,
|
||||
'null' => true,
|
||||
],
|
||||
'case_id' => [
|
||||
'type' => 'BIGINT',
|
||||
'constraint' => 20,
|
||||
'unsigned' => true,
|
||||
'null' => true,
|
||||
],
|
||||
'case_code' => [
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 50,
|
||||
'null' => true,
|
||||
],
|
||||
'credit_category' => [
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 30,
|
||||
'null' => false,
|
||||
],
|
||||
'line_amount' => [
|
||||
'type' => 'DECIMAL',
|
||||
'constraint' => '12,2',
|
||||
'default' => 0,
|
||||
],
|
||||
'allocated_amount' => [
|
||||
'type' => 'DECIMAL',
|
||||
'constraint' => '12,2',
|
||||
'default' => 0,
|
||||
],
|
||||
'pending_amount' => [
|
||||
'type' => 'DECIMAL',
|
||||
'constraint' => '12,2',
|
||||
'default' => 0,
|
||||
],
|
||||
'line_status' => [
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 20,
|
||||
'default' => 'open',
|
||||
],
|
||||
'line_remarks' => [
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 250,
|
||||
'null' => true,
|
||||
],
|
||||
'line_order' => [
|
||||
'type' => 'INT',
|
||||
'constraint' => 11,
|
||||
'default' => 0,
|
||||
],
|
||||
'created_at' => [
|
||||
'type' => 'DATETIME',
|
||||
'null' => true,
|
||||
],
|
||||
'updated_at' => [
|
||||
'type' => 'DATETIME',
|
||||
'null' => true,
|
||||
],
|
||||
]);
|
||||
$this->forge->addKey('id', true);
|
||||
$this->forge->addKey('request_id');
|
||||
$this->forge->addKey(['line_status', 'pending_amount']);
|
||||
$this->forge->addKey(['source_type', 'source_ref_id']);
|
||||
$this->forge->addUniqueKey(['request_id', 'source_type', 'source_ref_id']);
|
||||
$this->forge->createTable('finance_payout_request_lines', true);
|
||||
}
|
||||
|
||||
if (! $this->hasTable('finance_outgoing_payment_history')) {
|
||||
$this->forge->addField([
|
||||
'id' => [
|
||||
'type' => 'BIGINT',
|
||||
'constraint' => 20,
|
||||
'unsigned' => true,
|
||||
'auto_increment' => true,
|
||||
],
|
||||
'payment_no' => [
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 40,
|
||||
'null' => false,
|
||||
],
|
||||
'payout_type' => [
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 30,
|
||||
'null' => false,
|
||||
],
|
||||
'payee_label' => [
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 150,
|
||||
'null' => true,
|
||||
],
|
||||
'request_id' => [
|
||||
'type' => 'BIGINT',
|
||||
'constraint' => 20,
|
||||
'unsigned' => true,
|
||||
'null' => true,
|
||||
],
|
||||
'payment_date' => [
|
||||
'type' => 'DATETIME',
|
||||
'null' => false,
|
||||
],
|
||||
'amount' => [
|
||||
'type' => 'DECIMAL',
|
||||
'constraint' => '12,2',
|
||||
'default' => 0,
|
||||
],
|
||||
'payment_mode' => [
|
||||
'type' => 'INT',
|
||||
'constraint' => 11,
|
||||
'default' => 1,
|
||||
],
|
||||
'pay_bank_id' => [
|
||||
'type' => 'INT',
|
||||
'constraint' => 11,
|
||||
'default' => 0,
|
||||
],
|
||||
'card_bank' => [
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 80,
|
||||
'null' => true,
|
||||
],
|
||||
'card_remark' => [
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 80,
|
||||
'null' => true,
|
||||
],
|
||||
'cust_card' => [
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 80,
|
||||
'null' => true,
|
||||
],
|
||||
'card_tran_id' => [
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 120,
|
||||
'null' => true,
|
||||
],
|
||||
'bankcard_machine' => [
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 120,
|
||||
'null' => true,
|
||||
],
|
||||
'insert_code' => [
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 40,
|
||||
'null' => true,
|
||||
],
|
||||
'credit_debit' => [
|
||||
'type' => 'INT',
|
||||
'constraint' => 11,
|
||||
'default' => 0,
|
||||
],
|
||||
'remark' => [
|
||||
'type' => 'TEXT',
|
||||
'null' => true,
|
||||
],
|
||||
'status' => [
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 20,
|
||||
'default' => 'paid',
|
||||
],
|
||||
'bank_reconcile_status' => [
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 20,
|
||||
'null' => true,
|
||||
],
|
||||
'bank_statement_entry_id' => [
|
||||
'type' => 'BIGINT',
|
||||
'constraint' => 20,
|
||||
'unsigned' => true,
|
||||
'null' => true,
|
||||
],
|
||||
'bank_reconcile_batch_ref' => [
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 50,
|
||||
'null' => true,
|
||||
],
|
||||
'bank_settlement_entry_id' => [
|
||||
'type' => 'BIGINT',
|
||||
'constraint' => 20,
|
||||
'unsigned' => true,
|
||||
'null' => true,
|
||||
],
|
||||
'created_by' => [
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 120,
|
||||
'null' => true,
|
||||
],
|
||||
'created_by_id' => [
|
||||
'type' => 'INT',
|
||||
'constraint' => 11,
|
||||
'null' => true,
|
||||
],
|
||||
'created_at' => [
|
||||
'type' => 'DATETIME',
|
||||
'null' => true,
|
||||
],
|
||||
'updated_at' => [
|
||||
'type' => 'DATETIME',
|
||||
'null' => true,
|
||||
],
|
||||
]);
|
||||
$this->forge->addKey('id', true);
|
||||
$this->forge->addUniqueKey('payment_no');
|
||||
$this->forge->addKey(['payout_type', 'payment_date']);
|
||||
$this->forge->addKey(['payment_mode', 'credit_debit']);
|
||||
$this->forge->addKey('bank_reconcile_status');
|
||||
$this->forge->addKey('request_id');
|
||||
$this->forge->createTable('finance_outgoing_payment_history', true);
|
||||
}
|
||||
|
||||
if (! $this->hasTable('finance_outgoing_payment_allocations')) {
|
||||
$this->forge->addField([
|
||||
'id' => [
|
||||
'type' => 'BIGINT',
|
||||
'constraint' => 20,
|
||||
'unsigned' => true,
|
||||
'auto_increment' => true,
|
||||
],
|
||||
'payment_id' => [
|
||||
'type' => 'BIGINT',
|
||||
'constraint' => 20,
|
||||
'unsigned' => true,
|
||||
'null' => false,
|
||||
],
|
||||
'request_id' => [
|
||||
'type' => 'BIGINT',
|
||||
'constraint' => 20,
|
||||
'unsigned' => true,
|
||||
'null' => false,
|
||||
],
|
||||
'request_line_id' => [
|
||||
'type' => 'BIGINT',
|
||||
'constraint' => 20,
|
||||
'unsigned' => true,
|
||||
'null' => false,
|
||||
],
|
||||
'allocated_amount' => [
|
||||
'type' => 'DECIMAL',
|
||||
'constraint' => '12,2',
|
||||
'default' => 0,
|
||||
],
|
||||
'allocation_order' => [
|
||||
'type' => 'INT',
|
||||
'constraint' => 11,
|
||||
'default' => 0,
|
||||
],
|
||||
'allocation_note' => [
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 250,
|
||||
'null' => true,
|
||||
],
|
||||
'created_by' => [
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 120,
|
||||
'null' => true,
|
||||
],
|
||||
'created_by_id' => [
|
||||
'type' => 'INT',
|
||||
'constraint' => 11,
|
||||
'null' => true,
|
||||
],
|
||||
'created_at' => [
|
||||
'type' => 'DATETIME',
|
||||
'null' => true,
|
||||
],
|
||||
]);
|
||||
$this->forge->addKey('id', true);
|
||||
$this->forge->addKey('payment_id');
|
||||
$this->forge->addKey(['request_id', 'request_line_id']);
|
||||
$this->forge->addUniqueKey(['payment_id', 'request_line_id']);
|
||||
$this->forge->createTable('finance_outgoing_payment_allocations', true);
|
||||
}
|
||||
|
||||
if (! $this->hasTable('finance_payout_request_audit')) {
|
||||
$this->forge->addField([
|
||||
'id' => [
|
||||
'type' => 'BIGINT',
|
||||
'constraint' => 20,
|
||||
'unsigned' => true,
|
||||
'auto_increment' => true,
|
||||
],
|
||||
'request_id' => [
|
||||
'type' => 'BIGINT',
|
||||
'constraint' => 20,
|
||||
'unsigned' => true,
|
||||
'null' => false,
|
||||
],
|
||||
'action_type' => [
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 40,
|
||||
'null' => false,
|
||||
],
|
||||
'old_status' => [
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 30,
|
||||
'null' => true,
|
||||
],
|
||||
'new_status' => [
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 30,
|
||||
'null' => true,
|
||||
],
|
||||
'action_note' => [
|
||||
'type' => 'TEXT',
|
||||
'null' => true,
|
||||
],
|
||||
'action_by' => [
|
||||
'type' => 'VARCHAR',
|
||||
'constraint' => 120,
|
||||
'null' => true,
|
||||
],
|
||||
'action_by_id' => [
|
||||
'type' => 'INT',
|
||||
'constraint' => 11,
|
||||
'null' => true,
|
||||
],
|
||||
'action_at' => [
|
||||
'type' => 'DATETIME',
|
||||
'null' => false,
|
||||
],
|
||||
]);
|
||||
$this->forge->addKey('id', true);
|
||||
$this->forge->addKey(['request_id', 'action_at']);
|
||||
$this->forge->createTable('finance_payout_request_audit', true);
|
||||
}
|
||||
}
|
||||
|
||||
public function down()
|
||||
{
|
||||
$this->forge->dropTable('finance_payout_request_audit', true);
|
||||
$this->forge->dropTable('finance_outgoing_payment_allocations', true);
|
||||
$this->forge->dropTable('finance_outgoing_payment_history', true);
|
||||
$this->forge->dropTable('finance_payout_request_lines', true);
|
||||
$this->forge->dropTable('finance_payout_requests', true);
|
||||
}
|
||||
}
|
||||
25
app/Models/FinanceOutgoingPaymentAllocationModel.php
Normal file
25
app/Models/FinanceOutgoingPaymentAllocationModel.php
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use CodeIgniter\Model;
|
||||
|
||||
class FinanceOutgoingPaymentAllocationModel extends Model
|
||||
{
|
||||
protected $table = 'finance_outgoing_payment_allocations';
|
||||
protected $primaryKey = 'id';
|
||||
protected $returnType = 'array';
|
||||
protected $useAutoIncrement = true;
|
||||
protected $useTimestamps = false;
|
||||
protected $allowedFields = [
|
||||
'payment_id',
|
||||
'request_id',
|
||||
'request_line_id',
|
||||
'allocated_amount',
|
||||
'allocation_order',
|
||||
'allocation_note',
|
||||
'created_by',
|
||||
'created_by_id',
|
||||
'created_at',
|
||||
];
|
||||
}
|
||||
41
app/Models/FinanceOutgoingPaymentHistoryModel.php
Normal file
41
app/Models/FinanceOutgoingPaymentHistoryModel.php
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use CodeIgniter\Model;
|
||||
|
||||
class FinanceOutgoingPaymentHistoryModel extends Model
|
||||
{
|
||||
protected $table = 'finance_outgoing_payment_history';
|
||||
protected $primaryKey = 'id';
|
||||
protected $returnType = 'array';
|
||||
protected $useAutoIncrement = true;
|
||||
protected $useTimestamps = false;
|
||||
protected $allowedFields = [
|
||||
'payment_no',
|
||||
'payout_type',
|
||||
'payee_label',
|
||||
'request_id',
|
||||
'payment_date',
|
||||
'amount',
|
||||
'payment_mode',
|
||||
'pay_bank_id',
|
||||
'card_bank',
|
||||
'card_remark',
|
||||
'cust_card',
|
||||
'card_tran_id',
|
||||
'bankcard_machine',
|
||||
'insert_code',
|
||||
'credit_debit',
|
||||
'remark',
|
||||
'status',
|
||||
'bank_reconcile_status',
|
||||
'bank_statement_entry_id',
|
||||
'bank_reconcile_batch_ref',
|
||||
'bank_settlement_entry_id',
|
||||
'created_by',
|
||||
'created_by_id',
|
||||
'created_at',
|
||||
'updated_at',
|
||||
];
|
||||
}
|
||||
24
app/Models/FinancePayoutRequestAuditModel.php
Normal file
24
app/Models/FinancePayoutRequestAuditModel.php
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use CodeIgniter\Model;
|
||||
|
||||
class FinancePayoutRequestAuditModel extends Model
|
||||
{
|
||||
protected $table = 'finance_payout_request_audit';
|
||||
protected $primaryKey = 'id';
|
||||
protected $returnType = 'array';
|
||||
protected $useAutoIncrement = true;
|
||||
protected $useTimestamps = false;
|
||||
protected $allowedFields = [
|
||||
'request_id',
|
||||
'action_type',
|
||||
'old_status',
|
||||
'new_status',
|
||||
'action_note',
|
||||
'action_by',
|
||||
'action_by_id',
|
||||
'action_at',
|
||||
];
|
||||
}
|
||||
35
app/Models/FinancePayoutRequestLineModel.php
Normal file
35
app/Models/FinancePayoutRequestLineModel.php
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use CodeIgniter\Model;
|
||||
|
||||
class FinancePayoutRequestLineModel extends Model
|
||||
{
|
||||
protected $table = 'finance_payout_request_lines';
|
||||
protected $primaryKey = 'id';
|
||||
protected $returnType = 'array';
|
||||
protected $useAutoIncrement = true;
|
||||
protected $useTimestamps = false;
|
||||
protected $allowedFields = [
|
||||
'request_id',
|
||||
'source_domain',
|
||||
'source_type',
|
||||
'source_ref_id',
|
||||
'invoice_id',
|
||||
'invoice_code',
|
||||
'ipd_id',
|
||||
'ipd_code',
|
||||
'case_id',
|
||||
'case_code',
|
||||
'credit_category',
|
||||
'line_amount',
|
||||
'allocated_amount',
|
||||
'pending_amount',
|
||||
'line_status',
|
||||
'line_remarks',
|
||||
'line_order',
|
||||
'created_at',
|
||||
'updated_at',
|
||||
];
|
||||
}
|
||||
38
app/Models/FinancePayoutRequestModel.php
Normal file
38
app/Models/FinancePayoutRequestModel.php
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use CodeIgniter\Model;
|
||||
|
||||
class FinancePayoutRequestModel extends Model
|
||||
{
|
||||
protected $table = 'finance_payout_requests';
|
||||
protected $primaryKey = 'id';
|
||||
protected $returnType = 'array';
|
||||
protected $useAutoIncrement = true;
|
||||
protected $useTimestamps = false;
|
||||
protected $allowedFields = [
|
||||
'request_no',
|
||||
'request_type',
|
||||
'requester_unit',
|
||||
'beneficiary_unit',
|
||||
'request_date',
|
||||
'requested_amount',
|
||||
'approved_amount',
|
||||
'paid_amount',
|
||||
'pending_amount',
|
||||
'status',
|
||||
'priority',
|
||||
'remarks',
|
||||
'submitted_at',
|
||||
'finance_reviewed_at',
|
||||
'approved_at',
|
||||
'closed_at',
|
||||
'created_by',
|
||||
'created_by_id',
|
||||
'updated_by',
|
||||
'updated_by_id',
|
||||
'created_at',
|
||||
'updated_at',
|
||||
];
|
||||
}
|
||||
|
|
@ -34,6 +34,10 @@
|
|||
|
||||
<section class="content">
|
||||
<style>
|
||||
.rx-right-panel .card-body {
|
||||
background: linear-gradient(180deg, #eaf1fb 0%, #f2f6fc 100%);
|
||||
border-top: 1px solid #d7e3f4;
|
||||
}
|
||||
.rx-sticky-actions {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
|
|
@ -80,9 +84,16 @@
|
|||
}
|
||||
.rx-panel {
|
||||
display: block;
|
||||
border-top: 1px solid #e9ecef;
|
||||
margin-top: 1rem;
|
||||
padding-top: 1rem;
|
||||
border: 1px solid #cad9ef;
|
||||
background: #ffffff;
|
||||
border-radius: .6rem;
|
||||
margin-top: .85rem;
|
||||
padding: .85rem;
|
||||
box-shadow: inset 0 1px 0 rgba(255, 255, 255, .6);
|
||||
}
|
||||
.rx-panel h6 {
|
||||
color: #274c77;
|
||||
margin-bottom: .65rem;
|
||||
}
|
||||
.rx-consult-visibility-panel {
|
||||
display: none;
|
||||
|
|
@ -100,10 +111,23 @@
|
|||
.rx-foldable {
|
||||
border: 1px solid #dbe3f8;
|
||||
border-radius: .5rem;
|
||||
background: #fff;
|
||||
background: #ffffff;
|
||||
padding: .6rem .7rem;
|
||||
margin-bottom: .65rem;
|
||||
}
|
||||
#panel_notes .small > label,
|
||||
#panel_notes .d-flex.flex-wrap.gap-2.small > label,
|
||||
#panel_notes .me-2 {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: .35rem;
|
||||
line-height: 1.3;
|
||||
}
|
||||
#panel_notes input[type="checkbox"] {
|
||||
margin: 0;
|
||||
margin-top: 1px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.rx-foldable .rx-fold-heading {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
|
@ -507,6 +531,22 @@
|
|||
<input class="form-check-input rx-consult-visibility-item" type="checkbox" data-rx-key="drug_allergy">
|
||||
<span class="form-check-label">Drug Allergy / ADR (NABH)</span>
|
||||
</label>
|
||||
<label class="form-check">
|
||||
<input class="form-check-input rx-consult-visibility-item" type="checkbox" data-rx-key="vitals">
|
||||
<span class="form-check-label">Vitals</span>
|
||||
</label>
|
||||
<label class="form-check">
|
||||
<input class="form-check-input rx-consult-visibility-item" type="checkbox" data-rx-key="complaints">
|
||||
<span class="form-check-label">Complaints</span>
|
||||
</label>
|
||||
<label class="form-check">
|
||||
<input class="form-check-input rx-consult-visibility-item" type="checkbox" data-rx-key="examination">
|
||||
<span class="form-check-label">Examination</span>
|
||||
</label>
|
||||
<label class="form-check">
|
||||
<input class="form-check-input rx-consult-visibility-item" type="checkbox" data-rx-key="diagnosis">
|
||||
<span class="form-check-label">Diagnosis</span>
|
||||
</label>
|
||||
<label class="form-check">
|
||||
<input class="form-check-input rx-consult-visibility-item" type="checkbox" data-rx-key="provisional_diagnosis">
|
||||
<span class="form-check-label">Provisional Diagnosis</span>
|
||||
|
|
@ -521,21 +561,24 @@
|
|||
<div id="panel_notes" class="rx-panel">
|
||||
<div class="mb-3">
|
||||
<h6 class="mb-2">General Examination</h6>
|
||||
<div class="row g-2">
|
||||
<div class="col-md-2"><input class="form-control form-control-sm rx-instant" id="pulse" placeholder="Pulse" value="<?= esc($opd_prescription[0]->pulse ?? '') ?>"></div>
|
||||
<div class="col-md-2"><input class="form-control form-control-sm rx-instant" id="spo2" placeholder="SPO2" value="<?= esc($opd_prescription[0]->spo2 ?? '') ?>"></div>
|
||||
<div class="col-md-2"><input class="form-control form-control-sm rx-instant" id="bp" placeholder="BP Systolic" value="<?= esc($opd_prescription[0]->bp ?? '') ?>"></div>
|
||||
<div class="col-md-2"><input class="form-control form-control-sm rx-instant" id="diastolic" placeholder="Diastolic" value="<?= esc($opd_prescription[0]->diastolic ?? '') ?>"></div>
|
||||
<div class="col-md-2"><input class="form-control form-control-sm rx-instant" id="temp" placeholder="Temp" value="<?= esc($opd_prescription[0]->temp ?? '') ?>"></div>
|
||||
<div class="col-md-2"><input class="form-control form-control-sm rx-instant" id="rr_min" placeholder="RR/min" value="<?= esc($opd_prescription[0]->rr_min ?? '') ?>"></div>
|
||||
<div class="col-md-2"><input class="form-control form-control-sm rx-instant" id="height" placeholder="Height" value="<?= esc($opd_prescription[0]->height ?? '') ?>"></div>
|
||||
<div class="col-md-2"><input class="form-control form-control-sm rx-instant" id="weight" placeholder="Weight" value="<?= esc($opd_prescription[0]->weight ?? '') ?>"></div>
|
||||
<div class="col-md-2"><input class="form-control form-control-sm rx-instant" id="waist" placeholder="Waist" value="<?= esc($opd_prescription[0]->waist ?? '') ?>"></div>
|
||||
<div class="col-md-2"><input class="form-control form-control-sm rx-instant" id="pallor" placeholder="Pallor" value="<?= esc($opd_prescription[0]->pallor ?? '') ?>"></div>
|
||||
<div class="col-md-2"><input class="form-control form-control-sm rx-instant" id="icterus" placeholder="Icterus" value="<?= esc($opd_prescription[0]->Icterus ?? '') ?>"></div>
|
||||
<div class="col-md-2"><input class="form-control form-control-sm rx-instant" id="cyanosis" placeholder="Cyanosis" value="<?= esc($opd_prescription[0]->cyanosis ?? '') ?>"></div>
|
||||
<div class="col-md-2"><input class="form-control form-control-sm rx-instant" id="clubbing" placeholder="Clubbing" value="<?= esc($opd_prescription[0]->clubbing ?? '') ?>"></div>
|
||||
<div class="col-md-2"><input class="form-control form-control-sm rx-instant" id="edema" placeholder="Edema" value="<?= esc($opd_prescription[0]->edema ?? '') ?>"></div>
|
||||
<div class="mb-3" id="rx_sec_vitals">
|
||||
<h6 class="mb-2">Vitals</h6>
|
||||
<div class="row g-2">
|
||||
<div class="col-md-2"><input class="form-control form-control-sm rx-instant" id="pulse" placeholder="Pulse" value="<?= esc($opd_prescription[0]->pulse ?? '') ?>"></div>
|
||||
<div class="col-md-2"><input class="form-control form-control-sm rx-instant" id="spo2" placeholder="SPO2" value="<?= esc($opd_prescription[0]->spo2 ?? '') ?>"></div>
|
||||
<div class="col-md-2"><input class="form-control form-control-sm rx-instant" id="bp" placeholder="BP Systolic" value="<?= esc($opd_prescription[0]->bp ?? '') ?>"></div>
|
||||
<div class="col-md-2"><input class="form-control form-control-sm rx-instant" id="diastolic" placeholder="Diastolic" value="<?= esc($opd_prescription[0]->diastolic ?? '') ?>"></div>
|
||||
<div class="col-md-2"><input class="form-control form-control-sm rx-instant" id="temp" placeholder="Temp" value="<?= esc($opd_prescription[0]->temp ?? '') ?>"></div>
|
||||
<div class="col-md-2"><input class="form-control form-control-sm rx-instant" id="rr_min" placeholder="RR/min" value="<?= esc($opd_prescription[0]->rr_min ?? '') ?>"></div>
|
||||
<div class="col-md-2"><input class="form-control form-control-sm rx-instant" id="height" placeholder="Height" value="<?= esc($opd_prescription[0]->height ?? '') ?>"></div>
|
||||
<div class="col-md-2"><input class="form-control form-control-sm rx-instant" id="weight" placeholder="Weight" value="<?= esc($opd_prescription[0]->weight ?? '') ?>"></div>
|
||||
<div class="col-md-2"><input class="form-control form-control-sm rx-instant" id="waist" placeholder="Waist" value="<?= esc($opd_prescription[0]->waist ?? '') ?>"></div>
|
||||
<div class="col-md-2"><input class="form-control form-control-sm rx-instant" id="pallor" placeholder="Pallor" value="<?= esc($opd_prescription[0]->pallor ?? '') ?>"></div>
|
||||
<div class="col-md-2"><input class="form-control form-control-sm rx-instant" id="icterus" placeholder="Icterus" value="<?= esc($opd_prescription[0]->Icterus ?? '') ?>"></div>
|
||||
<div class="col-md-2"><input class="form-control form-control-sm rx-instant" id="cyanosis" placeholder="Cyanosis" value="<?= esc($opd_prescription[0]->cyanosis ?? '') ?>"></div>
|
||||
<div class="col-md-2"><input class="form-control form-control-sm rx-instant" id="clubbing" placeholder="Clubbing" value="<?= esc($opd_prescription[0]->clubbing ?? '') ?>"></div>
|
||||
<div class="col-md-2"><input class="form-control form-control-sm rx-instant" id="edema" placeholder="Edema" value="<?= esc($opd_prescription[0]->edema ?? '') ?>"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row g-3 mt-2">
|
||||
<div class="col-md-12" id="rx_sec_pain_scale">
|
||||
|
|
@ -656,28 +699,31 @@
|
|||
<?php } ?>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<label class="form-label">Smart Complaints Picker (English + Hinglish)</label>
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control" id="complaint_lookup" list="complaint_suggest" placeholder="Type: bukhar, khansi, pet dard, chakkar...">
|
||||
<button type="button" class="btn btn-outline-primary" id="btn_add_complaint">Add</button>
|
||||
<button type="button" class="btn btn-outline-success" id="btn_ai_complaint_draft">AI Draft</button>
|
||||
<div class="mb-3" id="rx_sec_complaints">
|
||||
<h6 class="mb-2">Complaints</h6>
|
||||
<div class="mb-2">
|
||||
<label class="form-label">Smart Complaints Picker (English + Hinglish)</label>
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control" id="complaint_lookup" list="complaint_suggest" placeholder="Type: bukhar, khansi, pet dard, chakkar...">
|
||||
<button type="button" class="btn btn-outline-primary" id="btn_add_complaint">Add</button>
|
||||
<button type="button" class="btn btn-outline-success" id="btn_ai_complaint_draft">AI Draft</button>
|
||||
</div>
|
||||
<datalist id="complaint_suggest"></datalist>
|
||||
<div id="complaint_chips" class="rx-chip-wrap mt-2"></div>
|
||||
</div>
|
||||
<div class="mb-0">
|
||||
<label class="form-label">Complaint Details
|
||||
<button type="button" class="btn btn-outline-primary btn-sm rx-ai-btn btn-ai-rewrite btn-complaints-rewrite" data-target="complaints" data-mode="hinglish_to_english" title="Convert Hinglish text to English">↔ Hinglish → English</button>
|
||||
<button type="button" class="btn btn-outline-success btn-sm rx-ai-btn" id="btn_complaints_mic" title="Speech to text for complaints">Mic</button>
|
||||
<button type="button" class="btn btn-outline-secondary btn-sm rx-ai-btn btn-save-autotype-keyword" data-section="complaints" data-target="complaints" title="Save keyword for autocomplete">Save keyword</button>
|
||||
</label>
|
||||
<textarea class="form-control rx-field" id="complaints" rows="4" maxlength="4000"><?= esc($opd_prescription[0]->complaints ?? '') ?></textarea>
|
||||
<div id="complaints_interim_preview" style="display:none;font-size:.8rem;color:#6c757d;padding:2px 4px;font-style:italic;"></div>
|
||||
<div class="rx-counter" id="counter_complaints">0/4000</div>
|
||||
</div>
|
||||
<datalist id="complaint_suggest"></datalist>
|
||||
<div id="complaint_chips" class="rx-chip-wrap mt-2"></div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Complaints
|
||||
<button type="button" class="btn btn-outline-primary btn-sm rx-ai-btn btn-ai-rewrite btn-complaints-rewrite" data-target="complaints" data-mode="hinglish_to_english" title="Convert Hinglish text to English">↔ Hinglish → English</button>
|
||||
<button type="button" class="btn btn-outline-success btn-sm rx-ai-btn" id="btn_complaints_mic" title="Speech to text for complaints">Mic</button>
|
||||
<button type="button" class="btn btn-outline-secondary btn-sm rx-ai-btn btn-save-autotype-keyword" data-section="complaints" data-target="complaints" title="Save keyword for autocomplete">Save keyword</button>
|
||||
</label>
|
||||
<textarea class="form-control rx-field" id="complaints" rows="4" maxlength="4000"><?= esc($opd_prescription[0]->complaints ?? '') ?></textarea>
|
||||
<div id="complaints_interim_preview" style="display:none;font-size:.8rem;color:#6c757d;padding:2px 4px;font-style:italic;"></div>
|
||||
<div class="rx-counter" id="counter_complaints">0/4000</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<div class="mb-3" id="rx_sec_examination">
|
||||
<label class="form-label">Examination
|
||||
</label>
|
||||
<div class="d-flex flex-wrap gap-1 mb-1">
|
||||
|
|
@ -692,7 +738,7 @@
|
|||
<div class="rx-counter" id="counter_finding_examinations">0/4000</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<div class="mb-3" id="rx_sec_diagnosis">
|
||||
<label class="form-label">Diagnosis
|
||||
</label>
|
||||
<div class="d-flex flex-wrap gap-1 mb-1">
|
||||
|
|
@ -1348,6 +1394,10 @@
|
|||
{ key: 'complication', selector: '#rx_sec_complication', title: 'Complication' },
|
||||
{ key: 'addiction', selector: '#rx_sec_addiction', title: 'Addiction (if any)' },
|
||||
{ key: 'drug_allergy', selector: '#rx_sec_drug_allergy', title: 'Drug Allergy / ADR (NABH)' },
|
||||
{ key: 'vitals', selector: '#rx_sec_vitals', title: 'Vitals' },
|
||||
{ key: 'complaints', selector: '#rx_sec_complaints', title: 'Complaints' },
|
||||
{ key: 'examination', selector: '#rx_sec_examination', title: 'Examination' },
|
||||
{ key: 'diagnosis', selector: '#rx_sec_diagnosis', title: 'Diagnosis' },
|
||||
{ key: 'provisional_diagnosis', selector: '#rx_sec_provisional_diagnosis', title: 'Provisional Diagnosis' },
|
||||
{ key: 'prescription', selector: '#rx_sec_prescription', title: 'Prescription' }
|
||||
];
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
<?php $canBillingSubmit = $user && method_exists($user, 'can') ? ($user->can('finance.cash.billing.submit') || $user->can('finance.*')) : false; ?>
|
||||
<?php $canAccountsVerify = $user && method_exists($user, 'can') ? ($user->can('finance.cash.accounts.accept') || $user->can('finance.cash.accounts.verify') || $user->can('finance.*')) : false; ?>
|
||||
<?php $canBankAudit = $user && method_exists($user, 'can') ? ($user->can('finance.bank.deposit.create') || $user->can('finance.bank.audit') || $user->can('finance.bank.statement.update') || $user->can('finance.*')) : false; ?>
|
||||
<?php $canMedicalStoreCredit = $user && method_exists($user, 'can') ? ($user->can('finance.workflow.view') || $user->can('finance.cash.accounts.accept') || $user->can('finance.cash.accounts.verify') || $user->can('finance.doctor_payout.manage') || $user->can('finance.*')) : false; ?>
|
||||
|
||||
<div class="mb-3">
|
||||
<h2 class="mb-1">Accounts And Finance</h2>
|
||||
|
|
@ -49,5 +50,18 @@
|
|||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php if ($canMedicalStoreCredit): ?>
|
||||
<div class="col-md-4">
|
||||
<div class="card h-100 border-warning">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">4) Medical Store Credit Account</h5>
|
||||
<p class="small text-muted">Review credit payout requests raised by Medical Store and post settlement payments.</p>
|
||||
<a class="btn btn-warning btn-sm text-white" href="javascript:load_form('<?= base_url('Finance/medical_store_credit_account') ?>','Medical Store Credit Account');">
|
||||
Open Medical Store Credit
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</section>
|
||||
|
|
|
|||
241
app/Views/finance/medical_store_credit_account.php
Normal file
241
app/Views/finance/medical_store_credit_account.php
Normal file
|
|
@ -0,0 +1,241 @@
|
|||
<section class="content finance-medical-store-credit">
|
||||
<div class="mb-3">
|
||||
<h2 class="mb-1">Medical Store Credit Account</h2>
|
||||
<p class="text-muted mb-0">Review medical store payout requests, approve them, and post settlement payments.</p>
|
||||
</div>
|
||||
|
||||
<div id="ms_credit_alert"></div>
|
||||
|
||||
<div class="row g-2 mb-3">
|
||||
<div class="col-md-3 col-6"><div class="card border-secondary"><div class="card-body py-2"><div class="small text-muted">Total Requested</div><div class="h5 mb-0" id="ms_total_requested"><?= esc(number_format((float) ($summary['total_requested'] ?? 0), 2)) ?></div></div></div></div>
|
||||
<div class="col-md-3 col-6"><div class="card border-success"><div class="card-body py-2"><div class="small text-muted">Total Paid</div><div class="h5 mb-0 text-success" id="ms_total_paid"><?= esc(number_format((float) ($summary['total_paid'] ?? 0), 2)) ?></div></div></div></div>
|
||||
<div class="col-md-3 col-6"><div class="card border-warning"><div class="card-body py-2"><div class="small text-muted">Total Pending</div><div class="h5 mb-0 text-warning" id="ms_total_pending"><?= esc(number_format((float) ($summary['total_pending'] ?? 0), 2)) ?></div></div></div></div>
|
||||
<div class="col-md-3 col-6"><div class="card border-info"><div class="card-body py-2"><div class="small text-muted">Open Requests</div><div class="h5 mb-0 text-info" id="ms_open_requests"><?= (int) ($summary['open_requests'] ?? 0) ?></div></div></div></div>
|
||||
</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="card-header d-flex justify-content-between align-items-center">
|
||||
<strong>Request List</strong>
|
||||
<div class="d-flex gap-2">
|
||||
<select id="ms_status_filter" class="form-select form-select-sm" style="min-width:180px;">
|
||||
<option value="">All Status</option>
|
||||
<option value="submitted">Submitted</option>
|
||||
<option value="finance_review">Finance Review</option>
|
||||
<option value="approved">Approved</option>
|
||||
<option value="partially_paid">Partially Paid</option>
|
||||
<option value="paid">Paid</option>
|
||||
<option value="rejected">Rejected</option>
|
||||
</select>
|
||||
<button type="button" class="btn btn-outline-secondary btn-sm" id="btn_ms_refresh">Refresh</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body p-0">
|
||||
<div id="ms_requests_table_wrap"><?= view('finance/partials/medical_store_requests_table', ['rows' => $rows ?? []]) ?></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="modal fade" id="msRequestDetailModal" tabindex="-1" aria-labelledby="msRequestDetailModalLabel">
|
||||
<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>
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
(function () {
|
||||
var BASE = '<?= base_url() ?>';
|
||||
|
||||
function showAlert(msg, ok) {
|
||||
var cls = ok ? 'alert-success' : 'alert-danger';
|
||||
var box = document.getElementById('ms_credit_alert');
|
||||
if (!box) return;
|
||||
box.innerHTML = '<div class="alert ' + cls + ' alert-dismissible fade show" role="alert">'
|
||||
+ msg + '<button type="button" class="btn-close" data-bs-dismiss="alert"></button></div>';
|
||||
}
|
||||
|
||||
function postForm(url, formData, cb) {
|
||||
fetch(url, {
|
||||
method: 'POST',
|
||||
headers: {'X-Requested-With': 'XMLHttpRequest'},
|
||||
body: formData
|
||||
}).then(function (res) {
|
||||
return res.json().then(function (data) { return { ok: res.ok, data: data }; });
|
||||
}).then(cb).catch(function () {
|
||||
showAlert('Network or server error.', false);
|
||||
});
|
||||
}
|
||||
|
||||
function refreshRequestsTable() {
|
||||
var status = document.getElementById('ms_status_filter') ? document.getElementById('ms_status_filter').value : '';
|
||||
load_form_div(BASE + 'Finance/medical_store_requests_table?status=' + encodeURIComponent(status), 'ms_requests_table_wrap');
|
||||
}
|
||||
|
||||
function refreshSummary() {
|
||||
fetch(BASE + 'Finance/medical_store_dashboard_card', {
|
||||
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) {
|
||||
return;
|
||||
}
|
||||
var s = result.data.summary || {};
|
||||
var tr = document.getElementById('ms_total_requested');
|
||||
var tp = document.getElementById('ms_total_paid');
|
||||
var tn = document.getElementById('ms_total_pending');
|
||||
var orq = document.getElementById('ms_open_requests');
|
||||
if (tr) tr.textContent = Number(s.total_requested || 0).toFixed(2);
|
||||
if (tp) tp.textContent = Number(s.total_paid || 0).toFixed(2);
|
||||
if (tn) tn.textContent = Number(s.total_pending || 0).toFixed(2);
|
||||
if (orq) orq.textContent = String(parseInt(s.open_requests || 0, 10));
|
||||
});
|
||||
}
|
||||
|
||||
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 : '';
|
||||
if (!requestId) {
|
||||
showAlert('Request ID is required.', false);
|
||||
return;
|
||||
}
|
||||
var fd = new window.FormData();
|
||||
fd.append('request_id', requestId);
|
||||
fd.append('remark', remark);
|
||||
|
||||
postForm(BASE + endpoint, fd, function (result) {
|
||||
if (!result.ok || !result.data || result.data.status !== 1) {
|
||||
showAlert((result.data && result.data.message) ? result.data.message : 'Action failed.', false);
|
||||
return;
|
||||
}
|
||||
showAlert(result.data.message || 'Updated successfully.', true);
|
||||
refreshRequestsTable();
|
||||
refreshSummary();
|
||||
});
|
||||
}
|
||||
|
||||
var paymentForm = document.getElementById('ms_payment_form');
|
||||
if (paymentForm) {
|
||||
paymentForm.addEventListener('submit', function (e) {
|
||||
e.preventDefault();
|
||||
var fd = new window.FormData(paymentForm);
|
||||
postForm(BASE + 'Finance/medical_store_payment_create', fd, function (result) {
|
||||
if (!result.ok || !result.data || result.data.status !== 1) {
|
||||
showAlert((result.data && result.data.message) ? result.data.message : 'Payment failed.', false);
|
||||
return;
|
||||
}
|
||||
showAlert(result.data.message || 'Payment posted successfully.', true);
|
||||
paymentForm.reset();
|
||||
refreshRequestsTable();
|
||||
refreshSummary();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
};
|
||||
|
||||
var btnRefresh = document.getElementById('btn_ms_refresh');
|
||||
if (btnRefresh) btnRefresh.addEventListener('click', refreshRequestsTable);
|
||||
|
||||
var statusFilter = document.getElementById('ms_status_filter');
|
||||
if (statusFilter) statusFilter.addEventListener('change', refreshRequestsTable);
|
||||
|
||||
var btnReview = document.getElementById('btn_ms_review');
|
||||
if (btnReview) btnReview.addEventListener('click', function () { doStatusAction('Finance/medical_store_request_review'); });
|
||||
|
||||
var btnApprove = document.getElementById('btn_ms_approve');
|
||||
if (btnApprove) btnApprove.addEventListener('click', function () { doStatusAction('Finance/medical_store_request_approve'); });
|
||||
|
||||
var btnReject = document.getElementById('btn_ms_reject');
|
||||
if (btnReject) btnReject.addEventListener('click', function () { doStatusAction('Finance/medical_store_request_reject'); });
|
||||
})();
|
||||
</script>
|
||||
|
|
@ -0,0 +1,90 @@
|
|||
<?php $req = $request ?? null; ?>
|
||||
<div class="p-3">
|
||||
<?php if (!is_array($req)): ?>
|
||||
<div class="alert alert-warning mb-0">Request not found.</div>
|
||||
<?php else: ?>
|
||||
<div class="row g-2 mb-3">
|
||||
<div class="col-md-3"><strong>Request:</strong> <?= esc((string) ($req['request_no'] ?? '')) ?></div>
|
||||
<div class="col-md-2"><strong>Status:</strong> <?= esc((string) ($req['status'] ?? '')) ?></div>
|
||||
<div class="col-md-2"><strong>Date:</strong> <?= esc((string) ($req['request_date'] ?? '')) ?></div>
|
||||
<div class="col-md-5 text-md-end">
|
||||
<strong>Requested:</strong> <?= esc(number_format((float) ($req['requested_amount'] ?? 0), 2)) ?> |
|
||||
<strong>Paid:</strong> <?= esc(number_format((float) ($req['paid_amount'] ?? 0), 2)) ?> |
|
||||
<strong>Pending:</strong> <?= esc(number_format((float) ($req['pending_amount'] ?? 0), 2)) ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="table-responsive mb-3">
|
||||
<table class="table table-sm table-bordered align-middle">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th>#</th>
|
||||
<th>Source</th>
|
||||
<th>Invoice</th>
|
||||
<th>IPD</th>
|
||||
<th>Case</th>
|
||||
<th>Category</th>
|
||||
<th class="text-end">Amount</th>
|
||||
<th class="text-end">Allocated</th>
|
||||
<th class="text-end">Pending</th>
|
||||
<th>Status</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php if (empty($lines ?? [])): ?>
|
||||
<tr><td colspan="10" class="text-center text-muted">No request lines.</td></tr>
|
||||
<?php else: ?>
|
||||
<?php foreach (($lines ?? []) as $i => $line): ?>
|
||||
<tr>
|
||||
<td><?= (int) $i + 1 ?></td>
|
||||
<td><?= esc((string) ($line['source_type'] ?? '')) ?>#<?= (int) ($line['source_ref_id'] ?? 0) ?></td>
|
||||
<td><?= esc((string) ($line['invoice_code'] ?? '')) ?></td>
|
||||
<td><?= esc((string) ($line['ipd_code'] ?? '')) ?></td>
|
||||
<td><?= esc((string) ($line['case_code'] ?? '')) ?></td>
|
||||
<td><?= esc((string) ($line['credit_category'] ?? '')) ?></td>
|
||||
<td class="text-end"><?= esc(number_format((float) ($line['line_amount'] ?? 0), 2)) ?></td>
|
||||
<td class="text-end"><?= esc(number_format((float) ($line['allocated_amount'] ?? 0), 2)) ?></td>
|
||||
<td class="text-end"><?= esc(number_format((float) ($line['pending_amount'] ?? 0), 2)) ?></td>
|
||||
<td><span class="badge bg-secondary"><?= esc((string) ($line['line_status'] ?? '')) ?></span></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<h6 class="mb-2">Payments</h6>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm table-bordered align-middle mb-0">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th>#</th>
|
||||
<th>Payment No</th>
|
||||
<th>Date</th>
|
||||
<th>Mode</th>
|
||||
<th class="text-end">Amount</th>
|
||||
<th>Reconcile</th>
|
||||
<th>Remark</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php if (empty($payments ?? [])): ?>
|
||||
<tr><td colspan="7" class="text-center text-muted">No payments posted yet.</td></tr>
|
||||
<?php else: ?>
|
||||
<?php foreach (($payments ?? []) as $j => $p): ?>
|
||||
<tr>
|
||||
<td><?= (int) $j + 1 ?></td>
|
||||
<td><?= esc((string) ($p['payment_no'] ?? '')) ?></td>
|
||||
<td><?= esc((string) ($p['payment_date'] ?? '')) ?></td>
|
||||
<td><?= (int) ($p['payment_mode'] ?? 0) === 2 ? 'Bank' : 'Cash' ?></td>
|
||||
<td class="text-end"><?= esc(number_format((float) ($p['amount'] ?? 0), 2)) ?></td>
|
||||
<td><?= esc((string) (($p['bank_reconcile_status'] ?? '') !== '' ? $p['bank_reconcile_status'] : 'unmatched')) ?></td>
|
||||
<td><?= esc((string) ($p['remark'] ?? '')) ?></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
38
app/Views/finance/partials/medical_store_requests_table.php
Normal file
38
app/Views/finance/partials/medical_store_requests_table.php
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
<div class="table-responsive">
|
||||
<table class="table table-sm table-bordered align-middle mb-0">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th>#</th>
|
||||
<th>Request No</th>
|
||||
<th>Date</th>
|
||||
<th>Status</th>
|
||||
<th class="text-end">Requested</th>
|
||||
<th class="text-end">Paid</th>
|
||||
<th class="text-end">Pending</th>
|
||||
<th>Created By</th>
|
||||
<th class="text-center">Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php if (empty($rows ?? [])): ?>
|
||||
<tr><td colspan="9" class="text-center text-muted py-3">No requests found.</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>
|
||||
<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>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
492
app/Views/medical/credit_payout_request.php
Normal file
492
app/Views/medical/credit_payout_request.php
Normal file
|
|
@ -0,0 +1,492 @@
|
|||
<div class="pagetitle">
|
||||
<h1>Medical Store Credit Payout Request</h1>
|
||||
<nav>
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item">Medical Store</li>
|
||||
<li class="breadcrumb-item active">Credit Payout Request</li>
|
||||
</ol>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<section class="section">
|
||||
<div id="med_credit_req_alert"></div>
|
||||
|
||||
<div class="row g-3">
|
||||
<div class="col-xl-7">
|
||||
<div class="card">
|
||||
<div class="card-header d-flex justify-content-between align-items-center">
|
||||
<strong>1) Select Credit Entries</strong>
|
||||
<div class="d-flex gap-2">
|
||||
<button type="button" class="btn btn-outline-secondary btn-sm" id="btn_med_pool_refresh">Refresh Pool</button>
|
||||
<button type="button" class="btn btn-outline-primary btn-sm" id="btn_med_pool_select_all">Select All</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body border-bottom pb-2">
|
||||
<div class="row g-2 align-items-end">
|
||||
<div class="col-md-4">
|
||||
<label class="form-label mb-1">Scope</label>
|
||||
<select class="form-select form-select-sm" id="med_pool_scope">
|
||||
<option value="all">All Eligible</option>
|
||||
<option value="ipd">IPD Discharged Only</option>
|
||||
<option value="org">Org OPD Submitted Only</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<label class="form-label mb-1">From</label>
|
||||
<input type="date" class="form-control form-control-sm" id="med_pool_from_date">
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<label class="form-label mb-1">To</label>
|
||||
<input type="date" class="form-control form-control-sm" id="med_pool_to_date">
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<label class="form-label mb-1">Group By</label>
|
||||
<select class="form-select form-select-sm" id="med_pool_group_by">
|
||||
<option value="none">None</option>
|
||||
<option value="ipd">IPD No.</option>
|
||||
<option value="case">Org Case ID</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<button type="button" class="btn btn-sm btn-outline-dark w-100" id="btn_med_pool_apply_filter">Apply</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body p-0">
|
||||
<div class="table-responsive" style="max-height: 420px; overflow:auto;">
|
||||
<table class="table table-sm table-bordered align-middle mb-0" id="med_credit_pool_table" style="width:100%;">
|
||||
<thead class="table-light" style="position:sticky;top:0;z-index:2;">
|
||||
<tr>
|
||||
<th style="width:42px;"><input type="checkbox" id="med_pool_master"></th>
|
||||
<th>Source</th>
|
||||
<th>Invoice</th>
|
||||
<th>IPD</th>
|
||||
<th>Case</th>
|
||||
<th>Category</th>
|
||||
<th>Date</th>
|
||||
<th class="text-end">Amount</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-xl-5">
|
||||
<div class="card h-100">
|
||||
<div class="card-header"><strong>2) Submit Payout Request</strong></div>
|
||||
<div class="card-body">
|
||||
<form id="med_credit_request_form" class="row g-2">
|
||||
<div class="col-md-6">
|
||||
<label class="form-label mb-1">Request Date</label>
|
||||
<input type="date" class="form-control" name="request_date" value="<?= date('Y-m-d') ?>" required>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label mb-1">Selected Lines</label>
|
||||
<input type="text" class="form-control" id="med_selected_count" value="0" readonly>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<label class="form-label mb-1">Total Amount</label>
|
||||
<input type="text" class="form-control" id="med_selected_total" value="0.00" readonly>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<label class="form-label mb-1">Remarks</label>
|
||||
<textarea class="form-control" name="remarks" rows="3" placeholder="Optional remarks for finance"></textarea>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<button type="submit" class="btn btn-primary btn-sm">Submit Request</button>
|
||||
</div>
|
||||
</form>
|
||||
<hr>
|
||||
<div class="small text-muted">Request history is shown below. Use Finance panel for approval and payment settlement.</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card mt-3">
|
||||
<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>
|
||||
</div>
|
||||
<div class="card-body p-0">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm table-bordered align-middle mb-0" id="med_request_history_table">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th>#</th>
|
||||
<th>Request No</th>
|
||||
<th>Date</th>
|
||||
<th>Status</th>
|
||||
<th class="text-end">Requested</th>
|
||||
<th class="text-end">Paid</th>
|
||||
<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>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<script>
|
||||
(function () {
|
||||
var BASE = '<?= base_url() ?>';
|
||||
var poolTable = null;
|
||||
var selectedMap = {};
|
||||
var poolRowCache = {};
|
||||
|
||||
function showAlert(msg, ok) {
|
||||
var box = document.getElementById('med_credit_req_alert');
|
||||
if (!box) return;
|
||||
box.innerHTML = '<div class="alert ' + (ok ? 'alert-success' : 'alert-danger') + ' alert-dismissible fade show" role="alert">'
|
||||
+ msg + '<button type="button" class="btn-close" data-bs-dismiss="alert"></button></div>';
|
||||
}
|
||||
|
||||
function getRowKey(row) {
|
||||
return String(row.source_type || '') + ':' + String(row.source_ref_id || 0);
|
||||
}
|
||||
|
||||
function selectedRows() {
|
||||
return Object.keys(selectedMap).map(function (key) {
|
||||
return selectedMap[key];
|
||||
});
|
||||
}
|
||||
|
||||
function clearSelectedRows() {
|
||||
selectedMap = {};
|
||||
refreshSelectionSummary();
|
||||
var master = document.getElementById('med_pool_master');
|
||||
if (master) {
|
||||
master.checked = false;
|
||||
}
|
||||
}
|
||||
|
||||
function refreshSelectionSummary() {
|
||||
var rows = selectedRows();
|
||||
var total = 0;
|
||||
rows.forEach(function (row) { total += Number(row.line_amount || 0); });
|
||||
var c = document.getElementById('med_selected_count');
|
||||
var t = document.getElementById('med_selected_total');
|
||||
if (c) c.value = String(rows.length);
|
||||
if (t) t.value = total.toFixed(2);
|
||||
}
|
||||
|
||||
function updateMasterCheckboxForPage() {
|
||||
var master = document.getElementById('med_pool_master');
|
||||
if (!master || !poolTable) {
|
||||
return;
|
||||
}
|
||||
var pageRows = poolTable.rows({ page: 'current' }).data().toArray();
|
||||
if (!pageRows.length) {
|
||||
master.checked = false;
|
||||
return;
|
||||
}
|
||||
var allSelected = pageRows.every(function (row) {
|
||||
return !!selectedMap[getRowKey(row)];
|
||||
});
|
||||
master.checked = allSelected;
|
||||
}
|
||||
|
||||
function loadPool() {
|
||||
if (poolTable) {
|
||||
poolTable.ajax.reload(null, false);
|
||||
}
|
||||
}
|
||||
|
||||
function applyPoolFilter() {
|
||||
clearSelectedRows();
|
||||
loadPool();
|
||||
}
|
||||
|
||||
function syncScopeWithGroupMode() {
|
||||
var scopeEl = document.getElementById('med_pool_scope');
|
||||
var groupEl = document.getElementById('med_pool_group_by');
|
||||
if (!scopeEl || !groupEl) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (groupEl.value === 'ipd') {
|
||||
scopeEl.value = 'ipd';
|
||||
scopeEl.setAttribute('disabled', 'disabled');
|
||||
} else if (groupEl.value === 'case') {
|
||||
scopeEl.value = 'org';
|
||||
scopeEl.setAttribute('disabled', 'disabled');
|
||||
} else {
|
||||
scopeEl.removeAttribute('disabled');
|
||||
}
|
||||
}
|
||||
|
||||
function initPoolTable() {
|
||||
if (typeof window.jQuery === 'undefined' || typeof window.jQuery.fn.DataTable === 'undefined') {
|
||||
showAlert('DataTable dependency missing. Unable to load credit entries.', false);
|
||||
return;
|
||||
}
|
||||
|
||||
poolTable = window.jQuery('#med_credit_pool_table').DataTable({
|
||||
processing: true,
|
||||
serverSide: true,
|
||||
searching: true,
|
||||
lengthMenu: [[10, 25, 50, 100], [10, 25, 50, 100]],
|
||||
pageLength: 25,
|
||||
order: [[6, 'desc']],
|
||||
ajax: {
|
||||
url: BASE + 'Medical/credit_payout_pool_datatable',
|
||||
type: 'POST',
|
||||
data: function (d) {
|
||||
d.scope = document.getElementById('med_pool_scope') ? document.getElementById('med_pool_scope').value : 'all';
|
||||
d.from_date = document.getElementById('med_pool_from_date') ? document.getElementById('med_pool_from_date').value : '';
|
||||
d.to_date = document.getElementById('med_pool_to_date') ? document.getElementById('med_pool_to_date').value : '';
|
||||
d.group_by = document.getElementById('med_pool_group_by') ? document.getElementById('med_pool_group_by').value : 'none';
|
||||
},
|
||||
error: function () {
|
||||
showAlert('Unable to load credit entry pool.', false);
|
||||
}
|
||||
},
|
||||
columns: [
|
||||
{
|
||||
data: null,
|
||||
orderable: false,
|
||||
searchable: false,
|
||||
className: 'text-center',
|
||||
render: function (data, type, row) {
|
||||
var key = getRowKey(row);
|
||||
poolRowCache[key] = row;
|
||||
var checked = selectedMap[key] ? ' checked' : '';
|
||||
return '<input type="checkbox" class="med-pool-check" data-key="' + key + '"' + checked + '>';
|
||||
}
|
||||
},
|
||||
{
|
||||
data: null,
|
||||
render: function (data, type, row) {
|
||||
return String(row.source_type || '') + '#' + Number(row.source_ref_id || 0);
|
||||
}
|
||||
},
|
||||
{ data: 'invoice_code', defaultContent: '' },
|
||||
{ data: 'ipd_code', defaultContent: '' },
|
||||
{ data: 'case_code', defaultContent: '' },
|
||||
{ data: 'credit_category', defaultContent: '' },
|
||||
{ data: 'inv_date', defaultContent: '' },
|
||||
{
|
||||
data: 'line_amount',
|
||||
className: 'text-end',
|
||||
render: function (v) {
|
||||
return Number(v || 0).toFixed(2);
|
||||
}
|
||||
}
|
||||
],
|
||||
drawCallback: function () {
|
||||
renderGroupRows();
|
||||
updateMasterCheckboxForPage();
|
||||
refreshSelectionSummary();
|
||||
},
|
||||
language: {
|
||||
emptyTable: 'No eligible credit entries found.'
|
||||
}
|
||||
});
|
||||
|
||||
window.jQuery('#med_credit_pool_table tbody').on('change', '.med-pool-check', function () {
|
||||
var key = this.getAttribute('data-key') || '';
|
||||
if (!key) {
|
||||
return;
|
||||
}
|
||||
if (this.checked) {
|
||||
selectedMap[key] = poolRowCache[key] || selectedMap[key] || {};
|
||||
} else {
|
||||
delete selectedMap[key];
|
||||
}
|
||||
updateMasterCheckboxForPage();
|
||||
refreshSelectionSummary();
|
||||
});
|
||||
|
||||
refreshSelectionSummary();
|
||||
}
|
||||
|
||||
function renderGroupRows() {
|
||||
if (!poolTable || typeof window.jQuery === 'undefined') {
|
||||
return;
|
||||
}
|
||||
var groupMode = document.getElementById('med_pool_group_by') ? document.getElementById('med_pool_group_by').value : 'none';
|
||||
var body = window.jQuery('#med_credit_pool_table tbody');
|
||||
body.find('tr.med-group-row').remove();
|
||||
if (groupMode === 'none') {
|
||||
return;
|
||||
}
|
||||
|
||||
var rows = poolTable.rows({ page: 'current' }).data().toArray();
|
||||
var domRows = body.find('tr');
|
||||
var lastGroup = null;
|
||||
rows.forEach(function (row, i) {
|
||||
var val = groupMode === 'ipd' ? String(row.ipd_code || '').trim() : String(row.case_code || '').trim();
|
||||
var groupValue = val !== '' ? val : 'Unmapped';
|
||||
if (groupValue !== lastGroup) {
|
||||
var title = (groupMode === 'ipd' ? 'IPD: ' : 'Case: ') + groupValue;
|
||||
var header = window.jQuery('<tr class="med-group-row table-secondary"><td colspan="8" class="fw-semibold small">' + title + '</td></tr>');
|
||||
window.jQuery(domRows[i]).before(header);
|
||||
lastGroup = groupValue;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
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 form = document.getElementById('med_credit_request_form');
|
||||
if (form) {
|
||||
form.addEventListener('submit', function (e) {
|
||||
e.preventDefault();
|
||||
var rows = selectedRows();
|
||||
if (!rows.length) {
|
||||
showAlert('Select at least one credit entry.', false);
|
||||
return;
|
||||
}
|
||||
var fd = new window.FormData(form);
|
||||
fd.append('line_items', JSON.stringify(rows));
|
||||
|
||||
fetch(BASE + 'Medical/credit_payout_request_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) {
|
||||
if (!result.ok || !result.data || result.data.status !== 1) {
|
||||
showAlert((result.data && result.data.message) ? result.data.message : 'Request submission failed.', false);
|
||||
return;
|
||||
}
|
||||
showAlert(result.data.message || 'Request created successfully.', true);
|
||||
form.reset();
|
||||
clearSelectedRows();
|
||||
loadPool();
|
||||
loadHistory();
|
||||
}).catch(function () {
|
||||
showAlert('Network or server error.', false);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
var btnPoolRefresh = document.getElementById('btn_med_pool_refresh');
|
||||
if (btnPoolRefresh) btnPoolRefresh.addEventListener('click', loadPool);
|
||||
|
||||
var btnHistRefresh = document.getElementById('btn_med_history_refresh');
|
||||
if (btnHistRefresh) btnHistRefresh.addEventListener('click', loadHistory);
|
||||
|
||||
var masterCb = document.getElementById('med_pool_master');
|
||||
if (masterCb) {
|
||||
masterCb.addEventListener('change', function () {
|
||||
if (!poolTable) {
|
||||
return;
|
||||
}
|
||||
var pageRows = poolTable.rows({ page: 'current' }).data().toArray();
|
||||
pageRows.forEach(function (row) {
|
||||
var key = getRowKey(row);
|
||||
if (masterCb.checked) {
|
||||
selectedMap[key] = row;
|
||||
} else {
|
||||
delete selectedMap[key];
|
||||
}
|
||||
});
|
||||
window.jQuery('#med_credit_pool_table tbody .med-pool-check').prop('checked', masterCb.checked);
|
||||
refreshSelectionSummary();
|
||||
});
|
||||
}
|
||||
|
||||
var btnSelectAll = document.getElementById('btn_med_pool_select_all');
|
||||
if (btnSelectAll) {
|
||||
btnSelectAll.addEventListener('click', function () {
|
||||
if (!poolTable) {
|
||||
return;
|
||||
}
|
||||
var pageRows = poolTable.rows({ page: 'current' }).data().toArray();
|
||||
var anyUnchecked = pageRows.some(function (row) {
|
||||
return !selectedMap[getRowKey(row)];
|
||||
});
|
||||
pageRows.forEach(function (row) {
|
||||
var key = getRowKey(row);
|
||||
if (anyUnchecked) {
|
||||
selectedMap[key] = row;
|
||||
} else {
|
||||
delete selectedMap[key];
|
||||
}
|
||||
});
|
||||
window.jQuery('#med_credit_pool_table tbody .med-pool-check').prop('checked', anyUnchecked);
|
||||
if (masterCb) masterCb.checked = anyUnchecked;
|
||||
refreshSelectionSummary();
|
||||
});
|
||||
}
|
||||
|
||||
var btnApplyFilter = document.getElementById('btn_med_pool_apply_filter');
|
||||
if (btnApplyFilter) {
|
||||
btnApplyFilter.addEventListener('click', applyPoolFilter);
|
||||
}
|
||||
|
||||
var groupModeEl = document.getElementById('med_pool_group_by');
|
||||
if (groupModeEl) {
|
||||
groupModeEl.addEventListener('change', function () {
|
||||
syncScopeWithGroupMode();
|
||||
applyPoolFilter();
|
||||
});
|
||||
}
|
||||
|
||||
var filterIds = ['med_pool_scope', 'med_pool_from_date', 'med_pool_to_date'];
|
||||
filterIds.forEach(function (id) {
|
||||
var el = document.getElementById(id);
|
||||
if (!el) return;
|
||||
el.addEventListener('change', applyPoolFilter);
|
||||
});
|
||||
|
||||
syncScopeWithGroupMode();
|
||||
initPoolTable();
|
||||
loadHistory();
|
||||
})();
|
||||
</script>
|
||||
|
|
@ -32,6 +32,9 @@
|
|||
<a class="btn btn-outline-primary btn-sm" href="javascript:load_form_div('<?= base_url('Medical/master') ?>','medical-main','Master :Pharmacy');">
|
||||
<i class="bi bi-sliders"></i> Master
|
||||
</a>
|
||||
<a class="btn btn-outline-warning btn-sm" href="javascript:load_form_div('<?= base_url('Medical/credit_payout_request') ?>','medical-main','Credit Payout Request :Pharmacy');">
|
||||
<i class="bi bi-cash-stack"></i> Credit Payout Request
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -37,6 +37,9 @@
|
|||
<li class="list-group-item px-0">
|
||||
<a class="medical-master-link" href="javascript:load_form_div('<?= base_url('Medical/supplier_account') ?>','medical-main','Supplier Account :Pharmacy');">Supplier Account</a>
|
||||
</li>
|
||||
<li class="list-group-item px-0">
|
||||
<a class="medical-master-link" href="javascript:load_form_div('<?= base_url('Medical/credit_payout_request') ?>','medical-main','Credit Payout Request :Pharmacy');">Credit Payout Request</a>
|
||||
</li>
|
||||
<li class="list-group-item px-0">
|
||||
<a class="medical-master-link" href="javascript:load_form_div('<?= base_url('Payment_Medical') ?>','medical-main','Med. Payment Edit :Pharmacy');">Med. Payment Edit</a>
|
||||
</li>
|
||||
|
|
@ -158,6 +161,7 @@
|
|||
var keywordMap = {
|
||||
'Purchase Invoice': 'purchase bill buy vendor supplier invoice pi',
|
||||
'Supplier Account': 'supplier vendor ledger account payable',
|
||||
'Credit Payout Request': 'credit payout request hospital due settlement finance',
|
||||
'Med. Payment Edit': 'payment edit correction cash bank',
|
||||
'Med. Payment Edit Logs': 'payment logs audit history',
|
||||
'Med. Invoice Item Update Logs': 'invoice item logs update history',
|
||||
|
|
|
|||
|
|
@ -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/medical_store_credit_account') ?>','Medical Store Credit Account')">
|
||||
<i class="bi bi-circle"></i>
|
||||
<span>Medical Store Credit Account</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<?php } ?>
|
||||
|
|
|
|||
|
|
@ -33,9 +33,11 @@
|
|||
<li><a href="javascript:load_form('<?= base_url('Report/report_opd_total') ?>','OPD Total Report');"><i class="bi bi-arrow-right-short"></i> OPD Total Report</a></li>
|
||||
<li><a href="javascript:load_form('<?= base_url('Report/billing_operations_report') ?>','Billing Operations Report');"><i class="bi bi-arrow-right-short"></i> Billing Operations Report</a></li>
|
||||
<li><a href="javascript:load_form('<?= base_url('Finance/index') ?>','Accounts And Finance');"><i class="bi bi-arrow-right-short"></i> Accounts And Finance Dashboard</a></li>
|
||||
<li><a href="javascript:load_form('<?= base_url('Finance/medical_store_credit_account') ?>','Medical Store Credit Account');"><i class="bi bi-arrow-right-short"></i> Medical Store Credit Account</a></li>
|
||||
<li><a href="javascript:load_form('<?= base_url('Finance/cashbook') ?>','Billing Cash Statement Submission');"><i class="bi bi-arrow-right-short"></i> Billing Cash Statement Submission</a></li>
|
||||
<li><a href="javascript:load_form('<?= base_url('Finance/cashbook') ?>','Accounts Accept and Verify');"><i class="bi bi-arrow-right-short"></i> Accounts Accept and Verify</a></li>
|
||||
<li><a href="javascript:load_form('<?= base_url('Finance/bank_deposits') ?>','Bank Transaction Audit');"><i class="bi bi-arrow-right-short"></i> Bank Transaction Audit</a></li>
|
||||
<li><a href="javascript:load_form('<?= base_url('Medical/credit_payout_request') ?>','Credit Payout Request :Pharmacy');"><i class="bi bi-arrow-right-short"></i> Medical Credit Payout Request</a></li>
|
||||
<li><a href="javascript:load_form('<?= base_url('Report/nabh_audit_report') ?>','NABH Audit Report');"><i class="bi bi-arrow-right-short"></i> NABH Audit Report</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@
|
|||
* Auto-hide sidebar after a short idle period on desktop
|
||||
* to provide larger work area for clinical forms.
|
||||
*/
|
||||
const configuredDelay = parseInt(window.SIDEBAR_AUTO_HIDE_DELAY_MS || 7000, 10)
|
||||
const configuredDelay = parseInt(window.SIDEBAR_AUTO_HIDE_DELAY_MS ?? 7000, 10)
|
||||
const SIDEBAR_AUTO_HIDE_DELAY_MS = isNaN(configuredDelay) ? 7000 : Math.max(0, configuredDelay)
|
||||
let sidebarAutoHideTimer = null
|
||||
let lastActivityTs = 0
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user