feat(medical-store): ABDM-compatible multi-building pharmacy, POS desktop keyboard workflow, unit dispensing, and split payments

This commit is contained in:
DevSoftTech Server 2026-09-08 02:19:11 +05:30
parent ee7cf59c49
commit 765286e7fa
255 changed files with 36101 additions and 8 deletions

14
.gitignore vendored
View File

@ -144,3 +144,17 @@ public/uploads/*
!public/assets/
!public/assets/img/
!public/assets/img/**
# Android build & local properties
android_opd_queue_tv/.gradle/
android_opd_queue_tv/*/build/
android_opd_queue_tv/build/
android_opd_queue_tv/local.properties
# Temporary and extraction directories
docx_extracted/
writable/debug/*
writable/github_update_cache.json
E-AtriaHMS.docx
E-AtriaHMS.zip

View File

@ -0,0 +1,77 @@
# HMS Multi-Building Medical Store & ABDM Pharmacy System
**Session Name:** `HMS Multi-Store Pharmacy & ABDM Integration`
**Antigravity Conversation ID:** `3731d0c5-f4df-435e-b54f-f690562e18e3`
**Last Updated:** September 8, 2026
---
## Quick Resume Instructions After Computer Restart
### 1. In Antigravity IDE
1. Open Antigravity IDE and reopen the workspace `HMS_CI4_OLD`.
2. In the Chat panel, click the **Chat History / Clock icon** (or open the left conversation drawer).
3. Select this session: **`HMS Multi-Store Pharmacy & ABDM Integration`** (ID: `3731d0c5-f4df-435e-b54f-f690562e18e3`).
4. Type your message and continue seamlessly!
*Tip: If you ever open a new chat, you can type `@HMS_MEDICAL_STORE_SESSION.md` or `@conversation` to instantly restore all context.*
---
## System Access & Credentials Summary
### Development Server
- Command: `php spark serve` (in `d:\Workplace\HMS_CI4_OLD`)
- Server URL: `http://localhost:8080`
### HMS Admin Management
- **URL:** [http://localhost:8080/setting/admin/medical-store](http://localhost:8080/setting/admin/medical-store)
- **Features:** Store configuration, DL 20B/21B licenses, GSTIN, Pharmacist details, ABDM HFR/HIP/HPR registries, 60-min OTP generator, terminal device authorization, and Marg ERP CSV import.
### Direct Counter Desks (React PWA)
- **Counter A (Main Hospital Block A):**
- Direct Link: [http://localhost:8080/MedicalStore/storeA](http://localhost:8080/MedicalStore/storeA)
- Store Code: `ST-MAIN`
- Permanent Security Key: `STORE-SEC-A91B4C72`
- ABDM HFR ID: `IN0710001234`
- Pharmacist HPR ID: `91-8822-4411-9901`
- **Counter B (OPD Block B):**
- Direct Link: [http://localhost:8080/MedicalStore/storeB](http://localhost:8080/MedicalStore/storeB)
- Store Code: `ST-OPD-B2`
- Permanent Security Key: `STORE-SEC-B84F29D1`
- ABDM HFR ID: `IN0710001234-OPD`
- Pharmacist HPR ID: `91-8822-4411-9902`
---
## Core Technical Reference
1. **Zero Legacy Disturbance:**
- Legacy pharmacy tables (`invoice_med_master`, `inv_med_item`, `med_*`) and controllers remain 100% untouched.
- All new tables use the **`mst_*`** prefix (15 tables).
2. **ABDM Compatibility (M1 & M2):**
- Patient search by 14-digit ABHA Number or ABHA Address.
- Care Context generation: `PHARM-{store_code}-{invoice_no}`.
- NRCES FHIR R4 Bundle profile: `https://nrces.in/ndhm/fhir/r4/StructureDefinition/MedicationDispenseDocument`.
- Coded with SNOMED-CT clinical terminology.
- Gateway Bridge queue integration: `abdm_sync_record`.
- Bundle preview endpoint: `GET /api/v1/medical-store/abdm/bundle/{sale_id}`.
3. **Multi-Format Invoices:**
- 80mm Thermal Slip (Fast 1–5 item OTC/OPD).
- A5 Invoice (Normal OPD/Walk-in with GST summary).
- A4 Hospital Tax Invoice (IPD admissions & large bills).
4. **Marg ERP Migration:**
- Supported CSV import with auto-detected columns. Sample test file: `scratch/test_marg_sample.csv`.
5. **Double-Entry Accounting & Statutory Drug Register:**
- Automated ledger postings, Daily Cash Book, GSTR-1 outward reports, and mandatory Schedule H1 drug register.
6. **Retail Loose Tablet & Unit Dispensing:**
- Exact per-tablet pricing (`Strip MRP / units_per_pack`), exact stock deduction in individual tablets, and support for Loose Tab, Strip, and Combo sales.
7. **Split / Mixed Payment Mode & Audit:**
- Supports partial payments (e.g. ₹35 Cash + ₹100 UPI) with 12-digit UTR and bank name capturing for bank reconciliation and audit.
8. **Keyboard-First Zero-Mouse POS Workflow (Marg ERP Standard):**
- High-contrast single-line Quick Keys ribbon.
- Hotkeys: `F2` (Medicine Search), `F4` (Patient Search), `F7` (Whole Discount), `F8` (Payment Mode), `F9 / Ctrl+Enter` (Complete Sale & Print), `Alt+T` (Loose Tab), `Alt+S` (Strip), `Shift+Delete` (Remove Item).
- Auto-focus on Medicine Search on page load; `Enter` on quantity immediately commits and jumps focus back to Medicine Lookup for the next scan.
9. **Compact "Qty & Unit" Cell:**
- Single-line input group with type selector dropdown (`Tab`, `Strip`, `Str+Tab`) positioned before the quantity box.
- Automatically defaults to `Tab` (loose units).

View File

@ -0,0 +1,13 @@
#This file is generated by updateDaemonJvm
toolchainUrl.FREE_BSD.AARCH64=https\://api.foojay.io/disco/v3.0/ids/b96cc4b9c59b5bff244af296e50d5ea3/redirect
toolchainUrl.FREE_BSD.X86_64=https\://api.foojay.io/disco/v3.0/ids/fdfa2f9fadcf75fb9616fde209d60b55/redirect
toolchainUrl.LINUX.AARCH64=https\://api.foojay.io/disco/v3.0/ids/b96cc4b9c59b5bff244af296e50d5ea3/redirect
toolchainUrl.LINUX.X86_64=https\://api.foojay.io/disco/v3.0/ids/fdfa2f9fadcf75fb9616fde209d60b55/redirect
toolchainUrl.MAC_OS.AARCH64=https\://api.foojay.io/disco/v3.0/ids/97b61e8788c7490a5f53d8951b11ea7a/redirect
toolchainUrl.MAC_OS.X86_64=https\://api.foojay.io/disco/v3.0/ids/feb8af2956f2e263086e0a8f9a1df390/redirect
toolchainUrl.UNIX.AARCH64=https\://api.foojay.io/disco/v3.0/ids/b96cc4b9c59b5bff244af296e50d5ea3/redirect
toolchainUrl.UNIX.X86_64=https\://api.foojay.io/disco/v3.0/ids/fdfa2f9fadcf75fb9616fde209d60b55/redirect
toolchainUrl.WINDOWS.AARCH64=https\://api.foojay.io/disco/v3.0/ids/eff42deed1d8947129a4e66cd07a89f4/redirect
toolchainUrl.WINDOWS.X86_64=https\://api.foojay.io/disco/v3.0/ids/8fe418192094ee8db0da620bcb6b1629/redirect
toolchainVendor=JETBRAINS
toolchainVersion=25

Binary file not shown.

View File

@ -0,0 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.0-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

248
android_opd_queue_tv/gradlew vendored Normal file
View File

@ -0,0 +1,248 @@
#!/bin/sh
#
# Copyright © 2015 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# SPDX-License-Identifier: Apache-2.0
#
##############################################################################
#
# Gradle start up script for POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done
# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
warn () {
echo "$*"
} >&2
die () {
echo
echo "$*"
echo
exit 1
} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
esac
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD=$JAVA_HOME/jre/sh/java
else
JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD=java
if ! command -v java >/dev/null 2>&1
then
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
fi
# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
fi
# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
fi
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# and any embedded shellness will be escaped.
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
# treated as '${Hostname}' itself on the command line.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
"$@"
# Stop when "xargs" is not available.
if ! command -v xargs >/dev/null 2>&1
then
die "xargs is not available"
fi
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@"

93
android_opd_queue_tv/gradlew.bat vendored Normal file
View File

@ -0,0 +1,93 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@rem SPDX-License-Identifier: Apache-2.0
@rem
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%"=="" set DIRNAME=.
@rem This is normally unused
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute
echo. 1>&2
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo. 1>&2
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
:execute
@rem Setup the command line
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
:end
@rem End local scope for the variables with windows NT shell
if %ERRORLEVEL% equ 0 goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

View File

@ -1413,6 +1413,14 @@ $routes->group('setting', static function($routes) {
$routes->post('admin/opd-queue-tv/save', 'Setting\\OpdQueueSetting::save');
$routes->post('admin/opd-queue-tv/upload-banner', 'Setting\\OpdQueueSetting::uploadBanner');
$routes->post('admin/opd-queue-tv/delete-banner', 'Setting\\OpdQueueSetting::deleteBanner');
$routes->get('admin/medical-store', 'Setting\\MedicalStoreAdmin::index');
$routes->post('admin/medical-store/save', 'Setting\\MedicalStoreAdmin::save');
$routes->post('admin/medical-store/delete', 'Setting\\MedicalStoreAdmin::delete');
$routes->post('admin/medical-store/generate-otp', 'Setting\\MedicalStoreAdmin::generateOtp');
$routes->post('admin/medical-store/regenerate-key', 'Setting\\MedicalStoreAdmin::regenerateSecurityKey');
$routes->post('admin/medical-store/revoke-device', 'Setting\\MedicalStoreAdmin::revokeDevice');
$routes->post('admin/medical-store/import-marg', 'Setting\\MedicalStoreAdmin::importMarg');
$routes->get('admin/bed-management', 'Setting\\BedManagement::index', ['filter' => $settingsBedFilter]);
$routes->get('admin/departments', 'Setting\\BedManagement::departments', ['filter' => $settingsBedFilter]);
$routes->post('admin/departments/save', 'Setting\\BedManagement::saveDepartment', ['filter' => $settingsBedFilter]);
@ -1641,5 +1649,45 @@ $routes->group('api/v1', ['namespace' => 'App\Controllers\Api\v1'], static funct
$routes->get('doctor/ipd/list/(:num)', 'DoctorApi::ipdList/$1');
$routes->get('doctor/ipd/workspace/(:num)', 'DoctorApi::patientWorkspace/$1');
$routes->post('doctor/ipd/treatment/save/(:num)', 'DoctorApi::saveTreatmentNote/$1');
// Medical Store PWA APIs
$routes->get('medical-store/stores', 'MedicalStoreApi::stores');
$routes->post('medical-store/stores/save', 'MedicalStoreApi::saveStore');
$routes->get('medical-store/patient/search', 'MedicalStoreApi::searchPatient');
$routes->get('medical-store/patient/prescription/(:segment)/(:num)', 'MedicalStoreApi::getPrescription/$1/$2');
$routes->get('medical-store/items/search', 'MedicalStoreApi::searchItems');
$routes->post('medical-store/stock/opening', 'MedicalStoreApi::saveOpeningStock');
$routes->get('medical-store/stock/list', 'MedicalStoreApi::stockList');
$routes->get('medical-store/stock/alerts', 'MedicalStoreApi::stockAlerts');
$routes->post('medical-store/sales/save', 'MedicalStoreApi::saveSale');
$routes->get('medical-store/sales/invoice/(:num)', 'MedicalStoreApi::getInvoice/$1');
$routes->get('medical-store/sales/recent', 'MedicalStoreApi::recentSales');
$routes->get('medical-store/suppliers', 'MedicalStoreApi::suppliers');
$routes->post('medical-store/suppliers/save', 'MedicalStoreApi::saveSupplier');
$routes->post('medical-store/purchase/save', 'MedicalStoreApi::savePurchase');
$routes->post('medical-store/purchase/payment', 'MedicalStoreApi::saveSupplierPayment');
$routes->post('medical-store/transfer/request', 'MedicalStoreApi::requestTransfer');
$routes->post('medical-store/transfer/dispatch', 'MedicalStoreApi::dispatchTransfer');
$routes->post('medical-store/transfer/receive', 'MedicalStoreApi::receiveTransfer');
$routes->get('medical-store/accounting/supplier-ledger/(:num)', 'MedicalStoreApi::getSupplierLedger/$1');
$routes->get('medical-store/accounting/patient-ledger/(:segment)', 'MedicalStoreApi::getPatientLedger/$1');
$routes->get('medical-store/accounting/daybook', 'MedicalStoreApi::getDaybook');
$routes->get('medical-store/accounting/gst-report', 'MedicalStoreApi::getGstReport');
$routes->get('medical-store/accounting/bank-reconciliation', 'MedicalStoreApi::getBankReconciliation');
$routes->post('medical-store/accounting/reconcile-bank', 'MedicalStoreApi::reconcileBank');
$routes->get('medical-store/compliance/schedule-h1-register', 'MedicalStoreApi::getScheduleH1Register');
$routes->post('medical-store/device/verify', 'MedicalStoreApi::verifyDevice');
$routes->post('medical-store/device/check', 'MedicalStoreApi::checkDeviceStatus');
$routes->post('medical-store/stock/import-marg', 'MedicalStoreApi::importMarg');
$routes->get('medical-store/abdm/bundle/(:num)', 'MedicalStoreApi::getAbdmBundle/$1');
$routes->post('medical-store/abdm/link-abha', 'MedicalStoreApi::linkAbha');
});
// Dedicated Direct Store Links (e.g. /MedicalStore/storeA, /MedicalStore/storeB)
$routes->get('MedicalStore', 'Api\v1\MedicalStoreApi::pwaIndex');
$routes->get('MedicalStore/(:any)', 'Api\v1\MedicalStoreApi::pwaIndex/$1');
$routes->get('app/medical-store', 'Api\v1\MedicalStoreApi::pwaIndex');
$routes->get('app/medical-store/(:any)', 'Api\v1\MedicalStoreApi::pwaIndex/$1');

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,452 @@
<?php
namespace App\Controllers\Setting;
use App\Controllers\BaseController;
class MedicalStoreAdmin extends BaseController
{
protected $db;
public function __construct()
{
$this->db = \Config\Database::connect();
}
public function index()
{
$q = trim((string)($this->request->getGet('q') ?? ''));
$builder = $this->db->table('mst_stores');
if (!empty($q)) {
$builder->groupStart()
->like('store_name', $q)
->orLike('store_code', $q)
->orLike('store_slug', $q)
->orLike('building_name', $q)
->orLike('drug_license_no_20b', $q)
->orLike('gstin', $q)
->groupEnd();
}
$stores = $builder->orderBy('is_main_store', 'DESC')->orderBy('store_id', 'ASC')->get()->getResultArray();
// Enforce security keys and slugs
foreach ($stores as &$st) {
$needsUpdate = false;
$updates = [];
if (empty($st['store_slug'])) {
$st['store_slug'] = strtolower(preg_replace('/[^a-zA-Z0-9]/', '', $st['store_code'] ?: ('store' . $st['store_id'])));
$updates['store_slug'] = $st['store_slug'];
$needsUpdate = true;
}
if (empty($st['security_key'])) {
$st['security_key'] = 'HMS-' . strtoupper(bin2hex(random_bytes(3))) . '-' . rand(100, 999);
$updates['security_key'] = $st['security_key'];
$needsUpdate = true;
}
if ($needsUpdate) {
$this->db->table('mst_stores')->where('store_id', $st['store_id'])->update($updates);
}
// Fetch authorized devices count
$deviceCount = $this->db->table('mst_store_devices')
->where('store_id', $st['store_id'])
->where('status', 'authorized')
->countAllResults();
$st['authorized_devices_count'] = $deviceCount;
}
// Fetch recent authorized devices across all stores
$devices = $this->db->table('mst_store_devices d')
->select('d.*, s.store_name, s.store_code')
->join('mst_stores s', 's.store_id = d.store_id')
->orderBy('d.device_id', 'DESC')
->limit(30)
->get()
->getResultArray();
return view('Setting/MedicalStore/medical_store_index', [
'stores' => $stores,
'devices' => $devices,
'searchQuery' => $q
]);
}
public function save()
{
if (!$this->request->isAJAX()) {
return $this->response->setStatusCode(400)->setJSON(['ok' => false, 'error' => 'Invalid request']);
}
$id = (int)($this->request->getPost('store_id') ?? 0);
$storeCode = trim((string)($this->request->getPost('store_code') ?? ''));
$storeSlug = strtolower(trim((string)($this->request->getPost('store_slug') ?? '')));
$storeName = trim((string)($this->request->getPost('store_name') ?? ''));
if (empty($storeName) || empty($storeCode)) {
return $this->response->setJSON(['ok' => false, 'error' => 'Store Name and Store Code are required.']);
}
if (empty($storeSlug)) {
$storeSlug = strtolower(preg_replace('/[^a-zA-Z0-9]/', '', $storeCode));
}
// Check unique slug
$checkSlug = $this->db->table('mst_stores')
->where('store_slug', $storeSlug)
->where('store_id !=', $id)
->get()
->getRowArray();
if ($checkSlug) {
return $this->response->setJSON(['ok' => false, 'error' => "URL Link Slug '{$storeSlug}' is already assigned to another store. Please choose a unique slug."]);
}
$secKey = trim((string)($this->request->getPost('security_key') ?? ''));
if (empty($secKey)) {
$secKey = 'HMS-' . strtoupper(bin2hex(random_bytes(3))) . '-' . rand(100, 999);
}
$data = [
'store_code' => $storeCode,
'store_slug' => $storeSlug,
'store_name' => $storeName,
'building_name' => trim((string)($this->request->getPost('building_name') ?? '')),
'floor_no' => trim((string)($this->request->getPost('floor_no') ?? '')),
'room_no' => trim((string)($this->request->getPost('room_no') ?? '')),
'is_main_store' => !empty($this->request->getPost('is_main_store')) ? 1 : 0,
'drug_license_no_20b' => trim((string)($this->request->getPost('drug_license_no_20b') ?? '')),
'drug_license_no_21b' => trim((string)($this->request->getPost('drug_license_no_21b') ?? '')),
'drug_license_no_20f_x' => trim((string)($this->request->getPost('drug_license_no_20f_x') ?? '')),
'gstin' => strtoupper(trim((string)($this->request->getPost('gstin') ?? ''))),
'pan_no' => strtoupper(trim((string)($this->request->getPost('pan_no') ?? ''))),
'fssai_no' => trim((string)($this->request->getPost('fssai_no') ?? '')),
'state_code' => trim((string)($this->request->getPost('state_code') ?? '07')),
'state_name' => trim((string)($this->request->getPost('state_name') ?? 'Delhi')),
'registered_pharmacist_name' => trim((string)($this->request->getPost('registered_pharmacist_name') ?? '')),
'pharmacist_reg_no' => trim((string)($this->request->getPost('pharmacist_reg_no') ?? '')),
'contact_phone' => trim((string)($this->request->getPost('contact_phone') ?? '')),
'contact_email' => trim((string)($this->request->getPost('contact_email') ?? '')),
'address' => trim((string)($this->request->getPost('address') ?? '')),
'invoice_prefix' => trim((string)($this->request->getPost('invoice_prefix') ?? 'INV/')),
'next_invoice_no' => !empty($this->request->getPost('next_invoice_no')) ? (int)$this->request->getPost('next_invoice_no') : 1001,
'bank_name' => trim((string)($this->request->getPost('bank_name') ?? '')),
'bank_account_no' => trim((string)($this->request->getPost('bank_account_no') ?? '')),
'bank_ifsc' => trim((string)($this->request->getPost('bank_ifsc') ?? '')),
'upi_id' => trim((string)($this->request->getPost('upi_id') ?? '')),
'abdm_hfr_id' => trim((string)($this->request->getPost('abdm_hfr_id') ?? '')),
'abdm_hip_id' => trim((string)($this->request->getPost('abdm_hip_id') ?? '')),
'pharmacist_hpr_id' => trim((string)($this->request->getPost('pharmacist_hpr_id') ?? '')),
'terms_conditions' => trim((string)($this->request->getPost('terms_conditions') ?? '')),
'security_key' => $secKey,
'is_active' => isset($_POST['is_active']) ? (int)$this->request->getPost('is_active') : 1,
];
if ($id > 0) {
$this->db->table('mst_stores')->where('store_id', $id)->update($data);
$msg = 'Medical Store details updated successfully.';
} else {
$this->db->table('mst_stores')->insert($data);
$id = $this->db->insertID();
$msg = 'New Medical Store Counter created successfully.';
}
return $this->response->setJSON(['ok' => true, 'message' => $msg, 'store_id' => $id, 'store_slug' => $storeSlug]);
}
public function generateOtp()
{
if (!$this->request->isAJAX()) {
return $this->response->setStatusCode(400)->setJSON(['ok' => false, 'error' => 'Invalid request']);
}
$storeId = (int)($this->request->getPost('store_id') ?? 0);
$store = $this->db->table('mst_stores')->where('store_id', $storeId)->get()->getRowArray();
if (!$store) {
return $this->response->setJSON(['ok' => false, 'error' => 'Store not found']);
}
$otp = (string)rand(100000, 999999);
$expiry = date('Y-m-d H:i:s', strtotime('+60 minutes'));
$this->db->table('mst_stores')->where('store_id', $storeId)->update([
'current_otp' => $otp,
'otp_expiry' => $expiry
]);
return $this->response->setJSON([
'ok' => true,
'message' => 'Terminal Verification OTP generated successfully.',
'otp' => $otp,
'expiry' => date('h:i A (d-m-Y)', strtotime($expiry)),
'store_name' => $store['store_name']
]);
}
public function regenerateSecurityKey()
{
if (!$this->request->isAJAX()) {
return $this->response->setStatusCode(400)->setJSON(['ok' => false, 'error' => 'Invalid request']);
}
$storeId = (int)($this->request->getPost('store_id') ?? 0);
$newKey = 'HMS-' . strtoupper(bin2hex(random_bytes(3))) . '-' . rand(100, 999);
$this->db->table('mst_stores')->where('store_id', $storeId)->update([
'security_key' => $newKey
]);
return $this->response->setJSON([
'ok' => true,
'message' => 'New Security Key generated successfully.',
'security_key' => $newKey
]);
}
public function revokeDevice()
{
if (!$this->request->isAJAX()) {
return $this->response->setStatusCode(400)->setJSON(['ok' => false, 'error' => 'Invalid request']);
}
$deviceId = (int)($this->request->getPost('device_id') ?? 0);
$this->db->table('mst_store_devices')->where('device_id', $deviceId)->update([
'status' => 'revoked'
]);
return $this->response->setJSON(['ok' => true, 'message' => 'Terminal computer access revoked successfully.']);
}
public function delete()
{
if (!$this->request->isAJAX()) {
return $this->response->setStatusCode(400)->setJSON(['ok' => false, 'error' => 'Invalid request']);
}
$storeId = (int)($this->request->getPost('store_id') ?? 0);
// Soft deactivate store
$this->db->table('mst_stores')->where('store_id', $storeId)->update(['is_active' => 0]);
return $this->response->setJSON(['ok' => true, 'message' => 'Store deactivated successfully.']);
}
/**
* Marg Pharmacy Software / Excel CSV Inventory Import
*/
public function importMarg()
{
$storeId = (int)($this->request->getPost('store_id') ?? 0);
if ($storeId <= 0) {
return $this->response->setJSON(['ok' => false, 'error' => 'Please select a destination Medical Store.']);
}
$file = $this->request->getFile('import_file');
if (!$file || !$file->isValid()) {
return $this->response->setJSON(['ok' => false, 'error' => 'Please upload a valid CSV or Excel file.']);
}
$ext = strtolower($file->getClientExtension());
if (!in_array($ext, ['csv', 'txt'])) {
return $this->response->setJSON(['ok' => false, 'error' => 'Please upload CSV formatted export from Marg ERP or Excel.']);
}
$handle = fopen($file->getTempName(), 'r');
if (!$handle) {
return $this->response->setJSON(['ok' => false, 'error' => 'Unable to read uploaded file.']);
}
$header = fgetcsv($handle, 4096, ',');
if (!$header) {
fclose($handle);
return $this->response->setJSON(['ok' => false, 'error' => 'CSV file is empty.']);
}
// Normalize header names
$headerMap = [];
foreach ($header as $idx => $h) {
$cleaned = strtolower(trim(preg_replace('/[^a-zA-Z0-9]/', '', $h)));
$headerMap[$cleaned] = $idx;
}
// Helper function to find column index
$findCol = function(array $aliases) use ($headerMap) {
foreach ($aliases as $a) {
$cleaned = strtolower(trim(preg_replace('/[^a-zA-Z0-9]/', '', $a)));
if (isset($headerMap[$cleaned])) return $headerMap[$cleaned];
}
return null;
};
$colName = $findCol(['itemname', 'product', 'particulars', 'medicinename', 'name', 'item']);
$colGeneric = $findCol(['generic', 'genericname', 'molecule', 'composition']);
$colCat = $findCol(['category', 'type', 'itemtype']);
$colPack = $findCol(['packing', 'pack', 'packsize']);
$colBatch = $findCol(['batch', 'batchno', 'batchnumber']);
$colExpiry = $findCol(['exp', 'expiry', 'expdate', 'expirydate']);
$colMrp = $findCol(['mrp', 'maxretailprice']);
$colPtr = $findCol(['ptr', 'purchaserate', 'costrate', 'rate', 'cost', 'purrate']);
$colHsn = $findCol(['hsn', 'hsncode', 'hsnno']);
$colGst = $findCol(['gst', 'gstpct', 'gstper', 'tax', 'taxpct']);
$colQty = $findCol(['qty', 'quantity', 'stock', 'openingstock', 'opqty']);
$colBarcode = $findCol(['barcode', 'itemcode', 'code']);
if ($colName === null) {
fclose($handle);
return $this->response->setJSON(['ok' => false, 'error' => "Could not locate 'Item Name' / 'Product' column in CSV header."]);
}
$importedRows = 0;
$newItems = 0;
$newBatches = 0;
$totalValuation = 0;
$this->db->transStart();
while (($row = fgetcsv($handle, 4096, ',')) !== false) {
$name = trim($row[$colName] ?? '');
if (empty($name)) continue;
$generic = ($colGeneric !== null) ? trim($row[$colGeneric] ?? '') : '';
$cat = ($colCat !== null) ? trim($row[$colCat] ?? 'Tablet') : 'Tablet';
$pack = ($colPack !== null) ? trim($row[$colPack] ?? '10 Tablets') : '10 Tablets';
$batchNo = ($colBatch !== null) ? trim($row[$colBatch] ?? 'OPN-01') : 'OPN-01';
$expRaw = ($colExpiry !== null) ? trim($row[$colExpiry] ?? '') : '';
$mrp = ($colMrp !== null) ? (float)preg_replace('/[^0-9.]/', '', $row[$colMrp] ?? '0') : 0;
$ptr = ($colPtr !== null) ? (float)preg_replace('/[^0-9.]/', '', $row[$colPtr] ?? '0') : ($mrp * 0.75);
$hsn = ($colHsn !== null) ? trim($row[$colHsn] ?? '3004') : '3004';
$gst = ($colGst !== null) ? (float)preg_replace('/[^0-9.]/', '', $row[$colGst] ?? '12') : 12;
$qty = ($colQty !== null) ? (int)preg_replace('/[^0-9]/', '', $row[$colQty] ?? '10') : 10;
$barcode = ($colBarcode !== null) ? trim($row[$colBarcode] ?? '') : '';
// Parse Marg Expiry formats (MM/YY, MM/YYYY, DD-MM-YYYY, YYYY-MM-DD)
$expiryDate = date('Y-12-31', strtotime('+1 year'));
if (!empty($expRaw)) {
if (preg_match('/^(\d{1,2})[\/\-](\d{2})$/', $expRaw, $m)) {
$expiryDate = '20' . $m[2] . '-' . str_pad($m[1], 2, '0', STR_PAD_LEFT) . '-28';
} elseif (preg_match('/^(\d{1,2})[\/\-](\d{4})$/', $expRaw, $m)) {
$expiryDate = $m[2] . '-' . str_pad($m[1], 2, '0', STR_PAD_LEFT) . '-28';
} elseif (strtotime($expRaw)) {
$expiryDate = date('Y-m-d', strtotime($expRaw));
}
}
// 1. Find or insert into mst_items (shared drug catalog)
$itemRow = $this->db->table('mst_items')->where('item_name', $name)->get()->getRowArray();
if ($itemRow) {
$itemId = (int)$itemRow['item_id'];
} else {
$this->db->table('mst_items')->insert([
'item_name' => $name,
'generic_name' => $generic,
'category' => $cat ?: 'Tablet',
'hsn_code' => $hsn ?: '3004',
'gst_rate' => $gst ?: 12,
'unit_pack' => $pack ?: '10 Tablets',
'barcode' => $barcode,
'drug_schedule' => 'Schedule H',
'manufacturer_name' => 'Imported Catalog'
]);
$itemId = $this->db->insertID();
$newItems++;
}
// 2. Find or insert into mst_batches for this store
$batchRow = $this->db->table('mst_batches')
->where('store_id', $storeId)
->where('item_id', $itemId)
->where('batch_no', $batchNo)
->get()
->getRowArray();
if ($batchRow) {
$batchId = (int)$batchRow['batch_id'];
$this->db->table('mst_batches')->where('batch_id', $batchId)->update([
'expiry_date' => $expiryDate,
'mrp' => $mrp,
'ptr' => $ptr,
'purchase_rate_net' => $ptr * (1 + $gst / 100),
'gst_rate' => $gst
]);
} else {
$this->db->table('mst_batches')->insert([
'store_id' => $storeId,
'item_id' => $itemId,
'batch_no' => $batchNo,
'mfg_date' => date('Y-01-01'),
'expiry_date' => $expiryDate,
'mrp' => $mrp,
'ptr' => $ptr,
'purchase_rate_net' => $ptr * (1 + $gst / 100),
'gst_rate' => $gst,
'barcode' => $barcode
]);
$batchId = $this->db->insertID();
$newBatches++;
}
// 3. Update stock in mst_stock
$stockRow = $this->db->table('mst_stock')
->where('store_id', $storeId)
->where('item_id', $itemId)
->where('batch_id', $batchId)
->get()
->getRowArray();
if ($stockRow) {
$this->db->table('mst_stock')->where('stock_id', $stockRow['stock_id'])->update([
'current_qty' => $stockRow['current_qty'] + $qty
]);
} else {
$this->db->table('mst_stock')->insert([
'store_id' => $storeId,
'item_id' => $itemId,
'batch_id' => $batchId,
'current_qty' => $qty
]);
}
$lineVal = $qty * $ptr;
$totalValuation += $lineVal;
$importedRows++;
}
fclose($handle);
// Record opening stock valuation journal voucher in double-entry ledgers
if ($totalValuation > 0) {
$voucherNo = 'MARG-IMP-' . $storeId . '-' . date('YmdHis');
$stockHead = $this->db->table('mst_account_heads')->where('head_code', '1003')->get()->getRowArray();
$equityHead = $this->db->table('mst_account_heads')->where('head_code', '3004')->get()->getRowArray();
if ($stockHead && $equityHead) {
$this->db->table('mst_ledger_entries')->insert([
'store_id' => $storeId, 'voucher_no' => $voucherNo, 'voucher_type' => 'OPENING_STOCK', 'voucher_date' => date('Y-m-d'),
'account_head_id' => $stockHead['head_id'], 'debit_amount' => $totalValuation, 'credit_amount' => 0,
'narration' => "Marg ERP Opening Stock Inward ($importedRows lines)"
]);
$this->db->table('mst_ledger_entries')->insert([
'store_id' => $storeId, 'voucher_no' => $voucherNo, 'voucher_type' => 'OPENING_STOCK', 'voucher_date' => date('Y-m-d'),
'account_head_id' => $equityHead['head_id'], 'debit_amount' => 0, 'credit_amount' => $totalValuation,
'narration' => "Marg ERP Opening Capital Credit"
]);
}
}
$this->db->transComplete();
return $this->response->setJSON([
'ok' => true,
'message' => "Successfully imported {$importedRows} inventory lines from Marg Pharmacy file.",
'details' => [
'imported_rows' => $importedRows,
'new_catalog_items'=> $newItems,
'new_batches' => $newBatches,
'total_valuation' => round($totalValuation, 2)
]
]);
}
}

View File

@ -3797,4 +3797,233 @@ class FhirR4Builder
'asNeeded' => false,
];
}
/**
* Build ABDM FHIR R4 MedicationDispense Document Bundle
*
* @param array<string, mixed> $patient
* @param array<string, mixed> $store
* @param array<string, mixed> $sale
* @param array<int, array<string, mixed>> $items
* @return array<string, mixed>
*/
public function buildPharmacyDispenseBundle(array $patient, array $store, array $sale, array $items): array
{
$issuedAt = $this->isoTimestamp();
$bundleUuid = $this->generateUuid();
$compositionUuid = $this->generateUuid();
$patientUuid = $this->generateUuid();
$practitionerUuid = $this->generateUuid();
$organizationUuid = $this->generateUuid();
$patientRef = 'urn:uuid:' . $patientUuid;
$practitionerRef = 'urn:uuid:' . $practitionerUuid;
$organizationRef = 'urn:uuid:' . $organizationUuid;
$resourceEntries = [];
// 1. Practitioner (Pharmacist or Prescribing Doctor)
$practName = trim((string)(($sale['doctor_name'] ?? '') ?: (($store['registered_pharmacist_name'] ?? '') ?: 'Pharmacist')));
$practRegNo = trim((string)(($sale['doctor_reg_no'] ?? '') ?: (($store['pharmacist_reg_no'] ?? '') ?: '')));
$practHprId = trim((string)($store['pharmacist_hpr_id'] ?? ''));
$practIdentifiers = [];
if ($practHprId !== '') {
$practIdentifiers[] = [
'type' => ['coding' => [['system' => 'http://terminology.hl7.org/CodeSystem/v2-0203', 'code' => 'PRN', 'display' => 'Provider number']]],
'system' => 'https://hpr.abdm.gov.in',
'value' => $practHprId
];
}
if ($practRegNo !== '') {
$practIdentifiers[] = [
'type' => ['coding' => [['system' => 'http://terminology.hl7.org/CodeSystem/v2-0203', 'code' => 'MD', 'display' => 'Medical License number']]],
'system' => 'https://doctor.ndhm.gov.in',
'value' => $practRegNo
];
}
$resourceEntries[] = [
'fullUrl' => $practitionerRef,
'resource' => [
'resourceType' => 'Practitioner',
'id' => $practitionerUuid,
'meta' => ['profile' => ['https://nrces.in/ndhm/fhir/r4/StructureDefinition/Practitioner']],
'identifier' => $practIdentifiers,
'name' => [['text' => $practName]]
]
];
// 2. Organization (Hospital Pharmacy / Store)
$hfrId = trim((string)(($store['abdm_hfr_id'] ?? '') ?: (($store['store_code'] ?? '') ?: 'IN0710001234')));
$resourceEntries[] = [
'fullUrl' => $organizationRef,
'resource' => [
'resourceType' => 'Organization',
'id' => $organizationUuid,
'meta' => ['profile' => ['https://nrces.in/ndhm/fhir/r4/StructureDefinition/Organization']],
'identifier' => [[
'type' => ['coding' => [['system' => 'http://terminology.hl7.org/CodeSystem/v2-0203', 'code' => 'PRN', 'display' => 'Provider number']]],
'system' => 'https://facility.ndhm.gov.in',
'value' => $hfrId
]],
'name' => (string)(($store['store_name'] ?? '') ?: 'Hospital Medical Store'),
'telecom' => [
['system' => 'phone', 'value' => (string)(($store['contact_phone'] ?? '') ?: '+91 9999999999')],
['system' => 'email', 'value' => (string)(($store['contact_email'] ?? '') ?: 'pharmacy@hospital.in')]
],
'address' => [[
'line' => [(string)(($store['address'] ?? '') ?: 'Hospital Building')],
'city' => (string)(($store['state_name'] ?? '') ?: 'Delhi'),
'state' => (string)(($store['state_name'] ?? '') ?: 'Delhi'),
'postalCode' => '110001',
'country' => 'IN'
]]
]
];
// 3. Patient
$patientResource = $this->buildPatientResource($patient);
$patientResource['id'] = $patientUuid;
// Inject ABHA identifiers if provided in sale or patient
$abhaNumber = trim((string)(($sale['abha_id'] ?? '') ?: ($patient['abha_id'] ?? '')));
$abhaAddress = trim((string)(($sale['abha_address'] ?? '') ?: ($patient['abha_address'] ?? '')));
if (!isset($patientResource['identifier'])) {
$patientResource['identifier'] = [];
}
if ($abhaNumber !== '') {
$patientResource['identifier'][] = [
'type' => ['coding' => [['system' => 'https://nrces.in/ndhm/fhir/r4/CodeSystem/ndhm-identifier-type-code', 'code' => 'ABHA', 'display' => 'Ayushman Bharat Health Account']]],
'system' => 'https://healthid.ndhm.gov.in',
'value' => $abhaNumber
];
}
if ($abhaAddress !== '') {
$patientResource['identifier'][] = [
'type' => ['coding' => [['system' => 'https://nrces.in/ndhm/fhir/r4/CodeSystem/ndhm-identifier-type-code', 'code' => 'ABHA-ADDRESS', 'display' => 'ABHA Address']]],
'system' => 'https://abdm.gov.in',
'value' => $abhaAddress
];
}
$resourceEntries[] = ['fullUrl' => $patientRef, 'resource' => $patientResource];
// 4. MedicationDispense Resources
$medicationRefs = [];
foreach ($items as $idx => $it) {
$medUuid = $this->generateUuid();
$medRef = 'urn:uuid:' . $medUuid;
$medicationRefs[] = ['reference' => $medRef];
$snomedCode = trim((string)($it['snomed_ct_code'] ?? ''));
$snomedDisplay = trim((string)($it['snomed_display'] ?? ($it['item_name'] ?? 'Medicine')));
$coding = [];
if ($snomedCode !== '') {
$coding[] = [
'system' => 'http://snomed.info/sct',
'code' => $snomedCode,
'display' => $snomedDisplay
];
}
$medResource = [
'resourceType' => 'MedicationDispense',
'id' => $medUuid,
'meta' => ['profile' => ['https://nrces.in/ndhm/fhir/r4/StructureDefinition/MedicationDispense']],
'status' => 'completed',
'medicationCodeableConcept' => [
'coding' => $coding,
'text' => (string)($it['item_name'] ?? 'Medicine')
],
'subject' => ['reference' => $patientRef, 'display' => (string)(($sale['patient_name'] ?? '') ?: 'Patient')],
'performer' => [[
'actor' => ['reference' => $practitionerRef, 'display' => $practName]
]],
'location' => ['reference' => $organizationRef, 'display' => (string)(($store['store_name'] ?? '') ?: 'Pharmacy')],
'quantity' => [
'value' => (int)($it['qty'] ?? 1),
'unit' => (string)($it['unit_pack'] ?? 'Units'),
'system' => 'http://unitsofmeasure.org',
'code' => '1'
],
'daysSupply' => [
'value' => 5,
'unit' => 'days',
'system' => 'http://unitsofmeasure.org',
'code' => 'd'
],
'whenPrepared' => $issuedAt,
'whenHandedOver' => $issuedAt,
'dosageInstruction' => [[
'text' => 'As directed by physician'
]]
];
$resourceEntries[] = ['fullUrl' => $medRef, 'resource' => $medResource];
}
// 5. Composition (Record Head)
$compositionResource = [
'resourceType' => 'Composition',
'id' => $compositionUuid,
'meta' => [
'profile' => ['https://nrces.in/ndhm/fhir/r4/StructureDefinition/MedicationDispenseDocument'],
'versionId' => '1',
'lastUpdated' => $issuedAt
],
'language' => 'en-IN',
'identifier' => [
'system' => 'https://ndhm.in/phr',
'value' => (string)(($sale['abdm_care_context_ref'] ?? '') ?: ('PHARM-' . (($sale['invoice_no'] ?? '') ?: $compositionUuid)))
],
'status' => 'final',
'type' => [
'coding' => [[
'system' => 'http://loinc.org',
'code' => '60590-7',
'display' => 'Medication dispensed.extended Document'
]],
'text' => 'Pharmacy Medication Dispense Record'
],
'subject' => ['reference' => $patientRef, 'display' => (string)(($sale['patient_name'] ?? '') ?: 'Patient')],
'date' => $issuedAt,
'author' => [['reference' => $practitionerRef, 'display' => $practName]],
'title' => 'Pharmacy Medication Dispensation Receipt',
'custodian' => ['reference' => $organizationRef, 'display' => (string)(($store['store_name'] ?? '') ?: 'Hospital Pharmacy')],
'section' => [[
'title' => 'Medications Dispensed',
'code' => [
'coding' => [[
'system' => 'http://loinc.org',
'code' => '10160-0',
'display' => 'History of Medication use'
]]
],
'entry' => $medicationRefs
]]
];
array_unshift($resourceEntries, [
'fullUrl' => 'urn:uuid:' . $compositionUuid,
'resource' => $compositionResource
]);
return [
'resourceType' => 'Bundle',
'id' => $bundleUuid,
'meta' => [
'versionId' => '1',
'lastUpdated' => $issuedAt,
'profile' => ['https://nrces.in/ndhm/fhir/r4/StructureDefinition/DocumentBundle']
],
'identifier' => [
'system' => 'https://ndhm.in/phr',
'value' => $bundleUuid
],
'type' => 'document',
'timestamp' => $issuedAt,
'entry' => $resourceEntries
];
}
}

View File

@ -70,6 +70,13 @@
<span>Nursing Station Master</span>
</a>
</div>
<div class="col-6 col-md-2 col-lg-2">
<a class="admin-tile" href="javascript:openAdminSubPage('<?= base_url('setting/admin/medical-store') ?>','Medical Stores & POS Setup');">
<i class="bi bi-capsule" style="color:#0891b2"></i>
<span>Medical Stores &amp; Counters</span>
</a>
</div>
<div class="col-6 col-md-2 col-lg-2">
<a class="admin-tile" href="javascript:openAdminSubPage('<?= base_url('setting/admin/user-management') ?>','User Management');">
<i class="bi bi-people"></i>

View File

@ -0,0 +1,686 @@
<div class="container-fluid p-0">
<div class="d-flex justify-content-between align-items-center mb-3">
<div>
<h5 class="text-primary mb-0"><i class="bi bi-capsule me-1"></i> Medical Stores &amp; Pharmacy Counters Master</h5>
<small class="text-muted">Manage hospital building stores, Indian Drug Licenses (20B/21B), GSTIN, direct URLs, and machine terminal security</small>
</div>
<div class="d-flex gap-2">
<button type="button" class="btn btn-outline-success btn-sm" id="btn_open_marg_import">
<i class="bi bi-file-earmark-arrow-up me-1"></i> Marg ERP / Excel Import
</button>
<button type="button" class="btn btn-primary btn-sm" id="btn_add_store">
<i class="bi bi-plus-lg me-1"></i> Add Medical Store
</button>
</div>
</div>
<!-- Search Card -->
<div class="card shadow-sm border-0 mb-3">
<div class="card-body py-2 px-3">
<div class="row g-2 align-items-center">
<div class="col-md-9">
<div class="input-group input-group-sm">
<span class="input-group-text bg-light border-end-0"><i class="bi bi-search text-muted"></i></span>
<input type="text" class="form-control border-start-0" id="store_search_input" placeholder="Search by store name, code, slug, building, DL 20B, GSTIN..." value="<?= esc($searchQuery ?? '') ?>">
<button class="btn btn-primary" type="button" id="btn_store_search">Search</button>
</div>
</div>
<div class="col-md-3 text-end">
<button type="button" class="btn btn-outline-secondary btn-sm" id="btn_refresh_store_list">
<i class="bi bi-arrow-clockwise me-1"></i> Refresh
</button>
</div>
</div>
</div>
</div>
<!-- Stores Table -->
<div class="card shadow-sm border-0 mb-4">
<div class="card-body p-0">
<div class="table-responsive">
<table class="table table-hover align-middle mb-0" id="stores_table">
<thead class="table-light">
<tr>
<th style="width: 40px;" class="ps-3">#</th>
<th>Store Name &amp; Building</th>
<th>Direct Counter Link (URL)</th>
<th>Drug Licenses (20B / 21B)</th>
<th>GSTIN &amp; State</th>
<th>Pharmacist</th>
<th>Terminal Security</th>
<th style="width: 130px;" class="text-end pe-3">Actions</th>
</tr>
</thead>
<tbody>
<?php if (empty($stores)): ?>
<tr>
<td colspan="8" class="text-center py-4 text-muted">
<i class="bi bi-hospital fs-3 d-block mb-1"></i>
No medical stores found. Click <strong>"Add Medical Store"</strong> to create a new pharmacy counter.
</td>
</tr>
<?php else: ?>
<?php foreach ($stores as $idx => $st): ?>
<?php
$directUrl = base_url('MedicalStore/' . ($st['store_slug'] ?: $st['store_code']));
?>
<tr>
<td class="ps-3 fw-bold text-secondary"><?= $idx + 1 ?></td>
<td>
<div class="fw-bold text-dark fs-6"><?= esc($st['store_name']) ?></div>
<small class="text-muted">
<i class="bi bi-geo-alt me-1"></i><?= esc($st['building_name'] ?: 'Main Block') ?> • <?= esc($st['floor_no'] ?: 'Ground Floor') ?>
<?php if ($st['is_main_store']): ?>
<span class="badge bg-primary-subtle text-primary border border-primary px-1.5 py-0.5 ms-1">CENTRAL STORE</span>
<?php endif; ?>
</small>
</td>
<td>
<div class="d-flex align-items-center gap-1">
<code class="text-primary fw-bold" style="font-size: 11px;">MedicalStore/<?= esc($st['store_slug']) ?></code>
<button type="button" class="btn btn-sm btn-light py-0 px-1 border" title="Copy Direct URL Link" onclick="copyText('<?= esc($directUrl) ?>')">
<i class="bi bi-copy text-muted" style="font-size: 11px;"></i>
</button>
<a href="<?= esc($directUrl) ?>" target="_blank" class="btn btn-sm btn-outline-primary py-0 px-1" title="Open Counter POS">
<i class="bi bi-box-arrow-up-right" style="font-size: 11px;"></i>
</a>
</div>
</td>
<td>
<div class="small"><strong>20B:</strong> <?= esc($st['drug_license_no_20b'] ?: 'N/A') ?></div>
<div class="small"><strong>21B:</strong> <?= esc($st['drug_license_no_21b'] ?: 'N/A') ?></div>
</td>
<td>
<div><code class="text-success fw-bold"><?= esc($st['gstin'] ?: 'N/A') ?></code></div>
<small class="text-muted d-block">State: <?= esc($st['state_code']) ?> (<?= esc($st['state_name']) ?>)</small>
<?php if (!empty($st['abdm_hfr_id'])): ?>
<span class="badge bg-primary-subtle text-primary border border-primary-subtle mt-0.5" style="font-size: 10px;" title="ABDM Health Facility Registry">
<i class="bi bi-shield-check me-0.5"></i> HFR: <?= esc($st['abdm_hfr_id']) ?>
</span>
<?php endif; ?>
</td>
<td>
<div class="small fw-semibold"><?= esc($st['registered_pharmacist_name'] ?: 'Not Assigned') ?></div>
<small class="text-muted"><?= esc($st['pharmacist_reg_no'] ?: '') ?></small>
</td>
<td>
<button type="button" class="btn btn-sm btn-outline-info py-0.5 px-2" onclick="openTerminalSecurityModal(<?= htmlspecialchars(json_encode($st), ENT_QUOTES, 'UTF-8') ?>)">
<i class="bi bi-shield-lock-fill me-1"></i> Key &amp; OTP
<span class="badge bg-secondary ms-1"><?= (int)$st['authorized_devices_count'] ?> PCs</span>
</button>
</td>
<td class="text-end pe-3">
<button type="button" class="btn btn-outline-primary btn-sm py-0.5 px-2" onclick="editStoreModal(<?= htmlspecialchars(json_encode($st), ENT_QUOTES, 'UTF-8') ?>)">
<i class="bi bi-pencil-square"></i>
</button>
<button type="button" class="btn btn-outline-danger btn-sm py-0.5 px-2 ms-1" onclick="deleteStore(<?= (int)$st['store_id'] ?>, '<?= esc($st['store_name']) ?>')">
<i class="bi bi-trash"></i>
</button>
</td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
</div>
<!-- Authorized Terminals / Machines Section -->
<div class="card shadow-sm border-0">
<div class="card-header bg-white py-2.5 px-3 d-flex justify-content-between align-items-center">
<h6 class="mb-0 fw-bold text-dark"><i class="bi bi-display me-1 text-primary"></i> Authorized Computer Terminals &amp; Machines</h6>
<small class="text-muted">Protected counter computers verified via HMS Admin Security Key or OTP</small>
</div>
<div class="card-body p-0">
<div class="table-responsive">
<table class="table table-hover align-middle mb-0" style="font-size: 12px;">
<thead class="table-light">
<tr>
<th class="ps-3">Machine / Computer Name</th>
<th>Medical Store</th>
<th>IP Address</th>
<th>Verified Method</th>
<th>Authorized Date</th>
<th>Status</th>
<th class="text-end pe-3">Action</th>
</tr>
</thead>
<tbody>
<?php if (empty($devices)): ?>
<tr>
<td colSpan="7" class="text-center py-3 text-muted">
No machine terminals registered yet. Open a store link on a counter PC and verify using the Security Key or OTP.
</td>
</tr>
<?php else: ?>
<?php foreach ($devices as $d): ?>
<tr>
<td class="ps-3 fw-bold">
<i class="bi bi-pc-display me-1 text-primary"></i> <?= esc($d['machine_name']) ?>
</td>
<td><?= esc($d['store_name']) ?></td>
<td><code><?= esc($d['ip_address'] ?: 'Localhost') ?></code></td>
<td><span class="badge bg-light text-dark border"><?= esc($d['verified_by_method']) ?></span></td>
<td><?= esc(date('d-m-Y h:i A', strtotime($d['authorized_at']))) ?></td>
<td>
<?php if ($d['status'] === 'authorized'): ?>
<span class="badge bg-success">Active Authorized</span>
<?php else: ?>
<span class="badge bg-danger">Access Revoked</span>
<?php endif; ?>
</td>
<td class="text-end pe-3">
<?php if ($d['status'] === 'authorized'): ?>
<button type="button" class="btn btn-outline-danger btn-sm py-0 px-2" onclick="revokeTerminal(<?= (int)$d['device_id'] ?>)">
<i class="bi bi-slash-circle me-1"></i> Revoke
</button>
<?php else: ?>
<span class="text-muted">Revoked</span>
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
</div>
</div>
<!-- Modal 1: Add / Edit Medical Store -->
<div class="modal fade" id="storeModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-lg modal-dialog-centered">
<div class="modal-content border-0 shadow">
<div class="modal-header bg-primary text-white py-2.5">
<h6 class="modal-title fw-bold" id="storeModalTitle"><i class="bi bi-capsule me-1"></i> Add Medical Store Counter</h6>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<form id="storeForm">
<input type="hidden" name="store_id" id="form_store_id" value="0">
<div class="modal-body p-3">
<div class="row g-2 mb-2">
<div class="col-md-4">
<label class="form-label small fw-bold mb-1">Store Code <span class="text-danger">*</span></label>
<input type="text" class="form-control form-control-sm" name="store_code" id="form_store_code" placeholder="e.g. ST-OPD-1" required>
</div>
<div class="col-md-4">
<label class="form-label small fw-bold mb-1">Unique URL Link Slug <span class="text-danger">*</span></label>
<div class="input-group input-group-sm">
<span class="input-group-text bg-light" style="font-size: 11px;">/MedicalStore/</span>
<input type="text" class="form-control form-control-sm" name="store_slug" id="form_store_slug" placeholder="storeA" required>
</div>
</div>
<div class="col-md-4">
<label class="form-label small fw-bold mb-1">Is Central Warehouse?</label>
<select class="form-select form-select-sm" name="is_main_store" id="form_is_main_store">
<option value="0">Sub-Counter / Building Store</option>
<option value="1">Central Main Pharmacy</option>
</select>
</div>
<div class="col-md-12">
<label class="form-label small fw-bold mb-1">Medical Store Name <span class="text-danger">*</span></label>
<input type="text" class="form-control form-control-sm" name="store_name" id="form_store_name" placeholder="e.g. Apollo City Medicos - OPD Counter" required>
</div>
<div class="col-md-4">
<label class="form-label small fw-bold mb-1">Building Name</label>
<input type="text" class="form-control form-control-sm" name="building_name" id="form_building_name" placeholder="e.g. OPD Block B">
</div>
<div class="col-md-4">
<label class="form-label small fw-bold mb-1">Floor</label>
<input type="text" class="form-control form-control-sm" name="floor_no" id="form_floor_no" placeholder="e.g. 1st Floor">
</div>
<div class="col-md-4">
<label class="form-label small fw-bold mb-1">Counter / Room No</label>
<input type="text" class="form-control form-control-sm" name="room_no" id="form_room_no" placeholder="e.g. Counter 2">
</div>
</div>
<h6 class="fw-bold text-primary mt-3 mb-2" style="font-size: 13px;"><i class="bi bi-shield-check me-1"></i> Indian Statutory Drug Licenses &amp; GST</h6>
<div class="row g-2 mb-2">
<div class="col-md-4">
<label class="form-label small fw-bold mb-1">Drug License Form 20-B (Allopathic)</label>
<input type="text" class="form-control form-control-sm" name="drug_license_no_20b" id="form_dl_20b" placeholder="DL-20B-DEL-XXXXX">
</div>
<div class="col-md-4">
<label class="form-label small fw-bold mb-1">Drug License Form 21-B (Schedule C/C1)</label>
<input type="text" class="form-control form-control-sm" name="drug_license_no_21b" id="form_dl_21b" placeholder="DL-21B-DEL-XXXXX">
</div>
<div class="col-md-4">
<label class="form-label small fw-bold mb-1">Drug License Form 20-F (Schedule X)</label>
<input type="text" class="form-control form-control-sm" name="drug_license_no_20f_x" id="form_dl_20f" placeholder="DL-20F-DEL-XXXXX">
</div>
<div class="col-md-4">
<label class="form-label small fw-bold mb-1">GSTIN (15-digit)</label>
<input type="text" class="form-control form-control-sm text-uppercase" name="gstin" id="form_gstin" placeholder="07AAAAA0000A1Z5">
</div>
<div class="col-md-4">
<label class="form-label small fw-bold mb-1">PAN Number</label>
<input type="text" class="form-control form-control-sm text-uppercase" name="pan_no" id="form_pan" placeholder="AAAAA0000A">
</div>
<div class="col-md-4">
<label class="form-label small fw-bold mb-1">FSSAI License No</label>
<input type="text" class="form-control form-control-sm" name="fssai_no" id="form_fssai" placeholder="100XXXXXXXXXX">
</div>
<div class="col-md-3">
<label class="form-label small fw-bold mb-1">State Code (GST)</label>
<input type="text" class="form-control form-control-sm" name="state_code" id="form_state_code" placeholder="07" value="07">
</div>
<div class="col-md-9">
<label class="form-label small fw-bold mb-1">State Name</label>
<input type="text" class="form-control form-control-sm" name="state_name" id="form_state_name" placeholder="Delhi" value="Delhi">
</div>
</div>
<h6 class="fw-bold text-primary mt-3 mb-2" style="font-size: 13px;"><i class="bi bi-shield-check me-1"></i> ABDM (Ayushman Bharat Digital Mission) Registration</h6>
<div class="row g-2">
<div class="col-md-4">
<label class="form-label small fw-bold mb-1">ABDM HFR Facility ID</label>
<input type="text" class="form-control form-control-sm" name="abdm_hfr_id" id="form_abdm_hfr_id" placeholder="e.g. IN0710001234">
<div class="form-text" style="font-size: 10px;">Health Facility Registry ID</div>
</div>
<div class="col-md-4">
<label class="form-label small fw-bold mb-1">ABDM HIP ID</label>
<input type="text" class="form-control form-control-sm" name="abdm_hip_id" id="form_abdm_hip_id" placeholder="e.g. HIP-CITYHOSP-01">
<div class="form-text" style="font-size: 10px;">Health Information Provider ID</div>
</div>
<div class="col-md-4">
<label class="form-label small fw-bold mb-1">Pharmacist ABDM HPR ID</label>
<input type="text" class="form-control form-control-sm" name="pharmacist_hpr_id" id="form_pharmacist_hpr_id" placeholder="e.g. 91-XXXX-XXXX-XXXX">
<div class="form-text" style="font-size: 10px;">Healthcare Professional Registry ID</div>
</div>
</div>
<h6 class="fw-bold text-primary mt-3 mb-2" style="font-size: 13px;"><i class="bi bi-person-badge me-1"></i> Pharmacist &amp; POS Bill Configuration</h6>
<div class="row g-2">
<div class="col-md-6">
<label class="form-label small fw-bold mb-1">Registered Pharmacist Name</label>
<input type="text" class="form-control form-control-sm" name="registered_pharmacist_name" id="form_pharmacist_name" placeholder="Pharmacist Full Name">
</div>
<div class="col-md-6">
<label class="form-label small fw-bold mb-1">State Pharmacy Council Reg No</label>
<input type="text" class="form-control form-control-sm" name="pharmacist_reg_no" id="form_pharmacist_reg_no" placeholder="e.g. DPC-Reg-45892">
</div>
<div class="col-md-4">
<label class="form-label small fw-bold mb-1">Bill Series Prefix</label>
<input type="text" class="form-control form-control-sm" name="invoice_prefix" id="form_invoice_prefix" placeholder="OPD1/26-27/">
</div>
<div class="col-md-4">
<label class="form-label small fw-bold mb-1">Next Sequence No</label>
<input type="number" class="form-control form-control-sm" name="next_invoice_no" id="form_next_invoice_no" value="1001">
</div>
<div class="col-md-4">
<label class="form-label small fw-bold mb-1">Store UPI VPA (for QR Code)</label>
<input type="text" class="form-control form-control-sm" name="upi_id" id="form_upi_id" placeholder="hospital@upi">
</div>
<div class="col-md-12">
<label class="form-label small fw-bold mb-1">Terms &amp; Conditions (Printed on Invoice)</label>
<textarea class="form-control form-control-sm" name="terms_conditions" id="form_terms" rows="2" placeholder="Goods once sold returnable within 7 days with bill. Store below 25°C."></textarea>
</div>
</div>
<h6 class="fw-bold text-primary mt-3 mb-2" style="font-size: 13px;"><i class="bi bi-geo-alt me-1"></i> Store Contact &amp; Physical Address</h6>
<div class="row g-2">
<div class="col-md-6">
<label class="form-label small fw-bold mb-1">Contact Phone / Mobile</label>
<input type="text" class="form-control form-control-sm" name="contact_phone" id="form_contact_phone" placeholder="e.g. +91 98765 43210">
</div>
<div class="col-md-6">
<label class="form-label small fw-bold mb-1">Contact Email</label>
<input type="email" class="form-control form-control-sm" name="contact_email" id="form_contact_email" placeholder="pharmacy@hospital.com">
</div>
<div class="col-md-12">
<label class="form-label small fw-bold mb-1">Store / Counter Physical Address</label>
<textarea class="form-control form-control-sm" name="address" id="form_address" rows="2" placeholder="Full address of the hospital pharmacy counter..."></textarea>
</div>
</div>
<h6 class="fw-bold text-primary mt-3 mb-2" style="font-size: 13px;"><i class="bi bi-bank me-1"></i> Bank Account Details (for Statement Reconciliation &amp; Invoices)</h6>
<div class="row g-2">
<div class="col-md-4">
<label class="form-label small fw-bold mb-1">Bank Name</label>
<input type="text" class="form-control form-control-sm" name="bank_name" id="form_bank_name" placeholder="e.g. State Bank of India">
</div>
<div class="col-md-4">
<label class="form-label small fw-bold mb-1">Bank Account Number</label>
<input type="text" class="form-control form-control-sm" name="bank_account_no" id="form_bank_account_no" placeholder="e.g. 10293847561">
</div>
<div class="col-md-4">
<label class="form-label small fw-bold mb-1">Bank IFSC Code</label>
<input type="text" class="form-control form-control-sm text-uppercase" name="bank_ifsc" id="form_bank_ifsc" placeholder="e.g. SBIN0001234">
</div>
</div>
</div>
<div class="modal-footer py-2 px-3 bg-light">
<button type="button" class="btn btn-secondary btn-sm" data-bs-dismiss="modal">Cancel</button>
<button type="submit" class="btn btn-primary btn-sm" id="btn_save_store">
<i class="bi bi-check-circle me-1"></i> Save Medical Store
</button>
</div>
</form>
</div>
</div>
</div>
<!-- Modal 2: Terminal Verification & Computer Security Keys -->
<div class="modal fade" id="terminalSecurityModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content border-0 shadow">
<div class="modal-header bg-dark text-white py-2.5">
<h6 class="modal-title fw-bold"><i class="bi bi-shield-lock me-1 text-warning"></i> Computer Machine Verification</h6>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body p-3">
<div class="alert alert-info py-2 mb-3" style="font-size: 12px;">
<i class="bi bi-info-circle me-1"></i> When opening the store link on a counter PC for the first time, staff must enter either the permanent <strong>Security Key</strong> or an <strong>Activation OTP</strong>.
</div>
<div class="mb-3">
<label class="form-label small fw-bold text-muted mb-1">Permanent Machine Security Key</label>
<div class="input-group">
<input type="text" class="form-control fw-bold text-primary font-monospace" id="modal_sec_key" readonly>
<button class="btn btn-outline-secondary" type="button" onclick="copyText(document.getElementById('modal_sec_key').value)">
<i class="bi bi-copy"></i>
</button>
<button class="btn btn-outline-warning" type="button" id="btn_regen_key" title="Regenerate Security Key">
<i class="bi bi-arrow-repeat"></i>
</button>
</div>
</div>
<div class="mb-3 p-3 bg-light rounded-3 border">
<div class="d-flex justify-content-between align-items-center mb-2">
<label class="form-label small fw-bold mb-0">6-Digit Counter Activation OTP</label>
<button type="button" class="btn btn-primary btn-sm py-0.5 px-2" id="btn_generate_otp">
<i class="bi bi-key-fill me-1"></i> Generate New OTP
</button>
</div>
<div id="otp_display_box" style="display: none;">
<div class="display-6 fw-bold text-success text-center letter-spacing-2" id="otp_code_val" style="letter-spacing: 6px;">
------
</div>
<small class="text-muted d-block text-center mt-1" id="otp_expiry_val"></small>
</div>
<small class="text-muted d-block" id="otp_idle_text">
Click "Generate New OTP" to generate an emergency 60-minute activation PIN for this store.
</small>
</div>
</div>
<div class="modal-footer py-2 bg-light">
<button type="button" class="btn btn-secondary btn-sm" data-bs-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
<!-- Modal 3: Marg ERP / Excel Inventory Import -->
<div class="modal fade" id="margImportModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content border-0 shadow">
<div class="modal-header bg-success text-white py-2.5">
<h6 class="modal-title fw-bold"><i class="bi bi-file-earmark-arrow-up me-1"></i> Marg ERP / Excel Inventory Import</h6>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<form id="margImportForm" enctype="multipart/form-data">
<div class="modal-body p-3">
<p class="small text-muted mb-3">
Quickly migrate stock from <strong>Marg Pharmacy Software</strong> or Microsoft Excel into the store's shared master catalog and batch stock.
</p>
<div class="mb-3">
<label class="form-label small fw-bold mb-1">Target Medical Store Counter <span class="text-danger">*</span></label>
<select class="form-select form-select-sm" name="store_id" id="marg_store_id" required>
<?php foreach ($stores as $st): ?>
<option value="<?= (int)$st['store_id'] ?>"><?= esc($st['store_name']) ?> (<?= esc($st['building_name'] ?: 'Building') ?>)</option>
<?php endforeach; ?>
</select>
</div>
<div class="mb-3">
<label class="form-label small fw-bold mb-1">Upload CSV / Excel Export <span class="text-danger">*</span></label>
<input type="file" class="form-control form-control-sm" name="import_file" id="marg_file" accept=".csv, .txt" required>
<small class="text-muted" style="font-size: 11px;">
Supported columns: Item Name, Generic, Batch No, Expiry (MM/YY), MRP, PTR/Cost, HSN, GST%, Opening Qty.
</small>
</div>
<div class="alert alert-light border py-2 mb-0" style="font-size: 11px;">
<i class="bi bi-download text-primary me-1"></i>
<a href="javascript:downloadSampleMargCsv()" class="text-decoration-none fw-bold">Download Sample Marg CSV Template</a>
</div>
</div>
<div class="modal-footer py-2 bg-light">
<button type="button" class="btn btn-secondary btn-sm" data-bs-dismiss="modal">Cancel</button>
<button type="submit" class="btn btn-success btn-sm" id="btn_submit_marg">
<i class="bi bi-upload me-1"></i> Start Import
</button>
</div>
</form>
</div>
</div>
</div>
<script>
var activeStoreObj = null;
function copyText(text) {
navigator.clipboard.writeText(text).then(function() {
alert('Copied to clipboard: ' + text);
});
}
function openTerminalSecurityModal(store) {
activeStoreObj = store;
document.getElementById('modal_sec_key').value = store.security_key || '';
document.getElementById('otp_display_box').style.display = 'none';
document.getElementById('otp_idle_text').style.display = 'block';
var modal = new bootstrap.Modal(document.getElementById('terminalSecurityModal'));
modal.show();
}
function editStoreModal(store) {
document.getElementById('storeModalTitle').innerHTML = '<i class="bi bi-pencil-square me-1"></i> Edit Medical Store: ' + store.store_name;
document.getElementById('form_store_id').value = store.store_id;
document.getElementById('form_store_code').value = store.store_code || '';
document.getElementById('form_store_slug').value = store.store_slug || '';
document.getElementById('form_store_name').value = store.store_name || '';
document.getElementById('form_building_name').value = store.building_name || '';
document.getElementById('form_floor_no').value = store.floor_no || '';
document.getElementById('form_room_no').value = store.room_no || '';
document.getElementById('form_is_main_store').value = store.is_main_store || 0;
document.getElementById('form_dl_20b').value = store.drug_license_no_20b || '';
document.getElementById('form_dl_21b').value = store.drug_license_no_21b || '';
document.getElementById('form_dl_20f').value = store.drug_license_no_20f_x || '';
document.getElementById('form_gstin').value = store.gstin || '';
document.getElementById('form_pan').value = store.pan_no || '';
document.getElementById('form_fssai').value = store.fssai_no || '';
document.getElementById('form_state_code').value = store.state_code || '07';
document.getElementById('form_state_name').value = store.state_name || 'Delhi';
document.getElementById('form_pharmacist_name').value = store.registered_pharmacist_name || '';
document.getElementById('form_pharmacist_reg_no').value = store.pharmacist_reg_no || '';
document.getElementById('form_invoice_prefix').value = store.invoice_prefix || 'INV/';
document.getElementById('form_next_invoice_no').value = store.next_invoice_no || 1001;
document.getElementById('form_abdm_hfr_id').value = store.abdm_hfr_id || '';
document.getElementById('form_abdm_hip_id').value = store.abdm_hip_id || '';
document.getElementById('form_pharmacist_hpr_id').value = store.pharmacist_hpr_id || '';
document.getElementById('form_upi_id').value = store.upi_id || '';
document.getElementById('form_terms').value = store.terms_conditions || '';
document.getElementById('form_contact_phone').value = store.contact_phone || '';
document.getElementById('form_contact_email').value = store.contact_email || '';
document.getElementById('form_address').value = store.address || '';
document.getElementById('form_bank_name').value = store.bank_name || '';
document.getElementById('form_bank_account_no').value = store.bank_account_no || '';
document.getElementById('form_bank_ifsc').value = store.bank_ifsc || '';
var modal = new bootstrap.Modal(document.getElementById('storeModal'));
modal.show();
}
document.getElementById('btn_add_store').addEventListener('click', function() {
document.getElementById('storeForm').reset();
document.getElementById('storeModalTitle').innerHTML = '<i class="bi bi-capsule me-1"></i> Add New Medical Store Counter';
document.getElementById('form_store_id').value = '0';
document.getElementById('form_store_code').value = 'ST-' + Math.floor(100 + Math.random() * 900);
document.getElementById('form_store_slug').value = 'counter' + Math.floor(1 + Math.random() * 9);
var modal = new bootstrap.Modal(document.getElementById('storeModal'));
modal.show();
});
document.getElementById('btn_open_marg_import').addEventListener('click', function() {
var modal = new bootstrap.Modal(document.getElementById('margImportModal'));
modal.show();
});
// Save Store Form
$('#storeForm').on('submit', function(e) {
e.preventDefault();
var formData = $(this).serialize();
$.ajax({
url: '<?= base_url('setting/admin/medical-store/save') ?>',
method: 'POST',
data: formData,
dataType: 'json',
success: function(res) {
if (res.ok) {
alert(res.message);
location.reload();
} else {
alert(res.error || 'Failed to save store');
}
},
error: function(err) {
alert('Request error: ' + err.statusText);
}
});
});
// Generate OTP
$('#btn_generate_otp').on('click', function() {
if (!activeStoreObj) return;
$.ajax({
url: '<?= base_url('setting/admin/medical-store/generate-otp') ?>',
method: 'POST',
data: { store_id: activeStoreObj.store_id },
dataType: 'json',
success: function(res) {
if (res.ok) {
document.getElementById('otp_code_val').innerText = res.otp;
document.getElementById('otp_expiry_val').innerText = 'Valid until ' + res.expiry;
document.getElementById('otp_display_box').style.display = 'block';
document.getElementById('otp_idle_text').style.display = 'none';
} else {
alert(res.error || 'Failed to generate OTP');
}
}
});
});
// Regenerate Security Key
$('#btn_regen_key').on('click', function() {
if (!activeStoreObj || !confirm('Are you sure you want to regenerate the Security Key?')) return;
$.ajax({
url: '<?= base_url('setting/admin/medical-store/regenerate-key') ?>',
method: 'POST',
data: { store_id: activeStoreObj.store_id },
dataType: 'json',
success: function(res) {
if (res.ok) {
document.getElementById('modal_sec_key').value = res.security_key;
alert('Security key updated! Please share this with counter staff.');
}
}
});
});
// Revoke Terminal Access
function revokeTerminal(deviceId) {
if (!confirm('Are you sure you want to revoke counter access for this computer?')) return;
$.ajax({
url: '<?= base_url('setting/admin/medical-store/revoke-device') ?>',
method: 'POST',
data: { device_id: deviceId },
dataType: 'json',
success: function(res) {
if (res.ok) {
alert(res.message);
location.reload();
}
}
});
}
function deleteStore(storeId, storeName) {
if (!confirm('Are you sure you want to deactivate store: ' + storeName + '?')) return;
$.ajax({
url: '<?= base_url('setting/admin/medical-store/delete') ?>',
method: 'POST',
data: { store_id: storeId },
dataType: 'json',
success: function(res) {
if (res.ok) {
alert(res.message);
location.reload();
}
}
});
}
// Marg CSV Import Submit
$('#margImportForm').on('submit', function(e) {
e.preventDefault();
var formData = new FormData(this);
var btn = $('#btn_submit_marg');
btn.prop('disabled', true).text('Importing records...');
$.ajax({
url: '<?= base_url('setting/admin/medical-store/import-marg') ?>',
method: 'POST',
data: formData,
contentType: false,
processData: false,
dataType: 'json',
success: function(res) {
btn.prop('disabled', false).html('<i class="bi bi-upload me-1"></i> Start Import');
if (res.ok) {
alert(res.message);
location.reload();
} else {
alert(res.error || 'Import failed');
}
},
error: function(xhr) {
btn.prop('disabled', false).html('<i class="bi bi-upload me-1"></i> Start Import');
alert('Import error occurred');
}
});
});
function downloadSampleMargCsv() {
var csvContent = "Item Name,Generic Name,Category,Packing,Batch No,Expiry Date,MRP,PTR,HSN Code,GST Rate,Opening Qty,Barcode\n" +
"Augmentin 625 Duo Tablet,Amoxycillin + Clavulanic Acid,Tablet,10 Tab,AUG-991,10/27,204.50,155.00,3004,12,100,890103000001\n" +
"Pan 40 Tablet,Pantoprazole 40mg,Tablet,15 Tab,PAN-442,04/27,162.00,115.00,3004,12,150,890103000002\n" +
"Dolo 650 Tablet,Paracetamol 650mg,Tablet,15 Tab,DOL-112,08/27,34.00,22.50,3004,12,200,890103000003\n" +
"Azithral 500 Tablet,Azithromycin 500mg,Tablet,5 Tab,AZI-771,12/26,125.00,89.00,3004,12,80,890103000004\n";
var blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
var link = document.createElement("a");
var url = URL.createObjectURL(blob);
link.setAttribute("href", url);
link.setAttribute("download", "Marg_Pharmacy_Sample_Import.csv");
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}
// Search and Refresh
$('#btn_store_search').on('click', function() {
var q = $('#store_search_input').val();
window.location.href = '<?= base_url('setting/admin/medical-store') ?>?q=' + encodeURIComponent(q);
});
$('#btn_refresh_store_list').on('click', function() {
window.location.href = '<?= base_url('setting/admin/medical-store') ?>';
});
</script>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,17 @@
{
"short_name": "Medical Store",
"name": "HMS Medical Store & Multi-Building Pharmacy POS",
"icons": [
{
"src": "/assets/img/logo.png",
"type": "image/png",
"sizes": "512x512",
"purpose": "any maskable"
}
],
"start_url": "/app/medical-store",
"background_color": "#0f172a",
"theme_color": "#0891b2",
"display": "standalone",
"orientation": "any"
}

View File

@ -0,0 +1,21 @@
const CACHE_NAME = 'medstore-pwa-v1';
const ASSETS_TO_CACHE = [
'/app/medical-store',
'https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css',
'https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.1/font/bootstrap-icons.css'
];
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open(CACHE_NAME).then((cache) => {
return cache.addAll(ASSETS_TO_CACHE);
})
);
});
self.addEventListener('fetch', (event) => {
if (event.request.method !== 'GET') return;
event.respondWith(
fetch(event.request).catch(() => caches.match(event.request))
);
});

View File

@ -176,24 +176,24 @@
</div>
</a>
<!-- Pharmacy App (Coming Soon) -->
<div class="app-option-card disabled">
<!-- Medical Store & Pharmacy App (Active PWA) -->
<a href="/app/medical-store" class="app-option-card">
<div class="d-flex align-items-center gap-3">
<div class="app-icon-circle bg-secondary-subtle text-secondary">
<div class="app-icon-circle bg-info-subtle text-info">
<i class="bi bi-capsule"></i>
</div>
<div class="flex-grow-1">
<div class="d-flex align-items-center justify-content-between">
<h6 class="fw-bold mb-0 text-secondary">Pharmacy App</h6>
<span class="badge bg-secondary" style="font-size: 10px;">Coming Soon</span>
<h6 class="fw-bold mb-0 text-dark">Medical Store &amp; Pharmacy POS</h6>
<span class="badge bg-info text-dark" style="font-size: 10px;">Active PWA</span>
</div>
<small class="text-muted d-block mt-1" style="font-size: 12px;">
Counter Sales, Medicine Dispense &amp; Stock Check
Multi-Building Counters, DL 20B/21B, GST, UHID/IPD/OPD &amp; Ledgers
</small>
</div>
<i class="bi bi-lock-fill text-muted"></i>
</div>
<i class="bi bi-chevron-right text-muted"></i>
</div>
</a>
</div>
<!-- Mobile Install Banner -->

View File

@ -0,0 +1,23 @@
<?php
$db = new mysqli('localhost', 'root', '', 'hms_data_ci4');
if ($db->connect_error) {
die("Connection failed: " . $db->connect_error . "\n");
}
$res = $db->query("SHOW COLUMNS FROM mst_sales LIKE 'bill_discount_type'");
if ($res->num_rows == 0) {
$db->query("ALTER TABLE mst_sales ADD COLUMN bill_discount_type varchar(10) DEFAULT 'pct' AFTER discount_amount");
$db->query("ALTER TABLE mst_sales ADD COLUMN bill_discount_val decimal(10,2) DEFAULT 0.00 AFTER bill_discount_type");
echo "Columns bill_discount_type and bill_discount_val added to mst_sales.\n";
} else {
echo "Columns already exist in mst_sales.\n";
}
$resItem = $db->query("SHOW COLUMNS FROM mst_sales_items LIKE 'discount_type'");
if ($resItem->num_rows == 0) {
$db->query("ALTER TABLE mst_sales_items ADD COLUMN discount_type varchar(10) DEFAULT 'pct' AFTER unit_mrp");
$db->query("ALTER TABLE mst_sales_items ADD COLUMN discount_val decimal(10,2) DEFAULT 0.00 AFTER discount_type");
echo "Columns discount_type and discount_val added to mst_sales_items.\n";
} else {
echo "Columns already exist in mst_sales_items.\n";
}

View File

@ -0,0 +1,43 @@
<?php
$db = new mysqli('localhost', 'root', '', 'hms_data_ci4');
if ($db->connect_error) {
die("Connection failed: " . $db->connect_error . "\n");
}
// Check and add columns to mst_sales
$salesCols = [
'bank_name' => "ALTER TABLE mst_sales ADD COLUMN bank_name varchar(100) DEFAULT '' AFTER payment_reference",
'upi_ref_no' => "ALTER TABLE mst_sales ADD COLUMN upi_ref_no varchar(100) DEFAULT '' AFTER bank_name",
'card_ref_no' => "ALTER TABLE mst_sales ADD COLUMN card_ref_no varchar(100) DEFAULT '' AFTER upi_ref_no",
'is_bank_reconciled' => "ALTER TABLE mst_sales ADD COLUMN is_bank_reconciled tinyint(1) DEFAULT 0 AFTER card_ref_no",
'reconciled_at' => "ALTER TABLE mst_sales ADD COLUMN reconciled_at datetime NULL AFTER is_bank_reconciled",
'reconciled_by' => "ALTER TABLE mst_sales ADD COLUMN reconciled_by varchar(100) DEFAULT '' AFTER reconciled_at",
'reconciled_notes' => "ALTER TABLE mst_sales ADD COLUMN reconciled_notes text NULL AFTER reconciled_by",
];
foreach ($salesCols as $col => $sql) {
$res = $db->query("SHOW COLUMNS FROM mst_sales LIKE '$col'");
if ($res->num_rows == 0) {
$db->query($sql);
echo "Added column $col to mst_sales.\n";
} else {
echo "Column $col already exists in mst_sales.\n";
}
}
// Check and add columns to mst_ledger_entries
$ledgerCols = [
'reconciled_flag' => "ALTER TABLE mst_ledger_entries ADD COLUMN reconciled_flag tinyint(1) DEFAULT 0 AFTER narration",
'reconciled_at' => "ALTER TABLE mst_ledger_entries ADD COLUMN reconciled_at datetime NULL AFTER reconciled_flag",
'reconciled_ref' => "ALTER TABLE mst_ledger_entries ADD COLUMN reconciled_ref varchar(100) DEFAULT '' AFTER reconciled_at",
];
foreach ($ledgerCols as $col => $sql) {
$res = $db->query("SHOW COLUMNS FROM mst_ledger_entries LIKE '$col'");
if ($res->num_rows == 0) {
$db->query($sql);
echo "Added column $col to mst_ledger_entries.\n";
} else {
echo "Column $col already exists in mst_ledger_entries.\n";
}
}

51
scratch/analyze_diffs.php Normal file
View File

@ -0,0 +1,51 @@
<?php
$our_json = json_decode(file_get_contents('d:/Workplace/HMS_CI4_OLD/writable/ipd9_bundle.json'), true);
$f1 = json_decode(file_get_contents('d:/Workplace/HMS_CI4_OLD/scratch/fhir_1.json'), true);
echo "=== FHIR_1 vs our_FHIR Analysis ===\n";
echo "\n--- FHIR_1 Sections in Composition ---\n";
foreach ($f1['entry'][0]['resource']['section'] as $s) {
echo "Title: " . $s['title'] . " | Code: " . $s['code']['coding'][0]['code'] . " | Entries: " . count($s['entry']) . "\n";
}
echo "\n--- our_FHIR Sections in Composition ---\n";
foreach ($our_json['entry'][0]['resource']['section'] as $s) {
echo "Title: " . $s['title'] . " | Code: " . $s['code']['coding'][0]['code'] . " | Entries: " . count($s['entry']) . "\n";
}
// Let's check differences in:
// 1. Encounter resource
echo "\n--- FHIR_1 Encounter ---\n";
print_r($f1['entry'][4]['resource']);
echo "\n--- our_FHIR Encounter ---\n";
foreach ($our_json['entry'] as $e) {
if ($e['resource']['resourceType'] === 'Encounter') {
print_r($e['resource']);
}
}
// Let's check differences in:
// 2. Patient resource
echo "\n--- FHIR_1 Patient identifiers ---\n";
print_r($f1['entry'][1]['resource']['identifier']);
echo "\n--- our_FHIR Patient identifiers ---\n";
print_r($our_json['entry'][1]['resource']['identifier']);
// Let's check differences in:
// 3. Organization resource
echo "\n--- FHIR_1 Organization identifiers ---\n";
print_r($f1['entry'][3]['resource']['identifier']);
echo "\n--- our_FHIR Organization identifiers ---\n";
print_r($our_json['entry'][3]['resource']['identifier']);
// Let's check differences in:
// 4. Practitioner resource
echo "\n--- FHIR_1 Practitioner identifiers ---\n";
print_r($f1['entry'][2]['resource']['identifier']);
echo "\n--- our_FHIR Practitioner identifiers ---\n";
print_r($our_json['entry'][2]['resource']['identifier']);

4
scratch/babel.min.js vendored Normal file

File diff suppressed because one or more lines are too long

912
scratch/bundle.js Normal file

File diff suppressed because one or more lines are too long

1483
scratch/bundle_with_pdf.json Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,23 @@
<?php
$dir = 'ABDM_FHIR/examples.json/';
$files = glob($dir . 'Bundle-*.json');
foreach ($files as $file) {
$b = json_decode(file_get_contents($file), true);
if (!isset($b['entry'][0]['resource']['resourceType'])) continue;
$comp = $b['entry'][0]['resource'];
echo basename($file) . ":\n";
echo " title: " . ($comp['title'] ?? 'NONE') . "\n";
echo " type: " . ($comp['type']['text'] ?? $comp['type']['coding'][0]['display'] ?? 'NONE') . "\n";
if (isset($comp['category'])) {
echo " category: " . json_encode($comp['category']) . "\n";
}
if (isset($comp['encounter'])) {
echo " encounter: " . json_encode($comp['encounter']) . "\n";
}
if (isset($comp['custodian'])) {
echo " custodian: " . json_encode($comp['custodian']) . "\n";
}
if (isset($comp['author'])) {
echo " author: " . json_encode($comp['author']) . "\n";
}
}

26
scratch/check_all_dbs.php Normal file
View File

@ -0,0 +1,26 @@
<?php
$conn = new mysqli('localhost', 'root', '');
$res = $conn->query("SHOW DATABASES");
echo "DATABASES ON LOCALHOST:\n";
while ($row = $res->fetch_assoc()) {
$dbName = $row['Database'];
echo "- " . $dbName;
// Check if dbName has ipd_master
$dbConn = @new mysqli('localhost', 'root', '', $dbName);
if (!$dbConn->connect_error) {
$check = $dbConn->query("SHOW TABLES LIKE 'ipd_master'");
if ($check && $check->num_rows > 0) {
$cnt = $dbConn->query("SELECT count(*) as c FROM ipd_master")->fetch_assoc()['c'];
echo " (has ipd_master with $cnt rows)";
$rec009 = $dbConn->query("SELECT id, ipd_code, p_id, r_doc_id, r_doc_name FROM ipd_master WHERE ipd_code LIKE '%0009%' OR ipd_code LIKE '%009%'");
if ($rec009 && $rec009->num_rows > 0) {
echo " *** FOUND 0009! ***\n";
while ($r = $rec009->fetch_assoc()) {
print_r($r);
}
}
}
}
echo "\n";
}

View File

@ -0,0 +1,17 @@
<?php
$files = glob(__DIR__ . '/../ABDM_FHIR/examples.json/Bundle-*.json');
foreach ($files as $f) {
$json = json_decode(file_get_contents($f), true);
if (!isset($json['entry'])) continue;
foreach ($json['entry'] as $e) {
$res = $e['resource'] ?? [];
if (($res['resourceType'] ?? '') === 'Organization') {
echo basename($f) . ":\n";
echo " name: " . ($res['name'] ?? '') . "\n";
echo " telecom: " . json_encode($res['telecom'] ?? '') . "\n";
echo " address: " . json_encode($res['address'] ?? '') . "\n";
echo " type: " . json_encode($res['type'] ?? '') . "\n";
break;
}
}
}

View File

@ -0,0 +1,18 @@
<?php
$host = '127.0.0.1';
$user = 'root';
$pass = '';
$dbname = 'hms_data_ci4';
$pdo = new PDO("mysql:host=$host;dbname=$dbname;charset=utf8mb4", $user, $pass);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$tables = ['ipd_discharge_prescrption_prescribed', 'ipd_discharge_drug', 'ipd_discharge', 'ipd_discharge_instructions'];
foreach ($tables as $t) {
if ($pdo->query("SHOW TABLES LIKE '$t'")->fetch()) {
$stmt = $pdo->query("SELECT * FROM $t WHERE ipd_id = 9");
echo "=== Table $t ===\n";
print_r($stmt->fetchAll(PDO::FETCH_ASSOC));
}
}

View File

@ -0,0 +1,8 @@
<?php
$conn = new mysqli('localhost', 'root', '', 'hms_ci4_2026');
$res = $conn->query("SELECT id, p_title, p_fname, p_mname, p_lname, email1, mphone1 FROM doctor_master");
echo "DOCTORS IN doctor_master:\n";
while ($d = $res->fetch_assoc()) {
print_r($d);
}

View File

@ -0,0 +1,7 @@
<?php
$def = json_decode(file_get_contents('ABDM_FHIR/definitions.json/StructureDefinition-CarePlan.json'), true);
foreach ($def['snapshot']['element'] as $el) {
if (strpos($el['id'], 'CarePlan.activity') === 0 || strpos($el['id'], 'CarePlan.category') === 0) {
echo $el['id'] . " [min: " . $el['min'] . ", max: " . $el['max'] . "]\n";
}
}

14
scratch/check_ci4_db.php Normal file
View File

@ -0,0 +1,14 @@
<?php
require __DIR__ . '/../vendor/codeigniter4/framework/system/Test/bootstrap.php';
$gateway = new \App\Controllers\AbdmGateway();
$req = \Config\Services::request();
$resp = \Config\Services::response();
$logger = \Config\Services::logger();
$gateway->initController($req, $resp, $logger);
$db = \Config\Database::connect();
echo "DB: " . $db->getDatabase() . "\n";
$row = $db->table('patient_master')->where('id', 11)->get(1)->getRowArray();
echo "Patient 11: " . json_encode($row) . "\n";

View File

@ -0,0 +1,13 @@
<?php
$sd = json_decode(file_get_contents('ABDM_FHIR/package/package/StructureDefinition-ImmunizationRecord.json'), true);
foreach ($sd['snapshot']['element'] as $el) {
if (strpos($el['id'], 'Composition.category') === 0 || strpos($el['id'], 'Composition.encounter') === 0) {
echo $el['id'] . ":\n";
if (isset($el['binding'])) {
echo " Binding: " . json_encode($el['binding']) . "\n";
}
if (isset($el['type'])) {
echo " Type: " . json_encode($el['type']) . "\n";
}
}
}

View File

@ -0,0 +1,13 @@
<?php
$sd = json_decode(file_get_contents(__DIR__ . '/../ABDM_FHIR/examples.json/StructureDefinition-ImmunizationRecord.json'), true);
foreach ($sd['snapshot']['element'] as $el) {
if (strpos($el['id'], 'Composition.category') === 0) {
echo $el['id'] . "\n";
if (isset($el['binding'])) {
echo " binding: " . json_encode($el['binding']) . "\n";
}
if (isset($el['definition'])) {
echo " def: " . $el['definition'] . "\n";
}
}
}

View File

@ -0,0 +1,7 @@
<?php
$lines = file(__DIR__ . '/../app/Libraries/FhirR4Builder.php');
foreach ($lines as $i => $l) {
if (strpos($l, "'encounter'") !== false && strpos($l, '=>') !== false) {
echo ($i + 1) . ': ' . trim($l) . PHP_EOL;
}
}

View File

@ -0,0 +1,7 @@
<?php
$def = json_decode(file_get_contents('ABDM_FHIR/definitions.json/StructureDefinition-DischargeSummaryRecord.json'), true);
foreach ($def['snapshot']['element'] as $el) {
if (strpos($el['id'], 'Composition.section:') === 0) {
echo $el['id'] . " (min=" . $el['min'] . ", max=" . $el['max'] . ")\n";
}
}

View File

@ -0,0 +1,10 @@
<?php
$def = json_decode(file_get_contents('ABDM_FHIR/definitions.json/StructureDefinition-Condition.json'), true);
foreach ($def['snapshot']['element'] as $el) {
if (strpos($el['id'], 'Condition.category') === 0) {
echo $el['id'] . "\n";
if (isset($el['binding'])) {
echo " Binding: " . json_encode($el['binding']) . "\n";
}
}
}

View File

@ -0,0 +1,7 @@
<?php
$sd = json_decode(file_get_contents('ABDM_FHIR/definitions.json/StructureDefinition-Condition.json'), true);
foreach ($sd['snapshot']['element'] as $el) {
if (str_starts_with($el['id'], 'Condition.recordedDate') || str_starts_with($el['id'], 'Condition.onset')) {
echo "{$el['id']} -> type: " . json_encode($el['type']) . ", min: {$el['min']}, max: {$el['max']}\n";
}
}

View File

@ -0,0 +1,7 @@
<?php
$def = json_decode(file_get_contents('ABDM_FHIR/definitions.json/StructureDefinition-Condition.json'), true);
foreach ($def['snapshot']['element'] as $el) {
if ($el['min'] > 0) {
echo $el['id'] . " min=" . $el['min'] . "\n";
}
}

7
scratch/check_cp_req.php Normal file
View File

@ -0,0 +1,7 @@
<?php
$def = json_decode(file_get_contents('ABDM_FHIR/definitions.json/StructureDefinition-CarePlan.json'), true);
foreach ($def['snapshot']['element'] as $el) {
if ($el['min'] > 0) {
echo $el['id'] . " min=" . $el['min'] . "\n";
}
}

View File

@ -0,0 +1,21 @@
<?php
$db = \Config\Database::connect();
if ($db->tableExists('health_records')) {
$rows = $db->table('health_records')
->where('entity_type', 'ipd')
->orderBy('id', 'DESC')
->limit(5)
->get()
->getResultArray();
echo "Found " . count($rows) . " health_records for IPD:\n";
foreach ($rows as $r) {
echo "ID: " . $r['id'] . " | Entity ID: " . $r['entity_id'] . " | Care Context: " . ($r['care_context_reference'] ?? '') . " | Status: " . ($r['push_status'] ?? '') . " | Updated: " . ($r['updated_at'] ?? '') . "\n";
$data = $r['record_data'] ?? '';
if ($data !== '') {
$bundle = json_decode($data, true);
echo " Bundle ID in DB: " . ($bundle['id'] ?? 'unknown') . " | Entry count: " . count($bundle['entry'] ?? []) . "\n";
}
}
} else {
echo "health_records table does not exist.\n";
}

View File

@ -0,0 +1,9 @@
<?php
$mysqli = new mysqli('localhost', 'root', '', 'hms_data_ci4', 3306);
if ($mysqli->connect_error) {
die('Connect Error (' . $mysqli->connect_errno . ') ' . $mysqli->connect_error);
}
$res = $mysqli->query("SELECT s_name, s_value FROM hospital_setting");
while ($row = $res->fetch_assoc()) {
echo $row['s_name'] . ' = ' . $row['s_value'] . PHP_EOL;
}

View File

@ -0,0 +1,12 @@
<?php
$data = json_decode(file_get_contents('ABDM_FHIR/examples.json/Bundle-DischargeSummary-example-04.json'), true);
foreach ($data['entry'] as $i => $e) {
$r = $e['resource'];
echo "Entry $i: " . $r['resourceType'] . "\n";
if ($r['resourceType'] === 'Composition') {
echo " title: " . ($r['title'] ?? '') . "\n";
echo " custodian: " . json_encode($r['custodian'] ?? '') . "\n";
echo " author: " . json_encode($r['author'] ?? '') . "\n";
echo " encounter: " . json_encode($r['encounter'] ?? '') . "\n";
}
}

View File

@ -0,0 +1,11 @@
<?php
$dir = 'd:/Workplace/HMS_CI4_OLD/ABDM_FHIR/definitions.json/';
$files = glob($dir . '*diagnosis-use*.json');
foreach ($files as $f) {
echo "Found: " . basename($f) . "\n";
$data = json_decode(file_get_contents($f), true);
if (isset($data['compose']['include'])) {
echo json_encode($data['compose']['include'], JSON_PRETTY_PRINT) . "\n";
}
}

View File

@ -0,0 +1,21 @@
<?php
require __DIR__ . '/../vendor/autoload.php';
// Bootstrap CodeIgniter
$_SERVER['CI_ENVIRONMENT'] = 'development';
$app = require __DIR__ . '/../app/Config/Boot/production.php';
// Let's test generating the discharge summary PDF for IPD 9
$controller = new \App\Controllers\Ipd_discharge();
$request = \Config\Services::request();
$response = \Config\Services::response();
$logger = \Config\Services::logger();
$controller->initController($request, $response, $logger);
$pdfPath = WRITEPATH . 'uploads/abdm/ipd/9/discharge-summary.pdf';
echo "Checking PDF at: $pdfPath\n";
if (file_exists($pdfPath)) {
echo "Existing PDF size: " . filesize($pdfPath) . " bytes\n";
} else {
echo "PDF does not exist yet on disk.\n";
}

View File

@ -0,0 +1,11 @@
<?php
$db = new mysqli('localhost', 'root', '', 'hms_data_ci4');
if ($db->connect_error) {
die("Connection failed: " . $db->connect_error . "\n");
}
echo "=== mst_ledger_entries ===\n";
$res = $db->query("DESCRIBE mst_ledger_entries");
while ($row = $res->fetch_assoc()) {
echo " {$row['Field']} - {$row['Type']}\n";
}

View File

@ -0,0 +1,7 @@
<?php
$sd = json_decode(file_get_contents('ABDM_FHIR/definitions.json/StructureDefinition-DocumentReference.json'), true);
foreach ($sd['snapshot']['element'] as $el) {
if ($el['id'] == 'DocumentReference.description') {
echo "DocumentReference.description: min={$el['min']}, max={$el['max']}\n";
}
}

View File

@ -0,0 +1,7 @@
<?php
$def = json_decode(file_get_contents('ABDM_FHIR/definitions.json/StructureDefinition-DocumentReference.json'), true);
foreach ($def['snapshot']['element'] as $el) {
if ($el['min'] > 0) {
echo $el['id'] . " min=" . $el['min'] . "\n";
}
}

View File

@ -0,0 +1,6 @@
<?php
$mysqli = new mysqli('localhost', 'root', '', 'hms_data_ci4', 3306);
$res = $mysqli->query("SELECT * FROM doctor_master LIMIT 5");
while ($row = $res->fetch_assoc()) {
print_r($row);
}

View File

@ -0,0 +1,13 @@
<?php
$sd = json_decode(file_get_contents(__DIR__ . '/../ABDM_FHIR/examples.json/StructureDefinition-Encounter.json'), true);
foreach ($sd['snapshot']['element'] as $el) {
if (strpos($el['id'], 'Encounter.serviceType') === 0 || strpos($el['id'], 'Encounter.type') === 0) {
echo $el['id'] . "\n";
if (isset($el['binding'])) {
echo " binding: " . json_encode($el['binding']) . "\n";
}
if (isset($el['definition'])) {
echo " def: " . $el['definition'] . "\n";
}
}
}

View File

@ -0,0 +1,18 @@
<?php
$sd = json_decode(file_get_contents('ABDM_FHIR/definitions.json/StructureDefinition-Encounter.json'), true);
echo "=== ENCOUNTER ELEMENTS IN NRCES ===\n";
foreach ($sd['snapshot']['element'] as $el) {
if (str_starts_with($el['id'], 'Encounter.hospitalization') || str_starts_with($el['id'], 'Encounter.diagnosis') || str_starts_with($el['id'], 'Encounter.reason')) {
echo "{$el['id']}\n";
if (isset($el['binding'])) {
echo " binding: " . json_encode($el['binding']) . "\n";
}
if (isset($el['type'])) {
foreach ($el['type'] as $t) {
echo " type: " . $t['code'] . (isset($t['targetProfile']) ? (' -> ' . implode(',', $t['targetProfile'])) : '') . "\n";
}
}
}
}

9
scratch/check_enc_sd.php Normal file
View File

@ -0,0 +1,9 @@
<?php
$sd = json_decode(file_get_contents(__DIR__ . '/../ABDM_FHIR/examples.json/StructureDefinition-Encounter.json'), true);
echo "=== Encounter elements ===\n";
foreach ($sd['snapshot']['element'] as $el) {
if (substr_count($el['id'], '.') === 1) {
$ms = !empty($el['mustSupport']) ? ' [MustSupport]' : '';
echo $el['id'] . $ms . "\n";
}
}

View File

@ -0,0 +1,7 @@
<?php
$def = json_decode(file_get_contents('ABDM_FHIR/definitions.json/StructureDefinition-Encounter.json'), true);
foreach ($def['snapshot']['element'] as $el) {
if ($el['min'] > 0) {
echo $el['id'] . " min=" . $el['min'] . "\n";
}
}

View File

@ -0,0 +1,10 @@
<?php
foreach (['Bundle-OPConsultNote-example-05.json', 'Bundle-DischargeSummary-example-04.json'] as $file) {
$json = json_decode(file_get_contents(__DIR__ . '/../ABDM_FHIR/examples.json/' . $file), true);
echo "=== $file Encounter ===\n";
foreach ($json['entry'] as $e) {
if ($e['resource']['resourceType'] === 'Encounter') {
echo json_encode($e['resource'], JSON_PRETTY_PRINT) . "\n";
}
}
}

View File

@ -0,0 +1,19 @@
<?php
$mysqli = new mysqli('localhost', 'root', '', 'hms_data_ci4');
if ($mysqli->connect_error) {
die("Connection failed: " . $mysqli->connect_error);
}
$res = $mysqli->query("SHOW COLUMNS FROM file_upload_data");
$cols = [];
while ($row = $res->fetch_assoc()) {
$cols[] = $row['Field'] . ' (' . $row['Type'] . ')';
}
echo "FILE_UPLOAD_DATA COLUMNS:\n";
print_r($cols);
$res = $mysqli->query("SELECT * FROM file_upload_data ORDER BY id DESC LIMIT 5");
echo "\nLAST 5 ROWS IN FILE_UPLOAD_DATA:\n";
while ($row = $res->fetch_assoc()) {
print_r($row);
}

View File

@ -0,0 +1,27 @@
<?php
$lines = file('C:/Users/d_s_b/.gemini/antigravity-ide/brain/5d74b30a-f6db-45f5-988c-4e3f71d58774/.system_generated/logs/transcript_full.jsonl');
$d = json_decode($lines[563], true);
$content = $d['content'];
$start = strpos($content, '{');
$end = strrpos($content, '}');
if ($start !== false && $end !== false) {
$jsonStr = substr($content, $start, $end - $start + 1);
$bundle = json_decode($jsonStr, true);
if ($bundle && isset($bundle['entry'])) {
foreach ($bundle['entry'] as $idx => $e) {
$r = $e['resource'];
echo "$idx: " . $r['resourceType'] . " (id: " . ($r['id'] ?? '') . ")\n";
if ($r['resourceType'] === 'Organization') {
echo "Organization:\n" . json_encode($r, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) . "\n";
}
if ($r['resourceType'] === 'Encounter') {
echo "Encounter:\n" . json_encode($r, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) . "\n";
}
if ($r['resourceType'] === 'Immunization') {
echo "Immunization:\n" . json_encode($r, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) . "\n";
}
}
} else {
echo "JSON decode error: " . json_last_error_msg() . "\n";
}
}

View File

@ -0,0 +1,7 @@
<?php
require 'd:/Workplace/HMS_CI4_OLD/vendor/codeigniter4/framework/system/Test/bootstrap.php';
$db = \Config\Database::connect('default');
$rows = $db->table('hospital_setting')->select('s_name, s_value')->like('s_name', 'H_')->orLike('s_name', 'ABDM')->get()->getResultArray();
foreach ($rows as $r) {
echo $r['s_name'] . ' = ' . $r['s_value'] . PHP_EOL;
}

View File

@ -0,0 +1,40 @@
<?php
$mysqli = new mysqli('localhost', 'root', '', 'hms_data_ci4');
if ($mysqli->connect_error) {
die("Connect Error: " . $mysqli->connect_error);
}
echo "=== patient_doc with ID 5 ===\n";
$res = $mysqli->query("SELECT * FROM patient_doc WHERE id = 5");
if ($row = $res->fetch_assoc()) {
print_r($row);
} else {
echo "No row with id=5 in patient_doc\n";
}
echo "\n=== recent patient_doc rows ===\n";
$res = $mysqli->query("SELECT id, p_id, dr_id, doc_format_id, created_at FROM patient_doc ORDER BY id DESC LIMIT 5");
while ($r = $res->fetch_assoc()) {
print_r($r);
}
echo "\n=== file_upload_data with ID 5 ===\n";
$res = $mysqli->query("SELECT * FROM file_upload_data WHERE id = 5");
if ($row = $res->fetch_assoc()) {
print_r($row);
} else {
echo "No row with id=5 in file_upload_data\n";
}
echo "\n=== recent file_upload_data rows ===\n";
$res = $mysqli->query("SELECT id, pid, insert_date FROM file_upload_data ORDER BY id DESC LIMIT 5");
while ($r = $res->fetch_assoc()) {
print_r($r);
}
echo "\n=== abdm_work_tasks with ID 585 ===\n";
$res = $mysqli->query("SELECT * FROM abdm_work_tasks WHERE id = 585");
if ($row = $res->fetch_assoc()) {
print_r($row);
}

View File

@ -0,0 +1,16 @@
<?php
define('FCPATH', __DIR__ . '/../public/');
require __DIR__ . '/../vendor/autoload.php';
$bootstrap = require __DIR__ . '/../app/Config/Boot/development.php';
$app = \Config\Services::codeigniter();
$app->initialize();
$db = \Config\Database::connect();
$rows = $db->table('health_records')->orderBy('id', 'DESC')->limit(10)->get()->getResultArray();
echo "=== Last 10 Health Records in DB ===\n";
foreach ($rows as $r) {
echo "ID: {$r['id']} | Entity: {$r['entity_type']} #{$r['entity_id']} | HI Type: {$r['hi_type']} | Status: {$r['push_status']} | Queue: {$r['queue_id']} | Date: {$r['created_at']}\n";
if (!empty($r['error_message'])) {
echo " Error: {$r['error_message']}\n";
}
}

View File

@ -0,0 +1,11 @@
<?php
$host = '127.0.0.1';
$user = 'root';
$pass = '';
$pdo1 = new PDO("mysql:host=$host;dbname=hms_ci4_2026;charset=utf8mb4", $user, $pass);
echo "Count in hms_ci4_2026 opd_med_master: " . $pdo1->query("SELECT COUNT(*) FROM opd_med_master")->fetchColumn() . "\n";
$stmt = $pdo1->query("SELECT id, item_name, genericname, salt_name FROM opd_med_master WHERE item_name LIKE '%PANTOP%' LIMIT 5");
print_r($stmt->fetchAll(PDO::FETCH_ASSOC));

View File

@ -0,0 +1,6 @@
<?php
$mysqli = new mysqli('localhost', 'root', '', 'hms_data_ci4', 3306);
$res = $mysqli->query("SELECT s_name, s_value FROM hospital_setting WHERE s_name LIKE '%H_%' OR s_name LIKE '%ABDM%' OR s_name LIKE '%addr%' OR s_name LIKE '%phone%'");
while ($row = $res->fetch_assoc()) {
echo $row['s_name'] . ' = ' . $row['s_value'] . PHP_EOL;
}

View File

@ -0,0 +1,8 @@
<?php
$sd = json_decode(file_get_contents('ABDM_FHIR/package/package/StructureDefinition-Immunization.json'), true);
foreach ($sd['snapshot']['element'] as $el) {
if (strpos($el['id'], 'Immunization.location') === 0) {
echo $el['id'] . ":\n";
echo " Type: " . json_encode($el['type']) . "\n";
}
}

8
scratch/check_imm_ms.php Normal file
View File

@ -0,0 +1,8 @@
<?php
$sd = json_decode(file_get_contents(__DIR__ . '/../ABDM_FHIR/examples.json/StructureDefinition-Immunization.json'), true);
echo "=== Immunization MustSupport elements ===\n";
foreach ($sd['snapshot']['element'] as $el) {
if (!empty($el['mustSupport'])) {
echo $el['id'] . " (min: " . $el['min'] . ")\n";
}
}

View File

@ -0,0 +1,21 @@
<?php
$lines = file('C:/Users/d_s_b/.gemini/antigravity-ide/brain/5d74b30a-f6db-45f5-988c-4e3f71d58774/.system_generated/logs/transcript.jsonl');
$d = json_decode($lines[0], true);
if (preg_match('/\{[\s\S]*"resourceType"\s*:\s*"Bundle"[\s\S]*\}/', $d['content'], $m)) {
$bundle = json_decode($m[0], true);
if ($bundle && isset($bundle['entry'])) {
foreach ($bundle['entry'] as $idx => $e) {
$r = $e['resource'];
echo "$idx: " . $r['resourceType'] . " id=" . ($r['id'] ?? '') . "\n";
if ($r['resourceType'] === 'Immunization') {
echo "Immunization keys: " . implode(', ', array_keys($r)) . "\n";
if (isset($r['location'])) {
echo "Location: " . json_encode($r['location']) . "\n";
}
if (isset($r['encounter'])) {
echo "Encounter: " . json_encode($r['encounter']) . "\n";
}
}
}
}
}

View File

@ -0,0 +1,14 @@
<?php
$lines = file('C:/Users/d_s_b/.gemini/antigravity-ide/brain/5d74b30a-f6db-45f5-988c-4e3f71d58774/.system_generated/logs/transcript.jsonl');
$d = json_decode($lines[0], true);
preg_match('/"resourceType": "Immunization".*?\}/s', $d['content'], $m);
if ($m) {
echo $m[0] . "\n";
} else {
echo "Not found in regex, showing lines with location or Immunization:\n";
foreach (explode("\n", $d['content']) as $l) {
if (stripos($l, 'location') !== false || stripos($l, 'resourceType') !== false) {
echo $l . "\n";
}
}
}

View File

@ -0,0 +1,15 @@
<?php
$sd = json_decode(file_get_contents('ABDM_FHIR/definitions.json/StructureDefinition-Invoice.json'), true);
echo "Checking Attachment in Invoice SD:\n";
foreach ($sd['snapshot']['element'] as $el) {
if (isset($el['type'])) {
foreach ($el['type'] as $t) {
if (in_array($t['code'], ['Attachment', 'Binary', 'base64Binary'])) {
echo "{$el['id']} has type {$t['code']}\n";
}
}
}
}
echo "Done checking Attachment in Invoice.\n";

View File

@ -0,0 +1,16 @@
<?php
$sd = json_decode(file_get_contents('ABDM_FHIR/definitions.json/StructureDefinition-Invoice.json'), true);
echo "Elements in StructureDefinition-Invoice:\n";
foreach ($sd['snapshot']['element'] as $el) {
$types = [];
if (isset($el['type'])) {
foreach ($el['type'] as $t) {
$code = $t['code'] ?? '';
$tp = isset($t['targetProfile']) ? (' -> ' . implode(',', $t['targetProfile'])) : '';
$types[] = $code . $tp;
}
}
echo "{$el['id']} [" . implode('|', $types) . "] min:{$el['min']} max:{$el['max']}\n";
}

View File

@ -0,0 +1,15 @@
<?php
$dir = 'd:/Workplace/HMS_CI4_OLD/ABDM_FHIR/definitions.json/';
$files = glob($dir . '*Invoice*.json');
echo "Definitions with Invoice:\n";
foreach ($files as $f) {
echo " - " . basename($f) . "\n";
}
$exDir = 'd:/Workplace/HMS_CI4_OLD/ABDM_FHIR/examples.json/';
$exFiles = glob($exDir . '*Invoice*.json');
echo "Examples with Invoice:\n";
foreach ($exFiles as $f) {
echo " - " . basename($f) . "\n";
}

View File

@ -0,0 +1,20 @@
<?php
$sd = json_decode(file_get_contents('ABDM_FHIR/definitions.json/StructureDefinition-InvoiceRecord.json'), true);
echo "Checking section slices in StructureDefinition-InvoiceRecord:\n";
$differential = $sd['differential']['element'];
foreach ($differential as $el) {
if (str_starts_with($el['id'], 'Composition.section')) {
echo "{$el['id']}\n";
if (isset($el['sliceName'])) {
echo " sliceName: {$el['sliceName']}\n";
}
if (isset($el['code'])) {
echo " code: " . json_encode($el['code']) . "\n";
}
if (isset($el['type'])) {
echo " type: " . json_encode($el['type']) . "\n";
}
}
}

View File

@ -0,0 +1,20 @@
<?php
$sd = json_decode(file_get_contents('ABDM_FHIR/definitions.json/StructureDefinition-InvoiceRecord.json'), true);
echo "Snapshot Elements in StructureDefinition-InvoiceRecord:\n";
foreach ($sd['snapshot']['element'] as $el) {
if (str_starts_with($el['id'], 'Composition.section')) {
echo "{$el['id']}\n";
if (isset($el['sliceName'])) {
echo " sliceName: {$el['sliceName']}\n";
}
if (isset($el['type'])) {
foreach ($el['type'] as $t) {
if (isset($t['targetProfile'])) {
echo " targetProfile: " . json_encode($t['targetProfile']) . "\n";
}
}
}
}
}

View File

@ -0,0 +1,10 @@
<?php
$sd = json_decode(file_get_contents('ABDM_FHIR/definitions.json/StructureDefinition-InvoiceRecord.json'), true);
foreach ($sd['snapshot']['element'] as $el) {
if (isset($el['slicing'])) {
echo "{$el['id']} has slicing:\n";
echo json_encode($el['slicing'], JSON_PRETTY_PRINT) . "\n";
}
}

13
scratch/check_ipd9.php Normal file
View File

@ -0,0 +1,13 @@
<?php
define('FCPATH', __DIR__ . '/../public/');
chdir(__DIR__ . '/../');
require 'vendor/autoload.php';
require 'app/Config/Paths.php';
$paths = new Config\Paths();
require 'system/bootstrap.php';
$app = Config\Services::codeigniter();
$app->initialize();
$db = \Config\Database::connect();
$ipd = $db->table('ipd_master')->where('id', 9)->get()->getRowArray();
echo "IPD 9: " . json_encode($ipd, JSON_PRETTY_PRINT) . "\n";

20
scratch/check_ipd_009.php Normal file
View File

@ -0,0 +1,20 @@
<?php
$conn = new mysqli('localhost', 'root', '', 'hms_ci4_2026');
echo "=== DOCTOR MASTER RECORDS ===\n";
$docRes = $conn->query("SELECT * FROM doctor_master");
while ($d = $docRes->fetch_assoc()) {
echo "ID: " . $d['id'] . " | Title: " . ($d['p_title'] ?? '') . " | Name: " . ($d['p_fname'] ?? '') . " " . ($d['p_lname'] ?? '') . "\n";
}
echo "\n=== IPD MASTER RECORDS ===\n";
$ipdRes = $conn->query("SELECT id, ipd_code, p_id, r_doc_id, r_doc_name, ipd_status, register_date FROM ipd_master ORDER BY id DESC LIMIT 10");
while ($i = $ipdRes->fetch_assoc()) {
print_r($i);
}
echo "\n=== IPD MASTER DOC LIST ===\n";
$docListRes = $conn->query("SELECT * FROM ipd_master_doc_list ORDER BY id DESC LIMIT 20");
while ($dl = $docListRes->fetch_assoc()) {
print_r($dl);
}

View File

@ -0,0 +1,38 @@
<?php
$host = '127.0.0.1';
$user = 'root';
$pass = '';
$dbname = 'hms_ci4_2026';
try {
$pdo = new PDO("mysql:host=$host;dbname=$dbname;charset=utf8mb4", $user, $pass);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch (Exception $e) {
die("Connection failed: " . $e->getMessage() . "\n");
}
$ipdId = 6;
echo "=== CHECKING MEDICATION TABLES FOR IPD ID $ipdId in DB [$dbname] ===\n";
$tables = [
'ipd_discharge_prescrption_prescribed',
'ipd_discharge_prescription_prescribed',
'ipd_discharge_drug',
'ipd_discharge_medication',
];
foreach ($tables as $t) {
try {
$stmt = $pdo->query("SELECT * FROM `$t` LIMIT 10");
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
echo "Table $t: " . count($rows) . " rows found\n";
foreach ($rows as $r) {
print_r($r);
}
} catch (Exception $e) {
echo "Table $t: " . $e->getMessage() . "\n";
}
}

View File

@ -0,0 +1,18 @@
<?php
$host = '127.0.0.1';
$user = 'root';
$pass = '';
$dbname = 'hms_ci4_2026';
$pdo = new PDO("mysql:host=$host;dbname=$dbname;charset=utf8mb4", $user, $pass);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$stmt = $pdo->query("SELECT id, ipd_code, p_id FROM ipd_master ORDER BY id DESC LIMIT 15");
print_r($stmt->fetchAll(PDO::FETCH_ASSOC));
$stmt = $pdo->query("SELECT DISTINCT ipd_id FROM ipd_discharge_prescrption_prescribed");
print_r($stmt->fetchAll(PDO::FETCH_ASSOC));
$stmt = $pdo->query("SELECT * FROM ipd_discharge_prescrption_prescribed LIMIT 10");
print_r($stmt->fetchAll(PDO::FETCH_ASSOC));

View File

@ -0,0 +1,18 @@
<?php
$host = '127.0.0.1';
$user = 'root';
$pass = '';
$dbname = 'hms_ci4_2026';
$pdo = new PDO("mysql:host=$host;dbname=$dbname;charset=utf8mb4", $user, $pass);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$stmt = $pdo->query("SELECT * FROM ipd_discharge WHERE ipd_id = 9 OR id = 9");
print_r($stmt->fetchAll(PDO::FETCH_ASSOC));
$stmt = $pdo->query("SELECT * FROM ipd_discharge_1_b WHERE ipd_id = 9");
print_r($stmt->fetchAll(PDO::FETCH_ASSOC));
$stmt = $pdo->query("SELECT * FROM ipd_discharge_1_b_final WHERE ipd_id = 9");
print_r($stmt->fetchAll(PDO::FETCH_ASSOC));

View File

@ -0,0 +1,15 @@
<?php
$host = '127.0.0.1';
$user = 'root';
$pass = '';
$dbname = 'hms_data_ci4';
$pdo = new PDO("mysql:host=$host;dbname=$dbname;charset=utf8mb4", $user, $pass);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$stmt = $pdo->query("SELECT * FROM ipd_discharge_instructions WHERE ipd_id = 9");
print_r($stmt->fetchAll(PDO::FETCH_ASSOC));
$stmt = $pdo->query("SELECT * FROM ipd_discharge WHERE ipd_id = 9");
print_r($stmt->fetchAll(PDO::FETCH_ASSOC));

View File

@ -0,0 +1,34 @@
<?php
$conn = new mysqli('localhost', 'root', '', 'hms_ci4_2026');
$sql = "SELECT i.id, i.ipd_code, p.p_code, p.p_fname, p.p_rname,
concat('Bed No :', b.bed_number, '[', w.ward_name, ']') as Bed_Desc,
date_format(i.register_date,'%d-%m-%Y') as str_register_date,
if(i.ipd_status = 0,(to_days(sysdate()) - to_days(i.register_date)),(to_days(i.discharge_date) - to_days(i.register_date))) as no_days,
ipd_doc_list.doc_name as doc_name,
ipd_doc_list.doc_list as doc_list,
i.r_doc_id,
i.r_doc_name
FROM ipd_master i
JOIN patient_master p ON i.p_id = p.id
LEFT JOIN (
select max(id) as id, ipd_id from bed_assignment_history group by ipd_id
) bah_latest ON bah_latest.ipd_id = i.id
LEFT JOIN bed_assignment_history bah ON bah.id = bah_latest.id
LEFT JOIN bed_master b ON b.id = bah.bed_id
LEFT JOIN ward_master w ON w.id = bah.ward_id
LEFT JOIN (
select i.ipd_id,
group_concat(distinct concat_ws(' ', 'Dr.', d.p_fname, d.p_mname, d.p_lname)) as doc_name,
group_concat(distinct d.id) as doc_list
from ipd_master_doc_list i
join doctor_master d on i.doc_id = d.id
group by i.ipd_id
) ipd_doc_list ON i.id = ipd_doc_list.ipd_id
WHERE i.ipd_status = 0";
$res = $conn->query($sql);
echo "HMS CURRENT ADMISSIONS QUERY RESULTS (" . $res->num_rows . " rows):\n\n";
while ($row = $res->fetch_assoc()) {
print_r($row);
}

19
scratch/check_ipd_db.php Normal file
View File

@ -0,0 +1,19 @@
<?php
require_once 'vendor/codeigniter4/framework/system/Test/bootstrap.php';
$db = \Config\Database::connect();
echo "=== IPD_DETAILS COLUMNS ===\n";
$cols = $db->getFieldNames('ipd_details');
echo implode(', ', $cols) . "\n\n";
if ($db->tableExists('ipd_discharge')) {
echo "=== IPD_DISCHARGE COLUMNS ===\n";
$dcols = $db->getFieldNames('ipd_discharge');
echo implode(', ', $dcols) . "\n\n";
$drow = $db->table('ipd_discharge')->where('ipd_id', 9)->get()->getRowArray();
echo "IPD 9 discharge row: " . json_encode($drow, JSON_PRETTY_PRINT) . "\n\n";
}
$irow = $db->table('ipd_details')->where('id', 9)->get()->getRowArray();
echo "IPD 9 details row: " . json_encode($irow, JSON_PRETTY_PRINT) . "\n";

View File

@ -0,0 +1,8 @@
<?php
$conn = new mysqli('localhost', 'root', '', 'hms_data_ci4');
$res = $conn->query("SHOW COLUMNS FROM ipd_nursing_entries");
echo "COLUMNS IN ipd_nursing_entries:\n";
while ($r = $res->fetch_assoc()) {
echo "- " . $r['Field'] . " (" . $r['Type'] . ")\n";
}

View File

@ -0,0 +1,7 @@
<?php
$db = new mysqli('localhost', 'root', '', 'hms_data_ci4');
$res = $db->query("SHOW TABLES LIKE '%ipd%'");
while ($r = $res->fetch_array()) {
echo $r[0] . "\n";
}
$db->close();

View File

@ -0,0 +1,7 @@
<?php
$db = new mysqli('localhost', 'root', '', 'hms_data_ci4');
$res = $db->query("SELECT item_id, item_name, unit_pack, units_per_pack, category FROM mst_items LIMIT 25");
while($r = $res->fetch_assoc()) {
echo "ID {$r['item_id']}: {$r['item_name']} | Pack: '{$r['unit_pack']}' | UnitsPerPack: {$r['units_per_pack']} | Cat: {$r['category']}\n";
}
$db->close();

View File

@ -0,0 +1,7 @@
<?php
$d = json_decode(file_get_contents('writable/tmp/abdm_lab_bundle_real_sample.json'), true);
foreach ($d['entry'] as $e) {
if ($e['resource']['resourceType'] === 'Encounter') {
print_r($e['resource']);
}
}

View File

@ -0,0 +1,12 @@
<?php
$data = json_decode(file_get_contents('ABDM_FHIR/examples.json/Bundle-DiagnosticReport-Lab-example-03.json'), true);
foreach ($data['entry'] as $i => $e) {
$r = $e['resource'];
echo "Entry $i: " . $r['resourceType'] . "\n";
if ($r['resourceType'] === 'Composition') {
echo " title: " . ($r['title'] ?? '') . "\n";
echo " custodian: " . json_encode($r['custodian'] ?? '') . "\n";
echo " author: " . json_encode($r['author'] ?? '') . "\n";
echo " encounter: " . json_encode($r['encounter'] ?? '') . "\n";
}
}

View File

@ -0,0 +1,7 @@
<?php
$d = json_decode(file_get_contents('writable/tmp/abdm_lab_bundle_real_sample.json'), true);
echo "Composition:\n";
print_r($d['entry'][0]['resource']);
foreach ($d['entry'] as $i => $e) {
echo "$i: " . $e['resource']['resourceType'] . "\n";
}

View File

@ -0,0 +1,14 @@
<?php
$host = '127.0.0.1';
$user = 'root';
$pass = '';
$dbname = 'hms_data_ci4';
$pdo = new PDO("mysql:host=$host;dbname=$dbname;charset=utf8mb4", $user, $pass);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$stmt = $pdo->query("SELECT id, item_name, dosage, dosage_when, dosage_freq, remark FROM opd_med_master LIMIT 10");
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
print_r($rows);

View File

@ -0,0 +1,20 @@
<?php
$host = '127.0.0.1';
$user = 'root';
$pass = '';
$dbname = 'hms_ci4_2026';
$pdo = new PDO("mysql:host=$host;dbname=$dbname;charset=utf8mb4", $user, $pass);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$tables = ['opd_med_master', 'med_product_master', 'med_genric_name', 'inv_med_item'];
foreach ($tables as $t) {
try {
echo "=== Table $t ===\n";
$stmt = $pdo->query("SELECT * FROM `$t` LIMIT 5");
print_r($stmt->fetchAll(PDO::FETCH_ASSOC));
} catch (Exception $e) {
echo $e->getMessage() . "\n";
}
}

View File

@ -0,0 +1,11 @@
<?php
$db = new mysqli('localhost', 'root', '', 'hms_data_ci4');
$res = $db->query("SHOW TABLES LIKE '%med%'");
while ($r = $res->fetch_array()) {
echo $r[0] . "\n";
}
$res = $db->query("SHOW TABLES LIKE '%pharm%'");
while ($r = $res->fetch_array()) {
echo $r[0] . "\n";
}
$db->close();

View File

@ -0,0 +1,6 @@
<?php
$lines = file('C:/Users/d_s_b/.gemini/antigravity-ide/brain/5d74b30a-f6db-45f5-988c-4e3f71d58774/.system_generated/logs/transcript.jsonl');
$line0 = json_decode($lines[0], true);
echo "Media paths in line 0:\n";
print_r($line0['media_paths'] ?? []);
print_r($line0['tool_calls'] ?? []);

View File

@ -0,0 +1,10 @@
<?php
$sd = json_decode(file_get_contents('ABDM_FHIR/definitions.json/StructureDefinition-MedicationRequest.json'), true);
foreach ($sd['snapshot']['element'] as $el) {
if (str_starts_with($el['id'], 'MedicationRequest.category') || str_starts_with($el['id'], 'MedicationRequest.reason')) {
echo "{$el['id']}\n";
if (isset($el['binding'])) {
echo " binding: " . json_encode($el['binding']) . "\n";
}
}
}

7
scratch/check_mr_req.php Normal file
View File

@ -0,0 +1,7 @@
<?php
$def = json_decode(file_get_contents('ABDM_FHIR/definitions.json/StructureDefinition-MedicationRequest.json'), true);
foreach ($def['snapshot']['element'] as $el) {
if ($el['min'] > 0) {
echo $el['id'] . " min=" . $el['min'] . "\n";
}
}

View File

@ -0,0 +1,7 @@
<?php
$db = new mysqli('localhost', 'root', '', 'hms_data_ci4');
$res = $db->query('SHOW FULL COLUMNS FROM mst_stores');
while($row = $res->fetch_assoc()) {
echo $row['Field'] . " | " . $row['Type'] . "\n";
}
$db->close();

View File

@ -0,0 +1,17 @@
<?php
$mysqli = new mysqli('localhost', 'root', '', 'hms_data_ci4');
$res = $mysqli->query("SELECT * FROM ipd_master WHERE id = 9");
echo "=== ipd_master row 9 ===\n";
print_r($res->fetch_assoc());
$res = $mysqli->query("SELECT * FROM ipd_discharge WHERE ipd_id = 9");
echo "=== ipd_discharge row for ipd 9 ===\n";
print_r($res->fetch_assoc());
$res = $mysqli->query("SELECT * FROM ipd_discharg_status");
echo "=== ipd_discharg_status rows ===\n";
while ($row = $res->fetch_assoc()) {
print_r($row);
}

View File

@ -0,0 +1,8 @@
<?php
$conn = new mysqli('localhost', 'root', '', 'hms_data_ci4');
$res = $conn->query("SHOW COLUMNS FROM nursing_treatment_entries");
echo "COLUMNS IN nursing_treatment_entries:\n";
while ($r = $res->fetch_assoc()) {
echo "- " . $r['Field'] . " (" . $r['Type'] . ")\n";
}

View File

@ -0,0 +1,8 @@
<?php
$conn = new mysqli('localhost', 'root', '', 'hms_data_ci4');
$res = $conn->query("SELECT opd_id, opd_code, p_id, doc_id, apointment_date, opd_status FROM opd_master ORDER BY opd_id DESC LIMIT 20");
echo "OPD MASTER DATES IN hms_data_ci4:\n";
while ($r = $res->fetch_assoc()) {
print_r($r);
}

View File

@ -0,0 +1,14 @@
<?php
$host = '127.0.0.1';
$user = 'root';
$pass = '';
$dbname = 'hms_data_ci4';
$pdo = new PDO("mysql:host=$host;dbname=$dbname;charset=utf8mb4", $user, $pass);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$stmt = $pdo->query("SELECT * FROM opd_dose_when");
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
print_r($rows);

View File

@ -0,0 +1,14 @@
<?php
$conn = new mysqli('localhost', 'root', '', 'hms_ci4_2026');
$tables = ['opd_prescrption_prescribed', 'opd_prescription_prescribed', 'opd_prescription_item'];
foreach ($tables as $t) {
$res = $conn->query("SHOW TABLES LIKE '$t'");
if ($res && $res->num_rows > 0) {
echo "TABLE $t EXISTS:\n";
$c = $conn->query("SHOW COLUMNS FROM $t");
while ($r = $c->fetch_assoc()) {
echo " - " . $r['Field'] . "\n";
}
}
}

View File

@ -0,0 +1,20 @@
<?php
$mysqli = new mysqli('localhost', 'root', '', 'hms_data_ci4');
if ($mysqli->connect_error) {
die("Connection failed: " . $mysqli->connect_error);
}
$res = $mysqli->query("SELECT opd_id, opd_code, p_id, doc_id, apointment_date, opd_book_date, opd_status FROM opd_master ORDER BY opd_id DESC LIMIT 10");
echo "\nLAST 10 OPD MASTER ROWS:\n";
while ($row = $res->fetch_assoc()) {
print_r($row);
}
$today = date('Y-m-d');
echo "\nTODAY DATE IS: " . $today . "\n";
foreach (['apointment_date', 'opd_book_date'] as $c) {
$res = $mysqli->query("SELECT count(*) as count FROM opd_master WHERE DATE({$c}) = '{$today}'");
$row = $res->fetch_assoc();
echo "Count for DATE({$c}) = '{$today}': " . $row['count'] . "\n";
}

View File

@ -0,0 +1,9 @@
<?php
$sd = json_decode(file_get_contents('ABDM_FHIR/package/package/StructureDefinition-Organization.json'), true);
foreach ($sd['snapshot']['element'] as $el) {
$id = $el['id'];
$min = $el['min'] ?? 0;
$max = $el['max'] ?? '*';
$type = isset($el['type']) ? implode(',', array_column($el['type'], 'code')) : '';
echo sprintf("%-40s | %s..%s | %s\n", $id, $min, $max, $type);
}

View File

@ -0,0 +1,7 @@
<?php
$sd = json_decode(file_get_contents('ABDM_FHIR/package/package/StructureDefinition-Organization.json'), true);
foreach ($sd['snapshot']['element'] as $el) {
if (str_starts_with($el['id'], 'Organization.address') || str_starts_with($el['id'], 'Organization.telecom')) {
echo $el['id'] . ' | min:' . $el['min'] . ' | max:' . $el['max'] . ' | mustSupport:' . (!empty($el['mustSupport']) ? 'YES' : 'NO') . PHP_EOL;
}
}

View File

@ -0,0 +1,7 @@
<?php
$sd = json_decode(file_get_contents('ABDM_FHIR/package/package/StructureDefinition-Organization.json'), true);
foreach ($sd['snapshot']['element'] as $el) {
if ($el['id'] === 'Organization.type') {
echo json_encode($el['binding'] ?? [], JSON_PRETTY_PRINT) . "\n";
}
}

Some files were not shown because too many files have changed in this diff Show More