From 252f911fadfc4e8adc40b84adaa3687fb30c48ab Mon Sep 17 00:00:00 2001 From: DevSoftTech Server Date: Mon, 1 Jun 2026 10:13:40 +0530 Subject: [PATCH] Add pharmacy invoice permissions and discount print fixes --- app/Config/AuthGroups.php | 12 ++ app/Controllers/Medical.php | 119 +++++++++++++++++- .../Admin/UserManagement/permissions.php | 3 +- app/Views/medical/invoice_print.php | 47 ++++++- app/Views/medical/invoice_print_pdf.php | 40 +++++- app/Views/medical/medical_final_invoice.php | 10 +- 6 files changed, 217 insertions(+), 14 deletions(-) diff --git a/app/Config/AuthGroups.php b/app/Config/AuthGroups.php index 4a238d3..4b7a92f 100644 --- a/app/Config/AuthGroups.php +++ b/app/Config/AuthGroups.php @@ -53,6 +53,10 @@ class AuthGroups extends ShieldAuthGroups 'title' => 'Developer', 'description' => 'Site programmers.', ], + 'pharmacy_admin' => [ + 'title' => 'Pharmacy Admin', + 'description' => 'Full pharmacy controls including old bill edits and discount overrides.', + ], 'stock_manager' => [ 'title' => 'Stock Manager', 'description' => 'Manages stock masters, approvals, and procurement.', @@ -148,6 +152,11 @@ class AuthGroups extends ShieldAuthGroups 'doctor_work.advice.manage' => 'Can manage OPD advice master', 'doctor_work.template_workspace.access' => 'Can access clinical templates workspace', 'pharmacy.access' => 'Can access pharmacy module', + 'pharmacy.invoice.admin' => 'Can fully manage pharmacy invoice controls (old bills and discount overrides)', + 'pharmacy.invoice.discount.10' => 'Can apply pharmacy invoice discount up to 10%', + 'pharmacy.invoice.discount.20' => 'Can apply pharmacy invoice discount up to 20%', + 'pharmacy.invoice.discount.30' => 'Can apply pharmacy invoice discount up to 30%', + 'pharmacy.invoice.edit-old' => 'Can edit past Walk-in/Registered pharmacy invoices', 'pharmacy.purchase.status-update' => 'Can update pharmacy purchase invoice status', 'template.pathology' => 'Can access pathology templates', 'template.ultrasound' => 'Can access ultrasound templates', @@ -247,6 +256,9 @@ class AuthGroups extends ShieldAuthGroups 'hospital_stock.*', 'abdm.*', ], + 'pharmacy_admin' => [ + 'pharmacy.*', + ], 'stock_manager' => [ 'hospital_stock.access', 'hospital_stock.master.manage', diff --git a/app/Controllers/Medical.php b/app/Controllers/Medical.php index d49e1bc..6f2d03b 100644 --- a/app/Controllers/Medical.php +++ b/app/Controllers/Medical.php @@ -46,6 +46,15 @@ class Medical extends BaseController return true; } + if ( + $permission !== 'pharmacy.invoice.admin' + && $user + && method_exists($user, 'can') + && $user->can('pharmacy.invoice.admin') + ) { + return true; + } + if ($user && method_exists($user, 'inGroup') && $user->inGroup('superadmin', 'admin', 'developer')) { return true; } @@ -53,6 +62,89 @@ class Medical extends BaseController return false; } + private function getHospitalSettingValue(string $name, string $default = ''): string + { + $name = trim($name); + if ($name === '' || ! $this->db->tableExists('hospital_setting')) { + return $default; + } + + $row = $this->db->table('hospital_setting') + ->select('s_value') + ->where('s_name', $name) + ->get() + ->getRowArray(); + + if (! is_array($row) || ! array_key_exists('s_value', $row)) { + return $default; + } + + $value = trim((string) ($row['s_value'] ?? '')); + return $value !== '' ? $value : $default; + } + + private function getPharmacyBaseDiscountPercent(): float + { + $raw = $this->getHospitalSettingValue('PHARMACY_NORMAL_MAX_DISCOUNT_PERCENT', '10'); + $percent = is_numeric($raw) ? (float) $raw : 10.0; + if ($percent < 0) { + $percent = 0.0; + } + if ($percent > 100) { + $percent = 100.0; + } + + return round($percent, 2); + } + + private function getPharmacyMaxDiscountPercent(): float + { + if ($this->canPharmacyPermission('pharmacy.invoice.admin')) { + return 100.0; + } + + $percent = $this->getPharmacyBaseDiscountPercent(); + + if ($this->canPharmacyPermission('pharmacy.invoice.discount.10')) { + $percent = max($percent, 10.0); + } + if ($this->canPharmacyPermission('pharmacy.invoice.discount.20')) { + $percent = max($percent, 20.0); + } + if ($this->canPharmacyPermission('pharmacy.invoice.discount.30')) { + $percent = max($percent, 30.0); + } + + if ($percent < 0) { + $percent = 0.0; + } + if ($percent > 100) { + $percent = 100.0; + } + + return round($percent, 2); + } + + private function validatePharmacyDiscountAmount(float $grossAmount, float $discountAmount, ?string &$errorText = null): bool + { + $errorText = null; + + if ($discountAmount < 0) { + $errorText = 'Discount amount cannot be negative.'; + return false; + } + + $maxPercent = $this->getPharmacyMaxDiscountPercent(); + $maxAmount = round(max(0.0, $grossAmount) * $maxPercent / 100, 2); + + if ($discountAmount > $maxAmount + 0.00001) { + $errorText = 'Discount Amount is greater than allowed limit. Max : ' . number_format($maxAmount, 2, '.', '') . ' (' . rtrim(rtrim(number_format($maxPercent, 2, '.', ''), '0'), '.') . '%)'; + return false; + } + + return true; + } + private function ensureMedicalAdminActionLogTable(): void { if ($this->db->tableExists('medical_admin_action_log')) { @@ -2064,6 +2156,8 @@ class Medical extends BaseController $message = trim((string) $this->request->getGet('msg')); $editBlockReason = null; $canEditInvoice = $this->canEditInvoiceRecord((array) $invoice, $editBlockReason); + $discountPercentLimit = $this->getPharmacyMaxDiscountPercent(); + $discountAmountLimit = round((float) ($invoice->gross_amount ?? 0) * $discountPercentLimit / 100, 2); return view('medical/medical_final_invoice', [ 'invoice' => $invoice, @@ -2077,6 +2171,8 @@ class Medical extends BaseController 'message' => $message, 'canEditInvoice' => $canEditInvoice, 'editBlockReason' => (string) ($editBlockReason ?? ''), + 'discountPercentLimit' => $discountPercentLimit, + 'discountAmountLimit' => $discountAmountLimit, ]); } @@ -2669,6 +2765,12 @@ class Medical extends BaseController return service('response')->setJSON(['update' => 0, 'msg_text' => 'No invoice found']); } + $grossAmount = (float) ($invoice->gross_amount ?? 0); + $discountError = null; + if (! $this->validatePharmacyDiscountAmount($grossAmount, $discount, $discountError)) { + return service('response')->setJSON(['update' => 0, 'msg_text' => (string) $discountError]); + } + $invoiceId = (int) ($invoice->id ?? 0); $update = []; if (in_array('discount_amount', $fields, true)) { @@ -3110,6 +3212,15 @@ class Medical extends BaseController $discountAmount = (float) $this->request->getPost('input_dis_amt'); $discountRemark = trim((string) $this->request->getPost('input_dis_desc')); + $grossAmount = (float) ($invoice->gross_amount ?? 0); + $discountError = null; + if (! $this->validatePharmacyDiscountAmount($grossAmount, $discountAmount, $discountError)) { + return service('response')->setJSON([ + 'update' => 0, + 'error_text' => (string) $discountError, + ]); + } + $user = service('auth')->user(); $userId = (int) ($user->id ?? 0); $userName = $user ? trim(($user->first_name ?? '') . ' ' . ($user->last_name ?? '')) : 'System'; @@ -3441,7 +3552,12 @@ class Medical extends BaseController $caseId = (int) ($invoiceRow['case_id'] ?? 0); $isWalkInOrRegistered = $ipdId === 0 && $caseId === 0 && in_array($customerType, [0, 1], true); - if ($isPastDate && $isWalkInOrRegistered && ! $this->canPharmacyPermission('pharmacy.invoice.edit-old')) { + if ( + $isPastDate + && $isWalkInOrRegistered + && ! $this->canPharmacyPermission('pharmacy.invoice.edit-old') + && ! $this->canPharmacyPermission('pharmacy.invoice.admin') + ) { $reason = 'No permission to edit past Walk-in/Registered pharmacy invoice.'; return false; } @@ -6209,7 +6325,6 @@ class Medical extends BaseController $key = $this->normalizeCompactKey($value); $map = [ - 'tab' => 'Tablet', 'tabs' => 'Tablet', 'tablet' => 'Tablet', 'tablets' => 'Tablet', diff --git a/app/Views/Setting/Admin/UserManagement/permissions.php b/app/Views/Setting/Admin/UserManagement/permissions.php index 95e6fab..fba3170 100644 --- a/app/Views/Setting/Admin/UserManagement/permissions.php +++ b/app/Views/Setting/Admin/UserManagement/permissions.php @@ -51,11 +51,12 @@
- Pharmacy module access is controlled by pharmacy.access. + Pharmacy module access is controlled by pharmacy.access. Editing past Walk-in/Registered pharmacy invoices requires pharmacy.invoice.edit-old (or pharmacy.invoice.admin). Discount caps can be granted with pharmacy.invoice.discount.10, pharmacy.invoice.discount.20, pharmacy.invoice.discount.30, or full override using pharmacy.invoice.admin. Base user discount can be set in hospital setting key PHARMACY_NORMAL_MAX_DISCOUNT_PERCENT.
getPermissions() ?? []; } diff --git a/app/Views/medical/invoice_print.php b/app/Views/medical/invoice_print.php index 20b7618..549911d 100644 --- a/app/Views/medical/invoice_print.php +++ b/app/Views/medical/invoice_print.php @@ -33,6 +33,19 @@ $doctorName = trim((string) ($invoice->doc_name ?? '')); $doctorLabel = $doctorName !== '' ? (preg_match('/^dr\.?\s*/i', $doctorName) ? $doctorName : 'Dr. ' . $doctorName) : '-'; + $items = $items ?? []; + $rowCount = count($items); + $baseGrossTotal = 0.0; + $baseDiscountTotal = 0.0; + if (! empty($items)) { + foreach ($items as $printItem) { + $baseGrossTotal += (float) ($printItem->amount ?? 0); + $baseDiscountTotal += (float) ($printItem->disc_amount ?? 0); + } + } + $invoiceDiscountTotal = (float) (($invoice->discount_amount ?? 0) + ($invoice->disc_amount ?? 0) + ($invoice->item_discount_amount ?? 0)); + $extraInvoiceDiscountTotal = max(0.0, $invoiceDiscountTotal - $baseDiscountTotal); + $allocatedExtraDiscountTotal = 0.0; ?>