diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py index 7a768d900d5..3b5725db93f 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,67 @@ 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 + else: + asset_filter = Asset.purchase_receipt == purchase_doc + + existing_asset_qty = ( + frappe.qb.from_(Asset) + .select(IfNull(Sum(Asset.asset_quantity), 0)) + .where((Asset.item_code == self.item_code) & (Asset.name != self.name) & (Asset.docstatus != 2)) + .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( + _( + "Cannot create asset.

" + "You're trying to create {0} asset(s) from {2} {3}.
" + "However, only {1} item(s) were purchased and {4} asset(s) already exist against {5}." + ).format( + self.asset_quantity, + purchased_qty, + doctype, + get_link_to_form(doctype, purchase_doc), + existing_asset_qty, + purchase_doc, + ) + ) def validate_gross_and_purchase_amount(self): if self.is_existing_asset: