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'
|
||||
)
|
||||
],
|
||||
"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()
|
||||
|
|
@ -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",
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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