devsofttech_hms_ci4/app/Controllers/Payment.php
DevSoftTech Server 6e78e6010e feat: add Day Care & Emergency admission module with IPD conversion and clean patient names
- Add Day Care (<24h) and Emergency (Casualty) admission pathways with prefixes DC- and ER-
- Add dedicated physical wards, ESI 5-level triage, MLC tracking, and PADSS day care discharge
- Implement audited 'Convert to IPD' workflow with SBAR handover and automated bed transfer
- Support unified IPD billing with dedicated group headers for Day Care and Emergency charges
- Redesign Hospital Admission Desk with executive card layout and modern triage controls
- Add comprehensive user documentation and SVG flowchart in public/help.html
- Completely remove legacy patient_master.p_lname column from database and application code
2026-09-21 23:41:24 +05:30

264 lines
9.4 KiB
PHP

<?php
namespace App\Controllers;
use App\Models\PaymentModel;
use RuntimeException;
use Throwable;
class Payment extends BaseController
{
private PaymentModel $paymentModel;
public function __construct()
{
$this->paymentModel = new PaymentModel();
}
public function index(): string
{
return view('payment/payment_search');
}
public function paymentRecord()
{
$paymentId = (int) preg_replace('/\D+/', '', (string) $this->request->getPost('txtsearch'));
if ($paymentId <= 0) {
return $this->searchResponse('<div class="alert alert-warning">Enter a valid Payment ID.</div>', 422);
}
$payment = $this->paymentModel->findPayment($paymentId);
if ($payment === null) {
return $this->searchResponse('<div class="alert alert-warning">Payment record not found.</div>', 404);
}
return $this->searchResponse($this->renderPayment($payment));
}
public function changeToBank()
{
$paymentId = (int) $this->request->getPost('pay_id');
$sourceId = (int) $this->request->getPost('cbo_pay_type');
$transactionId = trim((string) $this->request->getPost('input_card_tran'));
$source = $this->findBankSource($sourceId);
if ($source === null || $transactionId === '') {
return $this->error('Bank/online source and transaction reference are required.', 422);
}
$label = (string) $source['label'];
return $this->applyCorrection($paymentId, [
'payment_mode' => 2,
'pay_bank_id' => $sourceId,
'insert_code' => (string) $sourceId,
'card_bank' => $label,
'card_tran_id' => $transactionId,
'update_remark' => 'Changed to Bank/Online',
], 2, 'Payment mode changed to BANK [' . $label . '], reference ' . $transactionId);
}
public function changeToCash()
{
$paymentId = (int) $this->request->getPost('pay_id');
return $this->applyCorrection($paymentId, [
'payment_mode' => 1,
'pay_bank_id' => 0,
'card_bank' => '',
'card_remark' => '',
'cust_card' => '',
'card_tran_id' => '',
'bankcard_machine' => '',
'insert_code' => '',
'update_remark' => 'Changed to Cash',
], 1, 'Payment mode changed to CASH');
}
public function changeUser()
{
$paymentId = (int) $this->request->getPost('pay_id');
$userId = (int) $this->request->getPost('user_list');
$user = $this->db->table('users')->select('id,username')->where('id', $userId)->where('active', 1)->get(1)->getRowArray();
if ($user === null) {
return $this->error('Select a valid active user.', 422);
}
$payment = $this->paymentModel->findPayment($paymentId);
$oldUser = trim((string) ($payment['update_by'] ?? '-'));
$newUser = (string) $user['username'] . '[' . $userId . ']';
return $this->applyCorrection($paymentId, [
'update_by_id' => $userId,
'update_by' => $newUser,
'update_remark' => 'Payment user corrected',
], 3, 'Payment user changed from ' . $oldUser . ' to ' . $newUser);
}
public function changeAmount()
{
$paymentId = (int) $this->request->getPost('pay_id');
$rawAmount = trim((string) $this->request->getPost('change_value'));
if (! is_numeric($rawAmount) || (float) $rawAmount < 0) {
return $this->error('Enter a valid amount of zero or greater.', 422);
}
$payment = $this->paymentModel->findPayment($paymentId);
$oldAmount = number_format((float) ($payment['amount'] ?? 0), 2, '.', '');
$newAmount = number_format((float) $rawAmount, 2, '.', '');
return $this->applyCorrection($paymentId, [
'amount' => $newAmount,
'update_remark' => 'Payment amount corrected',
], 4, 'Payment amount changed from ' . $oldAmount . ' to ' . $newAmount);
}
private function applyCorrection(int $paymentId, array $changes, int $updateType, string $remark)
{
if ($paymentId <= 0) {
return $this->error('Payment record not found.', 404);
}
$reason = trim((string) $this->request->getPost('correction_reason'));
if ($reason === '') {
return $this->error('Correction reason is required.', 422);
}
try {
$payment = $this->paymentModel->correctPayment($paymentId, $changes, $updateType, $this->actorName(), $remark . '. Reason: ' . $reason);
return $this->response->setJSON([
'status' => 1,
'message' => 'Payment updated successfully.',
'html' => $this->renderPayment($payment),
'csrf_hash' => csrf_hash(),
]);
} catch (RuntimeException $e) {
$status = str_contains(strtolower($e->getMessage()), 'not found') ? 404 : 409;
return $this->error($e->getMessage(), $status);
} catch (Throwable $e) {
log_message('error', 'Payment correction failed: {message}', ['message' => $e->getMessage()]);
return $this->error('Unable to update the payment record.', 500);
}
}
private function renderPayment(array $payment): string
{
$context = $this->paymentContext($payment);
return view('payment/payment_edit', [
'payment' => $payment,
'context' => $context,
'users' => $this->db->table('users')->select('id,username')->where('active', 1)->orderBy('username')->get()->getResultArray(),
'bank_sources' => $this->bankSources(),
]);
}
private function paymentContext(array $payment): array
{
$type = (int) ($payment['payof_type'] ?? 0);
$referenceId = (int) ($payment['payof_id'] ?? 0);
$context = [
'type' => [1 => 'OPD', 2 => 'OPD Charge', 3 => 'Organization Charge', 4 => 'IPD Payment'][$type] ?? 'Payment',
'code' => (string) ($payment['payof_code'] ?? ''),
'patient' => '',
];
$tableMap = [
1 => ['opd_master', 'opd_id', ['opd_code'], ['p_id'], ['p_name', 'P_name']],
2 => ['invoice_master', 'id', ['invoice_code'], ['attach_id'], ['inv_name']],
3 => ['organization_case_master', 'id', ['case_id_code'], ['p_id'], ['p_name', 'insurance_company_name']],
4 => ['ipd_master', 'id', ['ipd_code'], ['p_id'], ['P_name', 'p_name']],
];
if (! isset($tableMap[$type])) {
return $context;
}
[$table, $key, $codeColumns, $patientColumns, $nameColumns] = $tableMap[$type];
if (! $this->db->tableExists($table)) {
return $context;
}
$row = $this->db->table($table)->where($key, $referenceId)->get(1)->getRowArray();
if ($row === null) {
return $context;
}
$context['code'] = $this->firstValue($row, $codeColumns, $context['code']);
$context['patient'] = $this->firstValue($row, $nameColumns);
$patientId = (int) $this->firstValue($row, $patientColumns, '0');
if ($context['patient'] === '' && $patientId > 0) {
foreach (['patient_master', 'patient_master_exten'] as $patientTable) {
if (! $this->db->tableExists($patientTable)) {
continue;
}
$patient = $this->db->table($patientTable)->where('id', $patientId)->get(1)->getRowArray();
if ($patient !== null) {
$context['patient'] = trim((string) ($patient['p_fname'] ?? ''));
break;
}
}
}
return $context;
}
private function bankSources(): array
{
if (! $this->db->tableExists('hospital_bank_payment_source') || ! $this->db->tableExists('hospital_bank')) {
return [];
}
return $this->db->table('hospital_bank_payment_source s')
->select("s.id, CONCAT(COALESCE(s.pay_type, ''), ' [', COALESCE(b.bank_name, ''), ']') AS label", false)
->join('hospital_bank b', 'b.id = s.bank_id', 'left')
->orderBy('s.pay_type')
->get()
->getResultArray();
}
private function findBankSource(int $sourceId): ?array
{
foreach ($this->bankSources() as $source) {
if ((int) $source['id'] === $sourceId) {
return $source;
}
}
return null;
}
private function firstValue(array $row, array $columns, string $default = ''): string
{
foreach ($columns as $column) {
if (isset($row[$column]) && trim((string) $row[$column]) !== '') {
return trim((string) $row[$column]);
}
}
return $default;
}
private function actorName(): string
{
$user = auth()->user();
return $user ? ((string) ($user->username ?? 'user') . '[' . (int) $user->id . ']') : 'system';
}
private function searchResponse(string $body, int $status = 200)
{
return $this->response
->setStatusCode($status)
->setHeader('X-CSRF-TOKEN', csrf_hash())
->setBody($body);
}
private function error(string $message, int $status)
{
return $this->response->setStatusCode($status)->setJSON([
'status' => 0,
'message' => $message,
'csrf_hash' => csrf_hash(),
]);
}
}