added tax free field to sales invoice tax table
This commit is contained in:
parent
e4cad58983
commit
68db53ad34
|
|
@ -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
|
||||||
|
|
@ -91,6 +91,16 @@ def create_custom_fields():
|
||||||
insert_after='comments_column_break'
|
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": [
|
"Item": [
|
||||||
# Product Group Code field in Details tab
|
# Product Group Code field in Details tab
|
||||||
dict(
|
dict(
|
||||||
|
|
@ -634,4 +644,7 @@ def create_custom_fields():
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Failed to create field {field['fieldname']} in {doctype}: {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()
|
create_custom_fields()
|
||||||
|
|
@ -9,12 +9,14 @@ app_license = "unlicense"
|
||||||
boot_session = "jey_erp.extend_party_types.add_company_to_party_account_types"
|
boot_session = "jey_erp.extend_party_types.add_company_to_party_account_types"
|
||||||
|
|
||||||
app_include_js = [
|
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 = {
|
doctype_js = {
|
||||||
"Payment Entry": "/public/js/payment_entry_mod.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 = {
|
override_doctype_class = {
|
||||||
|
|
@ -24,6 +26,10 @@ override_doctype_class = {
|
||||||
doc_events = {
|
doc_events = {
|
||||||
"Employee Onboarding": {
|
"Employee Onboarding": {
|
||||||
"on_update": "jey_erp.custom_employee_onboarding.on_update"
|
"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",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
};
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue