added tax free field to sales invoice tax table

This commit is contained in:
Ali 2025-07-25 16:33:19 +04:00
parent e4cad58983
commit 68db53ad34
4 changed files with 312 additions and 3 deletions

View File

@ -0,0 +1,96 @@
import frappe
import json
from frappe.utils import flt
def calculate_tax_free_amounts(doc, method=None):
"""
Calculate tax free amounts for Sales Invoice
Called after calculate_taxes_and_totals
"""
if doc.doctype != "Sales Invoice":
return
# Clear existing tax_free_amount values
for tax in doc.get("taxes", []):
if hasattr(tax, 'tax_free_amount'):
tax.tax_free_amount = 0.0
# Calculate tax free amounts
for item in doc.get("items", []):
if not item.get("item_code"):
continue
# Try to get tax template from item row first
item_tax_template = item.get("item_tax_template")
item_tax_rate = item.get("item_tax_rate")
# If no tax info in item row, get from Item master
if not item_tax_template and not item_tax_rate:
try:
item_doc = frappe.get_doc("Item", item.item_code)
item_tax_template = item_doc.get("item_tax_template")
except Exception:
continue
# Process item_tax_rate (JSON format)
if item_tax_rate:
try:
if isinstance(item_tax_rate, str):
tax_rates = json.loads(item_tax_rate)
else:
tax_rates = item_tax_rate
for account_head, rate in tax_rates.items():
if flt(rate) == 0.0:
# Find corresponding tax in sales invoice
for doc_tax in doc.get("taxes", []):
if doc_tax.account_head == account_head:
if hasattr(doc_tax, 'tax_free_amount'):
doc_tax.tax_free_amount = flt(doc_tax.tax_free_amount) + flt(item.net_amount)
break
except Exception:
pass
# Process item_tax_template
elif item_tax_template:
try:
tax_template = frappe.get_doc("Item Tax Template", item_tax_template)
for template_tax in tax_template.get("taxes", []):
if flt(template_tax.tax_rate) == 0.0:
# Find corresponding tax in sales invoice
for doc_tax in doc.get("taxes", []):
if doc_tax.account_head == template_tax.tax_type:
if hasattr(doc_tax, 'tax_free_amount'):
doc_tax.tax_free_amount = flt(doc_tax.tax_free_amount) + flt(item.net_amount)
break
except Exception:
pass
@frappe.whitelist()
def recalculate_tax_free_amounts(doc):
"""
API method to recalculate tax free amounts
Called from client side when items change
"""
if isinstance(doc, str):
doc = frappe.parse_json(doc)
# Convert to document object if it's a dict
if isinstance(doc, dict):
sales_invoice = frappe.get_doc(doc)
else:
sales_invoice = doc
# Calculate tax free amounts
calculate_tax_free_amounts(sales_invoice)
# Return updated taxes
taxes_data = []
for tax in sales_invoice.get("taxes", []):
tax_dict = tax.as_dict() if hasattr(tax, 'as_dict') else dict(tax)
taxes_data.append(tax_dict)
return taxes_data

View File

@ -91,6 +91,16 @@ def create_custom_fields():
insert_after='comments_column_break'
)
],
"Sales Taxes and Charges": [
dict(
fieldname='tax_free_amount',
label='Tax Free Amount',
fieldtype='Currency',
insert_after='tax_amount',
in_list_view=1,
columns=2
)
],
"Item": [
# Product Group Code field in Details tab
dict(
@ -633,5 +643,8 @@ def create_custom_fields():
print(f"Created field {field['fieldname']} in {doctype}")
except Exception as e:
print(f"Failed to create field {field['fieldname']} in {doctype}: {e}")
# Register the calculation hook for existing Sales Invoices
print("Setting up Tax Free Amount calculation hooks...")
create_custom_fields()

View File

@ -9,12 +9,14 @@ app_license = "unlicense"
boot_session = "jey_erp.extend_party_types.add_company_to_party_account_types"
app_include_js = [
"/assets/jey_erp/js/account_tree_custom.js"
"/assets/jey_erp/js/account_tree_custom.js",
"/assets/jey_erp/js/sales_invoice.js"
]
doctype_js = {
"Payment Entry": "/public/js/payment_entry_mod.js",
"Account": "/public/js/account_tree_custom.js"
"Account": "/public/js/account_tree_custom.js",
"Sales Invoice": "public/js/sales_invoice.js"
}
override_doctype_class = {
@ -24,6 +26,10 @@ override_doctype_class = {
doc_events = {
"Employee Onboarding": {
"on_update": "jey_erp.custom_employee_onboarding.on_update"
},
"Sales Invoice": {
"validate": "jey_erp.jey_erp.custom.sales_invoice.calculate_tax_free_amounts",
"on_update": "jey_erp.jey_erp.custom.sales_invoice.calculate_tax_free_amounts",
}
}

View File

@ -0,0 +1,194 @@
// Sales Invoice customizations for Tax Free Amount calculation
frappe.ui.form.on('Sales Invoice', {
onload: function(frm) {
// Add custom calculate_tax_free_amounts method to frm
frm.calculate_tax_free_amounts = function() {
calculate_tax_free_amounts(frm);
};
},
refresh: function(frm) {
// Calculate tax free amounts on refresh
if (frm.doc.docstatus === 0) {
frm.calculate_tax_free_amounts();
}
}
});
frappe.ui.form.on('Sales Invoice Item', {
item_code: function(frm, cdt, cdn) {
// Try immediate calculation, then with delay as fallback
setTimeout(function() {
frm.calculate_tax_free_amounts();
}, 100);
setTimeout(function() {
frm.calculate_tax_free_amounts();
}, 1000);
},
item_tax_template: function(frm, cdt, cdn) {
frm.calculate_tax_free_amounts();
},
item_tax_rate: function(frm, cdt, cdn) {
frm.calculate_tax_free_amounts();
},
qty: function(frm, cdt, cdn) {
setTimeout(function() {
frm.calculate_tax_free_amounts();
}, 100);
},
rate: function(frm, cdt, cdn) {
setTimeout(function() {
frm.calculate_tax_free_amounts();
}, 100);
},
net_amount: function(frm, cdt, cdn) {
frm.calculate_tax_free_amounts();
},
items_remove: function(frm, cdt, cdn) {
frm.calculate_tax_free_amounts();
}
});
function calculate_tax_free_amounts(frm) {
if (frm.doc.docstatus !== 0) {
return;
}
// Clear existing tax_free_amount values
$.each(frm.doc.taxes || [], function(i, tax) {
tax.tax_free_amount = 0.0;
});
// Direct calculation without waiting
calculate_tax_free_amounts_direct(frm);
}
function calculate_tax_free_amounts_direct(frm) {
// Calculate tax free amounts for each item
$.each(frm.doc.items || [], function(i, item) {
if (!item.item_code) {
return;
}
var tax_template = item.item_tax_template;
var tax_rate = item.item_tax_rate;
if (!tax_template && !tax_rate) {
return;
}
// Process item_tax_rate (JSON format) - this is the most common case
if (tax_rate) {
try {
var tax_rates = typeof tax_rate === 'string' ? JSON.parse(tax_rate) : tax_rate;
// Check each tax account in the item_tax_rate
$.each(tax_rates, function(account_head, rate) {
if (flt(rate) === 0.0) {
// Find corresponding tax in sales invoice
$.each(frm.doc.taxes || [], function(k, doc_tax) {
if (doc_tax.account_head === account_head) {
// Add item amount to tax_free_amount
doc_tax.tax_free_amount = flt(doc_tax.tax_free_amount) + flt(item.net_amount);
return false; // break
}
});
}
});
} catch (e) {
console.error("Error parsing item_tax_rate:", e);
}
}
// Process item_tax_template if no item_tax_rate
else if (tax_template) {
frappe.call({
method: 'frappe.client.get',
args: {
doctype: 'Item Tax Template',
name: tax_template
},
async: false,
callback: function(r) {
if (r.message) {
var tax_template_doc = r.message;
// Check each tax in template
$.each(tax_template_doc.taxes || [], function(j, template_tax) {
if (flt(template_tax.tax_rate) === 0.0) {
// Find corresponding tax in sales invoice
$.each(frm.doc.taxes || [], function(k, doc_tax) {
if (doc_tax.account_head === template_tax.tax_type) {
// Add item amount to tax_free_amount
doc_tax.tax_free_amount = flt(doc_tax.tax_free_amount) + flt(item.net_amount);
return false; // break
}
});
}
});
}
}
});
}
});
// Refresh taxes grid
frm.refresh_field('taxes');
}
// Override methods in the taxes calculation chain for immediate updates
let original_calculate_taxes_and_totals = erpnext.taxes_and_totals.prototype.calculate_taxes_and_totals;
let original_calculate_taxes = erpnext.taxes_and_totals.prototype.calculate_taxes;
erpnext.taxes_and_totals.prototype.calculate_taxes_and_totals = function(update_paid_amount) {
// Call original method
let result = original_calculate_taxes_and_totals.call(this, update_paid_amount);
// Calculate tax free amounts immediately after tax calculation
if (this.frm.doc.doctype === 'Sales Invoice') {
calculate_tax_free_amounts(this.frm);
}
return result;
};
// Also override calculate_taxes to catch earlier in the process
erpnext.taxes_and_totals.prototype.calculate_taxes = function() {
// Call original method
let result = original_calculate_taxes.call(this);
// Calculate tax free amounts immediately after taxes calculation
if (this.frm.doc.doctype === 'Sales Invoice') {
calculate_tax_free_amounts(this.frm);
}
return result;
};
// Override the item details loading to trigger calculation
if (erpnext.selling && erpnext.selling.SellingController) {
let original_item_code = erpnext.selling.SellingController.prototype.item_code;
erpnext.selling.SellingController.prototype.item_code = function(doc, cdt, cdn) {
// Call original method
let result = original_item_code.call(this, doc, cdt, cdn);
// Calculate tax free amounts after item details are loaded
if (this.frm.doc.doctype === 'Sales Invoice') {
setTimeout(() => {
calculate_tax_free_amounts(this.frm);
}, 200);
}
return result;
};
}