Fix GST calculations and synchronize GRN & Purchase Invoice columns with vendor bill structure

This commit is contained in:
DevSoftTech Server 2026-10-22 09:58:04 +05:30
parent 367f63f4f3
commit 09cd46d344
7 changed files with 569 additions and 195 deletions

View File

@ -569,6 +569,8 @@ class MedicalProcurement extends BaseController
$grnDate = trim((string)$this->request->getPost('grn_date') ?: date('Y-m-d'));
$remarks = trim((string)$this->request->getPost('remarks') ?: '');
$postToInventory = (int)($this->request->getPost('post_to_inventory') ?: 0);
$gstType = trim((string)$this->request->getPost('gst_type') ?: 'intra_state');
$roundOff = (float)($this->request->getPost('round_off') ?: 0.0);
$itemsRaw = $this->request->getPost('items');
$items = is_array($itemsRaw) ? $itemsRaw : [];
@ -586,9 +588,13 @@ class MedicalProcurement extends BaseController
$grnNumber = $this->grnModel->generateGrnNumber();
$userId = $this->getCurrentUserId();
$totalGross = 0.0;
$totalDisc = 0.0;
$totalTaxable = 0.0;
$totalCgst = 0.0;
$totalSgst = 0.0;
$totalIgst = 0.0;
$totalTax = 0.0;
$netTotal = 0.0;
$validItems = [];
foreach ($items as $row) {
@ -615,9 +621,12 @@ class MedicalProcurement extends BaseController
}
$discPercent = (float)($row['discount_percent'] ?? 0);
$cgstPer = (float)($row['cgst_percent'] ?? 6.0);
$sgstPer = (float)($row['sgst_percent'] ?? 6.0);
$igstPer = (float)($row['igst_percent'] ?? 0.0);
$schDiscPer = (float)($row['sch_disc_per'] ?? 0);
$schDiscAmt = (float)($row['sch_disc_amt'] ?? 0);
// Determine GST total rate: check gst_rate, then fallback to cgst_percent * 2
$gstRate = isset($row['gst_rate']) ? (float)$row['gst_rate'] : ((float)($row['cgst_percent'] ?? 2.5) * 2);
$hsnCode = trim((string)($row['hsn_code'] ?? ''));
$rackNo = trim((string)($row['rack_no'] ?? ''));
$shelfNo = trim((string)($row['shelf_no'] ?? ''));
@ -631,20 +640,46 @@ class MedicalProcurement extends BaseController
continue;
}
// Calculations
// Calculations strictly aligned with Indian Pharma Invoicing:
$grossAmount = round($receivedPacks * $unitPurchaseRate, 2);
$discountAmt = round($grossAmount * ($discPercent / 100), 2);
$lineTaxable = $grossAmount - $discountAmt;
$cgstAmt = round($lineTaxable * ($cgstPer / 100), 2);
$sgstAmt = round($lineTaxable * ($sgstPer / 100), 2);
$igstAmt = round($lineTaxable * ($igstPer / 100), 2);
$lineTax = $cgstAmt + $sgstAmt + $igstAmt;
$lineTotal = $lineTaxable + $lineTax;
// 1. Scheme discount (percent or amount)
$schPerAmt = ($schDiscPer > 0) ? round($grossAmount * ($schDiscPer / 100), 2) : 0.0;
$lineSchDisc = $schPerAmt + $schDiscAmt;
$afterSch = max(0.0, $grossAmount - $lineSchDisc);
// 2. Trade discount
$tradeDiscAmt = ($discPercent > 0) ? round($afterSch * ($discPercent / 100), 2) : 0.0;
$lineTaxable = max(0.0, $afterSch - $tradeDiscAmt);
// 3. GST Calculation (Equal CGST/SGST split or 100% IGST)
if ($gstType === 'inter_state') {
$cgstPer = 0.0;
$sgstPer = 0.0;
$igstPer = $gstRate;
$cgstAmt = 0.0;
$sgstAmt = 0.0;
$igstAmt = round($lineTaxable * ($igstPer / 100), 2);
$lineTax = $igstAmt;
} else {
$cgstPer = round($gstRate / 2, 2);
$sgstPer = round($gstRate / 2, 2);
$igstPer = 0.0;
$cgstAmt = round($lineTaxable * ($cgstPer / 100), 2);
$sgstAmt = round($lineTaxable * ($sgstPer / 100), 2);
$igstAmt = 0.0;
$lineTax = $cgstAmt + $sgstAmt;
}
$lineTotal = round($lineTaxable + $lineTax, 2);
$totalGross += $grossAmount;
$totalDisc += ($lineSchDisc + $tradeDiscAmt);
$totalTaxable += $lineTaxable;
$totalCgst += $cgstAmt;
$totalSgst += $sgstAmt;
$totalIgst += $igstAmt;
$totalTax += $lineTax;
$netTotal += $lineTotal;
$validItems[] = [
'po_item_id' => $poItemId > 0 ? $poItemId : null,
@ -659,9 +694,12 @@ class MedicalProcurement extends BaseController
'rejected_packs' => $rejectedPacks,
'rejection_reason' => $rejectionReason ?: null,
'unit_purchase_rate' => $unitPurchaseRate,
'gross_amount' => $grossAmount,
'mrp' => $mrp,
'selling_unit_rate' => $sellingUnitRate,
'discount_percent' => $discPercent,
'sch_disc_per' => $schDiscPer,
'sch_disc_amt' => $schDiscAmt,
'taxable_amount' => $lineTaxable,
'cgst_percent' => $cgstPer,
'cgst_amount' => $cgstAmt,
@ -681,6 +719,8 @@ class MedicalProcurement extends BaseController
return $this->response->setJSON(['status' => 0, 'message' => 'No valid item rows with batch and expiry were found.']);
}
$netTotal = round($totalTaxable + $totalTax + $roundOff, 2);
$this->db->transStart();
$grnHeader = [
@ -694,6 +734,11 @@ class MedicalProcurement extends BaseController
'grn_date' => $grnDate,
'total_received_amount' => $totalTaxable,
'total_tax_amount' => $totalTax,
'cgst_amount' => $totalCgst,
'sgst_amount' => $totalSgst,
'igst_amount' => $totalIgst,
'round_off' => $roundOff,
'gst_type' => $gstType,
'net_amount' => $netTotal,
'status' => 'verified',
'received_by' => $userId,
@ -701,7 +746,6 @@ class MedicalProcurement extends BaseController
'verified_at' => date('Y-m-d H:i:s'),
'remarks' => $remarks,
'created_at' => date('Y-m-d H:i:s'),
'updated_at' => date('Y-m-d H:i:s'),
];
$this->grnModel->insert($grnHeader);
@ -812,8 +856,8 @@ class MedicalProcurement extends BaseController
'date_of_invoice' => $grn['invoice_date'],
'Invoice_no' => $grn['supplier_invoice_no'],
'Taxable_Amt' => $grn['total_received_amount'],
'CGST_Amt' => round($grn['total_tax_amount'] / 2, 2),
'SGST_Amt' => round($grn['total_tax_amount'] / 2, 2),
'CGST_Amt' => (float)($grn['cgst_amount'] ?? round($grn['total_tax_amount'] / 2, 2)),
'SGST_Amt' => (float)($grn['sgst_amount'] ?? round($grn['total_tax_amount'] / 2, 2)),
'T_Net_Amount' => $grn['net_amount'],
'inv_status' => 1,
'inv_status_update_by' => $userId,
@ -851,14 +895,16 @@ class MedicalProcurement extends BaseController
'qty' => $receivedPacks,
'qty_free' => $freePacks,
'tqty' => $receivedPacks + $freePacks,
'amount' => round($receivedPacks * $purchasePricePerPack, 2),
'discount' => $gItem['discount_percent'],
'taxable_amount' => $gItem['taxable_amount'],
'CGST_per' => $gItem['cgst_percent'],
'CGST' => $gItem['cgst_amount'],
'SGST_per' => $gItem['sgst_percent'],
'SGST' => $gItem['sgst_amount'],
'net_amount' => $gItem['total_amount'],
'amount' => (float)($gItem['gross_amount'] ?: round($receivedPacks * $purchasePricePerPack, 2)),
'discount' => (float)($gItem['discount_percent'] ?? 0),
'sch_disc_amt' => (float)($gItem['sch_disc_amt'] ?? 0),
'sch_disc_per' => (float)($gItem['sch_disc_per'] ?? 0),
'taxable_amount' => (float)$gItem['taxable_amount'],
'CGST_per' => (float)$gItem['cgst_percent'],
'CGST' => (float)$gItem['cgst_amount'],
'SGST_per' => (float)$gItem['sgst_percent'],
'SGST' => (float)$gItem['sgst_amount'],
'net_amount' => (float)$gItem['total_amount'],
'HSNCODE' => $gItem['hsn_code'] ?: '',
'selling_price' => $gItem['selling_unit_rate'] * $packing,
'total_unit' => $totalUnits,

View File

@ -24,9 +24,12 @@ class PharmacyGrnItemModel extends Model
'rejected_packs',
'rejection_reason',
'unit_purchase_rate',
'gross_amount',
'mrp',
'selling_unit_rate',
'discount_percent',
'sch_disc_per',
'sch_disc_amt',
'taxable_amount',
'cgst_percent',
'cgst_amount',

View File

@ -21,6 +21,11 @@ class PharmacyGrnModel extends Model
'grn_date',
'total_received_amount',
'total_tax_amount',
'cgst_amount',
'sgst_amount',
'igst_amount',
'round_off',
'gst_type',
'net_amount',
'status',
'purchase_id',

View File

@ -62,45 +62,73 @@
</div>
<div class="card-body p-0">
<div class="table-responsive">
<table class="table table-sm table-striped align-middle mb-0">
<table class="table table-sm table-striped align-middle mb-0" style="font-size: 13px;">
<thead class="table-light">
<tr>
<th style="width: 40px;">#</th>
<th>Product Name</th>
<th>Batch No</th>
<th>Expiry Date</th>
<th class="text-center">Pack</th>
<th class="text-center">Rec. Packs</th>
<th class="text-center">Free</th>
<th class="text-center">Rejected</th>
<th class="text-end">Rate/Pack</th>
<th class="text-end">MRP</th>
<th class="text-center">GST %</th>
<th class="text-end">Line Total</th>
<th class="text-center">Live Stock ID</th>
<th style="width: 30px;">#</th>
<th>Medicine Name</th>
<th style="width: 90px;">Batch No</th>
<th style="width: 95px;">Expiry</th>
<th class="text-center" style="width: 50px;">Pack</th>
<th class="text-center" style="width: 65px;">Rec. Qty</th>
<th class="text-center" style="width: 50px;">Free</th>
<th class="text-end" style="width: 75px;">Rate/Pack</th>
<th class="text-end" style="width: 70px;">MRP</th>
<th class="text-end" style="width: 80px;">Gross Amt</th>
<th class="text-center" style="width: 55px;">Disc %</th>
<th class="text-end" style="width: 75px;">Sch Disc</th>
<th class="text-end" style="width: 85px;">Taxable Amt</th>
<th class="text-center" style="width: 90px;">GST</th>
<th class="text-end" style="width: 90px;">Line Total</th>
<th class="text-center" style="width: 85px;">Live Stock ID</th>
</tr>
</thead>
<tbody>
<?php
$totalGross = 0.0;
$totalDiscount = 0.0;
?>
<?php if (!empty($grnItems)): ?>
<?php foreach ($grnItems as $idx => $it): ?>
<?php
$gross = (float)($it['gross_amount'] ?: ((float)$it['received_packs'] * (float)$it['unit_purchase_rate']));
$schPer = (float)($it['sch_disc_per'] ?? 0);
$schAmt = (float)($it['sch_disc_amt'] ?? 0);
$schTotal = ($schPer > 0 ? ($gross * $schPer / 100) : 0) + $schAmt;
$tradeDisc = (float)$it['discount_percent'];
$afterSch = max(0, $gross - $schTotal);
$tradeDiscAmt = ($tradeDisc > 0) ? ($afterSch * $tradeDisc / 100) : 0;
$rowDisc = $schTotal + $tradeDiscAmt;
$totalGross += $gross;
$totalDiscount += $rowDisc;
$isInter = (!empty($grn['gst_type']) && $grn['gst_type'] === 'inter_state') || ((float)$it['igst_percent'] > 0);
$gstLabel = $isInter
? number_format((float)$it['igst_percent'], 1) . '% IGST'
: number_format((float)$it['cgst_percent'] + (float)$it['sgst_percent'], 0) . '% (' . number_format((float)$it['cgst_percent'], 1) . '+' . number_format((float)$it['sgst_percent'], 1) . '%)';
?>
<tr>
<td><?= $idx + 1 ?></td>
<td><strong><?= esc($it['item_name']) ?></strong></td>
<td>
<strong><?= esc($it['item_name']) ?></strong>
<?php if (!empty($it['rejected_packs']) && (float)$it['rejected_packs'] > 0): ?>
<small class="d-block text-danger">Damaged: <?= (float)$it['rejected_packs'] ?> (<?= esc($it['rejection_reason'] ?: 'Damaged') ?>)</small>
<?php endif; ?>
</td>
<td><span class="badge bg-light text-dark font-monospace border"><?= esc($it['batch_no']) ?></span></td>
<td><?= date('d-M-Y', strtotime($it['expiry_date'])) ?></td>
<td class="text-center"><?= (int)$it['packing'] ?>s</td>
<td class="text-center fw-bold"><?= (float)$it['received_packs'] ?></td>
<td class="text-center text-success"><?= (float)$it['free_packs'] ?></td>
<td class="text-center <?= ((float)$it['rejected_packs'] > 0) ? 'text-danger fw-bold' : 'text-muted' ?>">
<?= (float)$it['rejected_packs'] ?>
<?php if (!empty($it['rejection_reason'])): ?>
<small class="d-block text-danger">(<?= esc($it['rejection_reason']) ?>)</small>
<?php endif; ?>
</td>
<td class="text-end">₹<?= number_format((float)$it['unit_purchase_rate'], 2) ?></td>
<td class="text-end">₹<?= number_format((float)$it['mrp'], 2) ?></td>
<td class="text-center"><?= number_format((float)$it['cgst_percent'] * 2, 0) ?>%</td>
<td class="text-end fw-semibold">₹<?= number_format((float)$it['total_amount'], 2) ?></td>
<td class="text-end font-monospace">₹<?= number_format($gross, 2) ?></td>
<td class="text-center"><?= number_format($tradeDisc, 1) ?>%</td>
<td class="text-end text-muted font-monospace">₹<?= number_format($schTotal, 2) ?></td>
<td class="text-end font-monospace fw-semibold">₹<?= number_format((float)$it['taxable_amount'], 2) ?></td>
<td class="text-center small"><?= esc($gstLabel) ?></td>
<td class="text-end font-monospace fw-semibold">₹<?= number_format((float)$it['total_amount'], 2) ?></td>
<td class="text-center">
<?php if (!empty($it['purchase_item_id'])): ?>
<span class="badge bg-success font-monospace">Item #<?= (int)$it['purchase_item_id'] ?></span>
@ -114,18 +142,53 @@
</tbody>
<tfoot class="table-light">
<tr>
<th colspan="11" class="text-end">Taxable Amount:</th>
<th class="text-end">₹<?= number_format((float)$grn['total_received_amount'], 2) ?></th>
<th colspan="12" class="text-end text-muted">Sub Total (Gross Amount):</th>
<th colspan="3" class="text-end font-monospace">₹<?= number_format($totalGross, 2) ?></th>
<th></th>
</tr>
<tr>
<th colspan="11" class="text-end">Total GST:</th>
<th class="text-end">₹<?= number_format((float)$grn['total_tax_amount'], 2) ?></th>
<th colspan="12" class="text-end text-muted">Total Discount (Trade &amp; Scheme):</th>
<th colspan="3" class="text-end font-monospace text-danger">₹<?= number_format($totalDiscount, 2) ?></th>
<th></th>
</tr>
<tr>
<th colspan="12" class="text-end text-dark">Taxable Amount:</th>
<th colspan="3" class="text-end font-monospace fw-bold">₹<?= number_format((float)$grn['total_received_amount'], 2) ?></th>
<th></th>
</tr>
<?php if (!empty($grn['gst_type']) && $grn['gst_type'] === 'inter_state'): ?>
<tr>
<th colspan="12" class="text-end text-muted">IGST Amount:</th>
<th colspan="3" class="text-end font-monospace text-primary">₹<?= number_format((float)($grn['igst_amount'] ?? $grn['total_tax_amount']), 2) ?></th>
<th></th>
</tr>
<?php else: ?>
<tr>
<th colspan="12" class="text-end text-muted">CGST Amount:</th>
<th colspan="3" class="text-end font-monospace text-primary">₹<?= number_format((float)($grn['cgst_amount'] ?? ((float)$grn['total_tax_amount'] / 2)), 2) ?></th>
<th></th>
</tr>
<tr>
<th colspan="12" class="text-end text-muted">SGST Amount:</th>
<th colspan="3" class="text-end font-monospace text-primary">₹<?= number_format((float)($grn['sgst_amount'] ?? ((float)$grn['total_tax_amount'] / 2)), 2) ?></th>
<th></th>
</tr>
<?php endif; ?>
<tr>
<th colspan="12" class="text-end text-dark">Total GST:</th>
<th colspan="3" class="text-end font-monospace fw-bold">₹<?= number_format((float)$grn['total_tax_amount'], 2) ?></th>
<th></th>
</tr>
<?php if (isset($grn['round_off']) && (float)$grn['round_off'] != 0): ?>
<tr>
<th colspan="12" class="text-end text-muted">Round Off:</th>
<th colspan="3" class="text-end font-monospace"><?= ((float)$grn['round_off'] > 0 ? '+' : '') . number_format((float)$grn['round_off'], 2) ?></th>
<th></th>
</tr>
<?php endif; ?>
<tr class="table-success">
<th colspan="11" class="text-end fs-6">Net Inward Bill Total:</th>
<th class="text-end fs-6 text-success fw-bold">₹<?= number_format((float)$grn['net_amount'], 2) ?></th>
<th colspan="12" class="text-end fs-6">Net Inward Bill Total (Grand Total):</th>
<th colspan="3" class="text-end fs-6 font-monospace text-success fw-bold">₹<?= number_format((float)$grn['net_amount'], 2) ?></th>
<th></th>
</tr>
</tfoot>

View File

@ -10,7 +10,7 @@
<i class="bi bi-box-arrow-in-down"></i> Create Goods Received Note (GRN)
<span class="badge bg-light text-dark border ms-2 font-monospace"><?= esc($nextGrnNumber) ?></span>
</h4>
<small class="text-muted">Inward Stock Physical Inspection &amp; Batch Entry</small>
<small class="text-muted">Inward Stock Physical Inspection &amp; Vendor Invoice Reconciliation</small>
</div>
<div>
<a href="javascript:load_form_div('<?= base_url('Medical/grn_list') ?>','medical-main','GRN Register :Pharmacy');" class="btn btn-outline-secondary btn-sm">
@ -21,14 +21,14 @@
<div class="card shadow-sm border-0 mb-3">
<div class="card-header bg-light py-2">
<strong class="text-secondary"><i class="bi bi-truck"></i> Inward Delivery Metadata</strong>
<strong class="text-secondary"><i class="bi bi-truck"></i> Inward Delivery &amp; Invoice Metadata</strong>
</div>
<div class="card-body p-3">
<form id="grn-create-form" onsubmit="event.preventDefault(); submitGrnForm();">
<?= csrf_field() ?>
<div class="row g-3">
<div class="row g-2 mb-2">
<div class="col-md-3">
<label class="form-label form-label-sm fw-semibold">Linked Purchase Order (PO)</label>
<label class="form-label form-label-sm fw-semibold mb-1">Linked Purchase Order (PO)</label>
<select name="po_id" id="grn_po_id" class="form-select form-select-sm" onchange="handlePoSelection(this.value);">
<option value="0">-- Direct Intake (No PO) --</option>
<?php if (!empty($approvedPos)): ?>
@ -41,7 +41,7 @@
</select>
</div>
<div class="col-md-3">
<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 mb-1">Supplier / Pharmaceutical Vendor <span class="text-danger">*</span></label>
<select name="supplier_id" id="grn_supplier_id" class="form-select form-select-sm" required>
<option value="">-- Select Supplier --</option>
<?php if (!empty($suppliers)): ?>
@ -54,91 +54,133 @@
</select>
</div>
<div class="col-md-2">
<label class="form-label form-label-sm fw-semibold">Vendor Bill / Invoice # <span class="text-danger">*</span></label>
<input type="text" name="supplier_invoice_no" id="supplier_invoice_no" class="form-control form-control-sm" placeholder="e.g., INV-88421" required>
<label class="form-label form-label-sm fw-semibold mb-1">Vendor Invoice / Bill # <span class="text-danger">*</span></label>
<input type="text" name="supplier_invoice_no" id="supplier_invoice_no" class="form-control form-control-sm" placeholder="e.g. INV-88421" required>
</div>
<div class="col-md-2">
<label class="form-label form-label-sm fw-semibold">Bill Date</label>
<label class="form-label form-label-sm fw-semibold mb-1">Invoice Date <span class="text-danger">*</span></label>
<input type="date" name="invoice_date" id="invoice_date" class="form-control form-control-sm" value="<?= date('Y-m-d') ?>" required>
</div>
<div class="col-md-2">
<label class="form-label form-label-sm fw-semibold">GRN Received Date</label>
<label class="form-label form-label-sm fw-semibold mb-1">GRN Received Date <span class="text-danger">*</span></label>
<input type="date" name="grn_date" id="grn_date" class="form-control form-control-sm" value="<?= date('Y-m-d') ?>" required>
</div>
<div class="col-md-3">
<label class="form-label form-label-sm fw-semibold">Delivery Challan / Gate Pass #</label>
<input type="text" name="challan_no" id="challan_no" class="form-control form-control-sm" placeholder="Optional Challan No.">
</div>
<div class="col-md-9">
<label class="form-label form-label-sm fw-semibold">Receiving Inspection Remarks</label>
<input type="text" name="remarks" id="grn_remarks" class="form-control form-control-sm" placeholder="Package integrity verified, cold-chain temperature logged, etc.">
</div>
</div>
<!-- Manual Drug Search (for direct items or extra items) -->
<div class="card border-success mt-3">
<div class="row g-2 mb-3">
<div class="col-md-3">
<label class="form-label form-label-sm fw-semibold mb-1">GST Supply Type</label>
<select name="gst_type" id="grn_gst_type" class="form-select form-select-sm" onchange="recalculateGrnTotals();">
<option value="intra_state" selected>Intra-State (CGST + SGST)</option>
<option value="inter_state">Inter-State (IGST)</option>
</select>
</div>
<div class="col-md-3">
<label class="form-label form-label-sm fw-semibold mb-1">Delivery Challan / Gate Pass #</label>
<input type="text" name="challan_no" id="challan_no" class="form-control form-control-sm" placeholder="Optional Challan No.">
</div>
<div class="col-md-6">
<label class="form-label form-label-sm fw-semibold mb-1">Receiving Inspection Remarks</label>
<input type="text" name="remarks" id="grn_remarks" class="form-control form-control-sm" placeholder="Package integrity verified, cold chain maintained, etc.">
</div>
</div>
<!-- Manual Medicine Search Bar -->
<div class="card border-success mt-2">
<div class="card-header bg-success text-white py-2 d-flex justify-content-between align-items-center">
<span><i class="bi bi-shield-check"></i> Physical Inspection &amp; Batch Intake Table</span>
<small class="text-white-50">Enter Batch, Expiry, Received Packs &amp; Pricing</small>
<small class="text-white-50">Match Vendor Invoice Columns (Gross, Discounts, Taxable, GST &amp; Net Total)</small>
</div>
<div class="card-body p-3">
<div class="row g-2 align-items-end mb-3">
<div class="col-md-4">
<label class="form-label form-label-sm mb-1 fw-semibold">Add Medicine / Drug Master</label>
<input type="text" id="grn_drug_search" class="form-control form-control-sm" placeholder="Search medicine to add row..." autocomplete="off">
<div class="row g-2 align-items-end mb-3 bg-light p-2 rounded border">
<div class="col-md-3">
<label class="form-label form-label-sm mb-1 fw-semibold">Search Medicine Master</label>
<input type="text" id="grn_drug_search" class="form-control form-control-sm" placeholder="Search medicine to add..." autocomplete="off">
<input type="hidden" id="grn_sel_item_code" value="">
<input type="hidden" id="grn_sel_item_name" value="">
<input type="hidden" id="grn_sel_packing" value="10">
</div>
<div class="col-md-2">
<label class="form-label form-label-sm mb-1 fw-semibold">Batch No.</label>
<input type="text" id="grn_sel_batch" class="form-control form-control-sm" placeholder="e.g. BATCH-01">
<input type="text" id="grn_sel_batch" class="form-control form-control-sm font-monospace" placeholder="e.g. 26DH220">
</div>
<div class="col-md-2">
<label class="form-label form-label-sm mb-1 fw-semibold">Expiry Date</label>
<input type="date" id="grn_sel_expiry" class="form-control form-control-sm">
</div>
<div class="col-md-2">
<label class="form-label form-label-sm mb-1 fw-semibold">Received Packs</label>
<input type="number" id="grn_sel_rec_packs" class="form-control form-control-sm" value="10" min="1">
<div class="col-md-1">
<label class="form-label form-label-sm mb-1 fw-semibold">Rec. Qty</label>
<input type="number" id="grn_sel_rec_packs" class="form-control form-control-sm text-center" value="10" min="1">
</div>
<div class="col-md-2">
<div class="col-md-1">
<label class="form-label form-label-sm mb-1 fw-semibold">Free</label>
<input type="number" id="grn_sel_free_packs" class="form-control form-control-sm text-center" value="0" min="0">
</div>
<div class="col-md-1">
<label class="form-label form-label-sm mb-1 fw-semibold">Rate/Pack</label>
<input type="number" id="grn_sel_rate" class="form-control form-control-sm text-end" value="100.00" min="0" step="0.01">
</div>
<div class="col-md-1">
<label class="form-label form-label-sm mb-1 fw-semibold">MRP</label>
<input type="number" id="grn_sel_mrp" class="form-control form-control-sm text-end" value="130.00" min="0" step="0.01">
</div>
<div class="col-md-1">
<button type="button" class="btn btn-success btn-sm w-100" id="btn-add-grn-row" onclick="addCustomItemToGrn();" disabled>
<i class="bi bi-plus-lg"></i> Add Row
<i class="bi bi-plus-lg"></i> Add
</button>
</div>
</div>
<div class="table-responsive">
<table class="table table-bordered table-sm align-middle mb-0" id="grn-items-table">
<table class="table table-bordered table-sm align-middle mb-0" id="grn-items-table" style="font-size: 13px;">
<thead class="table-light">
<tr>
<th style="width: 30px;">#</th>
<th>Medicine Name</th>
<th style="width: 110px;">Batch No <span class="text-danger">*</span></th>
<th style="width: 120px;">Expiry <span class="text-danger">*</span></th>
<th class="text-center" style="width: 70px;">Pack</th>
<th class="text-center" style="width: 80px;">Ordered</th>
<th class="text-center" style="width: 85px;">Rec. Packs <span class="text-danger">*</span></th>
<th class="text-center" style="width: 75px;">Free</th>
<th class="text-center" style="width: 75px;">Damaged</th>
<th class="text-end" style="width: 100px;">Rate/Pack</th>
<th class="text-end" style="width: 90px;">MRP</th>
<th class="text-center" style="width: 70px;">GST %</th>
<th class="text-end" style="width: 110px;">Line Total</th>
<th style="width: 40px;"></th>
<tr class="text-center align-middle">
<th style="width: 25px;">#</th>
<th style="min-width: 160px;" class="text-start">Medicine Name</th>
<th style="width: 95px;">Batch No <span class="text-danger">*</span></th>
<th style="width: 105px;">Expiry <span class="text-danger">*</span></th>
<th style="width: 50px;">Pack</th>
<th style="width: 65px;">Rec. Qty <span class="text-danger">*</span></th>
<th style="width: 50px;">Free</th>
<th style="width: 75px;" class="text-end">Rate/Pack</th>
<th style="width: 70px;" class="text-end">MRP</th>
<th style="width: 80px;" class="text-end bg-light">Gross Amt</th>
<th style="width: 60px;">Disc %</th>
<th style="width: 60px;">Sch %</th>
<th style="width: 65px;">Sch Amt</th>
<th style="width: 85px;" class="text-end bg-light">Taxable Amt</th>
<th style="width: 75px;">GST %</th>
<th style="width: 90px;" class="text-end bg-light">Line Total</th>
<th style="width: 35px;"></th>
</tr>
</thead>
<tbody id="grn-items-body">
<?php if (!empty($poItems)): ?>
<?php foreach ($poItems as $idx => $it): ?>
<?php
$defGst = (float)($it['gst_rate'] ?? 5);
$defRate = (float)($it['estimated_ptr'] ?? 0);
$defQty = (float)($it['remaining_packs'] ?? 0);
$defGross = $defQty * $defRate;
$defTaxable = $defGross;
$defTax = $defTaxable * ($defGst / 100);
$defLineTotal = $defTaxable + $defTax;
?>
<tr>
<td class="row-num"><?= $idx + 1 ?></td>
<td class="row-num text-center"><?= $idx + 1 ?></td>
<td>
<strong><?= esc($it['item_name']) ?></strong>
<strong class="text-dark"><?= esc($it['item_name']) ?></strong>
<input type="hidden" name="items[<?= $idx ?>][item_code]" value="<?= (int)$it['item_id'] ?>">
<input type="hidden" name="items[<?= $idx ?>][item_name]" value="<?= esc($it['item_name']) ?>">
<input type="hidden" name="items[<?= $idx ?>][po_item_id]" value="<?= (int)$it['po_item_id'] ?>">
<input type="hidden" name="items[<?= $idx ?>][order_packs]" value="<?= (float)$it['remaining_packs'] ?>">
<input type="hidden" name="items[<?= $idx ?>][rejected_packs]" value="0">
<input type="hidden" name="items[<?= $idx ?>][hsn_code]" value="3004">
<input type="hidden" name="items[<?= $idx ?>][cgst_percent]" class="line-cgst" value="<?= $defGst / 2 ?>">
<input type="hidden" name="items[<?= $idx ?>][sgst_percent]" class="line-sgst" value="<?= $defGst / 2 ?>">
<input type="hidden" name="items[<?= $idx ?>][igst_percent]" class="line-igst" value="0">
<input type="hidden" name="items[<?= $idx ?>][gross_amount]" class="line-gross-hid" value="<?= number_format($defGross, 2, '.', '') ?>">
<input type="hidden" name="items[<?= $idx ?>][taxable_amount]" class="line-taxable-hid" value="<?= number_format($defTaxable, 2, '.', '') ?>">
</td>
<td>
<input type="text" name="items[<?= $idx ?>][batch_no]" class="form-control form-control-sm font-monospace line-batch" placeholder="Batch #" required>
@ -147,64 +189,100 @@
<input type="date" name="items[<?= $idx ?>][expiry_date]" class="form-control form-control-sm line-expiry" required>
</td>
<td class="text-center">
<input type="number" name="items[<?= $idx ?>][packing]" class="form-control form-control-sm text-center line-pack" value="<?= (int)$it['units_per_pack'] ?>" min="1">
</td>
<td class="text-center text-muted">
<?= (float)$it['remaining_packs'] ?>
<input type="hidden" name="items[<?= $idx ?>][order_packs]" value="<?= (float)$it['remaining_packs'] ?>">
<input type="number" name="items[<?= $idx ?>][packing]" class="form-control form-control-sm text-center line-pack px-1" value="<?= (int)$it['units_per_pack'] ?>" min="1">
</td>
<td class="text-center">
<input type="number" name="items[<?= $idx ?>][received_packs]" class="form-control form-control-sm text-center line-rec" value="<?= (float)$it['remaining_packs'] ?>" min="0" step="1" onchange="recalculateGrnTotals();" required>
<input type="number" name="items[<?= $idx ?>][received_packs]" class="form-control form-control-sm text-center fw-bold line-rec px-1" value="<?= $defQty ?>" min="0" step="1" oninput="recalculateGrnTotals();" required>
</td>
<td class="text-center">
<input type="number" name="items[<?= $idx ?>][free_packs]" class="form-control form-control-sm text-center line-free" value="0" min="0" step="1">
</td>
<td class="text-center">
<input type="number" name="items[<?= $idx ?>][rejected_packs]" class="form-control form-control-sm text-center line-rej" value="0" min="0" step="1">
<input type="number" name="items[<?= $idx ?>][free_packs]" class="form-control form-control-sm text-center text-success line-free px-1" value="0" min="0" step="1" oninput="recalculateGrnTotals();">
</td>
<td class="text-end">
<input type="number" name="items[<?= $idx ?>][unit_purchase_rate]" class="form-control form-control-sm text-end line-rate" value="<?= number_format((float)$it['estimated_ptr'], 2, '.', '') ?>" min="0" step="0.01" onchange="recalculateGrnTotals();">
<input type="number" name="items[<?= $idx ?>][unit_purchase_rate]" class="form-control form-control-sm text-end line-rate px-1" value="<?= number_format($defRate, 2, '.', '') ?>" min="0" step="0.01" oninput="recalculateGrnTotals();">
</td>
<td class="text-end">
<input type="number" name="items[<?= $idx ?>][mrp]" class="form-control form-control-sm text-end line-mrp" value="<?= number_format((float)$it['estimated_ptr'] * 1.3, 2, '.', '') ?>" min="0" step="0.01">
<input type="number" name="items[<?= $idx ?>][mrp]" class="form-control form-control-sm text-end line-mrp px-1" value="<?= number_format($defRate * 1.3, 2, '.', '') ?>" min="0" step="0.01">
</td>
<td class="text-end font-monospace line-gross-disp bg-light">₹<?= number_format($defGross, 2) ?></td>
<td class="text-center">
<input type="number" name="items[<?= $idx ?>][discount_percent]" class="form-control form-control-sm text-center line-disc px-1" value="0.00" min="0" max="100" step="0.01" oninput="recalculateGrnTotals();">
</td>
<td class="text-center">
<select name="items[<?= $idx ?>][cgst_percent]" class="form-select form-select-sm text-center line-gst" onchange="recalculateGrnTotals();">
<option value="6" <?= ((float)$it['gst_rate'] == 12) ? 'selected' : '' ?>>12%</option>
<option value="2.5" <?= ((float)$it['gst_rate'] == 5) ? 'selected' : '' ?>>5%</option>
<option value="9" <?= ((float)$it['gst_rate'] == 18) ? 'selected' : '' ?>>18%</option>
<option value="0" <?= ((float)$it['gst_rate'] == 0) ? 'selected' : '' ?>>0%</option>
<input type="number" name="items[<?= $idx ?>][sch_disc_per]" class="form-control form-control-sm text-center line-sch-per px-1" value="0.00" min="0" max="100" step="0.01" oninput="recalculateGrnTotals();">
</td>
<td class="text-center">
<input type="number" name="items[<?= $idx ?>][sch_disc_amt]" class="form-control form-control-sm text-center line-sch-amt px-1" value="0.00" min="0" step="0.01" oninput="recalculateGrnTotals();">
</td>
<td class="text-end font-monospace line-taxable-disp bg-light">₹<?= number_format($defTaxable, 2) ?></td>
<td class="text-center">
<select name="items[<?= $idx ?>][gst_rate]" class="form-select form-select-sm text-center line-gst px-1" onchange="recalculateGrnTotals();">
<option value="5" <?= ($defGst == 5) ? 'selected' : '' ?>>5%</option>
<option value="12" <?= ($defGst == 12) ? 'selected' : '' ?>>12%</option>
<option value="18" <?= ($defGst == 18) ? 'selected' : '' ?>>18%</option>
<option value="28" <?= ($defGst == 28) ? 'selected' : '' ?>>28%</option>
<option value="0" <?= ($defGst == 0) ? 'selected' : '' ?>>0%</option>
</select>
<input type="hidden" name="items[<?= $idx ?>][sgst_percent]" class="line-sgst" value="6">
</td>
<td class="text-end fw-semibold line-total-disp">₹0.00</td>
<td class="text-end font-monospace fw-semibold line-total-disp bg-light">₹<?= number_format($defLineTotal, 2) ?></td>
<td class="text-center">
<button type="button" class="btn btn-outline-danger btn-sm py-0" onclick="removeGrnRow(this);">✖</button>
<button type="button" class="btn btn-outline-danger btn-sm py-0 px-1" onclick="removeGrnRow(this);">✖</button>
</td>
</tr>
<?php endforeach; ?>
<?php else: ?>
<tr id="grn-no-items">
<td colspan="14" class="text-center py-4 text-muted">
No items loaded. Select a PO above or use the Search box to add items.
<td colspan="17" class="text-center py-4 text-muted">
No items loaded. Select an approved PO above or use the Search box to add medicines.
</td>
</tr>
<?php endif; ?>
</tbody>
<tfoot class="table-light">
<tr>
<th colspan="12" class="text-end">Taxable Amount:</th>
<th class="text-end" id="grn-taxable-disp">₹0.00</th>
<th colspan="13" class="text-end text-muted">Sub Total (Gross Amount):</th>
<th colspan="3" class="text-end font-monospace" id="grn-gross-disp">₹0.00</th>
<th></th>
</tr>
<tr>
<th colspan="12" class="text-end">GST Tax Amount:</th>
<th class="text-end" id="grn-tax-disp">₹0.00</th>
<th colspan="13" class="text-end text-muted">Total Discount (Trade &amp; Scheme):</th>
<th colspan="3" class="text-end font-monospace text-danger" id="grn-discount-disp">₹0.00</th>
<th></th>
</tr>
<tr>
<th colspan="13" class="text-end text-dark">Taxable Amount:</th>
<th colspan="3" class="text-end font-monospace fw-bold" id="grn-taxable-disp">₹0.00</th>
<th></th>
</tr>
<tr id="row-cgst">
<th colspan="13" class="text-end text-muted">CGST Amount:</th>
<th colspan="3" class="text-end font-monospace text-primary" id="grn-cgst-disp">₹0.00</th>
<th></th>
</tr>
<tr id="row-sgst">
<th colspan="13" class="text-end text-muted">SGST Amount:</th>
<th colspan="3" class="text-end font-monospace text-primary" id="grn-sgst-disp">₹0.00</th>
<th></th>
</tr>
<tr id="row-igst" style="display:none;">
<th colspan="13" class="text-end text-muted">IGST Amount:</th>
<th colspan="3" class="text-end font-monospace text-primary" id="grn-igst-disp">₹0.00</th>
<th></th>
</tr>
<tr>
<th colspan="13" class="text-end text-dark">Total GST:</th>
<th colspan="3" class="text-end font-monospace fw-bold" id="grn-tax-disp">₹0.00</th>
<th></th>
</tr>
<tr>
<th colspan="13" class="text-end text-muted">Round Off (+ / -):</th>
<th colspan="3" class="text-end">
<input type="number" step="0.01" name="round_off" id="grn_round_off" class="form-control form-control-sm text-end font-monospace d-inline-block" style="width: 105px;" value="0.00" oninput="recalculateGrnTotals();">
</th>
<th></th>
</tr>
<tr class="table-success">
<th colspan="12" class="text-end fs-6">Net Inward Bill Total:</th>
<th class="text-end fs-6 text-success fw-bold" id="grn-net-disp">₹0.00</th>
<th colspan="13" class="text-end fs-6">Net Inward Bill Total (Grand Total):</th>
<th colspan="3" class="text-end fs-6 font-monospace text-success fw-bold" id="grn-net-disp">₹0.00</th>
<th></th>
</tr>
</tfoot>
@ -220,7 +298,7 @@
<label class="form-check-label fw-bold text-dark" for="post_to_inventory">
Immediately Post Verified Quantities to Live Pharmacy Store Stock &amp; Purchase Ledgers
</label>
<small class="d-block text-muted">Calls stored procedure <code>p_stock_update_purchase_id</code> to make batches immediately sellable.</small>
<small class="d-block text-muted">Creates verified purchase invoice records and updates stock ledgers.</small>
</div>
<div class="d-flex gap-2">
<button type="button" class="btn btn-outline-secondary" onclick="load_form_div('<?= base_url('Medical/grn_list') ?>','medical-main');">Cancel</button>
@ -264,6 +342,8 @@ function initGrnDrugAutocomplete() {
document.getElementById('grn_sel_item_code').value = item.item_code || item.l_item_code || '';
document.getElementById('grn_sel_item_name').value = item.value || '';
document.getElementById('grn_sel_packing').value = item.l_packing || 10;
if (item.mrp) document.getElementById('grn_sel_mrp').value = parseFloat(item.mrp).toFixed(2);
if (item.purchase_price) document.getElementById('grn_sel_rate').value = parseFloat(item.purchase_price).toFixed(2);
document.getElementById('btn-add-grn-row').disabled = false;
}
});
@ -281,7 +361,10 @@ function addCustomItemToGrn() {
var batch = document.getElementById('grn_sel_batch').value.trim();
var expiry = document.getElementById('grn_sel_expiry').value;
var packs = parseFloat(document.getElementById('grn_sel_rec_packs').value || 1);
var freePacks = parseFloat(document.getElementById('grn_sel_free_packs').value || 0);
var packing = parseInt(document.getElementById('grn_sel_packing').value || 10);
var rate = parseFloat(document.getElementById('grn_sel_rate').value || 100.00);
var mrp = parseFloat(document.getElementById('grn_sel_mrp').value || 130.00);
if (!code || !name) {
alert('Please search and select a medicine.');
@ -305,13 +388,23 @@ function addCustomItemToGrn() {
var rowIdx = tbody.querySelectorAll('tr').length;
var tr = document.createElement('tr');
var gross = packs * rate;
tr.innerHTML = `
<td class="row-num">${rowIdx + 1}</td>
<td class="row-num text-center">${rowIdx + 1}</td>
<td>
<strong>${escapeHtml(name)}</strong>
<strong class="text-dark">${escapeHtml(name)}</strong>
<input type="hidden" name="items[${rowIdx}][item_code]" value="${code}">
<input type="hidden" name="items[${rowIdx}][item_name]" value="${escapeHtml(name)}">
<input type="hidden" name="items[${rowIdx}][po_item_id]" value="">
<input type="hidden" name="items[${rowIdx}][order_packs]" value="0">
<input type="hidden" name="items[${rowIdx}][rejected_packs]" value="0">
<input type="hidden" name="items[${rowIdx}][hsn_code]" value="3004">
<input type="hidden" name="items[${rowIdx}][cgst_percent]" class="line-cgst" value="2.5">
<input type="hidden" name="items[${rowIdx}][sgst_percent]" class="line-sgst" value="2.5">
<input type="hidden" name="items[${rowIdx}][igst_percent]" class="line-igst" value="0">
<input type="hidden" name="items[${rowIdx}][gross_amount]" class="line-gross-hid" value="${gross.toFixed(2)}">
<input type="hidden" name="items[${rowIdx}][taxable_amount]" class="line-taxable-hid" value="${gross.toFixed(2)}">
</td>
<td>
<input type="text" name="items[${rowIdx}][batch_no]" class="form-control form-control-sm font-monospace line-batch" value="${escapeHtml(batch)}" required>
@ -320,36 +413,43 @@ function addCustomItemToGrn() {
<input type="date" name="items[${rowIdx}][expiry_date]" class="form-control form-control-sm line-expiry" value="${expiry}" required>
</td>
<td class="text-center">
<input type="number" name="items[${rowIdx}][packing]" class="form-control form-control-sm text-center line-pack" value="${packing}" min="1">
</td>
<td class="text-center text-muted">0</td>
<td class="text-center">
<input type="number" name="items[${rowIdx}][received_packs]" class="form-control form-control-sm text-center line-rec" value="${packs}" min="1" onchange="recalculateGrnTotals();" required>
<input type="number" name="items[${rowIdx}][packing]" class="form-control form-control-sm text-center line-pack px-1" value="${packing}" min="1">
</td>
<td class="text-center">
<input type="number" name="items[${rowIdx}][free_packs]" class="form-control form-control-sm text-center line-free" value="0" min="0">
<input type="number" name="items[${rowIdx}][received_packs]" class="form-control form-control-sm text-center fw-bold line-rec px-1" value="${packs}" min="1" oninput="recalculateGrnTotals();" required>
</td>
<td class="text-center">
<input type="number" name="items[${rowIdx}][rejected_packs]" class="form-control form-control-sm text-center line-rej" value="0" min="0">
<input type="number" name="items[${rowIdx}][free_packs]" class="form-control form-control-sm text-center text-success line-free px-1" value="${freePacks}" min="0" oninput="recalculateGrnTotals();">
</td>
<td class="text-end">
<input type="number" name="items[${rowIdx}][unit_purchase_rate]" class="form-control form-control-sm text-end line-rate" value="100.00" min="0" step="0.01" onchange="recalculateGrnTotals();">
<input type="number" name="items[${rowIdx}][unit_purchase_rate]" class="form-control form-control-sm text-end line-rate px-1" value="${rate.toFixed(2)}" min="0" step="0.01" oninput="recalculateGrnTotals();">
</td>
<td class="text-end">
<input type="number" name="items[${rowIdx}][mrp]" class="form-control form-control-sm text-end line-mrp" value="130.00" min="0" step="0.01">
<input type="number" name="items[${rowIdx}][mrp]" class="form-control form-control-sm text-end line-mrp px-1" value="${mrp.toFixed(2)}" min="0" step="0.01">
</td>
<td class="text-end font-monospace line-gross-disp bg-light">₹${gross.toFixed(2)}</td>
<td class="text-center">
<input type="number" name="items[${rowIdx}][discount_percent]" class="form-control form-control-sm text-center line-disc px-1" value="0.00" min="0" max="100" step="0.01" oninput="recalculateGrnTotals();">
</td>
<td class="text-center">
<select name="items[${rowIdx}][cgst_percent]" class="form-select form-select-sm text-center line-gst" onchange="recalculateGrnTotals();">
<option value="6" selected>12%</option>
<option value="2.5">5%</option>
<option value="9">18%</option>
<input type="number" name="items[${rowIdx}][sch_disc_per]" class="form-control form-control-sm text-center line-sch-per px-1" value="0.00" min="0" max="100" step="0.01" oninput="recalculateGrnTotals();">
</td>
<td class="text-center">
<input type="number" name="items[${rowIdx}][sch_disc_amt]" class="form-control form-control-sm text-center line-sch-amt px-1" value="0.00" min="0" step="0.01" oninput="recalculateGrnTotals();">
</td>
<td class="text-end font-monospace line-taxable-disp bg-light">₹${gross.toFixed(2)}</td>
<td class="text-center">
<select name="items[${rowIdx}][gst_rate]" class="form-select form-select-sm text-center line-gst px-1" onchange="recalculateGrnTotals();">
<option value="5" selected>5%</option>
<option value="12">12%</option>
<option value="18">18%</option>
<option value="28">28%</option>
<option value="0">0%</option>
</select>
<input type="hidden" name="items[${rowIdx}][sgst_percent]" class="line-sgst" value="6">
</td>
<td class="text-end fw-semibold line-total-disp">₹0.00</td>
<td class="text-end font-monospace fw-semibold line-total-disp bg-light">₹0.00</td>
<td class="text-center">
<button type="button" class="btn btn-outline-danger btn-sm py-0" onclick="removeGrnRow(this);">✖</button>
<button type="button" class="btn btn-outline-danger btn-sm py-0 px-1" onclick="removeGrnRow(this);">✖</button>
</td>
`;
@ -368,7 +468,7 @@ function removeGrnRow(btn) {
var tbody = document.getElementById('grn-items-body');
var rows = tbody.querySelectorAll('tr');
if (rows.length === 0) {
tbody.innerHTML = '<tr id="grn-no-items"><td colspan="14" class="text-center py-4 text-muted">No items loaded.</td></tr>';
tbody.innerHTML = '<tr id="grn-no-items"><td colspan="17" class="text-center py-4 text-muted">No items loaded.</td></tr>';
} else {
rows.forEach(function(r, idx) {
var numTd = r.querySelector('.row-num');
@ -380,28 +480,114 @@ function removeGrnRow(btn) {
function recalculateGrnTotals() {
var rows = document.querySelectorAll('#grn-items-body tr:not(#grn-no-items)');
var taxable = 0.0;
var isInterState = (document.getElementById('grn_gst_type')?.value === 'inter_state');
var totalGross = 0.0;
var totalDisc = 0.0;
var totalTaxable = 0.0;
var totalCgst = 0.0;
var totalSgst = 0.0;
var totalIgst = 0.0;
var totalTax = 0.0;
rows.forEach(function(r) {
var recPacks = parseFloat(r.querySelector('.line-rec')?.value || 0);
var rate = parseFloat(r.querySelector('.line-rate')?.value || 0);
var gstVal = parseFloat(r.querySelector('.line-gst')?.value || 0) * 2; // CGST + SGST
var gross = recPacks * rate;
var lineSub = recPacks * rate;
var lineTax = lineSub * (gstVal / 100);
var lineTotal = lineSub + lineTax;
var discPer = parseFloat(r.querySelector('.line-disc')?.value || 0);
var schPer = parseFloat(r.querySelector('.line-sch-per')?.value || 0);
var schAmt = parseFloat(r.querySelector('.line-sch-amt')?.value || 0);
taxable += lineSub;
totalTax += lineTax;
// Scheme discount calculation
var schPerAmt = (schPer > 0) ? (gross * (schPer / 100)) : 0.0;
var lineSch = schPerAmt + schAmt;
var afterSch = Math.max(0.0, gross - lineSch);
// Trade discount calculation
var tradeDiscAmt = (discPer > 0) ? (afterSch * (discPer / 100)) : 0.0;
var taxable = Math.max(0.0, afterSch - tradeDiscAmt);
// GST calculation
var gstRate = parseFloat(r.querySelector('.line-gst')?.value || 0);
var cgstPer = 0.0;
var sgstPer = 0.0;
var igstPer = 0.0;
var cgstAmt = 0.0;
var sgstAmt = 0.0;
var igstAmt = 0.0;
if (isInterState) {
igstPer = gstRate;
igstAmt = taxable * (igstPer / 100);
} else {
cgstPer = gstRate / 2;
sgstPer = gstRate / 2;
cgstAmt = taxable * (cgstPer / 100);
sgstAmt = taxable * (sgstPer / 100);
}
var lineTax = cgstAmt + sgstAmt + igstAmt;
var lineTotal = taxable + lineTax;
// Update displays
var grossDisp = r.querySelector('.line-gross-disp');
if (grossDisp) grossDisp.innerText = '₹' + gross.toFixed(2);
var taxableDisp = r.querySelector('.line-taxable-disp');
if (taxableDisp) taxableDisp.innerText = '₹' + taxable.toFixed(2);
var totalDisp = r.querySelector('.line-total-disp');
if (totalDisp) totalDisp.innerText = '₹' + lineTotal.toFixed(2);
// Update hidden inputs
var cgstInp = r.querySelector('.line-cgst');
if (cgstInp) cgstInp.value = cgstPer;
var sgstInp = r.querySelector('.line-sgst');
if (sgstInp) sgstInp.value = sgstPer;
var igstInp = r.querySelector('.line-igst');
if (igstInp) igstInp.value = igstPer;
var grossInp = r.querySelector('.line-gross-hid');
if (grossInp) grossInp.value = gross.toFixed(2);
var taxableInp = r.querySelector('.line-taxable-hid');
if (taxableInp) taxableInp.value = taxable.toFixed(2);
// Accumulate
totalGross += gross;
totalDisc += (lineSch + tradeDiscAmt);
totalTaxable += taxable;
totalCgst += cgstAmt;
totalSgst += sgstAmt;
totalIgst += igstAmt;
totalTax += lineTax;
});
document.getElementById('grn-taxable-disp').innerText = '₹' + taxable.toFixed(2);
document.getElementById('grn-gross-disp').innerText = '₹' + totalGross.toFixed(2);
document.getElementById('grn-discount-disp').innerText = '₹' + totalDisc.toFixed(2);
document.getElementById('grn-taxable-disp').innerText = '₹' + totalTaxable.toFixed(2);
if (isInterState) {
document.getElementById('row-cgst').style.display = 'none';
document.getElementById('row-sgst').style.display = 'none';
document.getElementById('row-igst').style.display = '';
document.getElementById('grn-igst-disp').innerText = '₹' + totalIgst.toFixed(2);
} else {
document.getElementById('row-cgst').style.display = '';
document.getElementById('row-sgst').style.display = '';
document.getElementById('row-igst').style.display = 'none';
document.getElementById('grn-cgst-disp').innerText = '₹' + totalCgst.toFixed(2);
document.getElementById('grn-sgst-disp').innerText = '₹' + totalSgst.toFixed(2);
}
document.getElementById('grn-tax-disp').innerText = '₹' + totalTax.toFixed(2);
document.getElementById('grn-net-disp').innerText = '₹' + (taxable + totalTax).toFixed(2);
var roundOff = parseFloat(document.getElementById('grn_round_off')?.value || 0);
var netPayable = totalTaxable + totalTax + roundOff;
document.getElementById('grn-net-disp').innerText = '₹' + netPayable.toFixed(2);
}
function submitGrnForm() {

View File

@ -62,37 +62,61 @@
<table class="items-table">
<thead>
<tr>
<th style="width: 25px;">#</th>
<th style="width: 20px;">#</th>
<th>Item Name</th>
<th style="width: 75px;">Batch No</th>
<th style="width: 65px;">Expiry</th>
<th class="text-center" style="width: 45px;">Pack</th>
<th class="text-center" style="width: 50px;">Ordered</th>
<th class="text-center" style="width: 50px;">Received</th>
<th class="text-center" style="width: 40px;">Free</th>
<th class="text-center" style="width: 45px;">Damaged</th>
<th class="text-end" style="width: 65px;">Rate/Pack</th>
<th class="text-end" style="width: 60px;">MRP</th>
<th class="text-end" style="width: 75px;">Line Total</th>
<th style="width: 65px;">Batch No</th>
<th style="width: 55px;">Expiry</th>
<th class="text-center" style="width: 35px;">Pack</th>
<th class="text-center" style="width: 45px;">Rec. Qty</th>
<th class="text-center" style="width: 35px;">Free</th>
<th class="text-end" style="width: 55px;">Rate</th>
<th class="text-end" style="width: 50px;">MRP</th>
<th class="text-end" style="width: 55px;">Gross</th>
<th class="text-center" style="width: 40px;">Disc%</th>
<th class="text-end" style="width: 55px;">Taxable</th>
<th class="text-center" style="width: 50px;">GST</th>
<th class="text-end" style="width: 65px;">Line Total</th>
</tr>
</thead>
<tbody>
<?php
$pdfGross = 0.0;
$pdfDisc = 0.0;
?>
<?php if (!empty($items)): ?>
<?php foreach ($items as $idx => $it): ?>
<?php
$gross = (float)($it['gross_amount'] ?: ((float)$it['received_packs'] * (float)$it['unit_purchase_rate']));
$schPer = (float)($it['sch_disc_per'] ?? 0);
$schAmt = (float)($it['sch_disc_amt'] ?? 0);
$schTotal = ($schPer > 0 ? ($gross * $schPer / 100) : 0) + $schAmt;
$tradeDisc = (float)$it['discount_percent'];
$afterSch = max(0, $gross - $schTotal);
$tradeDiscAmt = ($tradeDisc > 0) ? ($afterSch * $tradeDisc / 100) : 0;
$rowDisc = $schTotal + $tradeDiscAmt;
$pdfGross += $gross;
$pdfDisc += $rowDisc;
$isInter = (!empty($grn['gst_type']) && $grn['gst_type'] === 'inter_state') || ((float)($it['igst_percent'] ?? 0) > 0);
$gstStr = $isInter
? number_format((float)($it['igst_percent'] ?? 0), 0) . '% IGST'
: number_format((float)$it['cgst_percent'] + (float)$it['sgst_percent'], 0) . '%';
?>
<tr>
<td class="text-center"><?= $idx + 1 ?></td>
<td><strong><?= esc($it['item_name']) ?></strong></td>
<td><?= esc($it['batch_no']) ?></td>
<td><?= date('d-M-Y', strtotime($it['expiry_date'])) ?></td>
<td class="text-center"><?= (int)$it['packing'] ?>s</td>
<td class="text-center"><?= (float)$it['order_packs'] ?></td>
<td class="text-center"><strong><?= (float)$it['received_packs'] ?></strong></td>
<td class="text-center"><?= (float)$it['free_packs'] ?></td>
<td class="text-center <?= ((float)$it['rejected_packs'] > 0) ? 'style=\"color:red;\"' : '' ?>">
<?= (float)$it['rejected_packs'] ?>
</td>
<td class="text-end">₹<?= number_format((float)$it['unit_purchase_rate'], 2) ?></td>
<td class="text-end">₹<?= number_format((float)$it['mrp'], 2) ?></td>
<td class="text-end">₹<?= number_format($gross, 2) ?></td>
<td class="text-center"><?= number_format($tradeDisc, 0) ?>%</td>
<td class="text-end">₹<?= number_format((float)$it['taxable_amount'], 2) ?></td>
<td class="text-center"><?= esc($gstStr) ?></td>
<td class="text-end">₹<?= number_format((float)$it['total_amount'], 2) ?></td>
</tr>
<?php endforeach; ?>
@ -101,16 +125,45 @@
</table>
<table class="totals-table">
<tr>
<td>Subtotal (Gross Amount):</td>
<td class="text-end">₹<?= number_format($pdfGross, 2) ?></td>
</tr>
<tr>
<td>Total Discount:</td>
<td class="text-end">₹<?= number_format($pdfDisc, 2) ?></td>
</tr>
<tr>
<td>Taxable Amount:</td>
<td class="text-end">₹<?= number_format((float)$grn['total_received_amount'], 2) ?></td>
</tr>
<?php if (!empty($grn['gst_type']) && $grn['gst_type'] === 'inter_state'): ?>
<tr>
<td>IGST Amount:</td>
<td class="text-end">₹<?= number_format((float)($grn['igst_amount'] ?? $grn['total_tax_amount']), 2) ?></td>
</tr>
<?php else: ?>
<tr>
<td>CGST Amount:</td>
<td class="text-end">₹<?= number_format((float)($grn['cgst_amount'] ?? ((float)$grn['total_tax_amount'] / 2)), 2) ?></td>
</tr>
<tr>
<td>SGST Amount:</td>
<td class="text-end">₹<?= number_format((float)($grn['sgst_amount'] ?? ((float)$grn['total_tax_amount'] / 2)), 2) ?></td>
</tr>
<?php endif; ?>
<tr>
<td>GST Tax:</td>
<td>Total GST:</td>
<td class="text-end">₹<?= number_format((float)$grn['total_tax_amount'], 2) ?></td>
</tr>
<?php if (isset($grn['round_off']) && (float)$grn['round_off'] != 0): ?>
<tr>
<td>Round Off:</td>
<td class="text-end"><?= ((float)$grn['round_off'] > 0 ? '+' : '') . number_format((float)$grn['round_off'], 2) ?></td>
</tr>
<?php endif; ?>
<tr class="grand-total">
<td>Net Payable Total:</td>
<td>Net Inward Bill Total:</td>
<td class="text-end">₹<?= number_format((float)$grn['net_amount'], 2) ?></td>
</tr>
</table>

View File

@ -102,25 +102,20 @@
</div>
<div class="col-md-2">
<label class="form-label">CGST</label>
<select class="form-select form-select-sm" name="input_CGST" id="input_CGST" onchange="calculate()">
<select class="form-select form-select-sm" name="input_CGST" id="input_CGST" onchange="onCgstChange()">
<?php
$formatGstLabel = static function($val) {
$f = (float)$val;
if ($f == 0.0) return '0% (Exempt)';
if ($f == 2.5) return '2.5% (5% GST)';
if ($f == 6.0) return '6.0% (12% GST)';
if ($f == 9.0) return '9.0% (18% GST)';
if ($f == 20.0) return '20% (40% GST)';
if ($f == 6.0) return '6.0% (12% Legacy)';
if ($f == 14.0) return '14% (28% Legacy)';
if ($f == 14.0) return '14.0% (28% GST)';
return $val . '%';
};
$stdRates = ['0', '2.5', '9', '20', '6', '14'];
$pRates = !empty($gstRates) ? array_map(static fn($r) => trim((string)($r->gst_per ?? '0')), $gstRates) : $stdRates;
foreach ($stdRates as $sr) {
if (!in_array($sr, $pRates, true)) $pRates[] = $sr;
}
$stdRates = ['0', '2.5', '6', '9', '14'];
?>
<?php foreach ($pRates as $gstValue): ?>
<?php foreach ($stdRates as $gstValue): ?>
<option value="<?= esc($gstValue) ?>" <?= ((float) $gstValue === 2.5) ? 'selected' : '' ?>><?= esc($formatGstLabel($gstValue)) ?></option>
<?php endforeach; ?>
</select>
@ -128,7 +123,7 @@
<div class="col-md-2">
<label class="form-label">SGST / IGST</label>
<select class="form-select form-select-sm" name="input_SGST" id="input_SGST" onchange="calculate()">
<?php foreach ($pRates as $gstValue): ?>
<?php foreach ($stdRates as $gstValue): ?>
<option value="<?= esc($gstValue) ?>" <?= ((float) $gstValue === 2.5) ? 'selected' : '' ?>><?= esc($formatGstLabel($gstValue)) ?></option>
<?php endforeach; ?>
</select>
@ -179,6 +174,12 @@
</div>
<script>
function onCgstChange() {
var cgst = $('#input_CGST').val();
$('#input_SGST').val(cgst);
calculate();
}
function calculate() {
var pp = parseFloat($('#input_purchase_price').val() || 0);
var qty = parseFloat($('#input_Qty').val() || 0);
@ -186,23 +187,26 @@ function calculate() {
var schamt = parseFloat($('#input_sch_amount').val() || 0);
var schdisc = parseFloat($('#input_sch_disc').val() || 0);
var gross = pp * qty;
var sch_per_amt = 0;
if (schdisc > 0) {
pp = pp - (pp * schdisc / 100);
sch_per_amt = gross * (schdisc / 100);
}
var after_sch = Math.max(0, gross - sch_per_amt - schamt);
var amount = pp * qty;
if (schamt > 0) {
amount = amount - schamt;
var trade_disc_amt = 0;
if (disc > 0) {
trade_disc_amt = after_sch * (disc / 100);
}
var taxamount = Math.max(0, after_sch - trade_disc_amt);
var taxamount = (amount - (amount * disc / 100));
var cgst = parseFloat($('#input_CGST').val() || 0);
var sgst = parseFloat($('#input_SGST').val() || 0);
var cgst_amt = taxamount * cgst / 100;
var sgst_amt = taxamount * sgst / 100;
var net_amount = taxamount + cgst_amt + sgst_amt;
$('#amount_price').val(amount.toFixed(2));
$('#amount_price').val(gross.toFixed(2));
$('#Tamount_price').val(taxamount.toFixed(2));
$('#Net_amount').val(net_amount.toFixed(2));
@ -332,9 +336,8 @@ function edit_item_invoice(inv_item_id) {
$('#input_storage').val(data.cold_storage || '');
$('#input_shelf_no').val(data.shelf_no || '');
$('#input_rack_no').val(data.rack_no || '');
$('#input_HSNCODE').val(data.HSNCODE || '');
$('#input_CGST').val(data.CGST_per || '0');
$('#input_SGST').val(data.SGST_per || '0');
setSelectByNumericValue('input_CGST', data.CGST_per || '0');
setSelectByNumericValue('input_SGST', data.SGST_per || '0');
$('#invoice_item_id').val(data.item_id || 0);
calculate();
@ -342,6 +345,21 @@ function edit_item_invoice(inv_item_id) {
}, 'json');
}
function setSelectByNumericValue(selectId, val) {
var fVal = parseFloat(val || 0);
var matched = false;
$('#' + selectId + ' option').each(function() {
if (Math.abs(parseFloat($(this).val()) - fVal) < 0.01) {
$('#' + selectId).val($(this).val());
matched = true;
return false;
}
});
if (!matched) {
$('#' + selectId).val(val);
}
}
$(document).ready(function () {
toggle_update_purchase_items(false);
load_form_div('<?= base_url('Medical/purchase_invoice_item_list/' . (int) ($invoice->id ?? 0)) ?>', 'invoice_item_list');