fix(medical-store): remove third-party brand Marg from application and replace with generic Excel/CSV import

This commit is contained in:
DevSoftTech Server 2026-09-10 04:58:59 +05:30
parent e635e823e5
commit ca6926015a
9 changed files with 51 additions and 1156 deletions

View File

@ -25,7 +25,7 @@
### 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.
- **Features:** Store configuration, DL 20B/21B licenses, GSTIN, Pharmacist details, ABDM HFR/HIP/HPR registries, 60-min OTP generator, terminal device authorization, and Excel / CSV stock import.
### Direct Counter Desks (React PWA)
- **Counter A (Main Hospital Block A):**
@ -59,15 +59,15 @@
- 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`.
4. **Excel / CSV Stock Migration:**
- Supported CSV import with auto-detected columns.
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):**
8. **Keyboard-First Zero-Mouse POS Workflow (Desktop Pharmacy 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.

View File

@ -1426,7 +1426,7 @@ $routes->group('setting', static function($routes) {
$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->post('admin/medical-store/import-excel', 'Setting\\MedicalStoreAdmin::importExcel');
$routes->get('admin/bed-management', 'Setting\\BedManagement::index', ['filter' => $settingsBedFilter]);
$routes->get('admin/departments', 'Setting\\BedManagement::departments', ['filter' => $settingsBedFilter]);
@ -1702,7 +1702,7 @@ $routes->group('api/v1', ['namespace' => 'App\Controllers\Api\v1'], static funct
$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->post('medical-store/stock/import-excel', 'MedicalStoreApi::importExcel');
$routes->get('medical-store/abdm/bundle/(:num)', 'MedicalStoreApi::getAbdmBundle/$1');
$routes->post('medical-store/abdm/link-abha', 'MedicalStoreApi::linkAbha');
$routes->get('medical-store/returns/lookup-invoice', 'MedicalStoreApi::lookupInvoiceForReturn');

View File

@ -3481,10 +3481,10 @@ class MedicalStoreApi extends BaseController
return $this->response->setJSON(['status' => 0, 'authorized' => false, 'message' => 'Device authorization revoked or not found.']);
}
public function importMarg()
public function importExcel()
{
$admin = new \App\Controllers\Setting\MedicalStoreAdmin();
return $admin->importMarg();
return $admin->importExcel();
}
// =========================================================================

View File

@ -235,9 +235,9 @@ class MedicalStoreAdmin extends BaseController
}
/**
* Marg Pharmacy Software / Excel CSV Inventory Import
* Excel / CSV Inventory Import
*/
public function importMarg()
public function importExcel()
{
$storeId = (int)($this->request->getPost('store_id') ?? 0);
if ($storeId <= 0) {
@ -251,7 +251,7 @@ class MedicalStoreAdmin extends BaseController
$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.']);
return $this->response->setJSON(['ok' => false, 'error' => 'Please upload CSV formatted export from Excel or CSV file.']);
}
$handle = fopen($file->getTempName(), 'r');
@ -322,7 +322,7 @@ class MedicalStoreAdmin extends BaseController
$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)
// Parse standard date and 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)) {
@ -418,7 +418,7 @@ class MedicalStoreAdmin extends BaseController
// Record opening stock valuation journal voucher in double-entry ledgers
if ($totalValuation > 0) {
$voucherNo = 'MARG-IMP-' . $storeId . '-' . date('YmdHis');
$voucherNo = 'CSV-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();
@ -426,12 +426,12 @@ class MedicalStoreAdmin extends BaseController
$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)"
'narration' => "Excel/CSV 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"
'narration' => "Excel/CSV Opening Capital Credit"
]);
}
}
@ -440,7 +440,7 @@ class MedicalStoreAdmin extends BaseController
return $this->response->setJSON([
'ok' => true,
'message' => "Successfully imported {$importedRows} inventory lines from Marg Pharmacy file.",
'message' => "Successfully imported {$importedRows} inventory lines from CSV file.",
'details' => [
'imported_rows' => $importedRows,
'new_catalog_items'=> $newItems,

View File

@ -5,8 +5,8 @@
<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 type="button" class="btn btn-outline-success btn-sm" id="btn_open_excel_import">
<i class="bi bi-file-earmark-arrow-up me-1"></i> Excel / CSV 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
@ -414,23 +414,23 @@
</div>
</div>
<!-- Modal 3: Marg ERP / Excel Inventory Import -->
<div class="modal fade" id="margImportModal" tabindex="-1" aria-hidden="true">
<!-- Modal 3: Excel / CSV Inventory Import -->
<div class="modal fade" id="excelImportModal" 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>
<h6 class="modal-title fw-bold"><i class="bi bi-file-earmark-arrow-up me-1"></i> Excel / CSV 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">
<form id="excelImportForm" 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.
Quickly migrate stock from Microsoft Excel or CSV files 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>
<select class="form-select form-select-sm" name="store_id" id="excel_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; ?>
@ -439,7 +439,7 @@
<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>
<input type="file" class="form-control form-control-sm" name="import_file" id="excel_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>
@ -447,12 +447,12 @@
<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>
<a href="javascript:downloadSampleCsv()" class="text-decoration-none fw-bold">Download Sample 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">
<button type="submit" class="btn btn-success btn-sm" id="btn_submit_excel">
<i class="bi bi-upload me-1"></i> Start Import
</button>
</div>
@ -528,8 +528,8 @@ document.getElementById('btn_add_store').addEventListener('click', function() {
modal.show();
});
document.getElementById('btn_open_marg_import').addEventListener('click', function() {
var modal = new bootstrap.Modal(document.getElementById('margImportModal'));
document.getElementById('btn_open_excel_import').addEventListener('click', function() {
var modal = new bootstrap.Modal(document.getElementById('excelImportModal'));
modal.show();
});
@ -627,15 +627,15 @@ function deleteStore(storeId, storeName) {
});
}
// Marg CSV Import Submit
$('#margImportForm').on('submit', function(e) {
// Excel / CSV Import Submit
$('#excelImportForm').on('submit', function(e) {
e.preventDefault();
var formData = new FormData(this);
var btn = $('#btn_submit_marg');
var btn = $('#btn_submit_excel');
btn.prop('disabled', true).text('Importing records...');
$.ajax({
url: '<?= base_url('setting/admin/medical-store/import-marg') ?>',
url: '<?= base_url('setting/admin/medical-store/import-excel') ?>',
method: 'POST',
data: formData,
contentType: false,
@ -657,7 +657,7 @@ $('#margImportForm').on('submit', function(e) {
});
});
function downloadSampleMargCsv() {
function downloadSampleCsv() {
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" +
@ -668,7 +668,7 @@ function downloadSampleMargCsv() {
var link = document.createElement("a");
var url = URL.createObjectURL(blob);
link.setAttribute("href", url);
link.setAttribute("download", "Marg_Pharmacy_Sample_Import.csv");
link.setAttribute("download", "Pharmacy_Sample_Stock_Import.csv");
document.body.appendChild(link);
link.click();
document.body.removeChild(link);

View File

@ -677,7 +677,7 @@ function App() {
<i className="bi bi-boxes me-1"></i> Live Stock &amp; Batches
</button>
<button className={`nav-link ${activeTab === 'purchase' ? 'active' : ''}`} onClick={() => setActiveTab('purchase')}>
<i className="bi bi-box-arrow-in-down me-1"></i> Purchases &amp; Marg Import
<i className="bi bi-box-arrow-in-down me-1"></i> Purchases &amp; Stock Import
</button>
<button className={`nav-link ${activeTab === 'transfer' ? 'active' : ''}`} onClick={() => setActiveTab('transfer')}>
<i className="bi bi-arrow-left-right me-1"></i> Inter-Store Indents
@ -1532,7 +1532,7 @@ function PosCounter({ store, notify }) {
}
};
// Global Keyboard Shortcuts (Marg ERP / Desktop Pharmacy Standard)
// Global Keyboard Shortcuts (Desktop Pharmacy Standard)
useEffect(() => {
const handleGlobalKeyDown = (e) => {
// Dismiss Modals on Escape
@ -3495,7 +3495,7 @@ function PosCounter({ store, notify }) {
</div>
)}
{/* Sales Return & Same-Invoice Exchange Modal (B2C Marg ERP Standard) */}
{/* Sales Return & Same-Invoice Exchange Modal (B2C Retail Standard) */}
{showReturnModal && (
<div className="modal fade show d-block" style={{ backgroundColor: 'rgba(15, 23, 42, 0.75)' }} tabIndex="-1">
<div className="modal-dialog modal-xl modal-dialog-scrollable">
@ -3506,7 +3506,7 @@ function PosCounter({ store, notify }) {
<div>
<h6 className="modal-title mb-0 fw-bold">Sales Return &amp; Same-Invoice Exchange Desk</h6>
<small className="text-secondary" style={{ fontSize: '0.75rem' }}>
B2C Retail Returns • Stock Replenishment • GST Credit Note • Marg ERP Standard (F6 / Alt+R)
B2C Retail Returns • Stock Replenishment • GST Credit Note • Fast POS Standard (F6 / Alt+R)
</small>
</div>
</div>
@ -4011,11 +4011,11 @@ function InventoryView({ store, notify }) {
}
// =========================================================================
// 3. INWARD PURCHASES, SUPPLIERS & MARG ERP IMPORT
// 3. INWARD PURCHASES, SUPPLIERS & EXCEL / CSV IMPORT
// =========================================================================
function PurchaseView({ store, notify }) {
if (!store) return null;
const [subTab, setSubTab] = useState('inward'); // inward, marg, suppliers
const [subTab, setSubTab] = useState('inward'); // inward, excel, suppliers
const [suppliers, setSuppliers] = useState([]);
const [selectedSupplierId, setSelectedSupplierId] = useState('');
const [invoiceNo, setInvoiceNo] = useState('');
@ -4759,8 +4759,8 @@ function PurchaseView({ store, notify }) {
<button className={`btn btn-sm ${subTab === 'suppliers' ? 'btn-primary shadow-sm' : 'btn-outline-secondary'}`} onClick={() => setSubTab('suppliers')}>
<i className="bi bi-truck me-1"></i> Supplier Directory ({suppliers.length})
</button>
<button className={`btn btn-sm ${subTab === 'marg' ? 'btn-success shadow-sm' : 'btn-outline-secondary'}`} onClick={() => setSubTab('marg')}>
<i className="bi bi-file-earmark-spreadsheet me-1"></i> Marg ERP / Excel Import
<button className={`btn btn-sm ${subTab === 'excel' ? 'btn-success shadow-sm' : 'btn-outline-secondary'}`} onClick={() => setSubTab('excel')}>
<i className="bi bi-file-earmark-spreadsheet me-1"></i> Excel / CSV Import
</button>
</div>
@ -5035,22 +5035,22 @@ function PurchaseView({ store, notify }) {
</div>
)}
{subTab === 'marg' && (
{subTab === 'excel' && (
<div className="counter-card">
<h5 className="fw-bold text-success mb-2"><i className="bi bi-file-earmark-spreadsheet me-1"></i> Marg ERP / Excel Inventory Importer</h5>
<h5 className="fw-bold text-success mb-2"><i className="bi bi-file-earmark-spreadsheet me-1"></i> Excel / CSV Inventory Importer</h5>
<p className="text-muted small mb-3">
Upload your stock export from <strong>Marg Pharmacy Software</strong> or Microsoft Excel to seamlessly import medicines, batch numbers, MRP, PTR, HSN codes, and opening stock into <strong>{store.store_name}</strong>.
Upload your stock export from Microsoft Excel or CSV format to seamlessly import medicines, batch numbers, MRP, PTR, HSN codes, and opening stock into <strong>{store.store_name}</strong>.
</p>
<div className="p-4 bg-light rounded-3 border text-center mb-3">
<i className="bi bi-cloud-arrow-up fs-1 text-success d-block mb-2"></i>
<h6 className="fw-bold">Upload Marg CSV / Excel File</h6>
<p className="small text-muted mb-3">Ensure file is exported in CSV format from Marg ERP.</p>
<input type="file" id="marg_file_input" accept=".csv, .txt" className="form-control form-control-sm w-50 mx-auto mb-3" />
<h6 className="fw-bold">Upload CSV / Excel File</h6>
<p className="small text-muted mb-3">Ensure file is formatted as standard CSV or Excel-exported CSV.</p>
<input type="file" id="excel_file_input" accept=".csv, .txt" className="form-control form-control-sm w-50 mx-auto mb-3" />
<button
className="btn btn-success-custom"
onClick={async () => {
const fileInput = document.getElementById('marg_file_input');
const fileInput = document.getElementById('excel_file_input');
if (!fileInput.files || fileInput.files.length === 0) {
alert('Please select a CSV file first.');
return;
@ -5060,7 +5060,7 @@ function PurchaseView({ store, notify }) {
formData.append('import_file', fileInput.files[0]);
try {
const res = await fetch(API_BASE + 'stock/import-marg', {
const res = await fetch(API_BASE + 'stock/import-excel', {
method: 'POST',
body: formData
});

File diff suppressed because one or more lines are too long

View File

@ -1,187 +0,0 @@
<?php
/**
* Test Terminal Verification and Marg ERP CSV Import
*/
$db = new mysqli('127.0.0.1', 'root', '', 'hms_data_ci4', 3306);
if ($db->connect_error) {
die("DB Connection failed: " . $db->connect_error . "\n");
}
echo "=== 1. TESTING TERMINAL DEVICE VERIFICATION VIA SECURITY KEY ===\n";
// Get store 1
$storeRes = $db->query("SELECT * FROM mst_stores WHERE store_id = 1");
$store1 = $storeRes->fetch_assoc();
echo " - Store: {$store1['store_name']} (Slug: {$store1['store_slug']}, Key: {$store1['security_key']})\n";
// Verify with Security Key
$machineName = "OPD-Counter-Dell-01";
$fingerprint = "fp_" . md5("screen_1920x1080_win64_dell");
$token = bin2hex(random_bytes(32));
$db->query("
INSERT INTO `mst_store_devices` (
`store_id`, `machine_name`, `device_token`, `device_fingerprint`,
`ip_address`, `user_agent`, `verified_by_method`, `status`
) VALUES (
1, '$machineName', '$token', '$fingerprint',
'192.168.1.101', 'Mozilla/5.0 (Windows NT 10.0; Win64)', 'SECURITY_KEY', 'authorized'
)
");
$deviceId = $db->insert_id;
echo " ✓ Machine '{$machineName}' registered (Device ID: {$deviceId}, Token: " . substr($token, 0, 16) . "...)\n";
// Verify status check
$chk = $db->query("SELECT * FROM mst_store_devices WHERE device_token = '$token' AND status = 'authorized'")->fetch_assoc();
if ($chk) {
echo " ✓ Device check passed: Machine is ACTIVE & AUTHORIZED for Store {$chk['store_id']}\n";
} else {
echo " ✗ Device check failed!\n";
}
echo "\n=== 2. TESTING OTP GENERATION & MACHINE VERIFICATION ===\n";
// Generate OTP for store 2
$store2Res = $db->query("SELECT * FROM mst_stores WHERE store_id = 2");
$store2 = $store2Res->fetch_assoc();
$testOtp = (string)rand(100000, 999999);
$testExpiry = date('Y-m-d H:i:s', strtotime('+60 minutes'));
$db->query("UPDATE mst_stores SET current_otp = '$testOtp', otp_expiry = '$testExpiry' WHERE store_id = 2");
echo " - Store: {$store2['store_name']} | Generated 60-min OTP: $testOtp (Expires: $testExpiry)\n";
// Machine verifies with OTP
$token2 = bin2hex(random_bytes(32));
$db->query("
INSERT INTO `mst_store_devices` (
`store_id`, `machine_name`, `device_token`, `device_fingerprint`,
`ip_address`, `user_agent`, `verified_by_method`, `status`
) VALUES (
2, 'IPD-Counter-HP-02', '$token2', 'fp_hp_terminal_02',
'192.168.1.102', 'Mozilla/5.0 (Windows NT 10.0)', 'OTP', 'authorized'
)
");
$dev2Id = $db->insert_id;
echo " ✓ Machine 'IPD-Counter-HP-02' authorized using OTP (Device ID: {$dev2Id})\n";
// Revoke machine terminal by Admin
$db->query("UPDATE mst_store_devices SET status = 'revoked' WHERE device_id = $dev2Id");
$chkRevoked = $db->query("SELECT * FROM mst_store_devices WHERE device_token = '$token2' AND status = 'authorized'")->fetch_assoc();
if (!$chkRevoked) {
echo " ✓ Admin Revocation confirmed: Device token rejected when revoked.\n";
} else {
echo " ✗ Revocation check failed!\n";
}
echo "\n=== 3. TESTING MARG ERP CSV IMPORT SIMULATION ===\n";
$csvPath = __DIR__ . '/test_marg_sample.csv';
$handle = fopen($csvPath, 'r');
$header = fgetcsv($handle);
echo " - CSV Header: " . implode(', ', $header) . "\n";
$imported = 0;
while (($row = fgetcsv($handle)) !== false) {
$itemName = trim($row[0]);
$pack = trim($row[1]);
$batchNo = trim($row[2]);
$expStr = trim($row[3]);
$mrp = (float)$row[4];
$ptr = (float)$row[5];
$hsn = trim($row[6]);
$gst = (float)$row[7];
$qty = (int)$row[8];
// Format Expiry MM/YYYY -> YYYY-MM-lastday
$parts = explode('/', $expStr);
$expDate = date('Y-m-t', strtotime("{$parts[1]}-{$parts[0]}-01"));
// Check/create master drug catalog
$findItem = $db->query("SELECT item_id FROM mst_items WHERE item_name = '" . $db->real_escape_string($itemName) . "'")->fetch_assoc();
if ($findItem) {
$itemId = $findItem['item_id'];
} else {
$db->query("
INSERT INTO `mst_items` (
`item_name`, `unit_pack`, `hsn_code`, `gst_rate`, `category`, `is_active`
) VALUES (
'" . $db->real_escape_string($itemName) . "', '$pack', '$hsn', $gst, 'Allopathy', 1
)
");
$itemId = $db->insert_id;
}
// Check/create batch for Store 1
$findBatch = $db->query("SELECT batch_id FROM mst_batches WHERE store_id = 1 AND item_id = $itemId AND batch_no = '$batchNo'")->fetch_assoc();
if ($findBatch) {
$batchId = $findBatch['batch_id'];
} else {
$db->query("
INSERT INTO `mst_batches` (
`store_id`, `item_id`, `batch_no`, `expiry_date`, `ptr`, `mrp`, `gst_rate`
) VALUES (
1, $itemId, '$batchNo', '$expDate', $ptr, $mrp, $gst
)
");
$batchId = $db->insert_id;
}
// Update or insert stock
$stockRow = $db->query("SELECT stock_id, current_qty FROM mst_stock WHERE store_id = 1 AND batch_id = $batchId")->fetch_assoc();
if ($stockRow) {
$newQty = $stockRow['current_qty'] + $qty;
$db->query("UPDATE mst_stock SET current_qty = $newQty WHERE stock_id = {$stockRow['stock_id']}");
} else {
$db->query("
INSERT INTO `mst_stock` (
`store_id`, `item_id`, `batch_id`, `current_qty`
) VALUES (
1, $itemId, $batchId, $qty
)
");
}
// Audit log
$db->query("
INSERT INTO `mst_stock_audit` (
`store_id`, `item_id`, `batch_id`, `audit_type`, `system_qty`, `physical_qty`,
`variation_qty`, `rate`, `total_value`, `remarks`
) VALUES (
1, $itemId, $batchId, 'OPENING_STOCK', 0, $qty,
$qty, $ptr, " . ($ptr * $qty) . ", 'Imported via Marg ERP CSV'
)
");
$imported++;
echo " ✓ Imported '{$itemName}' | Batch: {$batchNo} | Exp: {$expDate} | MRP: ₹{$mrp} | Qty: {$qty}\n";
}
fclose($handle);
echo "\n - Total rows imported from Marg format: $imported\n";
echo "\n=== 4. VERIFYING ISOLATION OF STOCKS ACROSS STORES ===\n";
// Check that Store 2 does NOT see Store 1's imported stock
$st2Check = $db->query("
SELECT i.item_name, s.current_qty
FROM mst_stock s
JOIN mst_items i ON i.item_id = s.item_id
WHERE s.store_id = 2 AND i.item_name = 'Azithral 500 Tablet'
")->fetch_assoc();
if (!$st2Check) {
echo " ✓ Store Isolation Verified: 'Azithral 500 Tablet' has 0 stock in Store 2 (OPD Counter)\n";
} else {
echo " - Store 2 stock: " . ($st2Check['current_qty'] ?? 0) . "\n";
}
$st1Check = $db->query("
SELECT i.item_name, s.current_qty
FROM mst_stock s
JOIN mst_items i ON i.item_id = s.item_id
WHERE s.store_id = 1 AND i.item_name = 'Azithral 500 Tablet'
")->fetch_assoc();
echo " ✓ Store 1 stock: '{$st1Check['item_name']}' has {$st1Check['current_qty']} units in Store 1 (Main Store)\n";
echo "\n=== ALL VERIFICATIONS PASSED SUCCESSFULLY ===\n";

View File

@ -1,6 +0,0 @@
Item Name,Packing,Batch,Exp Date,MRP,PTR,HSN,GST%,Qty
Azithral 500 Tablet,1x5,AZI-901,12/2026,125.00,92.50,3004,12,50
Pantocid DSR Capsule,1x10,PNT-441,08/2027,210.00,155.00,3004,12,100
Paracetamol 650 Tablet,1x15,PCM-102,05/2027,32.50,22.00,3004,12,200
Betadine 10% Ointment 20gm,1x1,BT-882,03/2028,140.00,105.00,3004,12,30
Monocef 1gm Injection,1 Vial,MNC-774,11/2026,68.00,51.00,3004,12,80
1 Item Name Packing Batch Exp Date MRP PTR HSN GST% Qty
2 Azithral 500 Tablet 1x5 AZI-901 12/2026 125.00 92.50 3004 12 50
3 Pantocid DSR Capsule 1x10 PNT-441 08/2027 210.00 155.00 3004 12 100
4 Paracetamol 650 Tablet 1x15 PCM-102 05/2027 32.50 22.00 3004 12 200
5 Betadine 10% Ointment 20gm 1x1 BT-882 03/2028 140.00 105.00 3004 12 30
6 Monocef 1gm Injection 1 Vial MNC-774 11/2026 68.00 51.00 3004 12 80