feat(pharmacy): enhance PO creation with medicine master search, last PTR auto-fill, exact GST selection, and scheme/discount support
This commit is contained in:
parent
083a82dcb6
commit
5a13ce2b56
|
|
@ -133,6 +133,7 @@ $routes->get('Medical/po_edit/(:num)', 'MedicalProcurement::po_edit/$1');
|
||||||
$routes->post('Medical/po_status/(:num)', 'MedicalProcurement::po_status/$1');
|
$routes->post('Medical/po_status/(:num)', 'MedicalProcurement::po_status/$1');
|
||||||
$routes->get('Medical/po_print/(:num)', 'MedicalProcurement::po_print/$1');
|
$routes->get('Medical/po_print/(:num)', 'MedicalProcurement::po_print/$1');
|
||||||
$routes->get('Medical/po_reorder_items/(:num)', 'MedicalProcurement::po_reorder_items/$1');
|
$routes->get('Medical/po_reorder_items/(:num)', 'MedicalProcurement::po_reorder_items/$1');
|
||||||
|
$routes->get('Medical/po_search_drug', 'MedicalProcurement::po_search_drug');
|
||||||
|
|
||||||
$routes->get('Medical/grn_list', 'MedicalProcurement::grn_list');
|
$routes->get('Medical/grn_list', 'MedicalProcurement::grn_list');
|
||||||
$routes->get('Medical/grn_new', 'MedicalProcurement::grn_new');
|
$routes->get('Medical/grn_new', 'MedicalProcurement::grn_new');
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,28 @@ class MedicalProcurement extends BaseController
|
||||||
$this->grnModel = new PharmacyGrnModel();
|
$this->grnModel = new PharmacyGrnModel();
|
||||||
$this->grnItemModel = new PharmacyGrnItemModel();
|
$this->grnItemModel = new PharmacyGrnItemModel();
|
||||||
$this->ensureGrnSchema();
|
$this->ensureGrnSchema();
|
||||||
|
$this->ensurePoSchema();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function ensurePoSchema(): void
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
if (!$this->db->tableExists('mst_purchase_order_items')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$this->db->fieldExists('discount_percent', 'mst_purchase_order_items')) {
|
||||||
|
$this->db->query("ALTER TABLE `mst_purchase_order_items` ADD COLUMN `discount_percent` DECIMAL(5,2) DEFAULT 0.00 AFTER `estimated_ptr`");
|
||||||
|
}
|
||||||
|
if (!$this->db->fieldExists('sch_disc_per', 'mst_purchase_order_items')) {
|
||||||
|
$this->db->query("ALTER TABLE `mst_purchase_order_items` ADD COLUMN `sch_disc_per` DECIMAL(5,2) DEFAULT 0.00 AFTER `discount_percent`");
|
||||||
|
}
|
||||||
|
if (!$this->db->fieldExists('scheme_info', 'mst_purchase_order_items')) {
|
||||||
|
$this->db->query("ALTER TABLE `mst_purchase_order_items` ADD COLUMN `scheme_info` VARCHAR(100) DEFAULT '' AFTER `sch_disc_per`");
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
log_message('error', 'ensurePoSchema: ' . $e->getMessage());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private function ensureGrnSchema(): void
|
private function ensureGrnSchema(): void
|
||||||
|
|
@ -215,6 +237,7 @@ class MedicalProcurement extends BaseController
|
||||||
return $deny;
|
return $deny;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$poId = (int)($this->request->getPost('po_id') ?? 0);
|
||||||
$supplierId = (int)$this->request->getPost('supplier_id');
|
$supplierId = (int)$this->request->getPost('supplier_id');
|
||||||
$storeId = (int)($this->request->getPost('store_id') ?: 1);
|
$storeId = (int)($this->request->getPost('store_id') ?: 1);
|
||||||
$poDate = trim((string)$this->request->getPost('po_date') ?: date('Y-m-d'));
|
$poDate = trim((string)$this->request->getPost('po_date') ?: date('Y-m-d'));
|
||||||
|
|
@ -233,7 +256,6 @@ class MedicalProcurement extends BaseController
|
||||||
return $this->response->setJSON(['status' => 0, 'message' => 'Please add at least one item to the purchase order.']);
|
return $this->response->setJSON(['status' => 0, 'message' => 'Please add at least one item to the purchase order.']);
|
||||||
}
|
}
|
||||||
|
|
||||||
$poNumber = $this->poModel->generatePoNumber();
|
|
||||||
$userId = $this->getCurrentUserId();
|
$userId = $this->getCurrentUserId();
|
||||||
|
|
||||||
$totalEstimatedAmount = 0.0;
|
$totalEstimatedAmount = 0.0;
|
||||||
|
|
@ -249,16 +271,23 @@ class MedicalProcurement extends BaseController
|
||||||
$unitsPerPack = (int)($row['units_per_pack'] ?? 10);
|
$unitsPerPack = (int)($row['units_per_pack'] ?? 10);
|
||||||
$estimatedPtr = (float)($row['estimated_ptr'] ?? 0);
|
$estimatedPtr = (float)($row['estimated_ptr'] ?? 0);
|
||||||
$gstRate = (float)($row['gst_rate'] ?? 12.0);
|
$gstRate = (float)($row['gst_rate'] ?? 12.0);
|
||||||
|
$discountPercent = (float)($row['discount_percent'] ?? 0.0);
|
||||||
|
$schDiscPer = (float)($row['sch_disc_per'] ?? 0.0);
|
||||||
|
$schemeInfo = trim((string)($row['scheme_info'] ?? ''));
|
||||||
|
|
||||||
if ($itemId <= 0 || $itemName === '' || $orderPacks <= 0) {
|
if ($itemId <= 0 || $itemName === '' || $orderPacks <= 0) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
$lineSubtotal = round($orderPacks * $estimatedPtr, 2);
|
$lineGross = round($orderPacks * $estimatedPtr, 2);
|
||||||
$lineTax = round($lineSubtotal * ($gstRate / 100), 2);
|
$schAmt = ($schDiscPer > 0) ? round($lineGross * ($schDiscPer / 100), 2) : 0.0;
|
||||||
$lineTotal = $lineSubtotal + $lineTax;
|
$afterSch = max(0.0, $lineGross - $schAmt);
|
||||||
|
$discAmt = ($discountPercent > 0) ? round($afterSch * ($discountPercent / 100), 2) : 0.0;
|
||||||
|
$taxable = max(0.0, $afterSch - $discAmt);
|
||||||
|
$lineTax = round($taxable * ($gstRate / 100), 2);
|
||||||
|
$lineTotal = $taxable + $lineTax;
|
||||||
|
|
||||||
$totalEstimatedAmount += $lineSubtotal;
|
$totalEstimatedAmount += $taxable;
|
||||||
$totalTaxAmount += $lineTax;
|
$totalTaxAmount += $lineTax;
|
||||||
|
|
||||||
$validItems[] = [
|
$validItems[] = [
|
||||||
|
|
@ -271,6 +300,9 @@ class MedicalProcurement extends BaseController
|
||||||
'received_packs' => 0,
|
'received_packs' => 0,
|
||||||
'remaining_packs' => $orderPacks,
|
'remaining_packs' => $orderPacks,
|
||||||
'estimated_ptr' => $estimatedPtr,
|
'estimated_ptr' => $estimatedPtr,
|
||||||
|
'discount_percent' => $discountPercent,
|
||||||
|
'sch_disc_per' => $schDiscPer,
|
||||||
|
'scheme_info' => $schemeInfo,
|
||||||
'gst_rate' => $gstRate,
|
'gst_rate' => $gstRate,
|
||||||
'estimated_amount' => $lineTotal,
|
'estimated_amount' => $lineTotal,
|
||||||
'status' => 'pending',
|
'status' => 'pending',
|
||||||
|
|
@ -283,35 +315,75 @@ class MedicalProcurement extends BaseController
|
||||||
|
|
||||||
$this->db->transStart();
|
$this->db->transStart();
|
||||||
|
|
||||||
$poHeader = [
|
if ($poId > 0) {
|
||||||
'store_id' => $storeId,
|
$existingPo = $this->poModel->find($poId);
|
||||||
'supplier_id' => $supplierId,
|
if (!$existingPo) {
|
||||||
'po_number' => $poNumber,
|
$this->db->transRollback();
|
||||||
'po_date' => $poDate,
|
return $this->response->setJSON(['status' => 0, 'message' => 'Existing purchase order not found.']);
|
||||||
'expected_delivery_date' => $expectedDelivery ?: null,
|
}
|
||||||
'payment_terms' => $paymentTerms,
|
|
||||||
'total_items' => count($validItems),
|
|
||||||
'total_estimated_amount' => $totalEstimatedAmount,
|
|
||||||
'tax_amount' => $totalTaxAmount,
|
|
||||||
'grand_total' => $totalEstimatedAmount + $totalTaxAmount,
|
|
||||||
'status' => $status,
|
|
||||||
'remarks' => $remarks,
|
|
||||||
'created_by' => $userId,
|
|
||||||
'created_at' => date('Y-m-d H:i:s'),
|
|
||||||
'updated_at' => date('Y-m-d H:i:s'),
|
|
||||||
];
|
|
||||||
|
|
||||||
if ($status === 'approved' || $status === 'ordered') {
|
$poNumber = $existingPo['po_number'];
|
||||||
$poHeader['approved_by'] = $userId;
|
|
||||||
$poHeader['approved_at'] = date('Y-m-d H:i:s');
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->poModel->insert($poHeader);
|
$poHeader = [
|
||||||
$poId = (int)$this->db->insertID();
|
'store_id' => $storeId,
|
||||||
|
'supplier_id' => $supplierId,
|
||||||
|
'po_date' => $poDate,
|
||||||
|
'expected_delivery_date' => $expectedDelivery ?: null,
|
||||||
|
'payment_terms' => $paymentTerms,
|
||||||
|
'total_items' => count($validItems),
|
||||||
|
'total_estimated_amount' => $totalEstimatedAmount,
|
||||||
|
'tax_amount' => $totalTaxAmount,
|
||||||
|
'grand_total' => $totalEstimatedAmount + $totalTaxAmount,
|
||||||
|
'status' => $status,
|
||||||
|
'remarks' => $remarks,
|
||||||
|
'updated_at' => date('Y-m-d H:i:s'),
|
||||||
|
];
|
||||||
|
|
||||||
foreach ($validItems as &$item) {
|
if (($status === 'approved' || $status === 'ordered') && empty($existingPo['approved_at'])) {
|
||||||
$item['po_id'] = $poId;
|
$poHeader['approved_by'] = $userId;
|
||||||
$this->poItemModel->insert($item);
|
$poHeader['approved_at'] = date('Y-m-d H:i:s');
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->poModel->update($poId, $poHeader);
|
||||||
|
$this->poItemModel->where('po_id', $poId)->delete();
|
||||||
|
|
||||||
|
foreach ($validItems as &$item) {
|
||||||
|
$item['po_id'] = $poId;
|
||||||
|
$this->poItemModel->insert($item);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$poNumber = $this->poModel->generatePoNumber();
|
||||||
|
|
||||||
|
$poHeader = [
|
||||||
|
'store_id' => $storeId,
|
||||||
|
'supplier_id' => $supplierId,
|
||||||
|
'po_number' => $poNumber,
|
||||||
|
'po_date' => $poDate,
|
||||||
|
'expected_delivery_date' => $expectedDelivery ?: null,
|
||||||
|
'payment_terms' => $paymentTerms,
|
||||||
|
'total_items' => count($validItems),
|
||||||
|
'total_estimated_amount' => $totalEstimatedAmount,
|
||||||
|
'tax_amount' => $totalTaxAmount,
|
||||||
|
'grand_total' => $totalEstimatedAmount + $totalTaxAmount,
|
||||||
|
'status' => $status,
|
||||||
|
'remarks' => $remarks,
|
||||||
|
'created_by' => $userId,
|
||||||
|
'created_at' => date('Y-m-d H:i:s'),
|
||||||
|
'updated_at' => date('Y-m-d H:i:s'),
|
||||||
|
];
|
||||||
|
|
||||||
|
if ($status === 'approved' || $status === 'ordered') {
|
||||||
|
$poHeader['approved_by'] = $userId;
|
||||||
|
$poHeader['approved_at'] = date('Y-m-d H:i:s');
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->poModel->insert($poHeader);
|
||||||
|
$poId = (int)$this->db->insertID();
|
||||||
|
|
||||||
|
foreach ($validItems as &$item) {
|
||||||
|
$item['po_id'] = $poId;
|
||||||
|
$this->poItemModel->insert($item);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->db->transComplete();
|
$this->db->transComplete();
|
||||||
|
|
@ -322,7 +394,7 @@ class MedicalProcurement extends BaseController
|
||||||
|
|
||||||
return $this->response->setJSON([
|
return $this->response->setJSON([
|
||||||
'status' => 1,
|
'status' => 1,
|
||||||
'message' => "Purchase Order {$poNumber} created successfully!",
|
'message' => "Purchase Order {$poNumber} saved successfully!",
|
||||||
'po_id' => $poId,
|
'po_id' => $poId,
|
||||||
'po_number' => $poNumber,
|
'po_number' => $poNumber,
|
||||||
]);
|
]);
|
||||||
|
|
@ -460,15 +532,19 @@ class MedicalProcurement extends BaseController
|
||||||
|
|
||||||
// Fetch drugs supplied by this vendor or general low stock items
|
// Fetch drugs supplied by this vendor or general low stock items
|
||||||
$sql = "SELECT p.id AS item_id, p.item_name, p.genericname AS generic_name,
|
$sql = "SELECT p.id AS item_id, p.item_name, p.genericname AS generic_name,
|
||||||
p.formulation AS category, 10 AS units_per_pack,
|
p.formulation AS category,
|
||||||
|
IFNULL(t.packing, IFNULL(p.packing, 10)) AS units_per_pack,
|
||||||
IFNULL(t.purchase_price, 0) AS estimated_ptr,
|
IFNULL(t.purchase_price, 0) AS estimated_ptr,
|
||||||
IFNULL(t.CGST_per + t.SGST_per, 12) AS gst_rate,
|
IFNULL(t.CGST_per + t.SGST_per, IFNULL(p.CGST_per + p.SGST_per, 12)) AS gst_rate,
|
||||||
|
IFNULL(t.discount, 0) AS discount_percent,
|
||||||
|
IFNULL(t.sch_disc_per, 0) AS sch_disc_per,
|
||||||
|
IF(t.qty_free > 0, CONCAT('Free: ', CAST(t.qty_free AS UNSIGNED)), '') AS scheme_info,
|
||||||
(IFNULL(t.total_unit, 0) - IFNULL(t.total_sale_unit, 0) - IFNULL(t.total_return_unit, 0) - IFNULL(t.total_lost_unit, 0)) AS current_stock
|
(IFNULL(t.total_unit, 0) - IFNULL(t.total_sale_unit, 0) - IFNULL(t.total_return_unit, 0) - IFNULL(t.total_lost_unit, 0)) AS current_stock
|
||||||
FROM med_product_master p
|
FROM med_product_master p
|
||||||
LEFT JOIN purchase_invoice_item t ON p.id = t.item_code
|
LEFT JOIN purchase_invoice_item t ON p.id = t.item_code
|
||||||
LEFT JOIN purchase_invoice m ON t.purchase_id = m.id
|
LEFT JOIN purchase_invoice m ON t.purchase_id = m.id
|
||||||
WHERE (m.sid = ? OR m.sid IS NULL)
|
WHERE (m.sid = ? OR m.sid IS NULL)
|
||||||
AND p.is_continue = 1
|
AND (p.is_continue = 1 OR p.is_continue IS NULL)
|
||||||
GROUP BY p.id
|
GROUP BY p.id
|
||||||
HAVING current_stock < 50
|
HAVING current_stock < 50
|
||||||
ORDER BY current_stock ASC
|
ORDER BY current_stock ASC
|
||||||
|
|
@ -483,6 +559,261 @@ class MedicalProcurement extends BaseController
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function po_search_drug()
|
||||||
|
{
|
||||||
|
if ($deny = $this->ensurePharmacyAccess()) {
|
||||||
|
return $deny;
|
||||||
|
}
|
||||||
|
|
||||||
|
$term = trim((string)$this->request->getGet('term'));
|
||||||
|
$supplierId = (int)($this->request->getGet('supplier_id') ?? 0);
|
||||||
|
|
||||||
|
if ($term === '') {
|
||||||
|
return $this->response->setJSON([]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$this->db->tableExists('med_product_master')) {
|
||||||
|
return $this->response->setJSON([]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$pFields = $this->db->getFieldNames('med_product_master') ?? [];
|
||||||
|
$hasGeneric = in_array('genericname', $pFields, true);
|
||||||
|
$hasCompany = in_array('company_name', $pFields, true);
|
||||||
|
$hasContinue = in_array('is_continue', $pFields, true);
|
||||||
|
|
||||||
|
$builder = $this->db->table('med_product_master p')
|
||||||
|
->select('p.id, p.item_name, p.formulation, '
|
||||||
|
. ($hasGeneric ? 'p.genericname' : "'' AS genericname") . ', '
|
||||||
|
. (in_array('packing', $pFields, true) ? 'p.packing' : "'10' AS packing") . ', '
|
||||||
|
. (in_array('CGST_per', $pFields, true) ? 'p.CGST_per' : '0 AS CGST_per') . ', '
|
||||||
|
. (in_array('SGST_per', $pFields, true) ? 'p.SGST_per' : '0 AS SGST_per') . ', '
|
||||||
|
. ($hasCompany ? 'p.company_name' : "'' AS company_name"));
|
||||||
|
|
||||||
|
if ($hasContinue) {
|
||||||
|
$builder->groupStart()
|
||||||
|
->where('p.is_continue', 1)
|
||||||
|
->orWhere('p.is_continue IS NULL')
|
||||||
|
->groupEnd();
|
||||||
|
}
|
||||||
|
|
||||||
|
$builder->groupStart()
|
||||||
|
->like('p.item_name', $term);
|
||||||
|
if ($hasGeneric) {
|
||||||
|
$builder->orLike('p.genericname', $term);
|
||||||
|
}
|
||||||
|
if ($hasCompany) {
|
||||||
|
$builder->orLike('p.company_name', $term);
|
||||||
|
}
|
||||||
|
$builder->groupEnd();
|
||||||
|
|
||||||
|
// Order: matches starting with term first, then alphabetical
|
||||||
|
$builder->orderBy("CASE WHEN p.item_name LIKE " . $this->db->escape($term . '%') . " THEN 1 ELSE 2 END", 'ASC', false)
|
||||||
|
->orderBy('p.item_name', 'ASC')
|
||||||
|
->limit(40);
|
||||||
|
|
||||||
|
$products = $builder->get()->getResultArray();
|
||||||
|
|
||||||
|
$hasPiItem = $this->db->tableExists('purchase_invoice_item');
|
||||||
|
$hasPi = $this->db->tableExists('purchase_invoice');
|
||||||
|
$hasGrnItem = $this->db->tableExists('mst_grn_items');
|
||||||
|
$hasGrn = $this->db->tableExists('mst_grns');
|
||||||
|
|
||||||
|
$result = [];
|
||||||
|
|
||||||
|
foreach ($products as $p) {
|
||||||
|
$itemId = (int)$p['id'];
|
||||||
|
$lastPur = null;
|
||||||
|
$isVendorMatch = false;
|
||||||
|
|
||||||
|
// 1. Check last purchase from selected supplier in purchase_invoice_item
|
||||||
|
if ($supplierId > 0 && $hasPiItem && $hasPi) {
|
||||||
|
$lastPur = $this->db->table('purchase_invoice_item pii')
|
||||||
|
->select('pii.purchase_price, pii.purchase_unit_rate, pii.packing, pii.CGST_per, pii.SGST_per, pii.discount, pii.sch_disc_per, pii.sch_disc_amt, pii.qty_free, pii.mrp, pi.sid, pi.supp_name, pi.date_of_invoice')
|
||||||
|
->join('purchase_invoice pi', 'pi.id = pii.purchase_id', 'left')
|
||||||
|
->where('pii.item_code', $itemId)
|
||||||
|
->where('pi.sid', $supplierId)
|
||||||
|
->orderBy('pii.id', 'DESC')
|
||||||
|
->get(1)
|
||||||
|
->getRowArray();
|
||||||
|
if ($lastPur) {
|
||||||
|
$isVendorMatch = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Check in mst_grn_items from selected supplier if still not found
|
||||||
|
if (!$lastPur && $supplierId > 0 && $hasGrnItem && $hasGrn) {
|
||||||
|
$lastGrn = $this->db->table('mst_grn_items gi')
|
||||||
|
->select('gi.unit_purchase_rate, gi.packing, gi.gross_amount, gi.cgst_percent, gi.sgst_percent, gi.igst_percent, gi.discount_percent, gi.sch_disc_per, gi.sch_disc_amt, gi.free_packs, gi.mrp, g.supplier_id AS sid, s.name_supplier AS supp_name, g.grn_date AS date_of_invoice')
|
||||||
|
->join('mst_grns g', 'g.grn_id = gi.grn_id', 'left')
|
||||||
|
->join('med_supplier s', 's.sid = g.supplier_id', 'left')
|
||||||
|
->where('gi.item_code', $itemId)
|
||||||
|
->where('g.supplier_id', $supplierId)
|
||||||
|
->orderBy('gi.grn_item_id', 'DESC')
|
||||||
|
->get(1)
|
||||||
|
->getRowArray();
|
||||||
|
|
||||||
|
if ($lastGrn) {
|
||||||
|
$isVendorMatch = true;
|
||||||
|
$pack = (int)($lastGrn['packing'] ?? 10);
|
||||||
|
$ratePerPack = (float)($lastGrn['unit_purchase_rate'] ?? 0) * ($pack > 0 ? $pack : 1);
|
||||||
|
$lastPur = [
|
||||||
|
'purchase_price' => $ratePerPack,
|
||||||
|
'purchase_unit_rate' => $lastGrn['unit_purchase_rate'],
|
||||||
|
'packing' => $pack,
|
||||||
|
'CGST_per' => $lastGrn['cgst_percent'],
|
||||||
|
'SGST_per' => $lastGrn['sgst_percent'],
|
||||||
|
'discount' => $lastGrn['discount_percent'],
|
||||||
|
'sch_disc_per' => $lastGrn['sch_disc_per'],
|
||||||
|
'sch_disc_amt' => $lastGrn['sch_disc_amt'],
|
||||||
|
'qty_free' => $lastGrn['free_packs'],
|
||||||
|
'mrp' => $lastGrn['mrp'],
|
||||||
|
'sid' => $lastGrn['sid'],
|
||||||
|
'supp_name' => $lastGrn['supp_name'],
|
||||||
|
'date_of_invoice' => $lastGrn['date_of_invoice'],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Fallback: check last purchase from ANY supplier in purchase_invoice_item
|
||||||
|
if (!$lastPur && $hasPiItem && $hasPi) {
|
||||||
|
$lastPur = $this->db->table('purchase_invoice_item pii')
|
||||||
|
->select('pii.purchase_price, pii.purchase_unit_rate, pii.packing, pii.CGST_per, pii.SGST_per, pii.discount, pii.sch_disc_per, pii.sch_disc_amt, pii.qty_free, pii.mrp, pi.sid, pi.supp_name, pi.date_of_invoice')
|
||||||
|
->join('purchase_invoice pi', 'pi.id = pii.purchase_id', 'left')
|
||||||
|
->where('pii.item_code', $itemId)
|
||||||
|
->orderBy('pii.id', 'DESC')
|
||||||
|
->get(1)
|
||||||
|
->getRowArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Fallback: check in mst_grn_items from ANY supplier
|
||||||
|
if (!$lastPur && $hasGrnItem && $hasGrn) {
|
||||||
|
$lastGrn = $this->db->table('mst_grn_items gi')
|
||||||
|
->select('gi.unit_purchase_rate, gi.packing, gi.gross_amount, gi.cgst_percent, gi.sgst_percent, gi.igst_percent, gi.discount_percent, gi.sch_disc_per, gi.sch_disc_amt, gi.free_packs, gi.mrp, g.supplier_id AS sid, s.name_supplier AS supp_name, g.grn_date AS date_of_invoice')
|
||||||
|
->join('mst_grns g', 'g.grn_id = gi.grn_id', 'left')
|
||||||
|
->join('med_supplier s', 's.sid = g.supplier_id', 'left')
|
||||||
|
->where('gi.item_code', $itemId)
|
||||||
|
->orderBy('gi.grn_item_id', 'DESC')
|
||||||
|
->get(1)
|
||||||
|
->getRowArray();
|
||||||
|
|
||||||
|
if ($lastGrn) {
|
||||||
|
$pack = (int)($lastGrn['packing'] ?? 10);
|
||||||
|
$ratePerPack = (float)($lastGrn['unit_purchase_rate'] ?? 0) * ($pack > 0 ? $pack : 1);
|
||||||
|
$lastPur = [
|
||||||
|
'purchase_price' => $ratePerPack,
|
||||||
|
'purchase_unit_rate' => $lastGrn['unit_purchase_rate'],
|
||||||
|
'packing' => $pack,
|
||||||
|
'CGST_per' => $lastGrn['cgst_percent'],
|
||||||
|
'SGST_per' => $lastGrn['sgst_percent'],
|
||||||
|
'discount' => $lastGrn['discount_percent'],
|
||||||
|
'sch_disc_per' => $lastGrn['sch_disc_per'],
|
||||||
|
'sch_disc_amt' => $lastGrn['sch_disc_amt'],
|
||||||
|
'qty_free' => $lastGrn['free_packs'],
|
||||||
|
'mrp' => $lastGrn['mrp'],
|
||||||
|
'sid' => $lastGrn['sid'],
|
||||||
|
'supp_name' => $lastGrn['supp_name'],
|
||||||
|
'date_of_invoice' => $lastGrn['date_of_invoice'],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine packing
|
||||||
|
$packing = (int)($lastPur['packing'] ?? 0);
|
||||||
|
if ($packing <= 0) {
|
||||||
|
$packing = (int)preg_replace('/[^0-9]/', '', (string)($p['packing'] ?? ''));
|
||||||
|
}
|
||||||
|
if ($packing <= 0) {
|
||||||
|
$packing = 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine Rate (estimated PTR per pack)
|
||||||
|
$rate = 0.0;
|
||||||
|
if ($lastPur && (float)($lastPur['purchase_price'] ?? 0) > 0) {
|
||||||
|
$rate = (float)$lastPur['purchase_price'];
|
||||||
|
} elseif ($lastPur && (float)($lastPur['purchase_unit_rate'] ?? 0) > 0) {
|
||||||
|
$rate = (float)$lastPur['purchase_unit_rate'] * $packing;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine GST %
|
||||||
|
$gstRate = null;
|
||||||
|
if ($lastPur) {
|
||||||
|
$purGst = (float)($lastPur['CGST_per'] ?? 0) + (float)($lastPur['SGST_per'] ?? 0);
|
||||||
|
if ($purGst > 0) {
|
||||||
|
$gstRate = $purGst;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($gstRate === null) {
|
||||||
|
$masterGst = (float)($p['CGST_per'] ?? 0) + (float)($p['SGST_per'] ?? 0);
|
||||||
|
if ($masterGst > 0) {
|
||||||
|
$gstRate = $masterGst;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($gstRate === null) {
|
||||||
|
$gstRate = 12.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scheme & Discount
|
||||||
|
$tradeDisc = (float)($lastPur['discount'] ?? 0);
|
||||||
|
$schDiscPer = (float)($lastPur['sch_disc_per'] ?? 0);
|
||||||
|
$schDiscAmt = (float)($lastPur['sch_disc_amt'] ?? 0);
|
||||||
|
$freeQty = (float)($lastPur['qty_free'] ?? 0);
|
||||||
|
|
||||||
|
$schemeParts = [];
|
||||||
|
if ($tradeDisc > 0) {
|
||||||
|
$schemeParts[] = "Disc: " . rtrim(rtrim(number_format($tradeDisc, 2), '0'), '.') . "%";
|
||||||
|
}
|
||||||
|
if ($schDiscPer > 0) {
|
||||||
|
$schemeParts[] = "Sch Disc: " . rtrim(rtrim(number_format($schDiscPer, 2), '0'), '.') . "%";
|
||||||
|
}
|
||||||
|
if ($schDiscAmt > 0) {
|
||||||
|
$schemeParts[] = "Sch Amt: ₹" . number_format($schDiscAmt, 2);
|
||||||
|
}
|
||||||
|
if ($freeQty > 0) {
|
||||||
|
$schemeParts[] = "Free: " . (int)$freeQty;
|
||||||
|
}
|
||||||
|
$schemeSummary = !empty($schemeParts) ? implode(', ', $schemeParts) : '';
|
||||||
|
|
||||||
|
$schemeInfo = '';
|
||||||
|
if ($freeQty > 0) {
|
||||||
|
$schemeInfo = "Free: " . (int)$freeQty;
|
||||||
|
} elseif ($schDiscPer > 0) {
|
||||||
|
$schemeInfo = "Sch: " . rtrim(rtrim(number_format($schDiscPer, 2), '0'), '.') . "%";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean Label for Autocomplete: Only Medicine Name, Formulation, and Generic Salt
|
||||||
|
$cleanLabel = trim($p['item_name']);
|
||||||
|
if (!empty($p['formulation'])) {
|
||||||
|
$cleanLabel .= ' [' . trim($p['formulation']) . ']';
|
||||||
|
}
|
||||||
|
if (!empty($p['genericname'])) {
|
||||||
|
$cleanLabel .= ' - ' . trim($p['genericname']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$result[] = [
|
||||||
|
'label' => $cleanLabel,
|
||||||
|
'value' => $p['item_name'],
|
||||||
|
'item_code' => (int)$p['id'],
|
||||||
|
'generic_name' => (string)($p['genericname'] ?? ''),
|
||||||
|
'formulation' => (string)($p['formulation'] ?? 'Tablet'),
|
||||||
|
'packing' => $packing,
|
||||||
|
'estimated_ptr' => number_format($rate, 2, '.', ''),
|
||||||
|
'gst_rate' => (float)$gstRate,
|
||||||
|
'discount_percent' => number_format($tradeDisc, 2, '.', ''),
|
||||||
|
'sch_disc_per' => number_format($schDiscPer, 2, '.', ''),
|
||||||
|
'sch_disc_amt' => number_format($schDiscAmt, 2, '.', ''),
|
||||||
|
'qty_free' => (float)$freeQty,
|
||||||
|
'scheme_info' => $schemeInfo,
|
||||||
|
'scheme_summary' => $schemeSummary,
|
||||||
|
'has_last_purchase' => $lastPur ? 1 : 0,
|
||||||
|
'is_vendor_match' => $isVendorMatch ? 1 : 0,
|
||||||
|
'last_supplier_name' => (string)($lastPur['supp_name'] ?? ''),
|
||||||
|
'last_purchase_date' => !empty($lastPur['date_of_invoice']) ? date('d-M-Y', strtotime($lastPur['date_of_invoice'])) : '',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->response->setJSON($result);
|
||||||
|
}
|
||||||
|
|
||||||
// =========================================================================
|
// =========================================================================
|
||||||
// 2. GOODS RECEIVED NOTE (GRN)
|
// 2. GOODS RECEIVED NOTE (GRN)
|
||||||
// =========================================================================
|
// =========================================================================
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,9 @@ class PharmacyPurchaseOrderItemModel extends Model
|
||||||
'received_packs',
|
'received_packs',
|
||||||
'remaining_packs',
|
'remaining_packs',
|
||||||
'estimated_ptr',
|
'estimated_ptr',
|
||||||
|
'discount_percent',
|
||||||
|
'sch_disc_per',
|
||||||
|
'scheme_info',
|
||||||
'gst_rate',
|
'gst_rate',
|
||||||
'estimated_amount',
|
'estimated_amount',
|
||||||
'status',
|
'status',
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,9 @@
|
||||||
<div class="card-body p-3">
|
<div class="card-body p-3">
|
||||||
<form id="po-create-form" onsubmit="event.preventDefault(); submitPoForm();">
|
<form id="po-create-form" onsubmit="event.preventDefault(); submitPoForm();">
|
||||||
<?= csrf_field() ?>
|
<?= csrf_field() ?>
|
||||||
|
<?php if ($isEdit): ?>
|
||||||
|
<input type="hidden" name="po_id" id="po_id" value="<?= (int)($po['po_id'] ?? 0) ?>">
|
||||||
|
<?php endif; ?>
|
||||||
<div class="row g-3">
|
<div class="row g-3">
|
||||||
<div class="col-md-4">
|
<div class="col-md-4">
|
||||||
<label class="form-label form-label-sm fw-semibold">Supplier / Pharmaceutical Vendor <span class="text-danger">*</span></label>
|
<label class="form-label form-label-sm fw-semibold">Supplier / Pharmaceutical Vendor <span class="text-danger">*</span></label>
|
||||||
|
|
@ -87,7 +90,7 @@
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body p-3">
|
<div class="card-body p-3">
|
||||||
<div class="row g-2 align-items-end mb-3">
|
<div class="row g-2 align-items-end mb-2">
|
||||||
<div class="col-md-5">
|
<div class="col-md-5">
|
||||||
<label class="form-label form-label-sm mb-1 fw-semibold">Search Medicine / Drug Master</label>
|
<label class="form-label form-label-sm mb-1 fw-semibold">Search Medicine / Drug Master</label>
|
||||||
<input type="text" id="quick_drug_search" class="form-control form-control-sm" placeholder="Type medicine brand name or generic salt..." autocomplete="off">
|
<input type="text" id="quick_drug_search" class="form-control form-control-sm" placeholder="Type medicine brand name or generic salt..." autocomplete="off">
|
||||||
|
|
@ -95,18 +98,22 @@
|
||||||
<input type="hidden" id="selected_item_name" value="">
|
<input type="hidden" id="selected_item_name" value="">
|
||||||
<input type="hidden" id="selected_generic_name" value="">
|
<input type="hidden" id="selected_generic_name" value="">
|
||||||
<input type="hidden" id="selected_category" value="">
|
<input type="hidden" id="selected_category" value="">
|
||||||
|
<input type="hidden" id="selected_gst_rate" value="12">
|
||||||
|
<input type="hidden" id="selected_discount_percent" value="0">
|
||||||
|
<input type="hidden" id="selected_sch_disc_per" value="0">
|
||||||
|
<input type="hidden" id="selected_scheme_info" value="">
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-2">
|
<div class="col-md-2">
|
||||||
<label class="form-label form-label-sm mb-1 fw-semibold">Pack Size (Units)</label>
|
<label class="form-label form-label-sm mb-1 fw-semibold">Pack Size (Units)</label>
|
||||||
<input type="number" id="quick_units_per_pack" class="form-control form-control-sm" value="10" min="1" step="1">
|
<input type="number" id="quick_units_per_pack" class="form-control form-control-sm text-center" value="10" min="1" step="1">
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-2">
|
<div class="col-md-2">
|
||||||
<label class="form-label form-label-sm mb-1 fw-semibold">Order Packs</label>
|
<label class="form-label form-label-sm mb-1 fw-semibold">Order Packs</label>
|
||||||
<input type="number" id="quick_order_packs" class="form-control form-control-sm" value="10" min="1" step="1">
|
<input type="number" id="quick_order_packs" class="form-control form-control-sm text-center" value="10" min="1" step="1">
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-2">
|
<div class="col-md-2">
|
||||||
<label class="form-label form-label-sm mb-1 fw-semibold">Est. Rate / Pack (₹)</label>
|
<label class="form-label form-label-sm mb-1 fw-semibold">Est. Rate / Pack (₹)</label>
|
||||||
<input type="number" id="quick_ptr" class="form-control form-control-sm" value="0.00" min="0" step="0.01">
|
<input type="number" id="quick_ptr" class="form-control form-control-sm text-end" value="0.00" min="0" step="0.01">
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-1">
|
<div class="col-md-1">
|
||||||
<button type="button" class="btn btn-primary btn-sm w-100" id="btn-add-line" onclick="addItemToPoTable();" disabled>
|
<button type="button" class="btn btn-primary btn-sm w-100" id="btn-add-line" onclick="addItemToPoTable();" disabled>
|
||||||
|
|
@ -115,25 +122,35 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Selected Drug History Pill -->
|
||||||
|
<div id="selected_drug_info_pill" class="mb-3" style="display: none;"></div>
|
||||||
|
|
||||||
<div class="table-responsive">
|
<div class="table-responsive">
|
||||||
<table class="table table-bordered table-sm align-middle mb-0" id="po-items-table">
|
<table class="table table-bordered table-sm align-middle mb-0" id="po-items-table">
|
||||||
<thead class="table-light">
|
<thead class="table-light">
|
||||||
<tr>
|
<tr>
|
||||||
<th style="width: 40px;">#</th>
|
<th style="width: 35px;">#</th>
|
||||||
<th>Product Name</th>
|
<th>Product Name</th>
|
||||||
<th>Generic / Salt</th>
|
<th>Generic / Salt</th>
|
||||||
<th>Formulation</th>
|
<th style="width: 85px;">Formulation</th>
|
||||||
<th class="text-center" style="width: 100px;">Units/Pack</th>
|
<th class="text-center" style="width: 85px;">Units/Pack</th>
|
||||||
<th class="text-center" style="width: 110px;">Order Packs</th>
|
<th class="text-center" style="width: 95px;">Order Packs</th>
|
||||||
<th class="text-end" style="width: 120px;">Est. Rate (₹)</th>
|
<th class="text-end" style="width: 105px;">Est. Rate (₹)</th>
|
||||||
<th class="text-center" style="width: 90px;">GST %</th>
|
<th class="text-center" style="width: 130px;">Scheme / Disc</th>
|
||||||
<th class="text-end" style="width: 130px;">Line Total (₹)</th>
|
<th class="text-center" style="width: 95px;">GST %</th>
|
||||||
<th style="width: 50px;"></th>
|
<th class="text-end" style="width: 115px;">Line Total (₹)</th>
|
||||||
|
<th style="width: 40px;"></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody id="po-items-body">
|
<tbody id="po-items-body">
|
||||||
<?php if (!empty($poItems)): ?>
|
<?php if (!empty($poItems)): ?>
|
||||||
<?php foreach ($poItems as $idx => $it): ?>
|
<?php foreach ($poItems as $idx => $it): ?>
|
||||||
|
<?php
|
||||||
|
$itGst = (float)($it['gst_rate'] ?? 12);
|
||||||
|
$discPer = (float)($it['discount_percent'] ?? 0);
|
||||||
|
$schDiscPer = (float)($it['sch_disc_per'] ?? 0);
|
||||||
|
$schInfo = (string)($it['scheme_info'] ?? '');
|
||||||
|
?>
|
||||||
<tr data-item-id="<?= (int)$it['item_id'] ?>">
|
<tr data-item-id="<?= (int)$it['item_id'] ?>">
|
||||||
<td class="row-num"><?= $idx + 1 ?></td>
|
<td class="row-num"><?= $idx + 1 ?></td>
|
||||||
<td>
|
<td>
|
||||||
|
|
@ -150,20 +167,37 @@
|
||||||
<input type="hidden" name="items[<?= $idx ?>][category]" value="<?= esc($it['category']) ?>">
|
<input type="hidden" name="items[<?= $idx ?>][category]" value="<?= esc($it['category']) ?>">
|
||||||
</td>
|
</td>
|
||||||
<td class="text-center">
|
<td class="text-center">
|
||||||
<input type="number" name="items[<?= $idx ?>][units_per_pack]" class="form-control form-control-sm text-center line-units" value="<?= (int)$it['units_per_pack'] ?>" min="1">
|
<input type="number" name="items[<?= $idx ?>][units_per_pack]" class="form-control form-control-sm text-center line-units px-1" value="<?= (int)$it['units_per_pack'] ?>" min="1">
|
||||||
</td>
|
</td>
|
||||||
<td class="text-center">
|
<td class="text-center">
|
||||||
<input type="number" name="items[<?= $idx ?>][order_packs]" class="form-control form-control-sm text-center line-packs" value="<?= (float)$it['order_packs'] ?>" min="1" onchange="recalculatePoTotals();">
|
<input type="number" name="items[<?= $idx ?>][order_packs]" class="form-control form-control-sm text-center line-packs px-1" value="<?= (float)$it['order_packs'] ?>" min="1" onchange="recalculatePoTotals();">
|
||||||
</td>
|
</td>
|
||||||
<td class="text-end">
|
<td class="text-end">
|
||||||
<input type="number" name="items[<?= $idx ?>][estimated_ptr]" class="form-control form-control-sm text-end line-rate" value="<?= number_format((float)$it['estimated_ptr'], 2, '.', '') ?>" min="0" step="0.01" onchange="recalculatePoTotals();">
|
<input type="number" name="items[<?= $idx ?>][estimated_ptr]" class="form-control form-control-sm text-end line-rate px-1" value="<?= number_format((float)$it['estimated_ptr'], 2, '.', '') ?>" min="0" step="0.01" onchange="recalculatePoTotals();">
|
||||||
</td>
|
</td>
|
||||||
<td class="text-center">
|
<td class="text-center">
|
||||||
<select name="items[<?= $idx ?>][gst_rate]" class="form-select form-select-sm text-center line-gst" onchange="recalculatePoTotals();">
|
<div class="d-flex flex-column gap-1">
|
||||||
<option value="0" <?= ((float)$it['gst_rate'] == 0) ? 'selected' : '' ?>>0%</option>
|
<div class="input-group input-group-sm">
|
||||||
<option value="5" <?= ((float)$it['gst_rate'] == 5) ? 'selected' : '' ?>>5%</option>
|
<span class="input-group-text py-0 px-1 bg-light text-muted" style="font-size:10px;">Disc%</span>
|
||||||
<option value="12" <?= ((float)$it['gst_rate'] == 12) ? 'selected' : '' ?>>12%</option>
|
<input type="number" name="items[<?= $idx ?>][discount_percent]" class="form-control form-control-sm text-end line-disc px-1" value="<?= $discPer > 0 ? number_format($discPer, 2, '.', '') : '0' ?>" min="0" max="100" step="0.01" onchange="recalculatePoTotals();" style="font-size:11px;">
|
||||||
<option value="18" <?= ((float)$it['gst_rate'] == 18) ? 'selected' : '' ?>>18%</option>
|
</div>
|
||||||
|
<div class="input-group input-group-sm">
|
||||||
|
<span class="input-group-text py-0 px-1 bg-light text-muted" style="font-size:10px;">Sch</span>
|
||||||
|
<input type="text" name="items[<?= $idx ?>][scheme_info]" class="form-control form-control-sm line-scheme px-1" value="<?= esc($schInfo) ?>" placeholder="e.g. 10+1" style="font-size:11px;">
|
||||||
|
</div>
|
||||||
|
<input type="hidden" name="items[<?= $idx ?>][sch_disc_per]" class="line-sch-per" value="<?= $schDiscPer ?>">
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td class="text-center">
|
||||||
|
<select name="items[<?= $idx ?>][gst_rate]" class="form-select form-select-sm text-center line-gst px-1" onchange="recalculatePoTotals();">
|
||||||
|
<option value="0" <?= ((float)$itGst == 0) ? 'selected' : '' ?>>0%</option>
|
||||||
|
<option value="5" <?= (abs((float)$itGst - 5) < 0.1) ? 'selected' : '' ?>>5%</option>
|
||||||
|
<option value="12" <?= (abs((float)$itGst - 12) < 0.1) ? 'selected' : '' ?>>12%</option>
|
||||||
|
<option value="18" <?= (abs((float)$itGst - 18) < 0.1) ? 'selected' : '' ?>>18%</option>
|
||||||
|
<option value="28" <?= (abs((float)$itGst - 28) < 0.1) ? 'selected' : '' ?>>28%</option>
|
||||||
|
<?php if (!in_array((float)$itGst, [0, 5, 12, 18, 28])): ?>
|
||||||
|
<option value="<?= (float)$itGst ?>" selected><?= (float)$itGst ?>%</option>
|
||||||
|
<?php endif; ?>
|
||||||
</select>
|
</select>
|
||||||
</td>
|
</td>
|
||||||
<td class="text-end fw-semibold line-total-display">
|
<td class="text-end fw-semibold line-total-display">
|
||||||
|
|
@ -176,7 +210,7 @@
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
<?php else: ?>
|
<?php else: ?>
|
||||||
<tr id="no-items-row">
|
<tr id="no-items-row">
|
||||||
<td colspan="10" class="text-center py-4 text-muted">
|
<td colspan="11" class="text-center py-4 text-muted">
|
||||||
No items added yet. Search above to add items or auto-load low stock items.
|
No items added yet. Search above to add items or auto-load low stock items.
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
@ -184,18 +218,23 @@
|
||||||
</tbody>
|
</tbody>
|
||||||
<tfoot class="table-light">
|
<tfoot class="table-light">
|
||||||
<tr>
|
<tr>
|
||||||
<th colspan="8" class="text-end">Estimated Subtotal:</th>
|
<th colspan="9" class="text-end">Estimated Subtotal (Taxable):</th>
|
||||||
<th class="text-end" id="po-subtotal-disp">₹0.00</th>
|
<th class="text-end font-monospace" id="po-subtotal-disp">₹0.00</th>
|
||||||
|
<th></th>
|
||||||
|
</tr>
|
||||||
|
<tr id="po-disc-row" style="display:none;">
|
||||||
|
<th colspan="9" class="text-end text-success">Total Discount Saved:</th>
|
||||||
|
<th class="text-end text-success font-monospace" id="po-discount-disp">-₹0.00</th>
|
||||||
<th></th>
|
<th></th>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th colspan="8" class="text-end">Estimated GST Tax:</th>
|
<th colspan="9" class="text-end">Estimated GST Tax:</th>
|
||||||
<th class="text-end" id="po-tax-disp">₹0.00</th>
|
<th class="text-end font-monospace" id="po-tax-disp">₹0.00</th>
|
||||||
<th></th>
|
<th></th>
|
||||||
</tr>
|
</tr>
|
||||||
<tr class="table-primary">
|
<tr class="table-primary">
|
||||||
<th colspan="8" class="text-end fs-6">Grand Total Commitment:</th>
|
<th colspan="9" class="text-end fs-6">Grand Total Commitment:</th>
|
||||||
<th class="text-end fs-6 text-primary fw-bold" id="po-grand-total-disp">₹0.00</th>
|
<th class="text-end fs-6 text-primary fw-bold font-monospace" id="po-grand-total-disp">₹0.00</th>
|
||||||
<th></th>
|
<th></th>
|
||||||
</tr>
|
</tr>
|
||||||
</tfoot>
|
</tfoot>
|
||||||
|
|
@ -235,20 +274,55 @@ function initPoDrugAutocomplete() {
|
||||||
|
|
||||||
$("#quick_drug_search").autocomplete({
|
$("#quick_drug_search").autocomplete({
|
||||||
source: function(request, response) {
|
source: function(request, response) {
|
||||||
$.getJSON("<?= base_url('Medical/get_drug') ?>", request, function(data) {
|
var suppId = $('#po_supplier_id').val() || 0;
|
||||||
|
$.getJSON("<?= base_url('Medical/po_search_drug') ?>", {
|
||||||
|
term: request.term,
|
||||||
|
supplier_id: suppId
|
||||||
|
}, function(data) {
|
||||||
response(data || []);
|
response(data || []);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
minLength: 1,
|
minLength: 1,
|
||||||
select: function(event, ui) {
|
select: function(event, ui) {
|
||||||
var item = ui.item || {};
|
var item = ui.item || {};
|
||||||
document.getElementById('selected_item_id').value = item.item_code || item.l_item_code || '';
|
document.getElementById('selected_item_id').value = item.item_code || '';
|
||||||
document.getElementById('selected_item_name').value = item.value || '';
|
document.getElementById('selected_item_name').value = item.value || '';
|
||||||
document.getElementById('selected_generic_name').value = item.generic_name || '';
|
document.getElementById('selected_generic_name').value = item.generic_name || '';
|
||||||
document.getElementById('selected_category').value = item.formulation || 'Tablet';
|
document.getElementById('selected_category').value = item.formulation || 'Tablet';
|
||||||
document.getElementById('quick_units_per_pack').value = item.l_packing || 10;
|
document.getElementById('quick_units_per_pack').value = item.packing || 10;
|
||||||
document.getElementById('quick_ptr').value = item.l_unit_rate ? (parseFloat(item.l_unit_rate) * (item.l_packing || 10)).toFixed(2) : '100.00';
|
document.getElementById('quick_ptr').value = item.estimated_ptr || '0.00';
|
||||||
|
document.getElementById('selected_gst_rate').value = item.gst_rate !== undefined ? item.gst_rate : 12;
|
||||||
|
document.getElementById('selected_discount_percent').value = item.discount_percent || 0;
|
||||||
|
document.getElementById('selected_sch_disc_per').value = item.sch_disc_per || 0;
|
||||||
|
document.getElementById('selected_scheme_info').value = item.scheme_info || '';
|
||||||
document.getElementById('btn-add-line').disabled = false;
|
document.getElementById('btn-add-line').disabled = false;
|
||||||
|
|
||||||
|
// Render Drug History / Scheme info pill
|
||||||
|
var pill = document.getElementById('selected_drug_info_pill');
|
||||||
|
if (pill) {
|
||||||
|
var h = '<div class="alert alert-light border py-1 px-3 d-flex flex-wrap align-items-center justify-content-between gap-2 shadow-sm" style="font-size:12px;">';
|
||||||
|
h += '<div>';
|
||||||
|
h += '<strong class="text-primary me-2"><i class="bi bi-capsule"></i> ' + escapeHtml(item.value) + '</strong>';
|
||||||
|
if (item.has_last_purchase) {
|
||||||
|
h += '<span class="badge bg-secondary me-1">Last Rate: ₹' + item.estimated_ptr + '</span>';
|
||||||
|
h += '<span class="badge bg-info text-dark me-1">GST: ' + item.gst_rate + '%</span>';
|
||||||
|
if (item.scheme_summary) {
|
||||||
|
h += '<span class="badge bg-success me-1"><i class="bi bi-tag-fill"></i> ' + escapeHtml(item.scheme_summary) + '</span>';
|
||||||
|
}
|
||||||
|
if (item.is_vendor_match) {
|
||||||
|
h += '<span class="badge bg-success-subtle text-success border border-success-subtle ms-1"><i class="bi bi-check-circle"></i> From selected vendor</span>';
|
||||||
|
} else if (item.last_supplier_name) {
|
||||||
|
h += '<span class="text-muted ms-1 small">(Last: ' + escapeHtml(item.last_supplier_name) + (item.last_purchase_date ? ' ' + item.last_purchase_date : '') + ')</span>';
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
h += '<span class="badge bg-light text-muted border">New Item • Master GST: ' + item.gst_rate + '%</span>';
|
||||||
|
}
|
||||||
|
h += '</div>';
|
||||||
|
h += '<span class="text-muted fst-italic small">Auto-filled rate & GST • Click + Add</span>';
|
||||||
|
h += '</div>';
|
||||||
|
pill.innerHTML = h;
|
||||||
|
pill.style.display = 'block';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -271,7 +345,10 @@ function addItemToPoTable(prefill) {
|
||||||
units_per_pack: document.getElementById('quick_units_per_pack').value || 10,
|
units_per_pack: document.getElementById('quick_units_per_pack').value || 10,
|
||||||
order_packs: document.getElementById('quick_order_packs').value || 10,
|
order_packs: document.getElementById('quick_order_packs').value || 10,
|
||||||
estimated_ptr: document.getElementById('quick_ptr').value || 0,
|
estimated_ptr: document.getElementById('quick_ptr').value || 0,
|
||||||
gst_rate: 12
|
gst_rate: document.getElementById('selected_gst_rate').value || 12,
|
||||||
|
discount_percent: document.getElementById('selected_discount_percent').value || 0,
|
||||||
|
sch_disc_per: document.getElementById('selected_sch_disc_per').value || 0,
|
||||||
|
scheme_info: document.getElementById('selected_scheme_info').value || ''
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!item.item_id || !item.item_name) {
|
if (!item.item_id || !item.item_name) {
|
||||||
|
|
@ -296,6 +373,21 @@ function addItemToPoTable(prefill) {
|
||||||
var tr = document.createElement('tr');
|
var tr = document.createElement('tr');
|
||||||
tr.setAttribute('data-item-id', item.item_id);
|
tr.setAttribute('data-item-id', item.item_id);
|
||||||
|
|
||||||
|
var itemGst = parseFloat(item.gst_rate !== undefined ? item.gst_rate : 12);
|
||||||
|
var discVal = parseFloat(item.discount_percent || 0);
|
||||||
|
var schVal = parseFloat(item.sch_disc_per || 0);
|
||||||
|
var schInfo = item.scheme_info || '';
|
||||||
|
|
||||||
|
// Standard GST options
|
||||||
|
var standardGsts = [0, 5, 12, 18, 28];
|
||||||
|
var gstOptionsHtml = '';
|
||||||
|
standardGsts.forEach(function(g) {
|
||||||
|
gstOptionsHtml += `<option value="${g}" ${Math.abs(itemGst - g) < 0.1 ? 'selected' : ''}>${g}%</option>`;
|
||||||
|
});
|
||||||
|
if (!standardGsts.some(g => Math.abs(itemGst - g) < 0.1)) {
|
||||||
|
gstOptionsHtml += `<option value="${itemGst}" selected>${itemGst}%</option>`;
|
||||||
|
}
|
||||||
|
|
||||||
tr.innerHTML = `
|
tr.innerHTML = `
|
||||||
<td class="row-num">${rowIdx + 1}</td>
|
<td class="row-num">${rowIdx + 1}</td>
|
||||||
<td>
|
<td>
|
||||||
|
|
@ -312,20 +404,30 @@ function addItemToPoTable(prefill) {
|
||||||
<input type="hidden" name="items[${rowIdx}][category]" value="${escapeHtml(item.category || '')}">
|
<input type="hidden" name="items[${rowIdx}][category]" value="${escapeHtml(item.category || '')}">
|
||||||
</td>
|
</td>
|
||||||
<td class="text-center">
|
<td class="text-center">
|
||||||
<input type="number" name="items[${rowIdx}][units_per_pack]" class="form-control form-control-sm text-center line-units" value="${item.units_per_pack}" min="1">
|
<input type="number" name="items[${rowIdx}][units_per_pack]" class="form-control form-control-sm text-center line-units px-1" value="${item.units_per_pack}" min="1">
|
||||||
</td>
|
</td>
|
||||||
<td class="text-center">
|
<td class="text-center">
|
||||||
<input type="number" name="items[${rowIdx}][order_packs]" class="form-control form-control-sm text-center line-packs" value="${item.order_packs}" min="1" onchange="recalculatePoTotals();">
|
<input type="number" name="items[${rowIdx}][order_packs]" class="form-control form-control-sm text-center line-packs px-1" value="${item.order_packs}" min="1" onchange="recalculatePoTotals();">
|
||||||
</td>
|
</td>
|
||||||
<td class="text-end">
|
<td class="text-end">
|
||||||
<input type="number" name="items[${rowIdx}][estimated_ptr]" class="form-control form-control-sm text-end line-rate" value="${parseFloat(item.estimated_ptr).toFixed(2)}" min="0" step="0.01" onchange="recalculatePoTotals();">
|
<input type="number" name="items[${rowIdx}][estimated_ptr]" class="form-control form-control-sm text-end line-rate px-1" value="${parseFloat(item.estimated_ptr).toFixed(2)}" min="0" step="0.01" onchange="recalculatePoTotals();">
|
||||||
</td>
|
</td>
|
||||||
<td class="text-center">
|
<td class="text-center">
|
||||||
<select name="items[${rowIdx}][gst_rate]" class="form-select form-select-sm text-center line-gst" onchange="recalculatePoTotals();">
|
<div class="d-flex flex-column gap-1">
|
||||||
<option value="0" ${item.gst_rate == 0 ? 'selected' : ''}>0%</option>
|
<div class="input-group input-group-sm">
|
||||||
<option value="5" ${item.gst_rate == 5 ? 'selected' : ''}>5%</option>
|
<span class="input-group-text py-0 px-1 bg-light text-muted" style="font-size:10px;">Disc%</span>
|
||||||
<option value="12" ${item.gst_rate == 12 ? 'selected' : ''}>12%</option>
|
<input type="number" name="items[${rowIdx}][discount_percent]" class="form-control form-control-sm text-end line-disc px-1" value="${discVal > 0 ? discVal.toFixed(2) : '0'}" min="0" max="100" step="0.01" onchange="recalculatePoTotals();" style="font-size:11px;">
|
||||||
<option value="18" ${item.gst_rate == 18 ? 'selected' : ''}>18%</option>
|
</div>
|
||||||
|
<div class="input-group input-group-sm">
|
||||||
|
<span class="input-group-text py-0 px-1 bg-light text-muted" style="font-size:10px;">Sch</span>
|
||||||
|
<input type="text" name="items[${rowIdx}][scheme_info]" class="form-control form-control-sm line-scheme px-1" value="${escapeHtml(schInfo)}" placeholder="e.g. 10+1" style="font-size:11px;">
|
||||||
|
</div>
|
||||||
|
<input type="hidden" name="items[${rowIdx}][sch_disc_per]" class="line-sch-per" value="${schVal}">
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td class="text-center">
|
||||||
|
<select name="items[${rowIdx}][gst_rate]" class="form-select form-select-sm text-center line-gst px-1" onchange="recalculatePoTotals();">
|
||||||
|
${gstOptionsHtml}
|
||||||
</select>
|
</select>
|
||||||
</td>
|
</td>
|
||||||
<td class="text-end fw-semibold line-total-display">₹0.00</td>
|
<td class="text-end fw-semibold line-total-display">₹0.00</td>
|
||||||
|
|
@ -344,7 +446,19 @@ function resetQuickAddInputs() {
|
||||||
document.getElementById('selected_item_id').value = '';
|
document.getElementById('selected_item_id').value = '';
|
||||||
document.getElementById('selected_item_name').value = '';
|
document.getElementById('selected_item_name').value = '';
|
||||||
document.getElementById('selected_generic_name').value = '';
|
document.getElementById('selected_generic_name').value = '';
|
||||||
|
document.getElementById('selected_gst_rate').value = '12';
|
||||||
|
document.getElementById('selected_discount_percent').value = '0';
|
||||||
|
document.getElementById('selected_sch_disc_per').value = '0';
|
||||||
|
document.getElementById('selected_scheme_info').value = '';
|
||||||
|
document.getElementById('quick_ptr').value = '0.00';
|
||||||
document.getElementById('btn-add-line').disabled = true;
|
document.getElementById('btn-add-line').disabled = true;
|
||||||
|
|
||||||
|
var pill = document.getElementById('selected_drug_info_pill');
|
||||||
|
if (pill) {
|
||||||
|
pill.innerHTML = '';
|
||||||
|
pill.style.display = 'none';
|
||||||
|
}
|
||||||
|
|
||||||
document.getElementById('quick_drug_search').focus();
|
document.getElementById('quick_drug_search').focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -353,7 +467,7 @@ function removePoRow(btn) {
|
||||||
var tbody = document.getElementById('po-items-body');
|
var tbody = document.getElementById('po-items-body');
|
||||||
var rows = tbody.querySelectorAll('tr');
|
var rows = tbody.querySelectorAll('tr');
|
||||||
if (rows.length === 0) {
|
if (rows.length === 0) {
|
||||||
tbody.innerHTML = '<tr id="no-items-row"><td colspan="10" class="text-center py-4 text-muted">No items added yet.</td></tr>';
|
tbody.innerHTML = '<tr id="no-items-row"><td colspan="11" class="text-center py-4 text-muted">No items added yet. Search above to add items or auto-load low stock items.</td></tr>';
|
||||||
} else {
|
} else {
|
||||||
rows.forEach(function(r, idx) {
|
rows.forEach(function(r, idx) {
|
||||||
var numTd = r.querySelector('.row-num');
|
var numTd = r.querySelector('.row-num');
|
||||||
|
|
@ -365,28 +479,48 @@ function removePoRow(btn) {
|
||||||
|
|
||||||
function recalculatePoTotals() {
|
function recalculatePoTotals() {
|
||||||
var rows = document.querySelectorAll('#po-items-body tr:not(#no-items-row)');
|
var rows = document.querySelectorAll('#po-items-body tr:not(#no-items-row)');
|
||||||
var subtotal = 0.0;
|
var totalGross = 0.0;
|
||||||
|
var totalDiscount = 0.0;
|
||||||
|
var totalTaxable = 0.0;
|
||||||
var totalTax = 0.0;
|
var totalTax = 0.0;
|
||||||
|
|
||||||
rows.forEach(function(r) {
|
rows.forEach(function(r) {
|
||||||
var packs = parseFloat(r.querySelector('.line-packs')?.value || 0);
|
var packs = parseFloat(r.querySelector('.line-packs')?.value || 0);
|
||||||
var rate = parseFloat(r.querySelector('.line-rate')?.value || 0);
|
var rate = parseFloat(r.querySelector('.line-rate')?.value || 0);
|
||||||
|
var discPer = parseFloat(r.querySelector('.line-disc')?.value || 0);
|
||||||
|
var schPer = parseFloat(r.querySelector('.line-sch-per')?.value || 0);
|
||||||
var gst = parseFloat(r.querySelector('.line-gst')?.value || 0);
|
var gst = parseFloat(r.querySelector('.line-gst')?.value || 0);
|
||||||
|
|
||||||
var lineSub = packs * rate;
|
var lineGross = packs * rate;
|
||||||
var lineTax = lineSub * (gst / 100);
|
var schAmt = (schPer > 0) ? (lineGross * (schPer / 100)) : 0.0;
|
||||||
var lineTotal = lineSub + lineTax;
|
var afterSch = Math.max(0.0, lineGross - schAmt);
|
||||||
|
var tradeDiscAmt = (discPer > 0) ? (afterSch * (discPer / 100)) : 0.0;
|
||||||
|
var lineTaxable = Math.max(0.0, afterSch - tradeDiscAmt);
|
||||||
|
var lineTax = lineTaxable * (gst / 100);
|
||||||
|
var lineTotal = lineTaxable + lineTax;
|
||||||
|
|
||||||
subtotal += lineSub;
|
totalGross += lineGross;
|
||||||
|
totalDiscount += (schAmt + tradeDiscAmt);
|
||||||
|
totalTaxable += lineTaxable;
|
||||||
totalTax += lineTax;
|
totalTax += lineTax;
|
||||||
|
|
||||||
var totalDisp = r.querySelector('.line-total-display');
|
var totalDisp = r.querySelector('.line-total-display');
|
||||||
if (totalDisp) totalDisp.innerText = '₹' + lineTotal.toFixed(2);
|
if (totalDisp) totalDisp.innerText = '₹' + lineTotal.toFixed(2);
|
||||||
});
|
});
|
||||||
|
|
||||||
document.getElementById('po-subtotal-disp').innerText = '₹' + subtotal.toFixed(2);
|
document.getElementById('po-subtotal-disp').innerText = '₹' + totalTaxable.toFixed(2);
|
||||||
document.getElementById('po-tax-disp').innerText = '₹' + totalTax.toFixed(2);
|
document.getElementById('po-tax-disp').innerText = '₹' + totalTax.toFixed(2);
|
||||||
document.getElementById('po-grand-total-disp').innerText = '₹' + (subtotal + totalTax).toFixed(2);
|
document.getElementById('po-grand-total-disp').innerText = '₹' + (totalTaxable + totalTax).toFixed(2);
|
||||||
|
|
||||||
|
var discRow = document.getElementById('po-disc-row');
|
||||||
|
if (discRow) {
|
||||||
|
if (totalDiscount > 0) {
|
||||||
|
discRow.style.display = '';
|
||||||
|
document.getElementById('po-discount-disp').innerText = '-₹' + totalDiscount.toFixed(2);
|
||||||
|
} else {
|
||||||
|
discRow.style.display = 'none';
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function fetchSupplierReorderItems() {
|
function fetchSupplierReorderItems() {
|
||||||
|
|
@ -406,8 +540,11 @@ function fetchSupplierReorderItems() {
|
||||||
category: it.category || 'Tablet',
|
category: it.category || 'Tablet',
|
||||||
units_per_pack: it.units_per_pack || 10,
|
units_per_pack: it.units_per_pack || 10,
|
||||||
order_packs: 10,
|
order_packs: 10,
|
||||||
estimated_ptr: it.estimated_ptr || 50,
|
estimated_ptr: it.estimated_ptr || 0,
|
||||||
gst_rate: it.gst_rate || 12
|
gst_rate: it.gst_rate !== undefined ? it.gst_rate : 12,
|
||||||
|
discount_percent: it.discount_percent || 0,
|
||||||
|
sch_disc_per: it.sch_disc_per || 0,
|
||||||
|
scheme_info: it.scheme_info || ''
|
||||||
});
|
});
|
||||||
added++;
|
added++;
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -102,19 +102,45 @@ if (!isset($branding) || !is_array($branding)) {
|
||||||
<table class="items-table">
|
<table class="items-table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th style="width: 30px;">#</th>
|
<th style="width: 25px;">#</th>
|
||||||
<th>Item / Brand Name</th>
|
<th>Item / Brand Name</th>
|
||||||
<th>Salt Composition</th>
|
<th>Salt Composition</th>
|
||||||
<th class="text-center" style="width: 70px;">Pack Size</th>
|
<th class="text-center" style="width: 55px;">Pack Size</th>
|
||||||
<th class="text-center" style="width: 70px;">Order Qty</th>
|
<th class="text-center" style="width: 55px;">Order Qty</th>
|
||||||
<th class="text-end" style="width: 80px;">Rate/Pack</th>
|
<th class="text-end" style="width: 75px;">Rate/Pack</th>
|
||||||
<th class="text-center" style="width: 50px;">GST %</th>
|
<th class="text-center" style="width: 80px;">Scheme / Disc</th>
|
||||||
<th class="text-end" style="width: 90px;">Total (₹)</th>
|
<th class="text-center" style="width: 48px;">GST %</th>
|
||||||
|
<th class="text-end" style="width: 85px;">Total (₹)</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<?php if (!empty($items)): ?>
|
<?php
|
||||||
|
$calcGross = 0.0;
|
||||||
|
$calcDiscount = 0.0;
|
||||||
|
if (!empty($items)):
|
||||||
|
?>
|
||||||
<?php foreach ($items as $idx => $it): ?>
|
<?php foreach ($items as $idx => $it): ?>
|
||||||
|
<?php
|
||||||
|
$lineGross = round((float)$it['order_packs'] * (float)$it['estimated_ptr'], 2);
|
||||||
|
$schPer = (float)($it['sch_disc_per'] ?? 0);
|
||||||
|
$schAmt = ($schPer > 0) ? round($lineGross * ($schPer / 100), 2) : 0.0;
|
||||||
|
$afterSch = max(0.0, $lineGross - $schAmt);
|
||||||
|
$discPer = (float)($it['discount_percent'] ?? 0);
|
||||||
|
$discAmt = ($discPer > 0) ? round($afterSch * ($discPer / 100), 2) : 0.0;
|
||||||
|
$calcGross += $lineGross;
|
||||||
|
$calcDiscount += ($schAmt + $discAmt);
|
||||||
|
|
||||||
|
$discTags = [];
|
||||||
|
if ($discPer > 0) {
|
||||||
|
$discTags[] = $discPer . '% Disc';
|
||||||
|
}
|
||||||
|
if ($schPer > 0) {
|
||||||
|
$discTags[] = $schPer . '% Sch';
|
||||||
|
}
|
||||||
|
if (!empty($it['scheme_info'])) {
|
||||||
|
$discTags[] = esc($it['scheme_info']);
|
||||||
|
}
|
||||||
|
?>
|
||||||
<tr>
|
<tr>
|
||||||
<td class="text-center"><?= $idx + 1 ?></td>
|
<td class="text-center"><?= $idx + 1 ?></td>
|
||||||
<td><strong><?= esc($it['item_name']) ?></strong></td>
|
<td><strong><?= esc($it['item_name']) ?></strong></td>
|
||||||
|
|
@ -122,6 +148,9 @@ if (!isset($branding) || !is_array($branding)) {
|
||||||
<td class="text-center"><?= (int)$it['units_per_pack'] ?>s</td>
|
<td class="text-center"><?= (int)$it['units_per_pack'] ?>s</td>
|
||||||
<td class="text-center"><strong><?= (float)$it['order_packs'] ?></strong></td>
|
<td class="text-center"><strong><?= (float)$it['order_packs'] ?></strong></td>
|
||||||
<td class="text-end">₹<?= number_format((float)$it['estimated_ptr'], 2) ?></td>
|
<td class="text-end">₹<?= number_format((float)$it['estimated_ptr'], 2) ?></td>
|
||||||
|
<td class="text-center" style="font-size: 9px;">
|
||||||
|
<?= !empty($discTags) ? implode('<br>', $discTags) : '<span style="color:#94a3b8;">-</span>' ?>
|
||||||
|
</td>
|
||||||
<td class="text-center"><?= (float)$it['gst_rate'] ?>%</td>
|
<td class="text-center"><?= (float)$it['gst_rate'] ?>%</td>
|
||||||
<td class="text-end">₹<?= number_format((float)$it['estimated_amount'], 2) ?></td>
|
<td class="text-end">₹<?= number_format((float)$it['estimated_amount'], 2) ?></td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
@ -131,8 +160,18 @@ if (!isset($branding) || !is_array($branding)) {
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
<table class="totals-table">
|
<table class="totals-table">
|
||||||
|
<?php if ($calcDiscount > 0.009): ?>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Estimated Subtotal:</td>
|
<td>Gross Amount:</td>
|
||||||
|
<td class="text-end">₹<?= number_format($calcGross, 2) ?></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td style="color: #166534;">Total Schemes / Discounts:</td>
|
||||||
|
<td class="text-end" style="color: #166534;">-₹<?= number_format($calcDiscount, 2) ?></td>
|
||||||
|
</tr>
|
||||||
|
<?php endif; ?>
|
||||||
|
<tr>
|
||||||
|
<td>Taxable Subtotal:</td>
|
||||||
<td class="text-end">₹<?= number_format((float)$po['total_estimated_amount'], 2) ?></td>
|
<td class="text-end">₹<?= number_format((float)$po['total_estimated_amount'], 2) ?></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user