fix: GST on freight charge in e-invoicing (#25000)
* fix: GST on freight charge in e-invocing * fix: Add patch for taxable value field * fix: Handle discounts on net total * fix: Handle all types of discounts for e-invoicing * fix: Absolute taxable values * fix: Use correct tax amount
This commit is contained in:
parent
9f73bd8040
commit
c36e48a869
@ -262,7 +262,8 @@ doc_events = {
|
|||||||
],
|
],
|
||||||
"on_trash": "erpnext.regional.check_deletion_permission",
|
"on_trash": "erpnext.regional.check_deletion_permission",
|
||||||
"validate": [
|
"validate": [
|
||||||
"erpnext.regional.india.utils.validate_document_name"
|
"erpnext.regional.india.utils.validate_document_name",
|
||||||
|
"erpnext.regional.india.utils.update_taxable_values"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"Purchase Invoice": {
|
"Purchase Invoice": {
|
||||||
|
@ -763,5 +763,6 @@ erpnext.patches.v13_0.setup_gratuity_rule_for_india_and_uae
|
|||||||
erpnext.patches.v13_0.setup_uae_vat_fields
|
erpnext.patches.v13_0.setup_uae_vat_fields
|
||||||
execute:frappe.db.set_value('System Settings', None, 'app_name', 'ERPNext')
|
execute:frappe.db.set_value('System Settings', None, 'app_name', 'ERPNext')
|
||||||
erpnext.patches.v13_0.rename_discharge_date_in_ip_record
|
erpnext.patches.v13_0.rename_discharge_date_in_ip_record
|
||||||
|
erpnext.patches.v12_0.create_taxable_value_field
|
||||||
erpnext.patches.v12_0.add_gst_category_in_delivery_note
|
erpnext.patches.v12_0.add_gst_category_in_delivery_note
|
||||||
erpnext.patches.v12_0.purchase_receipt_status
|
erpnext.patches.v12_0.purchase_receipt_status
|
||||||
|
18
erpnext/patches/v12_0/create_taxable_value_field.py
Normal file
18
erpnext/patches/v12_0/create_taxable_value_field.py
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
import frappe
|
||||||
|
from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
|
||||||
|
|
||||||
|
def execute():
|
||||||
|
company = frappe.get_all('Company', filters = {'country': 'India'})
|
||||||
|
if not company:
|
||||||
|
return
|
||||||
|
|
||||||
|
custom_fields = {
|
||||||
|
'Sales Invoice Item': [
|
||||||
|
dict(fieldname='taxable_value', label='Taxable Value',
|
||||||
|
fieldtype='Currency', insert_after='base_net_amount', hidden=1, options="Company:company:default_currency",
|
||||||
|
print_hide=1)
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
create_custom_fields(custom_fields, update=True)
|
@ -171,10 +171,15 @@ def get_item_list(invoice):
|
|||||||
item.description = sanitize_for_json(d.item_name)
|
item.description = sanitize_for_json(d.item_name)
|
||||||
|
|
||||||
item.qty = abs(item.qty)
|
item.qty = abs(item.qty)
|
||||||
item.discount_amount = 0
|
|
||||||
item.unit_rate = abs(item.base_net_amount / item.qty)
|
if invoice.apply_discount_on == 'Net Total' and invoice.discount_amount:
|
||||||
item.gross_amount = abs(item.base_net_amount)
|
item.discount_amount = abs(item.base_amount - item.base_net_amount)
|
||||||
item.taxable_value = abs(item.base_net_amount)
|
else:
|
||||||
|
item.discount_amount = 0
|
||||||
|
|
||||||
|
item.unit_rate = abs((abs(item.taxable_value) - item.discount_amount)/ item.qty)
|
||||||
|
item.gross_amount = abs(item.taxable_value) + item.discount_amount
|
||||||
|
item.taxable_value = abs(item.taxable_value)
|
||||||
|
|
||||||
item.batch_expiry_date = frappe.db.get_value('Batch', d.batch_no, 'expiry_date') if d.batch_no else None
|
item.batch_expiry_date = frappe.db.get_value('Batch', d.batch_no, 'expiry_date') if d.batch_no else None
|
||||||
item.batch_expiry_date = format_date(item.batch_expiry_date, 'dd/mm/yyyy') if item.batch_expiry_date else None
|
item.batch_expiry_date = format_date(item.batch_expiry_date, 'dd/mm/yyyy') if item.batch_expiry_date else None
|
||||||
@ -211,7 +216,7 @@ def update_item_taxes(invoice, item):
|
|||||||
|
|
||||||
item_tax_rate = item_tax_detail[0]
|
item_tax_rate = item_tax_detail[0]
|
||||||
# item tax amount excluding discount amount
|
# item tax amount excluding discount amount
|
||||||
item_tax_amount = (item_tax_rate / 100) * item.base_net_amount
|
item_tax_amount = (item_tax_rate / 100) * item.taxable_value
|
||||||
|
|
||||||
if t.account_head in gst_accounts.cess_account:
|
if t.account_head in gst_accounts.cess_account:
|
||||||
item_tax_amount_after_discount = item_tax_detail[1]
|
item_tax_amount_after_discount = item_tax_detail[1]
|
||||||
@ -232,10 +237,14 @@ def get_invoice_value_details(invoice):
|
|||||||
invoice_value_details = frappe._dict(dict())
|
invoice_value_details = frappe._dict(dict())
|
||||||
|
|
||||||
if invoice.apply_discount_on == 'Net Total' and invoice.discount_amount:
|
if invoice.apply_discount_on == 'Net Total' and invoice.discount_amount:
|
||||||
invoice_value_details.base_total = abs(invoice.base_total)
|
# Discount already applied on net total which means on items
|
||||||
invoice_value_details.invoice_discount_amt = abs(invoice.base_discount_amount)
|
invoice_value_details.base_total = abs(sum([i.taxable_value for i in invoice.get('items')]))
|
||||||
|
invoice_value_details.invoice_discount_amt = 0
|
||||||
|
elif invoice.apply_discount_on == 'Grand Total' and invoice.discount_amount:
|
||||||
|
invoice_value_details.invoice_discount_amt = invoice.base_discount_amount
|
||||||
|
invoice_value_details.base_total = abs(sum([i.taxable_value for i in invoice.get('items')]))
|
||||||
else:
|
else:
|
||||||
invoice_value_details.base_total = abs(invoice.base_net_total)
|
invoice_value_details.base_total = abs(sum([i.taxable_value for i in invoice.get('items')]))
|
||||||
# since tax already considers discount amount
|
# since tax already considers discount amount
|
||||||
invoice_value_details.invoice_discount_amt = 0
|
invoice_value_details.invoice_discount_amt = 0
|
||||||
|
|
||||||
@ -256,7 +265,11 @@ def update_invoice_taxes(invoice, invoice_value_details):
|
|||||||
invoice_value_details.total_igst_amt = 0
|
invoice_value_details.total_igst_amt = 0
|
||||||
invoice_value_details.total_cess_amt = 0
|
invoice_value_details.total_cess_amt = 0
|
||||||
invoice_value_details.total_other_charges = 0
|
invoice_value_details.total_other_charges = 0
|
||||||
|
considered_rows = []
|
||||||
|
|
||||||
for t in invoice.taxes:
|
for t in invoice.taxes:
|
||||||
|
tax_amount = t.base_tax_amount if (invoice.apply_discount_on == 'Grand Total' and invoice.discount_amount) \
|
||||||
|
else t.base_tax_amount_after_discount_amount
|
||||||
if t.account_head in gst_accounts_list:
|
if t.account_head in gst_accounts_list:
|
||||||
if t.account_head in gst_accounts.cess_account:
|
if t.account_head in gst_accounts.cess_account:
|
||||||
# using after discount amt since item also uses after discount amt for cess calc
|
# using after discount amt since item also uses after discount amt for cess calc
|
||||||
@ -264,12 +277,26 @@ def update_invoice_taxes(invoice, invoice_value_details):
|
|||||||
|
|
||||||
for tax_type in ['igst', 'cgst', 'sgst']:
|
for tax_type in ['igst', 'cgst', 'sgst']:
|
||||||
if t.account_head in gst_accounts[f'{tax_type}_account']:
|
if t.account_head in gst_accounts[f'{tax_type}_account']:
|
||||||
invoice_value_details[f'total_{tax_type}_amt'] += abs(t.base_tax_amount_after_discount_amount)
|
|
||||||
|
invoice_value_details[f'total_{tax_type}_amt'] += abs(tax_amount)
|
||||||
|
update_other_charges(t, invoice_value_details, gst_accounts_list, invoice, considered_rows)
|
||||||
else:
|
else:
|
||||||
invoice_value_details.total_other_charges += abs(t.base_tax_amount_after_discount_amount)
|
invoice_value_details.total_other_charges += abs(tax_amount)
|
||||||
|
|
||||||
return invoice_value_details
|
return invoice_value_details
|
||||||
|
|
||||||
|
def update_other_charges(tax_row, invoice_value_details, gst_accounts_list, invoice, considered_rows):
|
||||||
|
prev_row_id = cint(tax_row.row_id) - 1
|
||||||
|
if tax_row.account_head in gst_accounts_list and prev_row_id not in considered_rows:
|
||||||
|
if tax_row.charge_type == 'On Previous Row Amount':
|
||||||
|
amount = invoice.get('taxes')[prev_row_id].tax_amount_after_discount_amount
|
||||||
|
invoice_value_details.total_other_charges -= abs(amount)
|
||||||
|
considered_rows.append(prev_row_id)
|
||||||
|
if tax_row.charge_type == 'On Previous Row Total':
|
||||||
|
amount = invoice.get('taxes')[prev_row_id].base_total - invoice.base_net_total
|
||||||
|
invoice_value_details.total_other_charges -= abs(amount)
|
||||||
|
considered_rows.append(prev_row_id)
|
||||||
|
|
||||||
def get_payment_details(invoice):
|
def get_payment_details(invoice):
|
||||||
payee_name = invoice.company
|
payee_name = invoice.company
|
||||||
mode_of_payment = ', '.join([d.mode_of_payment for d in invoice.payments])
|
mode_of_payment = ', '.join([d.mode_of_payment for d in invoice.payments])
|
||||||
|
@ -127,6 +127,9 @@ def make_custom_fields(update=True):
|
|||||||
is_non_gst = dict(fieldname='is_non_gst', label='Is Non GST',
|
is_non_gst = dict(fieldname='is_non_gst', label='Is Non GST',
|
||||||
fieldtype='Check', fetch_from='item_code.is_non_gst', insert_after='is_nil_exempt',
|
fieldtype='Check', fetch_from='item_code.is_non_gst', insert_after='is_nil_exempt',
|
||||||
print_hide=1)
|
print_hide=1)
|
||||||
|
taxable_value = dict(fieldname='taxable_value', label='Taxable Value',
|
||||||
|
fieldtype='Currency', insert_after='base_net_amount', hidden=1, options="Company:company:default_currency",
|
||||||
|
print_hide=1)
|
||||||
|
|
||||||
purchase_invoice_gst_category = [
|
purchase_invoice_gst_category = [
|
||||||
dict(fieldname='gst_section', label='GST Details', fieldtype='Section Break',
|
dict(fieldname='gst_section', label='GST Details', fieldtype='Section Break',
|
||||||
@ -460,7 +463,7 @@ def make_custom_fields(update=True):
|
|||||||
'Supplier Quotation Item': [hsn_sac_field, nil_rated_exempt, is_non_gst],
|
'Supplier Quotation Item': [hsn_sac_field, nil_rated_exempt, is_non_gst],
|
||||||
'Sales Order Item': [hsn_sac_field, nil_rated_exempt, is_non_gst],
|
'Sales Order Item': [hsn_sac_field, nil_rated_exempt, is_non_gst],
|
||||||
'Delivery Note Item': [hsn_sac_field, nil_rated_exempt, is_non_gst],
|
'Delivery Note Item': [hsn_sac_field, nil_rated_exempt, is_non_gst],
|
||||||
'Sales Invoice Item': [hsn_sac_field, nil_rated_exempt, is_non_gst],
|
'Sales Invoice Item': [hsn_sac_field, nil_rated_exempt, is_non_gst, taxable_value],
|
||||||
'Purchase Order Item': [hsn_sac_field, nil_rated_exempt, is_non_gst],
|
'Purchase Order Item': [hsn_sac_field, nil_rated_exempt, is_non_gst],
|
||||||
'Purchase Receipt Item': [hsn_sac_field, nil_rated_exempt, is_non_gst],
|
'Purchase Receipt Item': [hsn_sac_field, nil_rated_exempt, is_non_gst],
|
||||||
'Purchase Invoice Item': [hsn_sac_field, nil_rated_exempt, is_non_gst],
|
'Purchase Invoice Item': [hsn_sac_field, nil_rated_exempt, is_non_gst],
|
||||||
|
@ -2,7 +2,7 @@ from __future__ import unicode_literals
|
|||||||
import frappe, re, json
|
import frappe, re, json
|
||||||
from frappe import _
|
from frappe import _
|
||||||
import erpnext
|
import erpnext
|
||||||
from frappe.utils import cstr, flt, date_diff, nowdate, round_based_on_smallest_currency_fraction, money_in_words, getdate
|
from frappe.utils import cstr, flt, cint, date_diff, nowdate, round_based_on_smallest_currency_fraction, money_in_words, getdate
|
||||||
from erpnext.regional.india import states, state_numbers
|
from erpnext.regional.india import states, state_numbers
|
||||||
from erpnext.controllers.taxes_and_totals import get_itemised_tax, get_itemised_taxable_amount
|
from erpnext.controllers.taxes_and_totals import get_itemised_tax, get_itemised_taxable_amount
|
||||||
from erpnext.controllers.accounts_controller import get_taxes_and_charges
|
from erpnext.controllers.accounts_controller import get_taxes_and_charges
|
||||||
@ -832,3 +832,48 @@ def get_regional_round_off_accounts(company, account_list):
|
|||||||
account_list.extend(gst_account_list)
|
account_list.extend(gst_account_list)
|
||||||
|
|
||||||
return account_list
|
return account_list
|
||||||
|
|
||||||
|
def update_taxable_values(doc, method):
|
||||||
|
country = frappe.get_cached_value('Company', doc.company, 'country')
|
||||||
|
|
||||||
|
if country != 'India':
|
||||||
|
return
|
||||||
|
|
||||||
|
gst_accounts = get_gst_accounts(doc.company)
|
||||||
|
|
||||||
|
# Only considering sgst account to avoid inflating taxable value
|
||||||
|
gst_account_list = gst_accounts.get('sgst_account', []) + gst_accounts.get('sgst_account', []) \
|
||||||
|
+ gst_accounts.get('igst_account', [])
|
||||||
|
|
||||||
|
additional_taxes = 0
|
||||||
|
total_charges = 0
|
||||||
|
item_count = 0
|
||||||
|
considered_rows = []
|
||||||
|
|
||||||
|
for tax in doc.get('taxes'):
|
||||||
|
prev_row_id = cint(tax.row_id) - 1
|
||||||
|
if tax.account_head in gst_account_list and prev_row_id not in considered_rows:
|
||||||
|
if tax.charge_type == 'On Previous Row Amount':
|
||||||
|
additional_taxes += doc.get('taxes')[prev_row_id].tax_amount_after_discount_amount
|
||||||
|
considered_rows.append(prev_row_id)
|
||||||
|
if tax.charge_type == 'On Previous Row Total':
|
||||||
|
additional_taxes += doc.get('taxes')[prev_row_id].base_total - doc.base_net_total
|
||||||
|
considered_rows.append(prev_row_id)
|
||||||
|
|
||||||
|
for item in doc.get('items'):
|
||||||
|
if doc.apply_discount_on == 'Grand Total' and doc.discount_amount:
|
||||||
|
proportionate_value = item.base_amount if doc.base_total else item.qty
|
||||||
|
total_value = doc.base_total if doc.base_total else doc.total_qty
|
||||||
|
else:
|
||||||
|
proportionate_value = item.base_net_amount if doc.base_net_total else item.qty
|
||||||
|
total_value = doc.base_net_total if doc.base_net_total else doc.total_qty
|
||||||
|
|
||||||
|
applicable_charges = flt(flt(proportionate_value * (flt(additional_taxes) / flt(total_value)),
|
||||||
|
item.precision('taxable_value')))
|
||||||
|
item.taxable_value = applicable_charges + proportionate_value
|
||||||
|
total_charges += applicable_charges
|
||||||
|
item_count += 1
|
||||||
|
|
||||||
|
if total_charges != additional_taxes:
|
||||||
|
diff = additional_taxes - total_charges
|
||||||
|
doc.get('items')[item_count - 1].taxable_value += diff
|
||||||
|
Loading…
x
Reference in New Issue
Block a user