Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions india_compliance/gst_india/constants/custom_fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -1556,6 +1556,41 @@
"Supplier Quotation": {**reverse_charge_field, "insert_after": "has_unit_price_items"},
}

# no fallback: a blank value means 0
SALES_ITEM_DOCTYPES = (
"Quotation Item",
"Sales Order Item",
"Delivery Note Item",
"Sales Invoice Item",
"POS Invoice Item",
)

RSP_FIELDS = {
SALES_ITEM_DOCTYPES: {
"fieldname": "gst_retail_sale_price",
"label": "Retail Sale Price (MRP)",
"fieldtype": "Currency",
"options": "Company:company:default_currency",
"insert_after": "price_list_rate",
"hidden": 1,
"print_hide": 1,
"translatable": 0,
}
}

MARGIN_FIELDS = {
SALES_ITEM_DOCTYPES: {
"fieldname": "gst_purchase_price",
"label": "Purchase Price",
"fieldtype": "Currency",
"options": "Company:company:default_currency",
"insert_after": "gst_retail_sale_price",
Comment thread
ljain112 marked this conversation as resolved.
"hidden": 1,
"print_hide": 1,
"translatable": 0,
}
}

E_INVOICE_FIELDS = {
"Sales Invoice": [
{
Expand Down
16 changes: 16 additions & 0 deletions india_compliance/gst_india/doctype/gst_settings/gst_settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
"hsn_wise_tax_breakup",
"enable_reverse_charge_in_sales",
"enable_overseas_transactions",
"enable_taxes_on_mrp",
"enable_margin_scheme",
"enable_sales_through_ecommerce_operators",
"round_off_gst_values",
"require_supplier_invoice_no",
Expand Down Expand Up @@ -258,6 +260,20 @@
"fieldtype": "Check",
"label": "Enable Reverse Charge in Sales Transactions"
},
{
"default": "0",
"description": "Adds the 'On MRP' charge type for RSP-based valuation (e.g. tobacco, Rule 31D). Enter the Retail Sale Price (MRP) in company currency per item row.",
"fieldname": "enable_taxes_on_mrp",
"fieldtype": "Check",
"label": "Enable Taxes on MRP"
},
{
"default": "0",
"description": "Adds the 'On Margin' charge type for the margin scheme (Rule 32(5)). Enter the Purchase Price in company currency per item row.",
"fieldname": "enable_margin_scheme",
"fieldtype": "Check",
"label": "Enable Margin Scheme"
},
{
"default": "0",
"depends_on": "eval: doc.enable_e_waybill",
Expand Down
15 changes: 15 additions & 0 deletions india_compliance/gst_india/doctype/gst_settings/gst_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
from india_compliance.gst_india.constants.custom_fields import (
E_INVOICE_FIELDS,
E_WAYBILL_FIELDS,
MARGIN_FIELDS,
RSP_FIELDS,
SALES_REVERSE_CHARGE_FIELDS,
)
from india_compliance.gst_india.doctype.gst_return_log.gst_return_log import (
Expand Down Expand Up @@ -217,6 +219,19 @@ def update_custom_fields(self):
if self.has_value_changed("enable_reverse_charge_in_sales"):
toggle_custom_fields(SALES_REVERSE_CHARGE_FIELDS, self.enable_reverse_charge_in_sales)

if self.has_value_changed("enable_taxes_on_mrp"):
toggle_custom_fields(RSP_FIELDS, self.enable_taxes_on_mrp)

if self.has_value_changed("enable_margin_scheme"):
toggle_custom_fields(MARGIN_FIELDS, self.enable_margin_scheme)

if self.has_value_changed("enable_taxes_on_mrp") or self.has_value_changed("enable_margin_scheme"):
from india_compliance.gst_india.setup.property_setters import (
toggle_charge_type_options,
)

toggle_charge_type_options(self)

if self.has_value_changed("enable_e_waybill_from_asset_movement") and (
self.enable_e_waybill_from_asset_movement
):
Expand Down
76 changes: 76 additions & 0 deletions india_compliance/gst_india/overrides/taxable_value.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import frappe
from frappe.utils import flt

from india_compliance.gst_india.constants import TAX_TYPES


def get_item_taxable_value(doc, item, default):
# reset: the same item is resolved again on every save
item._dont_update_taxable_value = False
item._deemed_taxable_value = None

resolvers = frappe.get_hooks("erpnext_taxable_base_resolvers") or {}
if not resolvers:
return default

for tax in doc.get("taxes") or []:
if tax.get("gst_tax_type") not in TAX_TYPES:
continue

path = resolvers.get(tax.charge_type)
if not path:
continue

method = path[-1] if isinstance(path, list | tuple) else path
base = flt(frappe.get_attr(method)(frappe._dict(doc=doc), item, tax))

# a resolver may tax a deemed base but report the net value (MRP)
if getattr(item, "_dont_update_taxable_value", None):
return default

return base * flt(doc.get("conversion_rate") or 1)
Comment thread
coderabbitai[bot] marked this conversation as resolved.

return default


def _item_rate(item, tax):
item_tax_rate = frappe.parse_json(item.get("item_tax_rate")) or {}

if tax.get("account_head") in item_tax_rate:
return flt(item_tax_rate[tax.account_head])

return flt(tax.get("rate"))


def _inclusive_rate(doc, item, tax):
# CGST and SGST rows share one base, so their rates add up
rates = [_item_rate(item, t) for t in doc.get("taxes") or [] if t.charge_type == tax.charge_type]
return sum(rates) or _item_rate(item, tax)


# Rule 31D: tax from the MRP, report the net sale value
def on_mrp(calc, item, tax):
conversion_rate = flt(calc.doc.get("conversion_rate")) or 1
rate = _inclusive_rate(calc.doc, item, tax)
rsp = flt(item.get("gst_retail_sale_price")) * flt(item.qty) / conversion_rate
deemed = rsp * 100 / (100 + rate) if rate else rsp

item._dont_update_taxable_value = True
item._deemed_taxable_value = deemed * conversion_rate

return deemed


# Rule 32(5): only the margin is taxed; Rule 35 takes GST out when the price includes it
def on_margin(calc, item, tax):
conversion_rate = flt(calc.doc.get("conversion_rate")) or 1
cost = flt(item.get("gst_purchase_price")) * flt(item.qty) / conversion_rate
margin = flt(item.amount) - cost
if abs(flt(item.amount)) < abs(cost):
margin = 0

if not tax.get("included_in_print_rate"):
return margin

rate = _inclusive_rate(calc.doc, item, tax)
return margin * 100 / (100 + rate) if rate else margin
Loading
Loading