feat: validate if removed item attributes exist in variants (#22911)

* feat: validate if removed item attributes exist in variants

* Update erpnext/stock/doctype/item/item.py

Co-authored-by: Marica <maricadsouza221197@gmail.com>

* Update erpnext/stock/doctype/item/item.py

Co-authored-by: Marica <maricadsouza221197@gmail.com>

* refactor: smaller loop

Co-authored-by: marination <maricadsouza221197@gmail.com>

* feat: don't run validation for new entries

Co-authored-by: Marica <maricadsouza221197@gmail.com>

* fix: use tuple as is

Co-authored-by: Marica <maricadsouza221197@gmail.com>

* refactor: error description

Co-authored-by: Marica <maricadsouza221197@gmail.com>

* refactor: remove unused variable

Co-authored-by: Marica <maricadsouza221197@gmail.com>
This commit is contained in:
Shivam Mishra 2020-08-24 14:07:09 +00:00 committed by GitHub
parent cf4268e21a
commit 7556517a4f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -111,6 +111,7 @@ class Item(WebsiteGenerator):
self.synced_with_hub = 0
self.validate_has_variants()
self.validate_attributes_in_variants()
self.validate_stock_exists_for_template_item()
self.validate_attributes()
self.validate_variant_attributes()
@ -806,6 +807,77 @@ class Item(WebsiteGenerator):
if frappe.db.exists("Item", {"variant_of": self.name}):
frappe.throw(_("Item has variants."))
def validate_attributes_in_variants(self):
if not self.has_variants or self.get("__islocal"):
return
old_doc = self.get_doc_before_save()
old_doc_attributes = set([attr.attribute for attr in old_doc.attributes])
own_attributes = [attr.attribute for attr in self.attributes]
# Check if old attributes were removed from the list
# Is old_attrs is a subset of new ones
# that means we need not check any changes
if old_doc_attributes.issubset(set(own_attributes)):
return
from collections import defaultdict
# get all item variants
items = [item["name"] for item in frappe.get_all("Item", {"variant_of": self.name})]
# get all deleted attributes
deleted_attribute = list(old_doc_attributes.difference(set(own_attributes)))
# fetch all attributes of these items
item_attributes = frappe.get_all(
"Item Variant Attribute",
filters={
"parent": ["in", items],
"attribute": ["in", deleted_attribute]
},
fields=["attribute", "parent"]
)
not_included = defaultdict(list)
for attr in item_attributes:
if attr["attribute"] not in own_attributes:
not_included[attr["parent"]].append(attr["attribute"])
if not len(not_included):
return
def body(docnames):
docnames.sort()
return "<br>".join(docnames)
def table_row(title, body):
return """<tr>
<td>{0}</td>
<td>{1}</td>
</tr>""".format(title, body)
rows = ''
for docname, attr_list in not_included.items():
link = "<a href='#Form/Item/{0}'>{0}</a>".format(frappe.bold(_(docname)))
rows += table_row(link, body(attr_list))
error_description = _('The following deleted attributes exist in Variants but not in the Template. You can either delete the Variants or keep the attribute(s) in template.')
message = """
<div>{0}</div><br>
<table class="table">
<thead>
<td>{1}</td>
<td>{2}</td>
</thead>
{3}
</table>
""".format(error_description, _('Variant Items'), _('Attributes'), rows)
frappe.throw(message, title=_("Variant Attribute Error"), is_minimizable=True, wide=True)
def validate_stock_exists_for_template_item(self):
if self.stock_ledger_created() and self._doc_before_save:
if (cint(self._doc_before_save.has_variants) != cint(self.has_variants)