feat(pharmacy): add Central Pharmacy Mobile PWA with IPD/OPD/UHID search, Rx picker, draft billing, and physical stock audit
This commit is contained in:
parent
3c69ba9fe8
commit
9f6d9d3799
|
|
@ -54,6 +54,19 @@ $routes->match(['GET', 'POST'], 'Medical/getInvoiceTable', 'Medical::getInvoiceT
|
||||||
$routes->get('Medical/invoice_print/(:num)', 'Medical::invoice_print/$1');
|
$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_print/(:num)/(:num)', 'Medical::invoice_print/$1/$2');
|
||||||
$routes->get('Medical/Invoice_Med_Draft', 'Medical::Invoice_Med_Draft');
|
$routes->get('Medical/Invoice_Med_Draft', 'Medical::Invoice_Med_Draft');
|
||||||
|
$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_prescription/(:num)', 'Medical::mobilePatientPrescription/$1');
|
||||||
|
$routes->get('Medical/mobile_medicine_search', 'Medical::mobileMedicineSearch');
|
||||||
|
$routes->post('Medical/mobile_save_draft', 'Medical::mobileSaveDraft');
|
||||||
|
$routes->get('Medical/mobile_recent_drafts', 'Medical::mobileRecentDrafts');
|
||||||
|
$routes->get('Medical/mobile_audit_racks', 'Medical::mobileAuditRacks');
|
||||||
|
$routes->get('Medical/mobile_audit_items', 'Medical::mobileAuditItems');
|
||||||
|
$routes->post('Medical/mobile_save_audit', 'Medical::mobileSaveAudit');
|
||||||
|
$routes->get('Medical/stock_audit_report', 'Medical::stockAuditReport');
|
||||||
|
$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_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)', 'Medical::list_med_orginv/$1');
|
||||||
$routes->get('Medical/list_med_orginv/(:num)/(:num)', 'Medical::list_med_orginv/$1/$2');
|
$routes->get('Medical/list_med_orginv/(:num)/(:num)', 'Medical::list_med_orginv/$1/$2');
|
||||||
|
|
|
||||||
|
|
@ -4198,11 +4198,13 @@ class Medical extends BaseController
|
||||||
$toDate = $this->normalizeDate((string) $this->request->getGet('to'));
|
$toDate = $this->normalizeDate((string) $this->request->getGet('to'));
|
||||||
$search = trim((string) $this->request->getGet('q'));
|
$search = trim((string) $this->request->getGet('q'));
|
||||||
$caseId = (int) $this->request->getGet('case_id');
|
$caseId = (int) $this->request->getGet('case_id');
|
||||||
|
$source = strtolower(trim((string) $this->request->getGet('source')));
|
||||||
|
|
||||||
$message = (string) $this->request->getGet('msg');
|
$message = (string) $this->request->getGet('msg');
|
||||||
|
|
||||||
return view('medical/invoice_med_draft', [
|
return view('medical/invoice_med_draft', [
|
||||||
'status' => $status,
|
'status' => $status,
|
||||||
|
'source' => $source,
|
||||||
'fromDate' => $fromDate,
|
'fromDate' => $fromDate,
|
||||||
'toDate' => $toDate,
|
'toDate' => $toDate,
|
||||||
'search' => $search,
|
'search' => $search,
|
||||||
|
|
@ -4268,6 +4270,11 @@ class Medical extends BaseController
|
||||||
$baseBuilder->where('DATE(m.inv_date) <=', $toDate);
|
$baseBuilder->where('DATE(m.inv_date) <=', $toDate);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$source = strtolower(trim((string) ($this->request->getVar('source') ?? '')));
|
||||||
|
if ($source === 'mobile' && in_array('draft_source', $fields, true)) {
|
||||||
|
$baseBuilder->where('m.draft_source', 'mobile_picker');
|
||||||
|
}
|
||||||
|
|
||||||
if ($quickSearch !== '') {
|
if ($quickSearch !== '') {
|
||||||
$quickCols = [];
|
$quickCols = [];
|
||||||
foreach (['inv_med_code', 'patient_code', 'inv_name', 'ipd_code'] as $col) {
|
foreach (['inv_med_code', 'patient_code', 'inv_name', 'ipd_code'] as $col) {
|
||||||
|
|
@ -4346,7 +4353,7 @@ class Medical extends BaseController
|
||||||
}
|
}
|
||||||
|
|
||||||
$selectParts = ['m.id'];
|
$selectParts = ['m.id'];
|
||||||
foreach (['inv_med_code', 'inv_name', 'patient_code', 'inv_date', 'net_amount', 'payment_received', 'payment_balance'] as $col) {
|
foreach (['inv_med_code', 'inv_name', 'patient_code', 'inv_date', 'net_amount', 'payment_received', 'payment_balance', 'draft_source', 'picker_name', 'bed_no'] as $col) {
|
||||||
if (in_array($col, $fields, true)) {
|
if (in_array($col, $fields, true)) {
|
||||||
$selectParts[] = 'm.' . $col;
|
$selectParts[] = 'm.' . $col;
|
||||||
}
|
}
|
||||||
|
|
@ -4401,9 +4408,21 @@ class Medical extends BaseController
|
||||||
$balance = (float) ($row->payment_balance ?? ($net - $paid));
|
$balance = (float) ($row->payment_balance ?? ($net - $paid));
|
||||||
$sumNet += $net;
|
$sumNet += $net;
|
||||||
|
|
||||||
$statusBadge = $isFinalized
|
$isMobileDraft = (property_exists($row, 'draft_source') && $row->draft_source === 'mobile_picker');
|
||||||
|
$mobileBadge = '';
|
||||||
|
if ($isMobileDraft) {
|
||||||
|
$picker = !empty($row->picker_name) ? esc($row->picker_name) : 'Mobile';
|
||||||
|
$mobileBadge = ' <span class="badge bg-primary text-white" style="font-size:10px;" title="Picked via Mobile by ' . $picker . '"><i class="bi bi-phone"></i> ' . $picker . '</span>';
|
||||||
|
}
|
||||||
|
|
||||||
|
$actionBtns = '';
|
||||||
|
if (!$isFinalized) {
|
||||||
|
$actionBtns = ' <a href="javascript:load_form_div(\'' . base_url('Medical/invoice_edit/' . $invoiceId) . '\',\'medical-main\',\'Medical Invoice\');" class="btn btn-sm btn-outline-primary py-0 px-1 ms-1" title="Open & Bill Draft" style="font-size:11px;"><i class="bi bi-lightning-charge-fill"></i> Bill</a>';
|
||||||
|
}
|
||||||
|
|
||||||
|
$statusBadge = ($isFinalized
|
||||||
? '<span class="badge bg-success">Finalized</span>'
|
? '<span class="badge bg-success">Finalized</span>'
|
||||||
: '<span class="badge bg-warning text-dark">Draft</span>';
|
: '<span class="badge bg-warning text-dark">Draft</span>') . $mobileBadge . $actionBtns;
|
||||||
|
|
||||||
$data[] = [
|
$data[] = [
|
||||||
$invoiceLink,
|
$invoiceLink,
|
||||||
|
|
@ -15465,4 +15484,687 @@ class Medical extends BaseController
|
||||||
|
|
||||||
return redirect()->to(base_url('Medical/master_report/' . $slug));
|
return redirect()->to(base_url('Medical/master_report/' . $slug));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// =========================================================================
|
||||||
|
// CENTRAL PHARMACY MOBILE APP & DRAFT PICKER & PHYSICAL STOCK AUDIT
|
||||||
|
// =========================================================================
|
||||||
|
|
||||||
|
public function mobile()
|
||||||
|
{
|
||||||
|
$hospitalSetting = [];
|
||||||
|
if ($this->db->tableExists('hospital_setting')) {
|
||||||
|
$hospitalSetting = $this->db->table('hospital_setting')->get()->getRowArray() ?: [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$sessionUser = session('name') ?: (session('user_name') ?: '');
|
||||||
|
$userId = session('user_id') ?: 0;
|
||||||
|
|
||||||
|
return view('medical/mobile_store', [
|
||||||
|
'hospitalSetting' => $hospitalSetting,
|
||||||
|
'sessionUser' => $sessionUser,
|
||||||
|
'userId' => $userId,
|
||||||
|
'pageTitle' => 'Central Pharmacy Mobile & Picker'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function mobilePatientSearch()
|
||||||
|
{
|
||||||
|
$term = trim((string)($this->request->getGet('q') ?? ''));
|
||||||
|
if ($term === '') {
|
||||||
|
return $this->response->setJSON(['status' => 1, 'patients' => []]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$intTerm = (int)$term;
|
||||||
|
|
||||||
|
// Unified search: UHID, OPD Code, IPD ID, IPD Code, Patient Name (p_fname), Mobile
|
||||||
|
$builder = $this->db->table('patient_master p');
|
||||||
|
$builder->select('
|
||||||
|
p.id AS patient_id,
|
||||||
|
p.p_code AS uhid,
|
||||||
|
p.p_fname AS patient_name,
|
||||||
|
p.mphone1 AS mobile,
|
||||||
|
p.age,
|
||||||
|
p.gender,
|
||||||
|
ipd.id AS ipd_id,
|
||||||
|
ipd.ipd_code,
|
||||||
|
ipd.bed_no,
|
||||||
|
ipd.r_doc_name AS ipd_doc_name,
|
||||||
|
ipd.ipd_status,
|
||||||
|
opd.opd_id,
|
||||||
|
opd.opd_code,
|
||||||
|
opd.doc_name AS opd_doc_name
|
||||||
|
', false);
|
||||||
|
|
||||||
|
// Join active or recent IPD
|
||||||
|
$builder->join('(SELECT id, ipd_code, p_id, bed_no, r_doc_name, ipd_status FROM ipd_master ORDER BY id DESC LIMIT 500) ipd', 'ipd.p_id = p.id', 'left');
|
||||||
|
|
||||||
|
// Join active or recent OPD
|
||||||
|
$builder->join('(SELECT opd_id, opd_code, p_id, doc_name FROM opd_master ORDER BY opd_id DESC LIMIT 500) opd', 'opd.p_id = p.id', 'left');
|
||||||
|
|
||||||
|
$builder->groupStart();
|
||||||
|
$builder->like('p.p_code', $term);
|
||||||
|
$builder->orLike('p.p_fname', $term);
|
||||||
|
$builder->orLike('p.mphone1', $term);
|
||||||
|
$builder->orLike('ipd.ipd_code', $term);
|
||||||
|
$builder->orLike('opd.opd_code', $term);
|
||||||
|
if ($intTerm > 0) {
|
||||||
|
$builder->orWhere('ipd.id', $intTerm);
|
||||||
|
$builder->orWhere('p.id', $intTerm);
|
||||||
|
$builder->orWhere('opd.opd_id', $intTerm);
|
||||||
|
}
|
||||||
|
$builder->groupEnd();
|
||||||
|
|
||||||
|
$builder->groupBy('p.id');
|
||||||
|
$builder->orderBy('p.id', 'DESC');
|
||||||
|
$builder->limit(15);
|
||||||
|
|
||||||
|
$results = $builder->get()->getResultArray();
|
||||||
|
$patients = [];
|
||||||
|
|
||||||
|
foreach ($results as $r) {
|
||||||
|
$isIpd = !empty($r['ipd_id']) && ((int)($r['ipd_status'] ?? 1) === 1 || empty($r['opd_id']));
|
||||||
|
$docName = $isIpd ? ($r['ipd_doc_name'] ?: ($r['opd_doc_name'] ?: 'Doctor')) : ($r['opd_doc_name'] ?: ($r['ipd_doc_name'] ?: 'Doctor'));
|
||||||
|
$genderStr = ((int)($r['gender'] ?? 0) === 1) ? 'Male' : (((int)($r['gender'] ?? 0) === 2) ? 'Female' : 'Other');
|
||||||
|
|
||||||
|
$patients[] = [
|
||||||
|
'patient_id' => (int)$r['patient_id'],
|
||||||
|
'uhid' => (string)($r['uhid'] ?: ('P' . $r['patient_id'])),
|
||||||
|
'patient_name' => (string)($r['patient_name'] ?: 'Unknown Patient'),
|
||||||
|
'mobile' => (string)($r['mobile'] ?: ''),
|
||||||
|
'age' => (string)($r['age'] ?: '-'),
|
||||||
|
'gender' => $genderStr,
|
||||||
|
'is_ipd' => $isIpd,
|
||||||
|
'ipd_id' => (int)($r['ipd_id'] ?? 0),
|
||||||
|
'ipd_code' => (string)($r['ipd_code'] ?? ''),
|
||||||
|
'bed_no' => (string)($r['bed_no'] ?? ''),
|
||||||
|
'opd_id' => (int)($r['opd_id'] ?? 0),
|
||||||
|
'opd_code' => (string)($r['opd_code'] ?? ''),
|
||||||
|
'doc_name' => (string)$docName,
|
||||||
|
'badge' => $isIpd ? ('IPD #' . ($r['ipd_id'] ?: $r['ipd_code']) . (!empty($r['bed_no']) ? ' [Bed: ' . $r['bed_no'] . ']' : '')) : ('OPD #' . ($r['opd_code'] ?: $r['opd_id']))
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->response->setJSON(['status' => 1, 'patients' => $patients]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function mobilePatientPrescription($patientId)
|
||||||
|
{
|
||||||
|
$patientId = (int)$patientId;
|
||||||
|
$items = [];
|
||||||
|
|
||||||
|
if ($this->db->tableExists('opd_prescription')) {
|
||||||
|
$presc = $this->db->table('opd_prescription op')
|
||||||
|
->select('op.medicine_name, op.dosage, op.duration, op.frequency, op.quantity')
|
||||||
|
->where('op.patient_id', $patientId)
|
||||||
|
->orderBy('op.id', 'DESC')
|
||||||
|
->limit(20)
|
||||||
|
->get()
|
||||||
|
->getResultArray();
|
||||||
|
if (!empty($presc)) {
|
||||||
|
$items = $presc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->response->setJSON(['status' => 1, 'prescriptions' => $items]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function mobileMedicineSearch()
|
||||||
|
{
|
||||||
|
$term = trim((string)($this->request->getGet('q') ?? ''));
|
||||||
|
$rack = trim((string)($this->request->getGet('rack') ?? ''));
|
||||||
|
|
||||||
|
$builder = $this->db->table('purchase_invoice_item p');
|
||||||
|
$builder->select('
|
||||||
|
p.id AS batch_id,
|
||||||
|
p.item_code,
|
||||||
|
m.item_name,
|
||||||
|
m.genericname,
|
||||||
|
m.formulation,
|
||||||
|
m.packing AS m_packing,
|
||||||
|
p.packing AS p_packing,
|
||||||
|
p.batch_no,
|
||||||
|
p.expiry_date,
|
||||||
|
DATE_FORMAT(p.expiry_date, "%m/%Y") AS exp_formatted,
|
||||||
|
p.mrp,
|
||||||
|
p.selling_unit_rate,
|
||||||
|
IFNULL(NULLIF(p.rack_no, ""), m.rack_no) AS rack_no,
|
||||||
|
IFNULL(NULLIF(p.shelf_no, ""), m.shelf_no) AS shelf_no,
|
||||||
|
p.cold_storage,
|
||||||
|
(p.total_unit - p.total_sale_unit + p.total_return_unit - p.total_lost_unit) AS available_units
|
||||||
|
', false);
|
||||||
|
$builder->join('med_product_master m', 'm.id = p.item_code');
|
||||||
|
$builder->where('p.item_code >', 0);
|
||||||
|
|
||||||
|
if ($term !== '') {
|
||||||
|
$builder->groupStart();
|
||||||
|
$builder->like('m.item_name', $term);
|
||||||
|
$builder->orLike('p.batch_no', $term);
|
||||||
|
$builder->orLike('m.genericname', $term);
|
||||||
|
$builder->groupEnd();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($rack !== '') {
|
||||||
|
$builder->groupStart();
|
||||||
|
$builder->where('p.rack_no', $rack);
|
||||||
|
$builder->orWhere('m.rack_no', $rack);
|
||||||
|
$builder->groupEnd();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($term === '' && $rack === '') {
|
||||||
|
$builder->having('available_units >', 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
$builder->orderBy('m.item_name', 'ASC');
|
||||||
|
$builder->orderBy('p.expiry_date', 'ASC');
|
||||||
|
$builder->limit(30);
|
||||||
|
|
||||||
|
$rows = $builder->get()->getResultArray();
|
||||||
|
|
||||||
|
$grouped = [];
|
||||||
|
foreach ($rows as $r) {
|
||||||
|
$itemId = (int)$r['item_code'];
|
||||||
|
if (!isset($grouped[$itemId])) {
|
||||||
|
$grouped[$itemId] = [
|
||||||
|
'item_code' => $itemId,
|
||||||
|
'item_name' => (string)$r['item_name'],
|
||||||
|
'genericname' => (string)($r['genericname'] ?? ''),
|
||||||
|
'formulation' => (string)($r['formulation'] ?? 'Medicine'),
|
||||||
|
'packing' => (string)($r['m_packing'] ?: ($r['p_packing'] ?: '1')),
|
||||||
|
'rack_no' => (string)($r['rack_no'] ?? ''),
|
||||||
|
'shelf_no' => (string)($r['shelf_no'] ?? ''),
|
||||||
|
'cold_storage' => (string)($r['cold_storage'] ?? ''),
|
||||||
|
'batches' => []
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
$avail = (int)$r['available_units'];
|
||||||
|
$grouped[$itemId]['batches'][] = [
|
||||||
|
'batch_id' => (int)$r['batch_id'],
|
||||||
|
'batch_no' => (string)($r['batch_no'] ?: 'DEFAULT'),
|
||||||
|
'expiry_date' => (string)($r['expiry_date'] ?? ''),
|
||||||
|
'exp_formatted' => (string)($r['exp_formatted'] ?? ''),
|
||||||
|
'mrp' => (float)($r['mrp'] ?? 0),
|
||||||
|
'selling_unit_rate' => (float)($r['selling_unit_rate'] ?? 0),
|
||||||
|
'available_units' => $avail,
|
||||||
|
'rack_no' => (string)($r['rack_no'] ?? ''),
|
||||||
|
'shelf_no' => (string)($r['shelf_no'] ?? '')
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->response->setJSON(['status' => 1, 'items' => array_values($grouped)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
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) ?: [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$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;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
$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)
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
$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]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function mobileAuditRacks()
|
||||||
|
{
|
||||||
|
$racks = [];
|
||||||
|
$res = $this->db->query("
|
||||||
|
SELECT DISTINCT rack_no FROM purchase_invoice_item WHERE rack_no IS NOT NULL AND rack_no != '' AND rack_no != '0'
|
||||||
|
UNION
|
||||||
|
SELECT DISTINCT rack_no FROM med_product_master WHERE rack_no IS NOT NULL AND rack_no != '' AND rack_no != '0'
|
||||||
|
ORDER BY rack_no ASC
|
||||||
|
")->getResultArray();
|
||||||
|
|
||||||
|
foreach ($res as $r) {
|
||||||
|
$racks[] = (string)$r['rack_no'];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->response->setJSON(['status' => 1, 'racks' => $racks]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function mobileAuditItems()
|
||||||
|
{
|
||||||
|
$rack = trim((string)($this->request->getGet('rack') ?? ''));
|
||||||
|
$shelf = trim((string)($this->request->getGet('shelf') ?? ''));
|
||||||
|
$q = trim((string)($this->request->getGet('q') ?? ''));
|
||||||
|
|
||||||
|
$builder = $this->db->table('purchase_invoice_item p');
|
||||||
|
$builder->select('
|
||||||
|
p.id AS batch_id,
|
||||||
|
p.item_code,
|
||||||
|
m.item_name,
|
||||||
|
m.formulation,
|
||||||
|
p.packing,
|
||||||
|
p.batch_no,
|
||||||
|
p.expiry_date,
|
||||||
|
DATE_FORMAT(p.expiry_date, "%m/%Y") AS exp_formatted,
|
||||||
|
p.mrp,
|
||||||
|
p.purchase_unit_rate,
|
||||||
|
p.selling_unit_rate,
|
||||||
|
IFNULL(NULLIF(p.rack_no, ""), m.rack_no) AS rack_no,
|
||||||
|
IFNULL(NULLIF(p.shelf_no, ""), m.shelf_no) AS shelf_no,
|
||||||
|
(p.total_unit - p.total_sale_unit + p.total_return_unit - p.total_lost_unit) AS system_qty
|
||||||
|
', false);
|
||||||
|
$builder->join('med_product_master m', 'm.id = p.item_code');
|
||||||
|
$builder->where('p.item_code >', 0);
|
||||||
|
|
||||||
|
if ($rack !== '') {
|
||||||
|
$builder->groupStart();
|
||||||
|
$builder->where('p.rack_no', $rack);
|
||||||
|
$builder->orWhere('m.rack_no', $rack);
|
||||||
|
$builder->groupEnd();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($shelf !== '') {
|
||||||
|
$builder->groupStart();
|
||||||
|
$builder->where('p.shelf_no', $shelf);
|
||||||
|
$builder->orWhere('m.shelf_no', $shelf);
|
||||||
|
$builder->groupEnd();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($q !== '') {
|
||||||
|
$builder->groupStart();
|
||||||
|
$builder->like('m.item_name', $q);
|
||||||
|
$builder->orLike('p.batch_no', $q);
|
||||||
|
$builder->groupEnd();
|
||||||
|
}
|
||||||
|
|
||||||
|
$builder->orderBy('m.item_name', 'ASC');
|
||||||
|
$builder->orderBy('p.expiry_date', 'ASC');
|
||||||
|
$builder->limit(50);
|
||||||
|
|
||||||
|
$rows = $builder->get()->getResultArray();
|
||||||
|
|
||||||
|
$auditedMap = [];
|
||||||
|
if (!empty($rows)) {
|
||||||
|
$batchIds = array_column($rows, 'batch_id');
|
||||||
|
if (!empty($batchIds)) {
|
||||||
|
$aRows = $this->db->table('mst_stock_audit')
|
||||||
|
->select('batch_id, physical_qty, variation_qty, audit_type, remarks, created_at')
|
||||||
|
->whereIn('batch_id', $batchIds)
|
||||||
|
->where('DATE(created_at)', date('Y-m-d'))
|
||||||
|
->get()
|
||||||
|
->getResultArray();
|
||||||
|
foreach ($aRows as $ar) {
|
||||||
|
$auditedMap[$ar['batch_id']] = $ar;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$items = [];
|
||||||
|
foreach ($rows as $r) {
|
||||||
|
$bId = (int)$r['batch_id'];
|
||||||
|
$audit = $auditedMap[$bId] ?? null;
|
||||||
|
|
||||||
|
$items[] = [
|
||||||
|
'batch_id' => $bId,
|
||||||
|
'item_code' => (int)$r['item_code'],
|
||||||
|
'item_name' => (string)$r['item_name'],
|
||||||
|
'formulation' => (string)($r['formulation'] ?? ''),
|
||||||
|
'packing' => (string)($r['packing'] ?: '1'),
|
||||||
|
'batch_no' => (string)($r['batch_no'] ?: 'DEFAULT'),
|
||||||
|
'expiry_date' => (string)($r['expiry_date'] ?? ''),
|
||||||
|
'exp_formatted' => (string)($r['exp_formatted'] ?? ''),
|
||||||
|
'mrp' => (float)($r['mrp'] ?? 0),
|
||||||
|
'rate' => (float)($r['selling_unit_rate'] ?? 0),
|
||||||
|
'rack_no' => (string)($r['rack_no'] ?? ''),
|
||||||
|
'shelf_no' => (string)($r['shelf_no'] ?? ''),
|
||||||
|
'system_qty' => (int)$r['system_qty'],
|
||||||
|
'last_audited_today' => $audit ? [
|
||||||
|
'physical_qty' => (int)$audit['physical_qty'],
|
||||||
|
'variation_qty' => (int)$audit['variation_qty'],
|
||||||
|
'audit_type' => (string)$audit['audit_type'],
|
||||||
|
'time' => date('h:i A', strtotime($audit['created_at']))
|
||||||
|
] : null
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->response->setJSON(['status' => 1, 'items' => $items]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function mobileSaveAudit()
|
||||||
|
{
|
||||||
|
$json = $this->request->getJSON(true) ?: $this->request->getPost();
|
||||||
|
if (empty($json) && !empty($this->request->getBody())) {
|
||||||
|
$json = json_decode($this->request->getBody(), true) ?: [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$batchId = (int)($json['batch_id'] ?? 0);
|
||||||
|
$itemCode = (int)($json['item_code'] ?? 0);
|
||||||
|
$physicalQty = (int)($json['physical_qty'] ?? 0);
|
||||||
|
$systemQty = (int)($json['system_qty'] ?? 0);
|
||||||
|
$auditType = trim((string)($json['audit_type'] ?? 'PHYSICAL_VARIATION'));
|
||||||
|
$remarks = trim((string)($json['remarks'] ?? ''));
|
||||||
|
$rackNo = trim((string)($json['rack_no'] ?? ''));
|
||||||
|
$shelfNo = trim((string)($json['shelf_no'] ?? ''));
|
||||||
|
$auditedByName = trim((string)($json['audited_by_name'] ?? (session('name') ?: 'Store Staff')));
|
||||||
|
$auditedBy = (int)(session('user_id') ?: 1);
|
||||||
|
|
||||||
|
if ($batchId <= 0) {
|
||||||
|
return $this->response->setStatusCode(400)->setJSON(['status' => 0, 'message' => 'Invalid batch selected.']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$batch = $this->db->table('purchase_invoice_item')->where('id', $batchId)->get()->getRowArray();
|
||||||
|
if (!$batch) {
|
||||||
|
return $this->response->setStatusCode(404)->setJSON(['status' => 0, 'message' => 'Batch not found.']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$rate = (float)($batch['selling_unit_rate'] ?? ($batch['mrp'] ?? 0));
|
||||||
|
$variationQty = $physicalQty - $systemQty;
|
||||||
|
$totalValue = abs($variationQty) * $rate;
|
||||||
|
|
||||||
|
$insertData = [
|
||||||
|
'store_id' => 1,
|
||||||
|
'item_id' => $itemCode > 0 ? $itemCode : (int)($batch['item_code'] ?? 0),
|
||||||
|
'batch_id' => $batchId,
|
||||||
|
'batch_no' => (string)($batch['batch_no'] ?? ''),
|
||||||
|
'expiry_date' => (string)($batch['expiry_date'] ?? ''),
|
||||||
|
'audit_type' => $auditType,
|
||||||
|
'system_qty' => $systemQty,
|
||||||
|
'physical_qty' => $physicalQty,
|
||||||
|
'variation_qty' => $variationQty,
|
||||||
|
'rate' => $rate,
|
||||||
|
'total_value' => round($totalValue, 2),
|
||||||
|
'remarks' => $remarks,
|
||||||
|
'rack_no' => $rackNo ?: (string)($batch['rack_no'] ?? ''),
|
||||||
|
'shelf_no' => $shelfNo ?: (string)($batch['shelf_no'] ?? ''),
|
||||||
|
'conducted_by' => $auditedBy,
|
||||||
|
'conducted_by_name' => $auditedByName,
|
||||||
|
'reconciled_status' => 'PENDING',
|
||||||
|
'created_at' => date('Y-m-d H:i:s')
|
||||||
|
];
|
||||||
|
|
||||||
|
$this->db->table('mst_stock_audit')->insert($insertData);
|
||||||
|
$auditId = (int)$this->db->insertID();
|
||||||
|
|
||||||
|
if ($rackNo !== '' || $shelfNo !== '') {
|
||||||
|
$locUpdate = [];
|
||||||
|
if ($rackNo !== '') $locUpdate['rack_no'] = $rackNo;
|
||||||
|
if ($shelfNo !== '') $locUpdate['shelf_no'] = $shelfNo;
|
||||||
|
$this->db->table('purchase_invoice_item')->where('id', $batchId)->update($locUpdate);
|
||||||
|
|
||||||
|
if ($itemCode > 0) {
|
||||||
|
$this->db->table('med_product_master')
|
||||||
|
->where('id', $itemCode)
|
||||||
|
->where('rack_no', '')
|
||||||
|
->update($locUpdate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->response->setJSON([
|
||||||
|
'status' => 1,
|
||||||
|
'message' => 'Audit record logged successfully.',
|
||||||
|
'audit_id' => $auditId,
|
||||||
|
'variation_qty' => $variationQty,
|
||||||
|
'status_tag' => $variationQty === 0 ? 'MATCH' : ($variationQty < 0 ? 'SHORTAGE' : 'SURPLUS')
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function stockAuditReport()
|
||||||
|
{
|
||||||
|
if ($deny = $this->ensurePharmacyAccess()) {
|
||||||
|
return $deny;
|
||||||
|
}
|
||||||
|
|
||||||
|
$fromDate = $this->request->getGet('from_date') ?: date('Y-m-01');
|
||||||
|
$toDate = $this->request->getGet('to_date') ?: date('Y-m-d');
|
||||||
|
$auditType = trim((string)($this->request->getGet('audit_type') ?? ''));
|
||||||
|
$status = trim((string)($this->request->getGet('status') ?? ''));
|
||||||
|
|
||||||
|
$builder = $this->db->table('mst_stock_audit a');
|
||||||
|
$builder->select('
|
||||||
|
a.*,
|
||||||
|
m.item_name,
|
||||||
|
m.formulation,
|
||||||
|
p.packing
|
||||||
|
');
|
||||||
|
$builder->join('med_product_master m', 'm.id = a.item_id', 'left');
|
||||||
|
$builder->join('purchase_invoice_item p', 'p.id = a.batch_id', 'left');
|
||||||
|
$builder->where('DATE(a.created_at) >=', $fromDate);
|
||||||
|
$builder->where('DATE(a.created_at) <=', $toDate);
|
||||||
|
|
||||||
|
if ($auditType !== '') {
|
||||||
|
$builder->where('a.audit_type', $auditType);
|
||||||
|
}
|
||||||
|
if ($status !== '') {
|
||||||
|
$builder->where('a.reconciled_status', $status);
|
||||||
|
}
|
||||||
|
|
||||||
|
$builder->orderBy('a.audit_id', 'DESC');
|
||||||
|
$audits = $builder->get()->getResultArray();
|
||||||
|
|
||||||
|
$totalAudited = count($audits);
|
||||||
|
$totalMatched = 0;
|
||||||
|
$totalShortageUnits = 0;
|
||||||
|
$totalSurplusUnits = 0;
|
||||||
|
$netVarianceVal = 0.0;
|
||||||
|
$pendingCount = 0;
|
||||||
|
|
||||||
|
foreach ($audits as $row) {
|
||||||
|
$v = (int)($row['variation_qty'] ?? 0);
|
||||||
|
if ($v === 0) {
|
||||||
|
$totalMatched++;
|
||||||
|
} elseif ($v < 0) {
|
||||||
|
$totalShortageUnits += abs($v);
|
||||||
|
} else {
|
||||||
|
$totalSurplusUnits += $v;
|
||||||
|
}
|
||||||
|
$netVarianceVal += ($v * (float)($row['rate'] ?? 0));
|
||||||
|
if (($row['reconciled_status'] ?? '') === 'PENDING') {
|
||||||
|
$pendingCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return view('medical/stock_audit_report', [
|
||||||
|
'audits' => $audits,
|
||||||
|
'fromDate' => $fromDate,
|
||||||
|
'toDate' => $toDate,
|
||||||
|
'auditType' => $auditType,
|
||||||
|
'status' => $status,
|
||||||
|
'totalAudited' => $totalAudited,
|
||||||
|
'totalMatched' => $totalMatched,
|
||||||
|
'totalShortageUnits' => $totalShortageUnits,
|
||||||
|
'totalSurplusUnits' => $totalSurplusUnits,
|
||||||
|
'netVarianceVal' => round($netVarianceVal, 2),
|
||||||
|
'pendingCount' => $pendingCount
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function stockAuditReconcile()
|
||||||
|
{
|
||||||
|
if ($deny = $this->ensurePharmacyAccess()) {
|
||||||
|
return $deny;
|
||||||
|
}
|
||||||
|
|
||||||
|
$auditId = (int)$this->request->getPost('audit_id');
|
||||||
|
if ($auditId <= 0) {
|
||||||
|
return $this->response->setStatusCode(400)->setJSON(['status' => 0, 'message' => 'Invalid audit record.']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$audit = $this->db->table('mst_stock_audit')->where('audit_id', $auditId)->get()->getRowArray();
|
||||||
|
if (!$audit) {
|
||||||
|
return $this->response->setStatusCode(404)->setJSON(['status' => 0, 'message' => 'Audit log not found.']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$batchId = (int)$audit['batch_id'];
|
||||||
|
$variation = (int)$audit['variation_qty'];
|
||||||
|
|
||||||
|
if ($batchId > 0 && $variation !== 0) {
|
||||||
|
$batch = $this->db->table('purchase_invoice_item')->where('id', $batchId)->get()->getRowArray();
|
||||||
|
if ($batch) {
|
||||||
|
if ($variation < 0) {
|
||||||
|
$lost = (int)($batch['total_lost_unit'] ?? 0) + abs($variation);
|
||||||
|
$this->db->table('purchase_invoice_item')->where('id', $batchId)->update(['total_lost_unit' => $lost]);
|
||||||
|
} else {
|
||||||
|
$tot = (int)($batch['total_unit'] ?? 0) + $variation;
|
||||||
|
$this->db->table('purchase_invoice_item')->where('id', $batchId)->update(['total_unit' => $tot]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$userName = session('name') ?: 'Store Manager';
|
||||||
|
$this->db->table('mst_stock_audit')->where('audit_id', $auditId)->update([
|
||||||
|
'reconciled_status' => 'RECONCILED',
|
||||||
|
'remarks' => trim(($audit['remarks'] ?? '') . ' [Reconciled by ' . $userName . ' on ' . date('d-m-Y H:i') . ']')
|
||||||
|
]);
|
||||||
|
|
||||||
|
return $this->response->setJSON(['status' => 1, 'message' => 'Stock inventory adjusted and audit marked as RECONCILED.']);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -46,6 +46,16 @@
|
||||||
<i class="bi bi-boxes text-info me-2"></i> Store Stock & Batches
|
<i class="bi bi-boxes text-info me-2"></i> Store Stock & Batches
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
<a class="dropdown-item py-1" href="javascript:load_form_div('<?= base_url('Medical/stock_audit_report') ?>','medical-main','Stock Audit :Pharmacy');">
|
||||||
|
<i class="bi bi-clipboard2-check text-warning me-2"></i> <strong>Stock Audit & Reconciliation</strong>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a class="dropdown-item py-1 text-primary" href="<?= base_url('Medical/mobile') ?>" target="_blank">
|
||||||
|
<i class="bi bi-phone text-primary me-2"></i> <strong>Main Pharmacy Mobile App</strong>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a class="dropdown-item py-1" href="javascript:load_form_div('<?= base_url('Medical/main_store') ?>','medical-main','Store Main :Pharmacy');">
|
<a class="dropdown-item py-1" href="javascript:load_form_div('<?= base_url('Medical/main_store') ?>','medical-main','Store Main :Pharmacy');">
|
||||||
<i class="bi bi-display text-dark me-2"></i> Central Store Main
|
<i class="bi bi-display text-dark me-2"></i> Central Store Main
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,8 @@
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
<div class="d-flex gap-2 mb-3 flex-wrap">
|
<div class="d-flex gap-2 mb-3 flex-wrap">
|
||||||
<a class="btn btn-sm <?= ($status ?? 'draft') === 'draft' ? 'btn-primary' : 'btn-outline-primary' ?>" href="javascript:load_form('<?= base_url('Medical/Invoice_Med_Draft?status=draft') ?>','Invoice List :Pharmacy');">Draft</a>
|
<a class="btn btn-sm <?= (($status ?? 'draft') === 'draft' && ($source ?? '') !== 'mobile') ? 'btn-primary' : 'btn-outline-primary' ?>" href="javascript:load_form('<?= base_url('Medical/Invoice_Med_Draft?status=draft') ?>','Invoice List :Pharmacy');">Draft</a>
|
||||||
|
<a class="btn btn-sm <?= ($source ?? '') === 'mobile' ? 'btn-info text-white fw-bold shadow-sm' : 'btn-outline-info' ?>" href="javascript:load_form('<?= base_url('Medical/Invoice_Med_Draft?status=draft&source=mobile') ?>','Invoice List :Pharmacy');"><i class="bi bi-phone"></i> Mobile Picker Drafts</a>
|
||||||
<a class="btn btn-sm <?= ($status ?? '') === 'all' ? 'btn-dark' : 'btn-outline-dark' ?>" href="javascript:load_form('<?= base_url('Medical/Invoice_Med_Draft?status=all') ?>','Invoice List :Pharmacy');">All</a>
|
<a class="btn btn-sm <?= ($status ?? '') === 'all' ? 'btn-dark' : 'btn-outline-dark' ?>" href="javascript:load_form('<?= base_url('Medical/Invoice_Med_Draft?status=all') ?>','Invoice List :Pharmacy');">All</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -15,6 +16,9 @@
|
||||||
<?php if (($status ?? 'draft') !== 'final'): ?>
|
<?php if (($status ?? 'draft') !== 'final'): ?>
|
||||||
<input type="hidden" name="status" value="<?= esc($status ?? 'draft') ?>">
|
<input type="hidden" name="status" value="<?= esc($status ?? 'draft') ?>">
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
<?php if (!empty($source)): ?>
|
||||||
|
<input type="hidden" name="source" value="<?= esc($source) ?>">
|
||||||
|
<?php endif; ?>
|
||||||
<?php if (!empty($caseId)): ?>
|
<?php if (!empty($caseId)): ?>
|
||||||
<input type="hidden" name="case_id" value="<?= (int) $caseId ?>">
|
<input type="hidden" name="case_id" value="<?= (int) $caseId ?>">
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
@ -93,12 +97,14 @@
|
||||||
type: 'GET',
|
type: 'GET',
|
||||||
data: function (data) {
|
data: function (data) {
|
||||||
var statusInput = filterForm.find('input[name="status"]');
|
var statusInput = filterForm.find('input[name="status"]');
|
||||||
|
var sourceInput = filterForm.find('input[name="source"]');
|
||||||
var caseInput = filterForm.find('input[name="case_id"]');
|
var caseInput = filterForm.find('input[name="case_id"]');
|
||||||
var fromInput = filterForm.find('input[name="from"]');
|
var fromInput = filterForm.find('input[name="from"]');
|
||||||
var toInput = filterForm.find('input[name="to"]');
|
var toInput = filterForm.find('input[name="to"]');
|
||||||
var qInput = filterForm.find('input[name="q"]');
|
var qInput = filterForm.find('input[name="q"]');
|
||||||
|
|
||||||
data.status = statusInput.length ? (statusInput.val() || defaultStatus) : defaultStatus;
|
data.status = statusInput.length ? (statusInput.val() || defaultStatus) : defaultStatus;
|
||||||
|
data.source = sourceInput.length ? (sourceInput.val() || '') : '<?= esc($source ?? '') ?>';
|
||||||
data.from = fromInput.length ? (fromInput.val() || '') : '';
|
data.from = fromInput.length ? (fromInput.val() || '') : '';
|
||||||
data.to = toInput.length ? (toInput.val() || '') : '';
|
data.to = toInput.length ? (toInput.val() || '') : '';
|
||||||
data.q = qInput.length ? (qInput.val() || '') : '';
|
data.q = qInput.length ? (qInput.val() || '') : '';
|
||||||
|
|
|
||||||
1141
app/Views/medical/mobile_store.php
Normal file
1141
app/Views/medical/mobile_store.php
Normal file
File diff suppressed because it is too large
Load Diff
219
app/Views/medical/stock_audit_report.php
Normal file
219
app/Views/medical/stock_audit_report.php
Normal file
|
|
@ -0,0 +1,219 @@
|
||||||
|
<div class="card shadow-sm border-0">
|
||||||
|
<div class="card-body pt-3">
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-3 flex-wrap gap-2 pb-2 border-bottom">
|
||||||
|
<div>
|
||||||
|
<h5 class="card-title mb-0 text-primary fw-bold">
|
||||||
|
<i class="bi bi-clipboard2-check-fill me-1"></i> Physical Stock Audit & Reconciliation
|
||||||
|
</h5>
|
||||||
|
<small class="text-muted">Compare System Book Stock vs Floor Physical Count from Main Pharmacy Mobile App</small>
|
||||||
|
</div>
|
||||||
|
<div class="d-flex gap-2">
|
||||||
|
<a href="<?= base_url('Medical/mobile') ?>" target="_blank" class="btn btn-sm btn-outline-primary shadow-sm fw-bold">
|
||||||
|
<i class="bi bi-phone me-1"></i> Launch Mobile Audit & Picker
|
||||||
|
</a>
|
||||||
|
<button type="button" class="btn btn-sm btn-primary shadow-sm" onclick="window.print();">
|
||||||
|
<i class="bi bi-printer me-1"></i> Print Report
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Metric KPI Cards -->
|
||||||
|
<div class="row g-2 mb-3">
|
||||||
|
<div class="col-6 col-md-3">
|
||||||
|
<div class="p-2 border rounded bg-light text-center">
|
||||||
|
<div class="text-muted small">Total Audits</div>
|
||||||
|
<div class="fs-5 fw-bold text-dark"><?= number_format($totalAudited ?? 0) ?></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-6 col-md-3">
|
||||||
|
<div class="p-2 border rounded bg-success-subtle text-center text-success">
|
||||||
|
<div class="small">100% Matched</div>
|
||||||
|
<div class="fs-5 fw-bold"><?= number_format($totalMatched ?? 0) ?></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-6 col-md-3">
|
||||||
|
<div class="p-2 border rounded bg-danger-subtle text-center text-danger">
|
||||||
|
<div class="small">Shortage / Damaged</div>
|
||||||
|
<div class="fs-5 fw-bold"><?= number_format($totalShortageUnits ?? 0) ?> <small style="font-size:11px;">units</small></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-6 col-md-3">
|
||||||
|
<div class="p-2 border rounded bg-warning-subtle text-center text-dark">
|
||||||
|
<div class="small">Pending Reconciliation</div>
|
||||||
|
<div class="fs-5 fw-bold text-warning-emphasis"><?= number_format($pendingCount ?? 0) ?></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Filter Form -->
|
||||||
|
<form method="get" action="<?= base_url('Medical/stock_audit_report') ?>" class="row g-2 mb-3 align-items-end bg-light p-2 rounded border">
|
||||||
|
<div class="col-6 col-md-3">
|
||||||
|
<label class="form-label form-label-sm mb-1 small fw-bold">From Date</label>
|
||||||
|
<input type="date" name="from_date" value="<?= esc($fromDate ?? date('Y-m-01')) ?>" class="form-control form-control-sm">
|
||||||
|
</div>
|
||||||
|
<div class="col-6 col-md-3">
|
||||||
|
<label class="form-label form-label-sm mb-1 small fw-bold">To Date</label>
|
||||||
|
<input type="date" name="to_date" value="<?= esc($toDate ?? date('Y-m-d')) ?>" class="form-control form-control-sm">
|
||||||
|
</div>
|
||||||
|
<div class="col-6 col-md-2">
|
||||||
|
<label class="form-label form-label-sm mb-1 small fw-bold">Audit Type</label>
|
||||||
|
<select name="audit_type" class="form-select form-select-sm">
|
||||||
|
<option value="">All Types</option>
|
||||||
|
<option value="PHYSICAL_VARIATION" <?= ($auditType ?? '') === 'PHYSICAL_VARIATION' ? 'selected' : '' ?>>Count Difference</option>
|
||||||
|
<option value="DAMAGED" <?= ($auditType ?? '') === 'DAMAGED' ? 'selected' : '' ?>>Damaged / Broken</option>
|
||||||
|
<option value="EXPIRED" <?= ($auditType ?? '') === 'EXPIRED' ? 'selected' : '' ?>>Expired on Shelf</option>
|
||||||
|
<option value="SHORTAGE" <?= ($auditType ?? '') === 'SHORTAGE' ? 'selected' : '' ?>>Shortage / Missing</option>
|
||||||
|
<option value="SURPLUS" <?= ($auditType ?? '') === 'SURPLUS' ? 'selected' : '' ?>>Found Surplus</option>
|
||||||
|
<option value="OK" <?= ($auditType ?? '') === 'OK' ? 'selected' : '' ?>>Matched (OK)</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="col-6 col-md-2">
|
||||||
|
<label class="form-label form-label-sm mb-1 small fw-bold">Status</label>
|
||||||
|
<select name="status" class="form-select form-select-sm">
|
||||||
|
<option value="">All Status</option>
|
||||||
|
<option value="PENDING" <?= ($status ?? '') === 'PENDING' ? 'selected' : '' ?>>Pending Approval</option>
|
||||||
|
<option value="RECONCILED" <?= ($status ?? '') === 'RECONCILED' ? 'selected' : '' ?>>Reconciled</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="col-12 col-md-2 d-flex gap-2">
|
||||||
|
<button type="submit" class="btn btn-sm btn-primary w-100"><i class="bi bi-filter"></i> Filter</button>
|
||||||
|
<a href="<?= base_url('Medical/stock_audit_report') ?>" class="btn btn-sm btn-outline-secondary"><i class="bi bi-arrow-counterclockwise"></i></a>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<!-- Audit Table -->
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-sm table-striped table-hover align-middle" id="stock-audit-table" style="font-size: 13px;">
|
||||||
|
<thead class="table-dark">
|
||||||
|
<tr>
|
||||||
|
<th>Date & Time</th>
|
||||||
|
<th>Medicine Name</th>
|
||||||
|
<th>Batch / Exp</th>
|
||||||
|
<th>Rack & Shelf</th>
|
||||||
|
<th class="text-center">System Qty</th>
|
||||||
|
<th class="text-center">Physical Qty</th>
|
||||||
|
<th class="text-center">Variance (+/-)</th>
|
||||||
|
<th>Audit Reason & Remarks</th>
|
||||||
|
<th>Audited By</th>
|
||||||
|
<th class="text-center">Status</th>
|
||||||
|
<th class="text-center">Action</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<?php if (empty($audits)): ?>
|
||||||
|
<tr>
|
||||||
|
<td colspan="11" class="text-center py-4 text-muted">
|
||||||
|
<i class="bi bi-clipboard-x fs-3 d-block mb-1"></i>
|
||||||
|
No stock audits recorded for this date range. Launch the Mobile App to audit shelves.
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<?php else: ?>
|
||||||
|
<?php foreach ($audits as $a):
|
||||||
|
$var = (int)($a['variation_qty'] ?? 0);
|
||||||
|
$isReconciled = ($a['reconciled_status'] ?? '') === 'RECONCILED';
|
||||||
|
?>
|
||||||
|
<tr id="audit_row_<?= (int)$a['audit_id'] ?>">
|
||||||
|
<td>
|
||||||
|
<strong><?= !empty($a['created_at']) ? date('d-m-Y', strtotime($a['created_at'])) : '-' ?></strong>
|
||||||
|
<div class="text-muted" style="font-size:10px;"><?= !empty($a['created_at']) ? date('h:i A', strtotime($a['created_at'])) : '' ?></div>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<strong><?= esc($a['item_name'] ?? 'Item #' . ($a['item_id'] ?? '')) ?></strong>
|
||||||
|
<?php if (!empty($a['formulation'])): ?>
|
||||||
|
<span class="badge bg-secondary-subtle text-secondary" style="font-size:10px;"><?= esc($a['formulation']) ?></span>
|
||||||
|
<?php endif; ?>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<code><?= esc($a['batch_no'] ?? '-') ?></code>
|
||||||
|
<?php if (!empty($a['expiry_date'])): ?>
|
||||||
|
<div class="text-muted" style="font-size:11px;"><?= date('m/Y', strtotime($a['expiry_date'])) ?></div>
|
||||||
|
<?php endif; ?>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<?php if (!empty($a['rack_no']) || !empty($a['shelf_no'])): ?>
|
||||||
|
<span class="badge bg-info-subtle text-info-emphasis">
|
||||||
|
R: <?= esc($a['rack_no'] ?: '-') ?> | S: <?= esc($a['shelf_no'] ?: '-') ?>
|
||||||
|
</span>
|
||||||
|
<?php else: ?>
|
||||||
|
<span class="text-muted">-</span>
|
||||||
|
<?php endif; ?>
|
||||||
|
</td>
|
||||||
|
<td class="text-center fw-bold"><?= (int)($a['system_qty'] ?? 0) ?></td>
|
||||||
|
<td class="text-center fw-bold text-primary"><?= (int)($a['physical_qty'] ?? 0) ?></td>
|
||||||
|
<td class="text-center">
|
||||||
|
<?php if ($var === 0): ?>
|
||||||
|
<span class="badge bg-success">0 (Match)</span>
|
||||||
|
<?php elseif ($var < 0): ?>
|
||||||
|
<span class="badge bg-danger"><?= $var ?> Shortage</span>
|
||||||
|
<?php else: ?>
|
||||||
|
<span class="badge bg-primary">+<?= $var ?> Surplus</span>
|
||||||
|
<?php endif; ?>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<span class="badge bg-light text-dark border"><?= esc($a['audit_type'] ?? 'AUDIT') ?></span>
|
||||||
|
<?php if (!empty($a['remarks'])): ?>
|
||||||
|
<div class="text-muted small mt-1"><?= esc($a['remarks']) ?></div>
|
||||||
|
<?php endif; ?>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<div class="fw-semibold"><?= esc($a['conducted_by_name'] ?? 'Staff') ?></div>
|
||||||
|
</td>
|
||||||
|
<td class="text-center" id="status_cell_<?= (int)$a['audit_id'] ?>">
|
||||||
|
<?php if ($isReconciled): ?>
|
||||||
|
<span class="badge bg-success-subtle text-success border border-success"><i class="bi bi-check-circle-fill"></i> Reconciled</span>
|
||||||
|
<?php else: ?>
|
||||||
|
<span class="badge bg-warning-subtle text-warning-emphasis border border-warning">Pending Approval</span>
|
||||||
|
<?php endif; ?>
|
||||||
|
</td>
|
||||||
|
<td class="text-center" id="action_cell_<?= (int)$a['audit_id'] ?>">
|
||||||
|
<?php if (!$isReconciled && $var !== 0): ?>
|
||||||
|
<button type="button" class="btn btn-xs btn-outline-success py-0 px-2 fw-bold" onclick="reconcileStockAudit(<?= (int)$a['audit_id'] ?>, <?= $var ?>);" style="font-size: 11px;">
|
||||||
|
<i class="bi bi-check2-all"></i> Adjust Stock
|
||||||
|
</button>
|
||||||
|
<?php elseif (!$isReconciled && $var === 0): ?>
|
||||||
|
<button type="button" class="btn btn-xs btn-outline-secondary py-0 px-2" onclick="reconcileStockAudit(<?= (int)$a['audit_id'] ?>, 0);" style="font-size: 11px;">
|
||||||
|
Approve
|
||||||
|
</button>
|
||||||
|
<?php else: ?>
|
||||||
|
<span class="text-muted small">Done</span>
|
||||||
|
<?php endif; ?>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
<?php endif; ?>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function reconcileStockAudit(auditId, variation) {
|
||||||
|
var msg = (variation !== 0)
|
||||||
|
? "Are you sure you want to adjust central stock inventory by " + (variation > 0 ? ("+" + variation) : variation) + " units?"
|
||||||
|
: "Approve this matched physical audit?";
|
||||||
|
|
||||||
|
if (!confirm(msg)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
jQuery.ajax({
|
||||||
|
url: '<?= base_url('Medical/stock_audit_reconcile') ?>',
|
||||||
|
type: 'POST',
|
||||||
|
data: { audit_id: auditId },
|
||||||
|
dataType: 'json',
|
||||||
|
success: function(res) {
|
||||||
|
if (res.status === 1) {
|
||||||
|
jQuery('#status_cell_' + auditId).html('<span class="badge bg-success-subtle text-success border border-success"><i class="bi bi-check-circle-fill"></i> Reconciled</span>');
|
||||||
|
jQuery('#action_cell_' + auditId).html('<span class="text-muted small">Done</span>');
|
||||||
|
alert(res.message);
|
||||||
|
} else {
|
||||||
|
alert(res.message || 'Error reconciling audit');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: function() {
|
||||||
|
alert('Failed to connect to server. Please try again.');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
@ -304,27 +304,34 @@
|
||||||
<!-- Step 1: App Selection Cards -->
|
<!-- Step 1: App Selection Cards -->
|
||||||
<label class="form-label fw-bold small text-secondary mb-2">1. SELECT APPLICATION</label>
|
<label class="form-label fw-bold small text-secondary mb-2">1. SELECT APPLICATION</label>
|
||||||
<div class="row g-2 mb-3">
|
<div class="row g-2 mb-3">
|
||||||
<div class="col-4">
|
<div class="col-6 col-md-3">
|
||||||
<div class="card p-2 text-center border-primary bg-primary-subtle text-primary h-100 app-card-item active" style="cursor: pointer;" id="app_card_nursing" data-app="nursing">
|
<div class="card p-2 text-center border-primary bg-primary-subtle text-primary h-100 app-card-item active" style="cursor: pointer;" id="app_card_nursing" data-app="nursing">
|
||||||
<i class="bi bi-heart-pulse-fill fs-3 mb-1"></i>
|
<i class="bi bi-heart-pulse-fill fs-3 mb-1"></i>
|
||||||
<div class="fw-bold small" style="font-size:12px;">Nursing Care</div>
|
<div class="fw-bold small" style="font-size:12px;">Nursing Care</div>
|
||||||
<span class="badge bg-primary mt-1" style="font-size:9px;">Active PWA</span>
|
<span class="badge bg-primary mt-1" style="font-size:9px;">Active PWA</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-4">
|
<div class="col-6 col-md-3">
|
||||||
<div class="card p-2 text-center border-secondary bg-light text-dark h-100 app-card-item" style="cursor: pointer;" id="app_card_doctor" data-app="doctor">
|
<div class="card p-2 text-center border-secondary bg-light text-dark h-100 app-card-item" style="cursor: pointer;" id="app_card_doctor" data-app="doctor">
|
||||||
<i class="bi bi-person-badge-fill fs-3 mb-1 text-primary"></i>
|
<i class="bi bi-person-badge-fill fs-3 mb-1 text-primary"></i>
|
||||||
<div class="fw-bold small" style="font-size:12px;">Doctor App</div>
|
<div class="fw-bold small" style="font-size:12px;">Doctor App</div>
|
||||||
<span class="badge bg-success mt-1" style="font-size:9px;">Active PWA</span>
|
<span class="badge bg-success mt-1" style="font-size:9px;">Active PWA</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-4">
|
<div class="col-6 col-md-3">
|
||||||
<div class="card p-2 text-center border-secondary bg-light text-dark h-100 app-card-item" style="cursor: pointer;" id="app_card_pharmacy" data-app="pharmacy">
|
<div class="card p-2 text-center border-secondary bg-light text-dark h-100 app-card-item" style="cursor: pointer;" id="app_card_pharmacy" data-app="pharmacy">
|
||||||
<i class="bi bi-capsule fs-3 mb-1" style="color: #0891b2;"></i>
|
<i class="bi bi-capsule fs-3 mb-1" style="color: #0891b2;"></i>
|
||||||
<div class="fw-bold small" style="font-size:12px;">Pharmacy POS</div>
|
<div class="fw-bold small" style="font-size:12px;">Pharmacy POS</div>
|
||||||
<span class="badge text-white mt-1" style="font-size:9px; background: #0891b2;">Active PWA</span>
|
<span class="badge text-white mt-1" style="font-size:9px; background: #0891b2;">Active PWA</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="col-6 col-md-3">
|
||||||
|
<div class="card p-2 text-center border-secondary bg-light text-dark h-100 app-card-item" style="cursor: pointer;" id="app_card_central_pharmacy" data-app="central_pharmacy">
|
||||||
|
<i class="bi bi-box-seam-fill fs-3 mb-1" style="color: #6366f1;"></i>
|
||||||
|
<div class="fw-bold small" style="font-size:12px;">Main Pharmacy</div>
|
||||||
|
<span class="badge text-white mt-1" style="font-size:9px; background: #6366f1;">Store & Picker</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Step 2: App Launch QR Code & Secure Intranet Link -->
|
<!-- Step 2: App Launch QR Code & Secure Intranet Link -->
|
||||||
|
|
@ -358,6 +365,9 @@
|
||||||
var selectedApp = 'nursing';
|
var selectedApp = 'nursing';
|
||||||
|
|
||||||
function getAppBaseUrl() {
|
function getAppBaseUrl() {
|
||||||
|
if (selectedApp === 'central_pharmacy') {
|
||||||
|
return '<?= base_url('Medical/mobile') ?>';
|
||||||
|
}
|
||||||
if (selectedApp === 'pharmacy') {
|
if (selectedApp === 'pharmacy') {
|
||||||
return '<?= base_url('MedicalStore') ?>';
|
return '<?= base_url('MedicalStore') ?>';
|
||||||
}
|
}
|
||||||
|
|
@ -378,8 +388,11 @@
|
||||||
if (urlInput) urlInput.value = appUrl;
|
if (urlInput) urlInput.value = appUrl;
|
||||||
if (launchLink) launchLink.href = appUrl;
|
if (launchLink) launchLink.href = appUrl;
|
||||||
|
|
||||||
var title = (selectedApp === 'doctor') ? 'DoctorCare EMR App' : ((selectedApp === 'pharmacy') ? 'Medical Store & Pharmacy POS' : 'Nursing Care App');
|
var title = (selectedApp === 'doctor') ? 'DoctorCare EMR App'
|
||||||
var path = (selectedApp === 'pharmacy') ? '/MedicalStore' : ('/app/' + selectedApp);
|
: ((selectedApp === 'central_pharmacy') ? 'Central Pharmacy & Floor Picker PWA'
|
||||||
|
: ((selectedApp === 'pharmacy') ? 'Medical Store & Pharmacy POS' : 'Nursing Care App'));
|
||||||
|
var path = (selectedApp === 'central_pharmacy') ? '/Medical/mobile'
|
||||||
|
: ((selectedApp === 'pharmacy') ? '/MedicalStore' : ('/app/' + selectedApp));
|
||||||
|
|
||||||
if (targetName) targetName.textContent = title;
|
if (targetName) targetName.textContent = title;
|
||||||
if (targetCode) targetCode.textContent = path;
|
if (targetCode) targetCode.textContent = path;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user