diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py index 7a768d900d5..9df5728fd3e 100644 --- a/erpnext/assets/doctype/asset/asset.py +++ b/erpnext/assets/doctype/asset/asset.py @@ -7,6 +7,7 @@ import math import frappe from frappe import _ +from frappe.query_builder.functions import IfNull, Sum from frappe.utils import ( cint, flt, @@ -121,7 +122,7 @@ class Asset(AccountsController): def validate(self): self.validate_category() self.validate_precision() - self.validate_linked_purchase_docs() + self.validate_linked_purchase_documents() self.set_purchase_doc_row_item() self.validate_asset_values() self.validate_asset_and_reference() @@ -422,20 +423,59 @@ class Asset(AccountsController): if self.available_for_use_date and getdate(self.available_for_use_date) < getdate(self.purchase_date): frappe.throw(_("Available-for-use Date should be after purchase date")) - def validate_linked_purchase_docs(self): - for doctype_field, doctype_name in [ + def validate_linked_purchase_documents(self): + for fieldname, doctype in [ ("purchase_receipt", "Purchase Receipt"), ("purchase_invoice", "Purchase Invoice"), ]: - linked_doc = getattr(self, doctype_field, None) - if linked_doc: - docstatus = frappe.db.get_value(doctype_name, linked_doc, "docstatus") - if docstatus == 0: - frappe.throw( - _("{0} is still in Draft. Please submit it before saving the Asset.").format( - get_link_to_form(doctype_name, linked_doc) - ) + purchase_doc = getattr(self, fieldname, None) + + if not purchase_doc: + continue + + if frappe.db.get_value(doctype, purchase_doc, "docstatus") == 0: + frappe.throw( + _("{0} is in Draft. Submit it before creating the Asset.").format( + get_link_to_form(doctype, purchase_doc) ) + ) + + self.validate_asset_qty_with_purchase_doc(doctype, purchase_doc) + + def validate_asset_qty_with_purchase_doc(self, doctype, purchase_doc): + Asset = frappe.qb.DocType("Asset") + + if doctype == "Purchase Invoice": + asset_filter = (Asset.purchase_invoice == purchase_doc) & (Asset.name != self.name) + else: + asset_filter = (Asset.purchase_receipt == purchase_doc) & (Asset.name != self.name) + + existing_asset_qty = ( + frappe.qb.from_(Asset).select(IfNull(Sum(Asset.asset_quantity), 0)).where(asset_filter) + ).run()[0][0] + + PurchaseDoc = frappe.qb.DocType(doctype) + PurchaseDocItems = frappe.qb.DocType(f"{doctype} Item") + + purchased_qty = ( + frappe.qb.from_(PurchaseDoc) + .join(PurchaseDocItems) + .on(PurchaseDoc.name == PurchaseDocItems.parent) + .select(IfNull(Sum(PurchaseDocItems.qty), 0)) + .where(PurchaseDoc.name == purchase_doc) + .where(PurchaseDocItems.item_code == self.item_code) + ).run()[0][0] + + if (existing_asset_qty + self.asset_quantity) > purchased_qty: + frappe.throw( + _("Created assets {0} exceed the purchased quantity {1} for item {2} " "in {3} {4}").format( + (existing_asset_qty + self.asset_quantity), + purchased_qty, + self.item_code, + doctype, + get_link_to_form(doctype, purchase_doc), + ) + ) def validate_gross_and_purchase_amount(self): if self.is_existing_asset: