diff --git a/app/Config/Filters.php b/app/Config/Filters.php index f1ce0c7..5702be3 100644 --- a/app/Config/Filters.php +++ b/app/Config/Filters.php @@ -94,6 +94,14 @@ class Filters extends BaseFilters 'stt-proxy/*', 'billing/charges', 'billing/charges/*', + 'Medical/mobile_save_draft', + 'Medical/mobile_save_audit', + 'Medical/stock_audit_reconcile', + 'Medical/mobile_*', + 'medical/mobile_save_draft', + 'medical/mobile_save_audit', + 'medical/stock_audit_reconcile', + 'medical/mobile_*', ], ], // 'honeypot', diff --git a/app/Config/Routes.php b/app/Config/Routes.php index d2513ef..dbad4e9 100644 --- a/app/Config/Routes.php +++ b/app/Config/Routes.php @@ -55,18 +55,29 @@ $routes->get('Medical/invoice_print/(:num)', 'Medical::invoice_print/$1'); $routes->get('Medical/invoice_print/(:num)/(:num)', 'Medical::invoice_print/$1/$2'); $routes->get('Medical/Invoice_Med_Draft', 'Medical::Invoice_Med_Draft'); $routes->get('Medical/mobile', 'Medical::mobile'); +$routes->get('medical/mobile', 'Medical::mobile'); $routes->get('app/central-pharmacy', 'Medical::mobile'); $routes->get('app/main-pharmacy', 'Medical::mobile'); $routes->get('Medical/mobile_patient_search', 'Medical::mobilePatientSearch'); +$routes->get('medical/mobile_patient_search', 'Medical::mobilePatientSearch'); $routes->get('Medical/mobile_patient_prescription/(:num)', 'Medical::mobilePatientPrescription/$1'); +$routes->get('medical/mobile_patient_prescription/(:num)', 'Medical::mobilePatientPrescription/$1'); $routes->get('Medical/mobile_medicine_search', 'Medical::mobileMedicineSearch'); +$routes->get('medical/mobile_medicine_search', 'Medical::mobileMedicineSearch'); $routes->post('Medical/mobile_save_draft', 'Medical::mobileSaveDraft'); +$routes->post('medical/mobile_save_draft', 'Medical::mobileSaveDraft'); $routes->get('Medical/mobile_recent_drafts', 'Medical::mobileRecentDrafts'); +$routes->get('medical/mobile_recent_drafts', 'Medical::mobileRecentDrafts'); $routes->get('Medical/mobile_audit_racks', 'Medical::mobileAuditRacks'); +$routes->get('medical/mobile_audit_racks', 'Medical::mobileAuditRacks'); $routes->get('Medical/mobile_audit_items', 'Medical::mobileAuditItems'); +$routes->get('medical/mobile_audit_items', 'Medical::mobileAuditItems'); $routes->post('Medical/mobile_save_audit', 'Medical::mobileSaveAudit'); +$routes->post('medical/mobile_save_audit', 'Medical::mobileSaveAudit'); $routes->get('Medical/stock_audit_report', 'Medical::stockAuditReport'); +$routes->get('medical/stock_audit_report', 'Medical::stockAuditReport'); $routes->post('Medical/stock_audit_reconcile', 'Medical::stockAuditReconcile'); +$routes->post('medical/stock_audit_reconcile', 'Medical::stockAuditReconcile'); $routes->get('Medical/list_med_inv/(:num)', 'Medical::list_med_inv/$1'); $routes->get('Medical/list_med_orginv/(:num)', 'Medical::list_med_orginv/$1'); $routes->get('Medical/list_med_orginv/(:num)/(:num)', 'Medical::list_med_orginv/$1/$2'); diff --git a/app/Controllers/Medical.php b/app/Controllers/Medical.php index c661cb4..2c25b7b 100644 --- a/app/Controllers/Medical.php +++ b/app/Controllers/Medical.php @@ -15491,6 +15491,8 @@ class Medical extends BaseController public function mobile() { + $this->ensureMobileStoreColumns(); + $hospitalSetting = []; if ($this->db->tableExists('hospital_setting')) { $hospitalSetting = $this->db->table('hospital_setting')->get()->getRowArray() ?: []; @@ -15503,6 +15505,7 @@ class Medical extends BaseController 'hospitalSetting' => $hospitalSetting, 'sessionUser' => $sessionUser, 'userId' => $userId, + 'footerVersion' => '26.117', 'pageTitle' => 'Central Pharmacy Mobile & Picker' ]); } @@ -15696,170 +15699,200 @@ class Medical extends BaseController public function mobileSaveDraft() { - $json = $this->request->getJSON(true) ?: $this->request->getPost(); - if (empty($json) && !empty($this->request->getBody())) { - $json = json_decode($this->request->getBody(), true) ?: []; - } + $this->ensureMobileStoreColumns(); - $items = $json['items'] ?? []; - if (empty($items) || !is_array($items)) { - return $this->response->setStatusCode(400)->setJSON(['status' => 0, 'message' => 'Please add at least one medicine item to basket.']); - } - - $patientId = (int)($json['patient_id'] ?? 0); - $uhid = trim((string)($json['uhid'] ?? '')); - $patientName = trim((string)($json['patient_name'] ?? 'Walk-in Customer')); - $mobile = trim((string)($json['mobile'] ?? '')); - $ipdId = (int)($json['ipd_id'] ?? 0); - $ipdCode = trim((string)($json['ipd_code'] ?? '')); - $bedNo = trim((string)($json['bed_no'] ?? '')); - $opdId = (int)($json['opd_id'] ?? 0); - $opdCode = trim((string)($json['opd_code'] ?? '')); - $docName = trim((string)($json['doc_name'] ?? '')); - $pickerName = trim((string)($json['picker_name'] ?? (session('name') ?: 'Mobile Picker'))); - - $invFields = $this->db->getFieldNames('invoice_med_master') ?? []; - $setIfExists = static function (array &$arr, array $fields, string $col, $val): void { - if (in_array($col, $fields, true)) { - $arr[$col] = $val; + try { + $json = (method_exists($this->request, 'getJSON') ? $this->request->getJSON(true) : null) ?: $this->request->getPost(); + if (empty($json) && !empty($this->request->getBody())) { + $json = json_decode($this->request->getBody(), true) ?: []; } - }; - $header = []; - $setIfExists($header, $invFields, 'store_id', 1); - $setIfExists($header, $invFields, 'inv_date', date('Y-m-d')); - $setIfExists($header, $invFields, 'inv_insert_datetime', date('Y-m-d H:i:s')); - $setIfExists($header, $invFields, 'customer_type', $ipdId > 0 ? 2 : 1); - $setIfExists($header, $invFields, 'patient_id', $patientId); - $setIfExists($header, $invFields, 'patient_code', $uhid); - $setIfExists($header, $invFields, 'inv_name', $patientName); - $setIfExists($header, $invFields, 'inv_phone_number', $mobile); - $setIfExists($header, $invFields, 'ipd_id', $ipdId); - $setIfExists($header, $invFields, 'ipd_code', $ipdCode); - $setIfExists($header, $invFields, 'bed_no', $bedNo); - $setIfExists($header, $invFields, 'doc_name', $docName); - $setIfExists($header, $invFields, 'draft_source', 'mobile_picker'); - $setIfExists($header, $invFields, 'picker_name', $pickerName); - $setIfExists($header, $invFields, 'prepared_by', $pickerName); - $setIfExists($header, $invFields, 'bill_final', 0); - $setIfExists($header, $invFields, 'invoice_status', 0); - $setIfExists($header, $invFields, 'log', 'Mobile draft basket created by ' . $pickerName . ' at ' . date('d-m-Y H:i:s')); + $items = $json['items'] ?? []; + if (empty($items) || !is_array($items)) { + return $this->response->setStatusCode(400)->setJSON(['status' => 0, 'message' => 'Please add at least one medicine item to basket.']); + } - $this->db->table('invoice_med_master')->insert($header); - $invoiceId = (int)$this->db->insertID(); + $patientId = (int)($json['patient_id'] ?? 0); + $uhid = trim((string)($json['uhid'] ?? '')); + $patientName = trim((string)($json['patient_name'] ?? 'Walk-in Customer')); + $mobile = trim((string)($json['mobile'] ?? '')); + $ipdId = (int)($json['ipd_id'] ?? 0); + $ipdCode = trim((string)($json['ipd_code'] ?? '')); + $bedNo = trim((string)($json['bed_no'] ?? '')); + $opdId = (int)($json['opd_id'] ?? 0); + $opdCode = trim((string)($json['opd_code'] ?? '')); + $docName = trim((string)($json['doc_name'] ?? '')); + $pickerName = trim((string)($json['picker_name'] ?? (session('name') ?: 'Mobile Picker'))); - if ($invoiceId <= 0) { - return $this->response->setStatusCode(500)->setJSON(['status' => 0, 'message' => 'Failed to initialize draft bill.']); + $invFields = $this->db->getFieldNames('invoice_med_master') ?? []; + $setIfExists = static function (array &$arr, array $fields, string $col, $val): void { + if (in_array($col, $fields, true)) { + $arr[$col] = $val; + } + }; + + $header = []; + $setIfExists($header, $invFields, 'store_id', 1); + $setIfExists($header, $invFields, 'inv_date', date('Y-m-d')); + $setIfExists($header, $invFields, 'inv_insert_datetime', date('Y-m-d H:i:s')); + $setIfExists($header, $invFields, 'customer_type', $ipdId > 0 ? 2 : 1); + $setIfExists($header, $invFields, 'patient_id', $patientId); + $setIfExists($header, $invFields, 'patient_code', $uhid); + $setIfExists($header, $invFields, 'inv_name', $patientName); + $setIfExists($header, $invFields, 'inv_phone_number', $mobile); + $setIfExists($header, $invFields, 'ipd_id', $ipdId); + $setIfExists($header, $invFields, 'ipd_code', $ipdCode); + $setIfExists($header, $invFields, 'bed_no', $bedNo); + $setIfExists($header, $invFields, 'doc_name', $docName); + $setIfExists($header, $invFields, 'draft_source', 'mobile_picker'); + $setIfExists($header, $invFields, 'picker_name', $pickerName); + $setIfExists($header, $invFields, 'prepared_by', $pickerName); + $setIfExists($header, $invFields, 'bill_final', 0); + $setIfExists($header, $invFields, 'invoice_status', 0); + $setIfExists($header, $invFields, 'log', 'Mobile draft basket created by ' . $pickerName . ' at ' . date('d-m-Y H:i:s')); + + $this->db->table('invoice_med_master')->insert($header); + $invoiceId = (int)$this->db->insertID(); + + if ($invoiceId <= 0) { + return $this->response->setStatusCode(500)->setJSON(['status' => 0, 'message' => 'Failed to initialize draft bill.']); + } + + $pid = str_pad(substr((string)$invoiceId, -7, 7), 7, '0', STR_PAD_LEFT); + $invoiceCode = 'M' . date('ym') . $pid; + if (in_array('inv_med_code', $invFields, true)) { + $this->db->table('invoice_med_master')->where('id', $invoiceId)->update(['inv_med_code' => $invoiceCode]); + } + + $itemFields = $this->db->getFieldNames('inv_med_item') ?? []; + $totalGross = 0; + foreach ($items as $it) { + $itemCode = (int)($it['item_code'] ?? 0); + $batchId = (int)($it['batch_id'] ?? 0); + $qty = max(1, (int)($it['qty'] ?? 1)); + $rate = (float)($it['rate'] ?? ($it['selling_unit_rate'] ?? ($it['mrp'] ?? 0))); + $mrp = (float)($it['mrp'] ?? $rate); + $packing = (float)($it['packing'] ?? 1); + $lineTotal = round($qty * $rate, 2); + $totalGross += $lineTotal; + + $taxBreakup = $this->calculateInvoiceItemTaxBreakup($lineTotal, 6.0, 6.0, 0.0); + + $line = []; + $setIfExists($line, $itemFields, 'inv_med_id', $invoiceId); + $setIfExists($line, $itemFields, 'item_code', $itemCode); + $setIfExists($line, $itemFields, 'store_stock_id', $batchId); + $setIfExists($line, $itemFields, 'purchase_item_id', $batchId); + $setIfExists($line, $itemFields, 'item_Name', (string)($it['item_name'] ?? 'Medicine')); + $setIfExists($line, $itemFields, 'formulation', (string)($it['formulation'] ?? '')); + $setIfExists($line, $itemFields, 'batch_no', (string)($it['batch_no'] ?? '')); + $setIfExists($line, $itemFields, 'expiry', (string)($it['expiry_date'] ?? '')); + $setIfExists($line, $itemFields, 'qty', $qty); + $setIfExists($line, $itemFields, 'price', $rate); + $setIfExists($line, $itemFields, 'mrp', $mrp); + $setIfExists($line, $itemFields, 'packing', $packing); + $setIfExists($line, $itemFields, 'amount', $lineTotal); + $setIfExists($line, $itemFields, 'tamount', $lineTotal); + $setIfExists($line, $itemFields, 'TaxableAmount', $taxBreakup['taxable']); + $setIfExists($line, $itemFields, 'CGST_per', 6.0); + $setIfExists($line, $itemFields, 'CGST', $taxBreakup['cgst']); + $setIfExists($line, $itemFields, 'SGST_per', 6.0); + $setIfExists($line, $itemFields, 'SGST', $taxBreakup['sgst']); + $setIfExists($line, $itemFields, 'update_by_remark', 'Mobile Picker: ' . $pickerName); + + $this->db->table('inv_med_item')->insert($line); + } + + $this->recalculateInvoiceTotals($invoiceId); + + return $this->response->setJSON([ + 'status' => 1, + 'message' => "Draft Bill #{$invoiceCode} created and sent to billing counter.", + 'draft_id' => $invoiceId, + 'draft_code' => $invoiceCode, + 'total_items' => count($items), + 'total_amount' => round($totalGross, 2) + ]); + } catch (\Throwable $e) { + log_message('error', 'mobileSaveDraft error: ' . $e->getMessage()); + return $this->response->setStatusCode(500)->setJSON([ + 'status' => 0, + 'message' => 'Server error creating draft: ' . $e->getMessage() + ]); } - - $pid = str_pad(substr((string)$invoiceId, -7, 7), 7, '0', STR_PAD_LEFT); - $invoiceCode = 'M' . date('ym') . $pid; - if (in_array('inv_med_code', $invFields, true)) { - $this->db->table('invoice_med_master')->where('id', $invoiceId)->update(['inv_med_code' => $invoiceCode]); - } - - $itemFields = $this->db->getFieldNames('inv_med_item') ?? []; - $totalGross = 0; - foreach ($items as $it) { - $itemCode = (int)($it['item_code'] ?? 0); - $batchId = (int)($it['batch_id'] ?? 0); - $qty = max(1, (int)($it['qty'] ?? 1)); - $rate = (float)($it['rate'] ?? ($it['selling_unit_rate'] ?? ($it['mrp'] ?? 0))); - $mrp = (float)($it['mrp'] ?? $rate); - $packing = (float)($it['packing'] ?? 1); - $lineTotal = round($qty * $rate, 2); - $totalGross += $lineTotal; - - $taxBreakup = $this->calculateInvoiceItemTaxBreakup($lineTotal, 6.0, 6.0, 0.0); - - $line = []; - $setIfExists($line, $itemFields, 'inv_med_id', $invoiceId); - $setIfExists($line, $itemFields, 'item_code', $itemCode); - $setIfExists($line, $itemFields, 'store_stock_id', $batchId); - $setIfExists($line, $itemFields, 'purchase_item_id', $batchId); - $setIfExists($line, $itemFields, 'item_Name', (string)($it['item_name'] ?? 'Medicine')); - $setIfExists($line, $itemFields, 'formulation', (string)($it['formulation'] ?? '')); - $setIfExists($line, $itemFields, 'batch_no', (string)($it['batch_no'] ?? '')); - $setIfExists($line, $itemFields, 'expiry', (string)($it['expiry_date'] ?? '')); - $setIfExists($line, $itemFields, 'qty', $qty); - $setIfExists($line, $itemFields, 'price', $rate); - $setIfExists($line, $itemFields, 'mrp', $mrp); - $setIfExists($line, $itemFields, 'packing', $packing); - $setIfExists($line, $itemFields, 'amount', $lineTotal); - $setIfExists($line, $itemFields, 'tamount', $lineTotal); - $setIfExists($line, $itemFields, 'TaxableAmount', $taxBreakup['taxable']); - $setIfExists($line, $itemFields, 'CGST_per', 6.0); - $setIfExists($line, $itemFields, 'CGST', $taxBreakup['cgst']); - $setIfExists($line, $itemFields, 'SGST_per', 6.0); - $setIfExists($line, $itemFields, 'SGST', $taxBreakup['sgst']); - $setIfExists($line, $itemFields, 'update_by_remark', 'Mobile Picker: ' . $pickerName); - - $this->db->table('inv_med_item')->insert($line); - } - - $this->recalculateInvoiceTotals($invoiceId); - - return $this->response->setJSON([ - 'status' => 1, - 'message' => "Draft Bill #{$invoiceCode} created and sent to billing counter.", - 'draft_id' => $invoiceId, - 'draft_code' => $invoiceCode, - 'total_items' => count($items), - 'total_amount' => round($totalGross, 2) - ]); } public function mobileRecentDrafts() { - $builder = $this->db->table('invoice_med_master m'); - $builder->select(' - m.id, - m.inv_med_code, - m.patient_code AS uhid, - m.inv_name AS patient_name, - m.ipd_id, - m.ipd_code, - m.bed_no, - m.net_amount, - m.bill_final, - m.draft_source, - m.picker_name, - m.inv_date, - m.inv_insert_datetime, - COUNT(i.id) AS total_items - ', false); - $builder->join('inv_med_item i', 'i.inv_med_id = m.id', 'left'); - $builder->groupStart(); - $builder->where('m.draft_source', 'mobile_picker'); - $builder->orWhere('m.inv_date', date('Y-m-d')); - $builder->groupEnd(); - $builder->groupBy('m.id'); - $builder->orderBy('m.id', 'DESC'); - $builder->limit(30); + $this->ensureMobileStoreColumns(); - $drafts = $builder->get()->getResultArray(); - $list = []; + try { + $invFields = $this->db->getFieldNames('invoice_med_master') ?? []; + $hasDraftSource = in_array('draft_source', $invFields, true); + $hasPickerName = in_array('picker_name', $invFields, true); + $hasBedNo = in_array('bed_no', $invFields, true); - foreach ($drafts as $d) { - $isFinal = (int)($d['bill_final'] ?? 0) === 1; - $timeStr = !empty($d['inv_insert_datetime']) ? date('h:i A', strtotime($d['inv_insert_datetime'])) : date('d-M', strtotime($d['inv_date'])); + $builder = $this->db->table('invoice_med_master m'); + $builder->select(' + m.id, + m.inv_med_code, + m.patient_code AS uhid, + m.inv_name AS patient_name, + m.ipd_id, + m.ipd_code, + ' . ($hasBedNo ? 'm.bed_no' : "'' AS bed_no") . ', + m.net_amount, + m.bill_final, + ' . ($hasDraftSource ? 'm.draft_source' : "'' AS draft_source") . ', + ' . ($hasPickerName ? 'm.picker_name' : "'' AS picker_name") . ', + m.inv_date, + m.inv_insert_datetime, + (SELECT COUNT(*) FROM inv_med_item i WHERE i.inv_med_id = m.id) AS total_items + ', false); - $list[] = [ - 'id' => (int)$d['id'], - 'draft_code' => (string)($d['inv_med_code'] ?: ('M' . $d['id'])), - 'uhid' => (string)($d['uhid'] ?: '-'), - 'patient_name' => (string)($d['patient_name'] ?: 'Patient'), - 'ipd_tag' => !empty($d['ipd_id']) ? ('IPD #' . ($d['ipd_code'] ?: $d['ipd_id']) . (!empty($d['bed_no']) ? ' [' . $d['bed_no'] . ']' : '')) : 'OPD', - 'net_amount' => (float)($d['net_amount'] ?? 0), - 'total_items' => (int)($d['total_items'] ?? 0), - 'picker_name' => (string)($d['picker_name'] ?: 'Picker'), - 'time_str' => $timeStr, - 'status' => $isFinal ? 'BILLED' : 'DRAFT', - 'status_label' => $isFinal ? 'Billed at Counter' : 'At Billing Counter' - ]; + $builder->groupStart(); + if ($hasDraftSource) { + $builder->where('m.draft_source', 'mobile_picker'); + $builder->orWhere('m.inv_date', date('Y-m-d')); + } else { + $builder->where('m.inv_date', date('Y-m-d')); + } + $builder->groupEnd(); + + $builder->orderBy('m.id', 'DESC'); + $builder->limit(30); + + $drafts = $builder->get()->getResultArray(); + $list = []; + + foreach ($drafts as $d) { + $isFinal = (int)($d['bill_final'] ?? 0) === 1; + $timeStr = !empty($d['inv_insert_datetime']) ? date('h:i A', strtotime($d['inv_insert_datetime'])) : date('d-M', strtotime($d['inv_date'])); + + $list[] = [ + 'id' => (int)$d['id'], + 'draft_code' => (string)($d['inv_med_code'] ?: ('M' . $d['id'])), + 'uhid' => (string)($d['uhid'] ?: '-'), + 'patient_name' => (string)($d['patient_name'] ?: 'Patient'), + 'ipd_tag' => !empty($d['ipd_id']) ? ('IPD #' . ($d['ipd_code'] ?: $d['ipd_id']) . (!empty($d['bed_no']) ? ' [' . $d['bed_no'] . ']' : '')) : 'OPD', + 'net_amount' => (float)($d['net_amount'] ?? 0), + 'total_items' => (int)($d['total_items'] ?? 0), + 'picker_name' => (string)($d['picker_name'] ?: 'Picker'), + 'time_str' => $timeStr, + 'status' => $isFinal ? 'BILLED' : 'DRAFT', + 'status_label' => $isFinal ? 'Billed at Counter' : 'At Billing Counter' + ]; + } + + return $this->response->setJSON(['status' => 1, 'drafts' => $list]); + } catch (\Throwable $e) { + log_message('error', 'mobileRecentDrafts error: ' . $e->getMessage()); + return $this->response->setStatusCode(500)->setJSON([ + 'status' => 0, + 'message' => 'Error retrieving drafts: ' . $e->getMessage(), + 'drafts' => [] + ]); } - - return $this->response->setJSON(['status' => 1, 'drafts' => $list]); } public function mobileAuditRacks() @@ -15981,7 +16014,9 @@ class Medical extends BaseController public function mobileSaveAudit() { - $json = $this->request->getJSON(true) ?: $this->request->getPost(); + $this->ensureMobileStoreColumns(); + + $json = (method_exists($this->request, 'getJSON') ? $this->request->getJSON(true) : null) ?: $this->request->getPost(); if (empty($json) && !empty($this->request->getBody())) { $json = json_decode($this->request->getBody(), true) ?: []; } @@ -16167,4 +16202,154 @@ class Medical extends BaseController return $this->response->setJSON(['status' => 1, 'message' => 'Stock inventory adjusted and audit marked as RECONCILED.']); } + + protected function ensureMobileStoreColumns(): void + { + static $ensured = false; + if ($ensured) { + return; + } + + try { + $forge = \Config\Database::forge(); + $fields = $this->db->getFieldNames('invoice_med_master') ?? []; + $missing = []; + + if (!in_array('draft_source', $fields, true)) { + $missing['draft_source'] = [ + 'type' => 'VARCHAR', + 'constraint' => 30, + 'null' => true, + 'default' => null, + ]; + } + if (!in_array('picker_name', $fields, true)) { + $missing['picker_name'] = [ + 'type' => 'VARCHAR', + 'constraint' => 100, + 'null' => true, + 'default' => null, + ]; + } + if (!in_array('bed_no', $fields, true)) { + $missing['bed_no'] = [ + 'type' => 'VARCHAR', + 'constraint' => 50, + 'null' => true, + 'default' => null, + ]; + } + + if (!empty($missing)) { + $forge->addColumn('invoice_med_master', $missing); + } + + if (!$this->db->tableExists('mst_stock_audit')) { + $forge->addField([ + 'audit_id' => [ + 'type' => 'INT', + 'constraint' => 11, + 'unsigned' => true, + 'auto_increment' => true, + ], + 'store_id' => [ + 'type' => 'INT', + 'constraint' => 11, + 'default' => 1, + ], + 'item_id' => [ + 'type' => 'INT', + 'constraint' => 11, + 'default' => 0, + ], + 'batch_id' => [ + 'type' => 'INT', + 'constraint' => 11, + 'default' => 0, + ], + 'batch_no' => [ + 'type' => 'VARCHAR', + 'constraint' => 50, + 'null' => true, + ], + 'expiry_date' => [ + 'type' => 'VARCHAR', + 'constraint' => 30, + 'null' => true, + ], + 'audit_type' => [ + 'type' => 'VARCHAR', + 'constraint' => 50, + 'default' => 'OK', + ], + 'system_qty' => [ + 'type' => 'INT', + 'constraint' => 11, + 'default' => 0, + ], + 'physical_qty' => [ + 'type' => 'INT', + 'constraint' => 11, + 'default' => 0, + ], + 'variation_qty' => [ + 'type' => 'INT', + 'constraint' => 11, + 'default' => 0, + ], + 'rate' => [ + 'type' => 'DECIMAL', + 'constraint' => '12,2', + 'default' => 0.00, + ], + 'total_value' => [ + 'type' => 'DECIMAL', + 'constraint' => '12,2', + 'default' => 0.00, + ], + 'remarks' => [ + 'type' => 'TEXT', + 'null' => true, + ], + 'rack_no' => [ + 'type' => 'VARCHAR', + 'constraint' => 30, + 'null' => true, + ], + 'shelf_no' => [ + 'type' => 'VARCHAR', + 'constraint' => 30, + 'null' => true, + ], + 'reconciled_status' => [ + 'type' => 'VARCHAR', + 'constraint' => 30, + 'default' => 'PENDING', + ], + 'conducted_by' => [ + 'type' => 'INT', + 'constraint' => 11, + 'default' => 0, + ], + 'conducted_by_name' => [ + 'type' => 'VARCHAR', + 'constraint' => 100, + 'null' => true, + ], + 'created_at' => [ + 'type' => 'DATETIME', + 'null' => true, + ], + ]); + $forge->addKey('audit_id', true); + $forge->addKey(['store_id', 'item_id', 'batch_id']); + $forge->createTable('mst_stock_audit', true); + } + + $ensured = true; + } catch (\Throwable $e) { + log_message('error', 'ensureMobileStoreColumns error: ' . $e->getMessage()); + } + } } + diff --git a/app/Views/medical/mobile_store.php b/app/Views/medical/mobile_store.php index d9f20b4..af32e08 100644 --- a/app/Views/medical/mobile_store.php +++ b/app/Views/medical/mobile_store.php @@ -384,6 +384,13 @@ + + + @@ -400,7 +407,7 @@ - @@ -424,7 +431,7 @@
Total Estimated Amount
₹0.00
- @@ -465,7 +472,19 @@ // ========================================================================= // STATE & GLOBAL VARIABLES // ========================================================================= - var baseUrl = ''; + var baseUrl = '/'; + var csrfHeader = ''; + var csrfTokenName = ''; + var csrfHash = ''; + + // Global AJAX setup: inject CSRF token header & X-Requested-With + jQuery.ajaxSetup({ + headers: { + '': '', + 'X-Requested-With': 'XMLHttpRequest' + } + }); + var selectedPatient = null; var cartItems = []; var html5QrCodeScanner = null; @@ -809,9 +828,10 @@ picker_name: operatorName, items: cartItems }; + payload[csrfTokenName] = csrfHash; - var btn = jQuery('#btn_send_draft'); - btn.prop('disabled', true).html(' Sending...'); + var btns = jQuery('.btn-send-draft-action'); + btns.prop('disabled', true).html(' Sending Draft...'); jQuery.ajax({ url: baseUrl + 'Medical/mobile_save_draft', @@ -820,7 +840,10 @@ contentType: 'application/json', dataType: 'json', success: function(res) { - btn.prop('disabled', false).html(' Send to Counter'); + btns.prop('disabled', false); + jQuery('#btn_send_draft').html(' Send to Counter'); + jQuery('#btn_modal_confirm_draft').html(' Confirm & Send Draft'); + if (res.status === 1) { var m = bootstrap.Modal.getInstance(document.getElementById('basketModal')); if (m) m.hide(); @@ -837,9 +860,19 @@ alert(res.message || 'Failed to save draft bill.'); } }, - error: function() { - btn.prop('disabled', false).html(' Send to Counter'); - alert("Failed to communicate with hospital server. Please check WiFi/intranet connection."); + error: function(xhr, status, error) { + btns.prop('disabled', false); + jQuery('#btn_send_draft').html(' Send to Counter'); + jQuery('#btn_modal_confirm_draft').html(' Confirm & Send Draft'); + + var errDetail = "HTTP " + xhr.status + " (" + (xhr.statusText || error || 'Connection Error') + ")"; + try { + var parsed = JSON.parse(xhr.responseText); + if (parsed && parsed.message) { + errDetail += "\n\nServer message: " + parsed.message; + } + } catch(e) {} + alert("Failed to communicate with hospital server.\n\n" + errDetail + "\n\nPlease check WiFi/intranet connection or contact IT support."); } }); } @@ -996,6 +1029,7 @@ shelf_no: shelfNo, audited_by_name: operatorName }; + payload[csrfTokenName] = csrfHash; jQuery.ajax({ url: baseUrl + 'Medical/mobile_save_audit', @@ -1014,6 +1048,10 @@ } else { alert(res.message || 'Error recording audit.'); } + }, + error: function(xhr, status, error) { + var errDetail = "HTTP " + xhr.status + " (" + (xhr.statusText || error || 'Connection Error') + ")"; + alert("Failed to save audit count.\n\n" + errDetail); } }); } @@ -1059,6 +1097,16 @@ }); container.html(html); + }, + error: function(xhr, status, error) { + var errDetail = "HTTP " + xhr.status + " (" + (xhr.statusText || error || 'Connection Error') + ")"; + container.html('
' + + '' + + 'Failed to load recent drafts: ' + escapeHtml(errDetail) + '
' + + '' + + '
'); } }); }