From 068cbe8ccdfa5b9d72a78fb892d4e32de5757da8 Mon Sep 17 00:00:00 2001 From: DevSoftTech Server Date: Thu, 2 Jul 2026 22:35:34 +0530 Subject: [PATCH] Medical GST: persist item tax breakup and add backfill --- app/Controllers/Medical.php | 75 +++++++++++++++++-- ...07-02_backfill_inv_med_item_tax_fields.sql | 57 ++++++++++++++ 2 files changed, 126 insertions(+), 6 deletions(-) create mode 100644 scripts/sql/2026-07-02_backfill_inv_med_item_tax_fields.sql diff --git a/app/Controllers/Medical.php b/app/Controllers/Medical.php index baa2d09..15ff287 100644 --- a/app/Controllers/Medical.php +++ b/app/Controllers/Medical.php @@ -1656,8 +1656,11 @@ class Medical extends BaseController return redirect()->to(base_url('Medical/invoice_edit/' . $invoiceId . '?msg=' . urlencode('Required stock tables are missing'))); } + $purchaseItemFields = $this->db->getFieldNames('purchase_invoice_item') ?? []; + $igstPerStockExpr = in_array('IGST_per', $purchaseItemFields, true) ? 't.IGST_per' : '0 AS IGST_per'; + $stockSql = "SELECT p.id AS item_code,p.item_name,p.formulation,t.id AS stock_id,t.batch_no,t.expiry_date,t.mrp,t.selling_unit_rate, - t.HSNCODE,t.CGST_per,t.SGST_per,t.packing, + t.HSNCODE,t.CGST_per,t.SGST_per," . $igstPerStockExpr . ",t.packing, (ifnull(t.total_unit,0)-ifnull(t.total_sale_unit,0)-ifnull(t.total_return_unit,0)-ifnull(t.total_lost_unit,0)) AS stock_qty FROM med_product_master p JOIN purchase_invoice_item t ON p.id=t.item_code @@ -1682,6 +1685,10 @@ class Medical extends BaseController $amount = $qty * $price; $discAmount = ($discPer > 0) ? ($amount * $discPer / 100) : 0; $netAmount = $amount - $discAmount; + $cgstPer = (float) ($stock->CGST_per ?? 0); + $sgstPer = (float) ($stock->SGST_per ?? 0); + $igstPer = (float) ($stock->IGST_per ?? 0); + $taxBreakup = $this->calculateInvoiceItemTaxBreakup($netAmount, $cgstPer, $sgstPer, $igstPer); $itemFields = $this->db->getFieldNames('inv_med_item') ?? []; $insert = []; @@ -1712,8 +1719,13 @@ class Medical extends BaseController $setIfExists($insert, $itemFields, 'amount', $amount); $setIfExists($insert, $itemFields, 'tamount', $netAmount); $setIfExists($insert, $itemFields, 'twdisc_amount', $netAmount); - $setIfExists($insert, $itemFields, 'CGST_per', (float) ($stock->CGST_per ?? 0)); - $setIfExists($insert, $itemFields, 'SGST_per', (float) ($stock->SGST_per ?? 0)); + $setIfExists($insert, $itemFields, 'TaxableAmount', $taxBreakup['taxable']); + $setIfExists($insert, $itemFields, 'CGST_per', $cgstPer); + $setIfExists($insert, $itemFields, 'CGST', $taxBreakup['cgst']); + $setIfExists($insert, $itemFields, 'SGST_per', $sgstPer); + $setIfExists($insert, $itemFields, 'SGST', $taxBreakup['sgst']); + $setIfExists($insert, $itemFields, 'IGST_per', $igstPer); + $setIfExists($insert, $itemFields, 'IGST', $taxBreakup['igst']); $setIfExists($insert, $itemFields, 'HSNCODE', (string) ($stock->HSNCODE ?? '')); $setIfExists($insert, $itemFields, 'store_stock_id', (int) ($stock->stock_id ?? 0)); $setIfExists($insert, $itemFields, 'packing', (int) ($stock->packing ?? 1)); @@ -1875,6 +1887,10 @@ class Medical extends BaseController $amountValue = $updateQty * $itemRate; $discAmount = $amountValue * $discPer / 100; $tamountValue = $amountValue - $discAmount; + $cgstPer = (float) ($item->CGST_per ?? 0); + $sgstPer = (float) ($item->SGST_per ?? 0); + $igstPer = (float) ($item->IGST_per ?? 0); + $taxBreakup = $this->calculateInvoiceItemTaxBreakup($tamountValue, $cgstPer, $sgstPer, $igstPer); $update = [ 'qty' => $updateQty, @@ -1885,6 +1901,18 @@ class Medical extends BaseController if (in_array('twdisc_amount', $itemFields, true)) { $update['twdisc_amount'] = $tamountValue; } + if (in_array('TaxableAmount', $itemFields, true)) { + $update['TaxableAmount'] = $taxBreakup['taxable']; + } + if (in_array('CGST', $itemFields, true)) { + $update['CGST'] = $taxBreakup['cgst']; + } + if (in_array('SGST', $itemFields, true)) { + $update['SGST'] = $taxBreakup['sgst']; + } + if (in_array('IGST', $itemFields, true)) { + $update['IGST'] = $taxBreakup['igst']; + } $this->db->table('inv_med_item')->where('id', $itemId)->update($update); @@ -1988,6 +2016,10 @@ class Medical extends BaseController $discAmount = $amountValue * $discPer / 100; $tamountValue = $amountValue - $discAmount; + $cgstPer = (float) ($source['CGST_per'] ?? 0); + $sgstPer = (float) ($source['SGST_per'] ?? 0); + $igstPer = (float) ($source['IGST_per'] ?? 0); + $taxBreakup = $this->calculateInvoiceItemTaxBreakup($tamountValue, $cgstPer, $sgstPer, $igstPer); $payload = []; foreach ($itemFields as $field) { @@ -2017,6 +2049,10 @@ class Medical extends BaseController $setIfExists($payload, $itemFields, 'disc_per', $discPer); $setIfExists($payload, $itemFields, 'disc_whole', 0); $setIfExists($payload, $itemFields, 'twdisc_amount', $tamountValue); + $setIfExists($payload, $itemFields, 'TaxableAmount', $taxBreakup['taxable']); + $setIfExists($payload, $itemFields, 'CGST', $taxBreakup['cgst']); + $setIfExists($payload, $itemFields, 'SGST', $taxBreakup['sgst']); + $setIfExists($payload, $itemFields, 'IGST', $taxBreakup['igst']); $setIfExists($payload, $itemFields, 'update_by_id', $userId); $setIfExists($payload, $itemFields, 'update_by_remark', $userInfo . '[' . $userId . '][' . date('d-m-Y H:i:s') . ']'); @@ -3423,6 +3459,23 @@ class Medical extends BaseController } } + private function calculateInvoiceItemTaxBreakup(float $netAmount, float $cgstPer, float $sgstPer, float $igstPer): array + { + $rateTotal = $cgstPer + $sgstPer + $igstPer; + $taxable = $netAmount; + + if ($rateTotal > 0) { + $taxable = round(($netAmount * 100) / (100 + $rateTotal), 2); + } + + return [ + 'taxable' => $taxable, + 'cgst' => round($taxable * $cgstPer / 100, 2), + 'sgst' => round($taxable * $sgstPer / 100, 2), + 'igst' => round($taxable * $igstPer / 100, 2), + ]; + } + private function refreshInvoicePaymentFields(int $invoiceId, bool $rebuildFromItems = false): void { if (! $this->db->tableExists('invoice_med_master')) { @@ -10780,6 +10833,7 @@ class Medical extends BaseController $lineAmount = $resolveField($iFields, ['twdisc_amount', 'tamount', 'amount']); $taxable = $resolveField($iFields, ['TaxableAmount', 'taxableamount']); $cgstPer = $resolveField($iFields, ['CGST_per', 'cgst_per']); + $sgstPer = $resolveField($iFields, ['SGST_per', 'sgst_per']); $cgst = $resolveField($iFields, ['CGST', 'c_gst_amt', 'cgst', 'cgst_amt']); $sgst = $resolveField($iFields, ['SGST', 's_gst_amt', 'sgst', 'sgst_amt']); @@ -10789,10 +10843,19 @@ class Medical extends BaseController $qtyExpr = $qty !== null ? ('IFNULL(i.' . $qty . ',0)') : '0'; $lineAmountExpr = $lineAmount !== null ? ('IFNULL(i.' . $lineAmount . ',0)') : '0'; - $taxableExpr = $taxable !== null ? ('IFNULL(i.' . $taxable . ',0)') : $lineAmountExpr; + $baseTaxableExpr = $taxable !== null ? ('IFNULL(i.' . $taxable . ',0)') : '0'; $cgstPerExpr = $cgstPer !== null ? ('IFNULL(i.' . $cgstPer . ',0)') : '0'; - $cgstExpr = $cgst !== null ? ('IFNULL(i.' . $cgst . ',0)') : '((' . $taxableExpr . ' * ' . $cgstPerExpr . ')/100)'; - $sgstExpr = $sgst !== null ? ('IFNULL(i.' . $sgst . ',0)') : '((' . $taxableExpr . ' * ' . $cgstPerExpr . ')/100)'; + $sgstPerExpr = $sgstPer !== null ? ('IFNULL(i.' . $sgstPer . ',0)') : $cgstPerExpr; + $taxableExpr = '(CASE ' + . 'WHEN ' . $baseTaxableExpr . ' > 0 THEN ' . $baseTaxableExpr . ' ' + . 'WHEN ((' . $cgstPerExpr . ' + ' . $sgstPerExpr . ') > 0 AND ' . $lineAmountExpr . ' > 0) THEN ROUND((' . $lineAmountExpr . ' * 100) / (100 + (' . $cgstPerExpr . ' + ' . $sgstPerExpr . ')), 2) ' + . 'ELSE ' . $lineAmountExpr . ' END)'; + $cgstExpr = $cgst !== null + ? '(CASE WHEN IFNULL(i.' . $cgst . ',0) > 0 THEN IFNULL(i.' . $cgst . ',0) WHEN ((' . $cgstPerExpr . ' + ' . $sgstPerExpr . ') > 0 AND ' . $taxableExpr . ' > 0) THEN ROUND((' . $taxableExpr . ' * ' . $cgstPerExpr . ') / 100, 2) ELSE 0 END)' + : '((' . $taxableExpr . ' * ' . $cgstPerExpr . ')/100)'; + $sgstExpr = $sgst !== null + ? '(CASE WHEN IFNULL(i.' . $sgst . ',0) > 0 THEN IFNULL(i.' . $sgst . ',0) WHEN ((' . $cgstPerExpr . ' + ' . $sgstPerExpr . ') > 0 AND ' . $taxableExpr . ' > 0) THEN ROUND((' . $taxableExpr . ' * ' . $sgstPerExpr . ') / 100, 2) ELSE 0 END)' + : '((' . $taxableExpr . ' * ' . $sgstPerExpr . ')/100)'; $invTypeExpr = "'CASH'"; if ($ipdId !== null && $ipdCredit !== null) { diff --git a/scripts/sql/2026-07-02_backfill_inv_med_item_tax_fields.sql b/scripts/sql/2026-07-02_backfill_inv_med_item_tax_fields.sql new file mode 100644 index 0000000..eb621eb --- /dev/null +++ b/scripts/sql/2026-07-02_backfill_inv_med_item_tax_fields.sql @@ -0,0 +1,57 @@ +-- Backfill GST breakup columns in inv_med_item from existing rate + net amount. +-- Safe to run multiple times; it only targets rows where tax values are currently zero/empty. + +UPDATE inv_med_item +SET + TaxableAmount = ROUND( + CASE + WHEN (IFNULL(CGST_per, 0) + IFNULL(SGST_per, 0) + IFNULL(IGST_per, 0)) > 0 + THEN (IFNULL(twdisc_amount, IFNULL(tamount, IFNULL(amount, 0))) * 100) + / (100 + IFNULL(CGST_per, 0) + IFNULL(SGST_per, 0) + IFNULL(IGST_per, 0)) + ELSE IFNULL(twdisc_amount, IFNULL(tamount, IFNULL(amount, 0))) + END + , 2), + CGST = ROUND( + ( + CASE + WHEN (IFNULL(CGST_per, 0) + IFNULL(SGST_per, 0) + IFNULL(IGST_per, 0)) > 0 + THEN (IFNULL(twdisc_amount, IFNULL(tamount, IFNULL(amount, 0))) * 100) + / (100 + IFNULL(CGST_per, 0) + IFNULL(SGST_per, 0) + IFNULL(IGST_per, 0)) + ELSE IFNULL(twdisc_amount, IFNULL(tamount, IFNULL(amount, 0))) + END + ) * IFNULL(CGST_per, 0) / 100 + , 2), + SGST = ROUND( + ( + CASE + WHEN (IFNULL(CGST_per, 0) + IFNULL(SGST_per, 0) + IFNULL(IGST_per, 0)) > 0 + THEN (IFNULL(twdisc_amount, IFNULL(tamount, IFNULL(amount, 0))) * 100) + / (100 + IFNULL(CGST_per, 0) + IFNULL(SGST_per, 0) + IFNULL(IGST_per, 0)) + ELSE IFNULL(twdisc_amount, IFNULL(tamount, IFNULL(amount, 0))) + END + ) * IFNULL(SGST_per, 0) / 100 + , 2), + IGST = ROUND( + ( + CASE + WHEN (IFNULL(CGST_per, 0) + IFNULL(SGST_per, 0) + IFNULL(IGST_per, 0)) > 0 + THEN (IFNULL(twdisc_amount, IFNULL(tamount, IFNULL(amount, 0))) * 100) + / (100 + IFNULL(CGST_per, 0) + IFNULL(SGST_per, 0) + IFNULL(IGST_per, 0)) + ELSE IFNULL(twdisc_amount, IFNULL(tamount, IFNULL(amount, 0))) + END + ) * IFNULL(IGST_per, 0) / 100 + , 2) +WHERE + (IFNULL(CGST_per, 0) + IFNULL(SGST_per, 0) + IFNULL(IGST_per, 0)) >= 0 + AND ( + IFNULL(TaxableAmount, 0) = 0 + OR IFNULL(CGST, 0) = 0 + OR IFNULL(SGST, 0) = 0 + OR IFNULL(IGST, 0) = 0 + ); + +-- Quick verification sample: +-- SELECT id, inv_med_id, qty, amount, tamount, twdisc_amount, TaxableAmount, CGST_per, SGST_per, IGST_per, CGST, SGST, IGST +-- FROM inv_med_item +-- WHERE inv_med_id IN (1002584, 1002585, 1002586) +-- ORDER BY id;