fix: Multiple issues in purchase invoice submission (#34600)
* fix: Multiple issues in purchase invoice submission * fix: Base grand total calculation * chore: Calculate base grand total separately only in multi currency docs * fix: Add gl entry for round off
This commit is contained in:
parent
74b29eb5e2
commit
4c61ee30bb
@ -117,7 +117,7 @@ class PurchaseInvoice(BuyingController):
|
|||||||
self.validate_expense_account()
|
self.validate_expense_account()
|
||||||
self.set_against_expense_account()
|
self.set_against_expense_account()
|
||||||
self.validate_write_off_account()
|
self.validate_write_off_account()
|
||||||
self.validate_multiple_billing("Purchase Receipt", "pr_detail", "amount", "items")
|
self.validate_multiple_billing("Purchase Receipt", "pr_detail", "amount")
|
||||||
self.create_remarks()
|
self.create_remarks()
|
||||||
self.set_status()
|
self.set_status()
|
||||||
self.validate_purchase_receipt_if_update_stock()
|
self.validate_purchase_receipt_if_update_stock()
|
||||||
@ -232,7 +232,7 @@ class PurchaseInvoice(BuyingController):
|
|||||||
)
|
)
|
||||||
|
|
||||||
if (
|
if (
|
||||||
cint(frappe.db.get_single_value("Buying Settings", "maintain_same_rate"))
|
cint(frappe.get_cached_value("Buying Settings", "None", "maintain_same_rate"))
|
||||||
and not self.is_return
|
and not self.is_return
|
||||||
and not self.is_internal_supplier
|
and not self.is_internal_supplier
|
||||||
):
|
):
|
||||||
@ -581,6 +581,7 @@ class PurchaseInvoice(BuyingController):
|
|||||||
|
|
||||||
self.make_supplier_gl_entry(gl_entries)
|
self.make_supplier_gl_entry(gl_entries)
|
||||||
self.make_item_gl_entries(gl_entries)
|
self.make_item_gl_entries(gl_entries)
|
||||||
|
self.make_precision_loss_gl_entry(gl_entries)
|
||||||
|
|
||||||
if self.check_asset_cwip_enabled():
|
if self.check_asset_cwip_enabled():
|
||||||
self.get_asset_gl_entry(gl_entries)
|
self.get_asset_gl_entry(gl_entries)
|
||||||
@ -975,6 +976,28 @@ class PurchaseInvoice(BuyingController):
|
|||||||
item.item_tax_amount, item.precision("item_tax_amount")
|
item.item_tax_amount, item.precision("item_tax_amount")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def make_precision_loss_gl_entry(self, gl_entries):
|
||||||
|
round_off_account, round_off_cost_center = get_round_off_account_and_cost_center(
|
||||||
|
self.company, "Purchase Invoice", self.name
|
||||||
|
)
|
||||||
|
|
||||||
|
precision_loss = self.get("base_net_total") - flt(
|
||||||
|
self.get("net_total") * self.conversion_rate, self.precision("net_total")
|
||||||
|
)
|
||||||
|
|
||||||
|
if precision_loss:
|
||||||
|
gl_entries.append(
|
||||||
|
self.get_gl_dict(
|
||||||
|
{
|
||||||
|
"account": round_off_account,
|
||||||
|
"against": self.supplier,
|
||||||
|
"credit": precision_loss,
|
||||||
|
"cost_center": self.cost_center or round_off_cost_center,
|
||||||
|
"remarks": _("Net total calculation precision loss"),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
def get_asset_gl_entry(self, gl_entries):
|
def get_asset_gl_entry(self, gl_entries):
|
||||||
arbnb_account = self.get_company_default("asset_received_but_not_billed")
|
arbnb_account = self.get_company_default("asset_received_but_not_billed")
|
||||||
eiiav_account = self.get_company_default("expenses_included_in_asset_valuation")
|
eiiav_account = self.get_company_default("expenses_included_in_asset_valuation")
|
||||||
|
@ -145,7 +145,7 @@ class SalesInvoice(SellingController):
|
|||||||
|
|
||||||
self.set_against_income_account()
|
self.set_against_income_account()
|
||||||
self.validate_time_sheets_are_submitted()
|
self.validate_time_sheets_are_submitted()
|
||||||
self.validate_multiple_billing("Delivery Note", "dn_detail", "amount", "items")
|
self.validate_multiple_billing("Delivery Note", "dn_detail", "amount")
|
||||||
if not self.is_return:
|
if not self.is_return:
|
||||||
self.validate_serial_numbers()
|
self.validate_serial_numbers()
|
||||||
else:
|
else:
|
||||||
|
@ -515,6 +515,8 @@ class AccountsController(TransactionBase):
|
|||||||
parent_dict.update({"customer": parent_dict.get("party_name")})
|
parent_dict.update({"customer": parent_dict.get("party_name")})
|
||||||
|
|
||||||
self.pricing_rules = []
|
self.pricing_rules = []
|
||||||
|
basic_item_details_map = {}
|
||||||
|
|
||||||
for item in self.get("items"):
|
for item in self.get("items"):
|
||||||
if item.get("item_code"):
|
if item.get("item_code"):
|
||||||
args = parent_dict.copy()
|
args = parent_dict.copy()
|
||||||
@ -533,7 +535,17 @@ class AccountsController(TransactionBase):
|
|||||||
if self.get("is_subcontracted"):
|
if self.get("is_subcontracted"):
|
||||||
args["is_subcontracted"] = self.is_subcontracted
|
args["is_subcontracted"] = self.is_subcontracted
|
||||||
|
|
||||||
ret = get_item_details(args, self, for_validate=True, overwrite_warehouse=False)
|
basic_details = basic_item_details_map.get(item.item_code)
|
||||||
|
ret, basic_item_details = get_item_details(
|
||||||
|
args,
|
||||||
|
self,
|
||||||
|
for_validate=True,
|
||||||
|
overwrite_warehouse=False,
|
||||||
|
return_basic_details=True,
|
||||||
|
basic_details=basic_details,
|
||||||
|
)
|
||||||
|
|
||||||
|
basic_item_details_map.setdefault(item.item_code, basic_item_details)
|
||||||
|
|
||||||
for fieldname, value in ret.items():
|
for fieldname, value in ret.items():
|
||||||
if item.meta.get_field(fieldname) and value is not None:
|
if item.meta.get_field(fieldname) and value is not None:
|
||||||
@ -1232,7 +1244,7 @@ class AccountsController(TransactionBase):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
def validate_multiple_billing(self, ref_dt, item_ref_dn, based_on, parentfield):
|
def validate_multiple_billing(self, ref_dt, item_ref_dn, based_on):
|
||||||
from erpnext.controllers.status_updater import get_allowance_for
|
from erpnext.controllers.status_updater import get_allowance_for
|
||||||
|
|
||||||
item_allowance = {}
|
item_allowance = {}
|
||||||
@ -1245,17 +1257,20 @@ class AccountsController(TransactionBase):
|
|||||||
|
|
||||||
total_overbilled_amt = 0.0
|
total_overbilled_amt = 0.0
|
||||||
|
|
||||||
|
reference_names = [d.get(item_ref_dn) for d in self.get("items") if d.get(item_ref_dn)]
|
||||||
|
reference_details = self.get_billing_reference_details(
|
||||||
|
reference_names, ref_dt + " Item", based_on
|
||||||
|
)
|
||||||
|
|
||||||
for item in self.get("items"):
|
for item in self.get("items"):
|
||||||
if not item.get(item_ref_dn):
|
if not item.get(item_ref_dn):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
ref_amt = flt(
|
ref_amt = flt(reference_details.get(item.get(item_ref_dn)), self.precision(based_on, item))
|
||||||
frappe.db.get_value(ref_dt + " Item", item.get(item_ref_dn), based_on),
|
|
||||||
self.precision(based_on, item),
|
|
||||||
)
|
|
||||||
if not ref_amt:
|
if not ref_amt:
|
||||||
frappe.msgprint(
|
frappe.msgprint(
|
||||||
_("System will not check overbilling since amount for Item {0} in {1} is zero").format(
|
_("System will not check over billing since amount for Item {0} in {1} is zero").format(
|
||||||
item.item_code, ref_dt
|
item.item_code, ref_dt
|
||||||
),
|
),
|
||||||
title=_("Warning"),
|
title=_("Warning"),
|
||||||
@ -1302,6 +1317,16 @@ class AccountsController(TransactionBase):
|
|||||||
alert=True,
|
alert=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def get_billing_reference_details(self, reference_names, reference_doctype, based_on):
|
||||||
|
return frappe._dict(
|
||||||
|
frappe.get_all(
|
||||||
|
reference_doctype,
|
||||||
|
filters={"name": ("in", reference_names)},
|
||||||
|
fields=["name", based_on],
|
||||||
|
as_list=1,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
def get_billed_amount_for_item(self, item, item_ref_dn, based_on):
|
def get_billed_amount_for_item(self, item, item_ref_dn, based_on):
|
||||||
"""
|
"""
|
||||||
Returns Sum of Amount of
|
Returns Sum of Amount of
|
||||||
|
@ -35,7 +35,14 @@ purchase_doctypes = [
|
|||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_item_details(args, doc=None, for_validate=False, overwrite_warehouse=True):
|
def get_item_details(
|
||||||
|
args,
|
||||||
|
doc=None,
|
||||||
|
for_validate=False,
|
||||||
|
overwrite_warehouse=True,
|
||||||
|
return_basic_details=False,
|
||||||
|
basic_details=None,
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
args = {
|
args = {
|
||||||
"item_code": "",
|
"item_code": "",
|
||||||
@ -73,7 +80,13 @@ def get_item_details(args, doc=None, for_validate=False, overwrite_warehouse=Tru
|
|||||||
if doc.get("doctype") == "Purchase Invoice":
|
if doc.get("doctype") == "Purchase Invoice":
|
||||||
args["bill_date"] = doc.get("bill_date")
|
args["bill_date"] = doc.get("bill_date")
|
||||||
|
|
||||||
out = get_basic_details(args, item, overwrite_warehouse)
|
if not basic_details:
|
||||||
|
out = get_basic_details(args, item, overwrite_warehouse)
|
||||||
|
else:
|
||||||
|
out = basic_details
|
||||||
|
|
||||||
|
basic_details = out.copy()
|
||||||
|
|
||||||
get_item_tax_template(args, item, out)
|
get_item_tax_template(args, item, out)
|
||||||
out["item_tax_rate"] = get_item_tax_map(
|
out["item_tax_rate"] = get_item_tax_map(
|
||||||
args.company,
|
args.company,
|
||||||
@ -141,7 +154,11 @@ def get_item_details(args, doc=None, for_validate=False, overwrite_warehouse=Tru
|
|||||||
out.amount = flt(args.qty) * flt(out.rate)
|
out.amount = flt(args.qty) * flt(out.rate)
|
||||||
|
|
||||||
out = remove_standard_fields(out)
|
out = remove_standard_fields(out)
|
||||||
return out
|
|
||||||
|
if return_basic_details:
|
||||||
|
return out, basic_details
|
||||||
|
else:
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
def remove_standard_fields(details):
|
def remove_standard_fields(details):
|
||||||
|
@ -58,11 +58,11 @@ class TransactionBase(StatusUpdater):
|
|||||||
|
|
||||||
def compare_values(self, ref_doc, fields, doc=None):
|
def compare_values(self, ref_doc, fields, doc=None):
|
||||||
for reference_doctype, ref_dn_list in ref_doc.items():
|
for reference_doctype, ref_dn_list in ref_doc.items():
|
||||||
|
prev_doc_detail_map = self.get_prev_doc_reference_details(
|
||||||
|
ref_dn_list, reference_doctype, fields
|
||||||
|
)
|
||||||
for reference_name in ref_dn_list:
|
for reference_name in ref_dn_list:
|
||||||
prevdoc_values = frappe.db.get_value(
|
prevdoc_values = prev_doc_detail_map.get(reference_name)
|
||||||
reference_doctype, reference_name, [d[0] for d in fields], as_dict=1
|
|
||||||
)
|
|
||||||
|
|
||||||
if not prevdoc_values:
|
if not prevdoc_values:
|
||||||
frappe.throw(_("Invalid reference {0} {1}").format(reference_doctype, reference_name))
|
frappe.throw(_("Invalid reference {0} {1}").format(reference_doctype, reference_name))
|
||||||
|
|
||||||
@ -70,6 +70,19 @@ class TransactionBase(StatusUpdater):
|
|||||||
if prevdoc_values[field] is not None and field not in self.exclude_fields:
|
if prevdoc_values[field] is not None and field not in self.exclude_fields:
|
||||||
self.validate_value(field, condition, prevdoc_values[field], doc)
|
self.validate_value(field, condition, prevdoc_values[field], doc)
|
||||||
|
|
||||||
|
def get_prev_doc_reference_details(self, reference_names, reference_doctype, fields):
|
||||||
|
prev_doc_detail_map = {}
|
||||||
|
details = frappe.get_all(
|
||||||
|
reference_doctype,
|
||||||
|
filters={"name": ("in", reference_names)},
|
||||||
|
fields=["name"] + [d[0] for d in fields],
|
||||||
|
)
|
||||||
|
|
||||||
|
for d in details:
|
||||||
|
prev_doc_detail_map.setdefault(d.name, d)
|
||||||
|
|
||||||
|
return prev_doc_detail_map
|
||||||
|
|
||||||
def validate_rate_with_reference_doc(self, ref_details):
|
def validate_rate_with_reference_doc(self, ref_details):
|
||||||
if self.get("is_internal_supplier"):
|
if self.get("is_internal_supplier"):
|
||||||
return
|
return
|
||||||
@ -77,23 +90,23 @@ class TransactionBase(StatusUpdater):
|
|||||||
buying_doctypes = ["Purchase Order", "Purchase Invoice", "Purchase Receipt"]
|
buying_doctypes = ["Purchase Order", "Purchase Invoice", "Purchase Receipt"]
|
||||||
|
|
||||||
if self.doctype in buying_doctypes:
|
if self.doctype in buying_doctypes:
|
||||||
action = frappe.db.get_single_value("Buying Settings", "maintain_same_rate_action")
|
action, role_allowed_to_override = frappe.get_cached_value(
|
||||||
settings_doc = "Buying Settings"
|
"Buying Settings", "None", ["maintain_same_rate_action", "role_to_override_stop_action"]
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
action = frappe.db.get_single_value("Selling Settings", "maintain_same_rate_action")
|
action, role_allowed_to_override = frappe.get_cached_value(
|
||||||
settings_doc = "Selling Settings"
|
"Selling Settings", "None", ["maintain_same_rate_action", "role_to_override_stop_action"]
|
||||||
|
)
|
||||||
|
|
||||||
for ref_dt, ref_dn_field, ref_link_field in ref_details:
|
for ref_dt, ref_dn_field, ref_link_field in ref_details:
|
||||||
|
reference_names = [d.get(ref_link_field) for d in self.get("items") if d.get(ref_link_field)]
|
||||||
|
reference_details = self.get_reference_details(reference_names, ref_dt + " Item")
|
||||||
for d in self.get("items"):
|
for d in self.get("items"):
|
||||||
if d.get(ref_link_field):
|
if d.get(ref_link_field):
|
||||||
ref_rate = frappe.db.get_value(ref_dt + " Item", d.get(ref_link_field), "rate")
|
ref_rate = reference_details.get(d.get(ref_link_field))
|
||||||
|
|
||||||
if abs(flt(d.rate - ref_rate, d.precision("rate"))) >= 0.01:
|
if abs(flt(d.rate - ref_rate, d.precision("rate"))) >= 0.01:
|
||||||
if action == "Stop":
|
if action == "Stop":
|
||||||
role_allowed_to_override = frappe.db.get_single_value(
|
|
||||||
settings_doc, "role_to_override_stop_action"
|
|
||||||
)
|
|
||||||
|
|
||||||
if role_allowed_to_override not in frappe.get_roles():
|
if role_allowed_to_override not in frappe.get_roles():
|
||||||
frappe.throw(
|
frappe.throw(
|
||||||
_("Row #{0}: Rate must be same as {1}: {2} ({3} / {4})").format(
|
_("Row #{0}: Rate must be same as {1}: {2} ({3} / {4})").format(
|
||||||
@ -109,6 +122,16 @@ class TransactionBase(StatusUpdater):
|
|||||||
indicator="orange",
|
indicator="orange",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def get_reference_details(self, reference_names, reference_doctype):
|
||||||
|
return frappe._dict(
|
||||||
|
frappe.get_all(
|
||||||
|
reference_doctype,
|
||||||
|
filters={"name": ("in", reference_names)},
|
||||||
|
fields=["name", "rate"],
|
||||||
|
as_list=1,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
def get_link_filters(self, for_doctype):
|
def get_link_filters(self, for_doctype):
|
||||||
if hasattr(self, "prev_link_mapper") and self.prev_link_mapper.get(for_doctype):
|
if hasattr(self, "prev_link_mapper") and self.prev_link_mapper.get(for_doctype):
|
||||||
fieldname = self.prev_link_mapper[for_doctype]["fieldname"]
|
fieldname = self.prev_link_mapper[for_doctype]["fieldname"]
|
||||||
|
Loading…
Reference in New Issue
Block a user