Everyting should be in the right place:
- The account assignment is on Party Account
- Extend Party Account with a Custom Field say customer_income_account
- Apply the Income Account over settings in Item (simply because the same product may be locally sold or exported)
- For overcoming permission issues and as a general design approach, leave logic to the server side and only do UX operations on clinet scripts. So, I create and api.py for the custom application collecting all customization and developments for the customer we are implementing ERPNext at.
It is downhill from here:
import frappe
@frappe.whitelist()
def get_customer_income_account(customer, company):
"""
Fetch the customer income account from the Party Account child table.
"""
try:
frappe.logger().info(f"Fetching customer income account for Customer: {customer}, Company: {company}")
# Fetch the value from the Party Account child table
customer_income_account = frappe.get_value(
'Party Account',
{'parent': customer, 'parenttype': 'Customer', 'company': company},
'customer_income_account' # Fetch the customer_income_account field
)
frappe.logger().info(f"Fetched customer income account: {customer_income_account}")
return customer_income_account
except Exception as e:
frappe.log_error(f"Error fetching customer income account: {e}")
return None
Then, client side would be:
frappe.ui.form.on('Sales Invoice', {
onload: function(frm) {
// Populate income account for all items when the form is loaded
frm.doc.items.forEach(function(item) {
if (item.item_code && frm.doc.company && frm.doc.customer) {
fetch_customer_income_account(frm, item);
}
});
},
refresh: function(frm) {
// Populate income account for all items when the form is refreshed
frm.doc.items.forEach(function(item) {
if (item.item_code && frm.doc.company && frm.doc.customer) {
fetch_customer_income_account(frm, item);
}
});
}
});
frappe.ui.form.on('Sales Invoice Item', {
item_code: function(frm, cdt, cdn) {
let item = frappe.get_doc(cdt, cdn);
if (item.item_code && frm.doc.company && frm.doc.customer) {
frappe.call({
method: 'CUSTOM_APP.api.get_customer_income_account', // Updated method path
args: {
'customer': frm.doc.customer,
'company': frm.doc.company
},
callback: function(r) {
if (r.message) {
frappe.model.set_value(cdt, cdn, 'income_account', r.message);
} else {
frappe.msgprint(__("No customer income account found for the selected customer and company."));
}
}
});
}
}
});
function fetch_customer_income_account(frm, item) {
frappe.call({
method: 'CUSTOM_APP.api.get_customer_income_account', // Updated method path
args: {
'customer': frm.doc.customer,
'company': frm.doc.company
},
callback: function(r) {
if (r.message) {
frappe.model.set_value(item.doctype, item.name, 'income_account', r.message);
} else {
frappe.msgprint(__("No customer income account found for the selected customer and company."));
}
}
});
}
Don’t bother with jscript side, get it done via AI on Cursor app… IMHO power is at the design and logic embedded in your CUSTOM_APP api…
TO DO: Refactor so that if enabled (make everything available to the customer and parametric), the Sales Invoice should compare the Customer’s Billing Adress.Country with the current Company.Country (or Billing Address.Country set on Company details) and determine Export Income and Local Sales Income Accounts accordingly