feat(tax): standardize current GST slabs (0%, 5%, 18%, 40%, legacy 12%, 28%) across HMS and Medical Store

This commit is contained in:
DevSoftTech Server 2026-09-10 07:13:04 +05:30
parent 358416fc10
commit 5187fcf3ed
8 changed files with 142 additions and 75 deletions

View File

@ -531,20 +531,27 @@ class MedicalStoreApi extends BaseController
}
}
$gstRates = [0, 5, 12, 18, 28];
// Current GST Slabs: 0% (Nil/Exempt), 5% (Common/Basic), 18% (Standard), 40% (Demerit/Sin), plus legacy 12% and 28%
$gstRates = [0, 5, 18, 40, 12, 28];
if ($this->db->tableExists('med_gst_per')) {
$gRows = $this->db->table('med_gst_per')->select('gst_per')->orderBy('gst_per', 'ASC')->get()->getResultArray();
if (!empty($gRows)) {
$customRates = [];
$mappedRates = [];
foreach ($gRows as $gr) {
$r = (float)$gr['gst_per'];
if (!in_array($r, $customRates, true)) {
$customRates[] = $r;
$half = (float)$gr['gst_per'];
$tot = round($half * 2, 2); // med_gst_per stores CGST half-rate
if ($tot > 0 && !in_array($tot, $mappedRates)) {
$mappedRates[] = $tot;
}
}
if (!empty($customRates)) {
$gstRates = $customRates;
foreach ([0, 5, 18, 40, 12, 28] as $reqRate) {
if (!in_array($reqRate, $mappedRates)) {
$mappedRates[] = $reqRate;
}
}
$mappedRates = array_values(array_unique($mappedRates));
sort($mappedRates);
$gstRates = $mappedRates;
}
}

View File

@ -11053,11 +11053,13 @@ class Medical extends BaseController
$camt = (float) ($row['cgst_2_5'] ?? 0)
+ (float) ($row['cgst_6'] ?? 0)
+ (float) ($row['cgst_9'] ?? 0)
+ (float) ($row['cgst_14'] ?? 0);
+ (float) ($row['cgst_14'] ?? 0)
+ (float) ($row['cgst_20'] ?? 0);
$samt = (float) ($row['sgst_2_5'] ?? 0)
+ (float) ($row['sgst_6'] ?? 0)
+ (float) ($row['sgst_9'] ?? 0)
+ (float) ($row['sgst_14'] ?? 0);
+ (float) ($row['sgst_14'] ?? 0)
+ (float) ($row['sgst_20'] ?? 0);
$hsnData[] = [
'num' => $num++,
@ -11240,6 +11242,9 @@ class Medical extends BaseController
. 'ROUND(SUM(CASE WHEN ' . $cgstPerExpr . '=14 THEN ' . $taxableExpr . ' ELSE 0 END),0) AS sale_28_amount,'
. 'SUM(CASE WHEN ' . $cgstPerExpr . '=14 THEN ' . $cgstExpr . ' ELSE 0 END) AS cgst_14,'
. 'SUM(CASE WHEN ' . $cgstPerExpr . '=14 THEN ' . $sgstExpr . ' ELSE 0 END) AS sgst_14,'
. 'ROUND(SUM(CASE WHEN ' . $cgstPerExpr . '=20 THEN ' . $taxableExpr . ' ELSE 0 END),0) AS sale_40_amount,'
. 'SUM(CASE WHEN ' . $cgstPerExpr . '=20 THEN ' . $cgstExpr . ' ELSE 0 END) AS cgst_20,'
. 'SUM(CASE WHEN ' . $cgstPerExpr . '=20 THEN ' . $sgstExpr . ' ELSE 0 END) AS sgst_20,'
. 'ROUND(SUM(CASE WHEN ' . $cgstPerExpr . '=0 THEN ' . $taxableExpr . ' ELSE 0 END),0) AS sale_0_amount '
. 'FROM invoice_med_master m '
. 'JOIN inv_med_item i ON m.' . $mid . '=i.' . $invMedId . ' '

View File

@ -0,0 +1,39 @@
<?php
namespace App\Database\Migrations;
use CodeIgniter\Database\Migration;
class UpdateGstSlabsAcrossMasters extends Migration
{
public function up()
{
// 1. Ensure med_gst_per exists and contains the current GST slabs (half-rates for CGST/SGST)
// Slabs: 0% -> 0.00, 5% -> 2.50, 18% -> 9.00, 40% -> 20.00, 12% (legacy) -> 6.00, 28% (legacy) -> 14.00
if ($this->db->tableExists('med_gst_per')) {
$requiredRates = [
0.00,
2.50,
6.00,
9.00,
14.00,
20.00
];
foreach ($requiredRates as $rate) {
$row = $this->db->query("SELECT id FROM `med_gst_per` WHERE ROUND(`gst_per`, 2) = ROUND(?, 2) LIMIT 1", [$rate])->getRowArray();
if (!$row) {
$this->db->table('med_gst_per')->insert([
'gst_per' => $rate
]);
}
}
}
}
public function down()
{
// Keep rates for safety of historical data
}
}

View File

@ -121,33 +121,34 @@ $flag = static function ($v): string {
<div class="col-md-1">
<label class="form-label">CGST</label>
<select class="form-select" name="input_CGST" id="input_CGST">
<?php if (! empty($gstRates)): ?>
<?php foreach ($gstRates as $rate): ?>
<?php $gstValue = trim((string) ($rate->gst_per ?? '0')); ?>
<?php if ($gstValue === '') { $gstValue = '0'; } ?>
<option value="<?= esc($gstValue) ?>" <?= ((float) $gstValue === (float) $cgst) ? 'selected' : '' ?>><?= esc($gstValue) ?></option>
<?php endforeach; ?>
<?php else: ?>
<?php foreach (['0', '2.5', '5', '6', '9', '12'] as $gstValue): ?>
<option value="<?= esc($gstValue) ?>" <?= ((float) $gstValue === (float) $cgst) ? 'selected' : '' ?>><?= esc($gstValue) ?></option>
<?php endforeach; ?>
<?php endif; ?>
<?php
$formatGstOptionLabel = static function($val) {
$f = (float)$val;
if ($f == 0.0) return '0% (Exempt)';
if ($f == 2.5) return '2.5% (5% 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)';
return $val . '%';
};
$standardGstValues = ['0', '2.5', '9', '20', '6', '14'];
$rawRates = !empty($gstRates) ? array_map(static fn($r) => trim((string)($r->gst_per ?? '0')), $gstRates) : $standardGstValues;
foreach ($standardGstValues as $sv) {
if (!in_array($sv, $rawRates, true)) $rawRates[] = $sv;
}
?>
<?php foreach ($rawRates as $gstValue): ?>
<option value="<?= esc($gstValue) ?>" <?= ((float) $gstValue === (float) $cgst) ? 'selected' : '' ?>><?= esc($formatGstOptionLabel($gstValue)) ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="col-md-1">
<label class="form-label">SGST</label>
<select class="form-select" name="input_SGST" id="input_SGST">
<?php if (! empty($gstRates)): ?>
<?php foreach ($gstRates as $rate): ?>
<?php $gstValue = trim((string) ($rate->gst_per ?? '0')); ?>
<?php if ($gstValue === '') { $gstValue = '0'; } ?>
<option value="<?= esc($gstValue) ?>" <?= ((float) $gstValue === (float) $sgst) ? 'selected' : '' ?>><?= esc($gstValue) ?></option>
<?php endforeach; ?>
<?php else: ?>
<?php foreach (['0', '2.5', '5', '6', '9', '12'] as $gstValue): ?>
<option value="<?= esc($gstValue) ?>" <?= ((float) $gstValue === (float) $sgst) ? 'selected' : '' ?>><?= esc($gstValue) ?></option>
<?php endforeach; ?>
<?php endif; ?>
<?php foreach ($rawRates as $gstValue): ?>
<option value="<?= esc($gstValue) ?>" <?= ((float) $gstValue === (float) $sgst) ? 'selected' : '' ?>><?= esc($formatGstOptionLabel($gstValue)) ?></option>
<?php endforeach; ?>
</select>
</div>

View File

@ -103,37 +103,34 @@
<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()">
<?php if (! empty($gstRates)): ?>
<?php foreach ($gstRates as $rate): ?>
<?php $gstValue = (string) ($rate->gst_per ?? '0'); ?>
<option value="<?= esc($gstValue) ?>" <?= ((float) $gstValue === 2.5) ? 'selected' : '' ?>><?= esc($gstValue) ?></option>
<?php endforeach; ?>
<?php else: ?>
<option value="0">0</option>
<option value="2.5" selected>2.5</option>
<option value="5">5</option>
<option value="6">6</option>
<option value="9">9</option>
<option value="12">12</option>
<?php endif; ?>
<?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 == 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)';
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;
}
?>
<?php foreach ($pRates as $gstValue): ?>
<option value="<?= esc($gstValue) ?>" <?= ((float) $gstValue === 2.5) ? 'selected' : '' ?>><?= esc($formatGstLabel($gstValue)) ?></option>
<?php endforeach; ?>
</select>
</div>
<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 if (! empty($gstRates)): ?>
<?php foreach ($gstRates as $rate): ?>
<?php $gstValue = (string) ($rate->gst_per ?? '0'); ?>
<option value="<?= esc($gstValue) ?>" <?= ((float) $gstValue === 2.5) ? 'selected' : '' ?>><?= esc($gstValue) ?></option>
<?php endforeach; ?>
<?php else: ?>
<option value="0">0</option>
<option value="2.5" selected>2.5</option>
<option value="5">5</option>
<option value="6">6</option>
<option value="9">9</option>
<option value="12">12</option>
<?php endif; ?>
<?php foreach ($pRates as $gstValue): ?>
<option value="<?= esc($gstValue) ?>" <?= ((float) $gstValue === 2.5) ? 'selected' : '' ?>><?= esc($formatGstLabel($gstValue)) ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="col-md-2">

View File

@ -30,6 +30,9 @@ $showHeader = !empty($showHeader);
<th class="text-end">Tax.Amt.28%</th>
<th class="text-end">CGST 14%</th>
<th class="text-end">SGST 14%</th>
<th class="text-end">Tax.Amt.40%</th>
<th class="text-end">CGST 20%</th>
<th class="text-end">SGST 20%</th>
<th class="text-end">Tax.Amt.0%</th>
</tr>
</thead>
@ -54,6 +57,9 @@ $showHeader = !empty($showHeader);
<td class="text-end"><?= esc(number_format((float) ($row['sale_28_amount'] ?? 0), 2)) ?></td>
<td class="text-end"><?= esc(number_format((float) ($row['cgst_14'] ?? 0), 2)) ?></td>
<td class="text-end"><?= esc(number_format((float) ($row['sgst_14'] ?? 0), 2)) ?></td>
<td class="text-end"><?= esc(number_format((float) ($row['sale_40_amount'] ?? 0), 2)) ?></td>
<td class="text-end"><?= esc(number_format((float) ($row['cgst_20'] ?? 0), 2)) ?></td>
<td class="text-end"><?= esc(number_format((float) ($row['sgst_20'] ?? 0), 2)) ?></td>
<td class="text-end"><?= esc(number_format((float) ($row['sale_0_amount'] ?? 0), 2)) ?></td>
</tr>
<?php endforeach; ?>

View File

@ -32,6 +32,9 @@ $showHeader = !empty($showHeader);
<th class="text-end">Tax.Amt.28%</th>
<th class="text-end">CGST 14%</th>
<th class="text-end">SGST 14%</th>
<th class="text-end">Tax.Amt.40%</th>
<th class="text-end">CGST 20%</th>
<th class="text-end">SGST 20%</th>
<th class="text-end">Tax.Amt.0%</th>
</tr>
</thead>
@ -58,6 +61,9 @@ $showHeader = !empty($showHeader);
<td class="text-end"><?= esc(number_format((float) ($row['sale_28_amount'] ?? 0), 2)) ?></td>
<td class="text-end"><?= esc(number_format((float) ($row['cgst_14'] ?? 0), 2)) ?></td>
<td class="text-end"><?= esc(number_format((float) ($row['sgst_14'] ?? 0), 2)) ?></td>
<td class="text-end"><?= esc(number_format((float) ($row['sale_40_amount'] ?? 0), 2)) ?></td>
<td class="text-end"><?= esc(number_format((float) ($row['cgst_20'] ?? 0), 2)) ?></td>
<td class="text-end"><?= esc(number_format((float) ($row['sgst_20'] ?? 0), 2)) ?></td>
<td class="text-end"><?= esc(number_format((float) ($row['sale_0_amount'] ?? 0), 2)) ?></td>
</tr>
<?php endforeach; ?>

View File

@ -4047,7 +4047,7 @@ function PurchaseView({ store, notify }) {
const [isSearchingMeds, setIsSearchingMeds] = useState(false);
const [showMasterModal, setShowMasterModal] = useState(false);
const [masterTargetRow, setMasterTargetRow] = useState(null);
const [supportData, setSupportData] = useState({ formulations: [], companies: [], gst_rates: [0, 5, 12, 18, 28], schedules: [] });
const [supportData, setSupportData] = useState({ formulations: [], companies: [], gst_rates: [0, 5, 18, 40, 12, 28], schedules: [] });
const [isSavingMaster, setIsSavingMaster] = useState(false);
const [masterForm, setMasterForm] = useState({
item_name: '',
@ -4056,7 +4056,7 @@ function PurchaseView({ store, notify }) {
units_per_pack: 10,
packing: '10',
hsn_code: '3004',
gst_rate: 12,
gst_rate: 5,
drug_schedule: 'Schedule H',
company_name: '',
min_reorder_level: 10
@ -5819,11 +5819,12 @@ function PurchaseView({ store, notify }) {
handleItemFormChange('sgst_pct', val);
}}
>
<option value="0">0%</option>
<option value="2.5">2.5%</option>
<option value="6">6.0%</option>
<option value="9">9.0%</option>
<option value="14">14.0%</option>
<option value="0">0% (Nil / Exempt)</option>
<option value="2.5">2.5% (5% GST)</option>
<option value="9">9.0% (18% GST)</option>
<option value="20">20.0% (40% GST)</option>
<option value="6">6.0% (12% Legacy)</option>
<option value="14">14.0% (28% Legacy)</option>
</select>
</div>
<div className="col-md-2 col-6">
@ -5833,11 +5834,12 @@ function PurchaseView({ store, notify }) {
value={itemForm.sgst_pct}
onChange={(e) => handleItemFormChange('sgst_pct', parseFloat(e.target.value) || 0)}
>
<option value="0">0%</option>
<option value="2.5">2.5%</option>
<option value="6">6.0%</option>
<option value="9">9.0%</option>
<option value="14">14.0%</option>
<option value="0">0% (Nil / Exempt)</option>
<option value="2.5">2.5% (5% GST)</option>
<option value="9">9.0% (18% GST)</option>
<option value="20">20.0% (40% GST)</option>
<option value="6">6.0% (12% Legacy)</option>
<option value="14">14.0% (28% Legacy)</option>
</select>
</div>
<div className="col-md-2 col-6">
@ -7270,9 +7272,12 @@ function PurchaseView({ store, notify }) {
value={masterForm.gst_rate}
onChange={(e) => setMasterForm({ ...masterForm, gst_rate: Number(e.target.value) })}
>
{(supportData.gst_rates || [0, 5, 12, 18, 28]).map((g, idx) => (
<option key={idx} value={g}>{g}% GST</option>
))}
<option value="0">0% (Nil / Exempt)</option>
<option value="5">5% (Common / Basic Services)</option>
<option value="18">18% (Standard - Most Products)</option>
<option value="40">40% (Demerit / Sin Tax)</option>
<option value="12">12% (Legacy Medicine Slab)</option>
<option value="28">28% (Legacy High Slab)</option>
</select>
</div>
<div className="col-md-3">
@ -7684,11 +7689,12 @@ function PurchaseView({ store, notify }) {
setPoCreateForm({ ...poCreateForm, items });
}}
>
<option value="0">0%</option>
<option value="0">0% (Exempt)</option>
<option value="5">5%</option>
<option value="12">12%</option>
<option value="18">18%</option>
<option value="28">28%</option>
<option value="40">40%</option>
<option value="12">12% (Legacy)</option>
<option value="28">28% (Legacy)</option>
</select>
</td>
<td className="text-end fw-bold">