GWM : tax calculation : using taxable amount column now

This commit is contained in:
Gowtham M 2026-07-20 09:48:31 +05:30
parent 6ba17a3a6f
commit 67b2c32884
6 changed files with 37 additions and 13 deletions

View File

@ -247,6 +247,8 @@ Each sub-master supports: `GET /` (list), `GET /:id`, `POST /`, `PUT /:id`, `DEL
| [x] | GET | `/purchase-orders/:poId/attachments/:attachmentId/download` | view | Download file (authenticated) |
| [x] | DELETE | `/purchase-orders/:poId/attachments/:attachmentId` | delete | Delete attachment + file |
**Totals:** `taxable_amount = sub_total + freight + other discount`; GST (`tax_total` / CGST+SGST / IGST) is calculated on `taxable_amount` (not on `sub_total`). Column persisted via `scripts/patch-po-taxable-amount.sql`.
---
### GRN (`/grn`) — module: `GRN`

View File

@ -698,6 +698,7 @@ model purchase_orders {
expected_delivery_date DateTime? @db.Date
sub_total Decimal @default(0) @db.Decimal(15, 4)
discount_amount Decimal @default(0) @db.Decimal(15, 4)
taxable_amount Decimal @default(0) @db.Decimal(15, 4)
tax_total Decimal @default(0) @db.Decimal(15, 4)
cgst Decimal @default(0) @db.Decimal(15, 4)
sgst Decimal @default(0) @db.Decimal(15, 4)

View File

@ -0,0 +1,21 @@
-- Persist PO header taxable amount and support GST-on-taxable calc
-- taxable_amount = sub_total + freight_charges + other_charges - discount_amount
ALTER TABLE purchase_orders
ADD COLUMN IF NOT EXISTS taxable_amount NUMERIC(15, 4) NOT NULL DEFAULT 0;
UPDATE purchase_orders
SET taxable_amount = ROUND(
COALESCE(sub_total, 0)
+ COALESCE(freight_charges, 0)
+ COALESCE(other_charges, 0)
- COALESCE(discount_amount, 0),
4
)
WHERE taxable_amount = 0
AND (
COALESCE(sub_total, 0) <> 0
OR COALESCE(freight_charges, 0) <> 0
OR COALESCE(other_charges, 0) <> 0
OR COALESCE(discount_amount, 0) <> 0
);

View File

@ -172,6 +172,10 @@ paths:
get:
tags: [Purchase Orders]
summary: Get purchase order detail
description: >
Includes line items and amount summary. Header taxable_amount =
sub_total + freight_charges + other_charges - discount_amount.
tax_total / cgst+sgst / igst are calculated on taxable_amount.
parameters:
- { name: id, in: path, required: true, schema: { type: string, example: '1' } }
responses:

View File

@ -33,7 +33,7 @@ const computeHeaderTotals = (
) => {
// Sub total = sum of line taxable amounts (after line-level discounts).
const subTotal = round4(lines.reduce((sum, line) => sum + toNum(line.taxable_amount), 0));
const taxTotal = round4(lines.reduce((sum, line) => sum + toNum(line.tax_amount), 0));
const lineTaxTotal = round4(lines.reduce((sum, line) => sum + toNum(line.tax_amount), 0));
const headerDiscountAmount = round4(headerDiscount);
const freightCharges = round4(freight);
const otherCharges = round4(other);
@ -41,6 +41,12 @@ const computeHeaderTotals = (
// Taxable amount = Sub Total + Freight + Other Discount (header-level).
const taxableAmount = round4(subTotal + freightCharges + otherCharges - headerDiscountAmount);
// GST is applied on header taxable amount (not on sub_total alone).
// Scale line taxes proportionally so multi-rate POs stay correct:
// each line's share of freight/other/discount is taxed at that line's GST %.
const taxTotal =
subTotal > 0 ? round4((lineTaxTotal * taxableAmount) / subTotal) : 0;
// TDS is calculated on sub total (line taxable), not on header taxable.
const tdsAmount = tdsApplicable ? round4((subTotal * toNum(tdsSectionPct)) / 100) : 0;
const adjustmentAmount = round4(adjustment);

View File

@ -21,7 +21,6 @@ const {
computeHeaderTotals,
splitGst,
toNum,
round4,
} = require('./purchase-orders.calculations');
const repository = require('./purchase-orders.repository');
const { assertAnyLocation } = require('../../utils/locations');
@ -144,13 +143,6 @@ const sanitizePo = (po) => {
return {
...rest,
// Header taxable = sub_total + freight + other discount (not a DB column).
taxable_amount: round4(
toNum(rest.sub_total) +
toNum(rest.freight_charges) +
toNum(rest.other_charges) -
toNum(rest.discount_amount)
),
vendor: vendors || null,
billing: billing_location || null,
shipping: shipping_location || null,
@ -320,10 +312,8 @@ const buildHeaderData = async (payload, builtItems, userId, { applyDefaultTerms
adjustment: payload.adjustment,
}
);
// taxable_amount is derived for API/PDF; not a persisted PO column.
const { taxable_amount: _taxableAmount, ...persistedTotals } = totals;
const gstSplit = splitGst(persistedTotals.tax_total, isInterStateSupply(billing, vendor));
const gstSplit = splitGst(totals.tax_total, isInterStateSupply(billing, vendor));
let termsAndConditions =
payload.terms_and_conditions !== undefined && payload.terms_and_conditions !== null
@ -349,7 +339,7 @@ const buildHeaderData = async (payload, builtItems, userId, { applyDefaultTerms
remarks: payload.remarks || null,
tds_applicable: tdsApplicable,
tds_section_pct: tdsApplicable ? tdsSectionPct : null,
...persistedTotals,
...totals,
...gstSplit,
updated_by: userId ? BigInt(userId) : null,
};