Merge pull request #27281 from DeeMysterio/party-specific-items

feat: link items to supplier / customer
diff --git a/erpnext/buying/doctype/supplier/supplier.json b/erpnext/buying/doctype/supplier/supplier.json
index c7a5db5..12a09cd 100644
--- a/erpnext/buying/doctype/supplier/supplier.json
+++ b/erpnext/buying/doctype/supplier/supplier.json
@@ -433,12 +433,12 @@
  "image_field": "image",
  "links": [
   {
-   "group": "Item Group",
-   "link_doctype": "Supplier Item Group",
-   "link_fieldname": "supplier"
+   "group": "Allowed Items",
+   "link_doctype": "Party Specific Item",
+   "link_fieldname": "party"
   }
  ],
- "modified": "2021-08-27 18:02:44.314077",
+ "modified": "2021-09-06 17:37:56.522233",
  "modified_by": "Administrator",
  "module": "Buying",
  "name": "Supplier",
diff --git a/erpnext/buying/doctype/supplier_item_group/supplier_item_group.json b/erpnext/buying/doctype/supplier_item_group/supplier_item_group.json
deleted file mode 100644
index 1971458..0000000
--- a/erpnext/buying/doctype/supplier_item_group/supplier_item_group.json
+++ /dev/null
@@ -1,77 +0,0 @@
-{
- "actions": [],
- "creation": "2021-05-07 18:16:40.621421",
- "doctype": "DocType",
- "editable_grid": 1,
- "engine": "InnoDB",
- "field_order": [
-  "supplier",
-  "item_group"
- ],
- "fields": [
-  {
-   "fieldname": "supplier",
-   "fieldtype": "Link",
-   "in_list_view": 1,
-   "label": "Supplier",
-   "options": "Supplier",
-   "reqd": 1
-  },
-  {
-   "fieldname": "item_group",
-   "fieldtype": "Link",
-   "in_list_view": 1,
-   "label": "Item Group",
-   "options": "Item Group",
-   "reqd": 1
-  }
- ],
- "index_web_pages_for_search": 1,
- "links": [],
- "modified": "2021-05-19 13:48:16.742303",
- "modified_by": "Administrator",
- "module": "Buying",
- "name": "Supplier Item Group",
- "owner": "Administrator",
- "permissions": [
-  {
-   "create": 1,
-   "delete": 1,
-   "email": 1,
-   "export": 1,
-   "print": 1,
-   "read": 1,
-   "report": 1,
-   "role": "System Manager",
-   "share": 1,
-   "write": 1
-  },
-  {
-   "create": 1,
-   "delete": 1,
-   "email": 1,
-   "export": 1,
-   "print": 1,
-   "read": 1,
-   "report": 1,
-   "role": "Purchase User",
-   "share": 1,
-   "write": 1
-  },
-  {
-   "create": 1,
-   "delete": 1,
-   "email": 1,
-   "export": 1,
-   "print": 1,
-   "read": 1,
-   "report": 1,
-   "role": "Purchase Manager",
-   "share": 1,
-   "write": 1
-  }
- ],
- "sort_field": "modified",
- "sort_order": "DESC",
- "track_changes": 1
-}
\ No newline at end of file
diff --git a/erpnext/buying/doctype/supplier_item_group/supplier_item_group.py b/erpnext/buying/doctype/supplier_item_group/supplier_item_group.py
deleted file mode 100644
index 6d71f7d..0000000
--- a/erpnext/buying/doctype/supplier_item_group/supplier_item_group.py
+++ /dev/null
@@ -1,20 +0,0 @@
-# -*- coding: utf-8 -*-
-# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
-# For license information, please see license.txt
-
-from __future__ import unicode_literals
-
-import frappe
-from frappe import _
-from frappe.model.document import Document
-
-
-class SupplierItemGroup(Document):
-	def validate(self):
-		exists = frappe.db.exists({
-			'doctype': 'Supplier Item Group',
-			'supplier': self.supplier,
-			'item_group': self.item_group
-		})
-		if exists:
-			frappe.throw(_("Item Group has already been linked to this supplier."))
diff --git a/erpnext/buying/doctype/supplier_item_group/test_supplier_item_group.py b/erpnext/buying/doctype/supplier_item_group/test_supplier_item_group.py
deleted file mode 100644
index 55ba85e..0000000
--- a/erpnext/buying/doctype/supplier_item_group/test_supplier_item_group.py
+++ /dev/null
@@ -1,11 +0,0 @@
-# -*- coding: utf-8 -*-
-# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
-# See license.txt
-from __future__ import unicode_literals
-
-# import frappe
-import unittest
-
-
-class TestSupplierItemGroup(unittest.TestCase):
-	pass
diff --git a/erpnext/controllers/queries.py b/erpnext/controllers/queries.py
index ccd417b..9f28646 100644
--- a/erpnext/controllers/queries.py
+++ b/erpnext/controllers/queries.py
@@ -7,6 +7,7 @@
 from collections import defaultdict
 
 import frappe
+from frappe import scrub
 from frappe.desk.reportview import get_filters_cond, get_match_cond
 from frappe.utils import nowdate, unique
 
@@ -223,18 +224,29 @@
 		if not field in searchfields]
 	searchfields = " or ".join([field + " like %(txt)s" for field in searchfields])
 
-	if filters and isinstance(filters, dict) and filters.get('supplier'):
-		item_group_list = frappe.get_all('Supplier Item Group',
-			filters = {'supplier': filters.get('supplier')}, fields = ['item_group'])
+	if filters and isinstance(filters, dict):
+		if filters.get('customer') or filters.get('supplier'):
+			party = filters.get('customer') or filters.get('supplier')
+			item_rules_list = frappe.get_all('Party Specific Item',
+				filters = {'party': party}, fields = ['restrict_based_on', 'based_on_value'])
 
-		item_groups = []
-		for i in item_group_list:
-			item_groups.append(i.item_group)
+			filters_dict = {}
+			for rule in item_rules_list:
+				if rule['restrict_based_on'] == 'Item':
+					rule['restrict_based_on'] = 'name'
+				filters_dict[rule.restrict_based_on] = []
 
-		del filters['supplier']
+			for rule in item_rules_list:
+				filters_dict[rule.restrict_based_on].append(rule.based_on_value)
 
-		if item_groups:
-			filters['item_group'] = ['in', item_groups]
+			for filter in filters_dict:
+				filters[scrub(filter)] = ['in', filters_dict[filter]]
+
+			if filters.get('customer'):
+				del filters['customer']
+			else:
+				del filters['supplier']
+
 
 	description_cond = ''
 	if frappe.db.count('Item', cache=True) < 50000:
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index 15b196f..3bc40a1 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -304,5 +304,6 @@
 erpnext.patches.v13_0.validate_options_for_data_field
 erpnext.patches.v13_0.create_gst_payment_entry_fields
 erpnext.patches.v14_0.delete_shopify_doctypes
+erpnext.patches.v13_0.replace_supplier_item_group_with_party_specific_item
 erpnext.patches.v13_0.update_dates_in_tax_withholding_category
 erpnext.patches.v14_0.update_opportunity_currency_fields
diff --git a/erpnext/patches/v13_0/replace_supplier_item_group_with_party_specific_item.py b/erpnext/patches/v13_0/replace_supplier_item_group_with_party_specific_item.py
new file mode 100644
index 0000000..ba96fdd
--- /dev/null
+++ b/erpnext/patches/v13_0/replace_supplier_item_group_with_party_specific_item.py
@@ -0,0 +1,17 @@
+# Copyright (c) 2019, Frappe and Contributors
+# License: GNU General Public License v3. See license.txt
+
+import frappe
+
+
+def execute():
+	if frappe.db.table_exists('Supplier Item Group'):
+		frappe.reload_doc("selling", "doctype", "party_specific_item")
+		sig = frappe.db.get_all("Supplier Item Group", fields=["name", "supplier", "item_group"])
+		for item in sig:
+			psi = frappe.new_doc("Party Specific Item")
+			psi.party_type = "Supplier"
+			psi.party = item.supplier
+			psi.restrict_based_on = "Item Group"
+			psi.based_on_value = item.item_group
+			psi.insert()
diff --git a/erpnext/selling/doctype/customer/customer.json b/erpnext/selling/doctype/customer/customer.json
index 5913b84..e811435 100644
--- a/erpnext/selling/doctype/customer/customer.json
+++ b/erpnext/selling/doctype/customer/customer.json
@@ -20,7 +20,6 @@
   "tax_withholding_category",
   "default_bank_account",
   "lead_name",
-  "prospect",
   "opportunity_name",
   "image",
   "column_break0",
@@ -214,7 +213,8 @@
    "fieldtype": "Link",
    "ignore_user_permissions": 1,
    "label": "Represents Company",
-   "options": "Company"
+   "options": "Company",
+   "unique": 1
   },
   {
    "depends_on": "represents_company",
@@ -498,14 +498,6 @@
    "options": "Tax Withholding Category"
   },
   {
-   "fieldname": "prospect",
-   "fieldtype": "Link",
-   "label": "Prospect",
-   "no_copy": 1,
-   "options": "Prospect",
-   "print_hide": 1
-  },
-  {
    "fieldname": "opportunity_name",
    "fieldtype": "Link",
    "label": "From Opportunity",
@@ -518,8 +510,14 @@
  "idx": 363,
  "image_field": "image",
  "index_web_pages_for_search": 1,
- "links": [],
- "modified": "2021-08-25 18:56:09.929905",
+ "links": [
+  {
+   "group": "Allowed Items",
+   "link_doctype": "Party Specific Item",
+   "link_fieldname": "party"
+  }
+ ],
+ "modified": "2021-09-06 17:38:54.196663",
  "modified_by": "Administrator",
  "module": "Selling",
  "name": "Customer",
diff --git a/erpnext/buying/doctype/supplier_item_group/__init__.py b/erpnext/selling/doctype/party_specific_item/__init__.py
similarity index 100%
rename from erpnext/buying/doctype/supplier_item_group/__init__.py
rename to erpnext/selling/doctype/party_specific_item/__init__.py
diff --git a/erpnext/buying/doctype/supplier_item_group/supplier_item_group.js b/erpnext/selling/doctype/party_specific_item/party_specific_item.js
similarity index 78%
rename from erpnext/buying/doctype/supplier_item_group/supplier_item_group.js
rename to erpnext/selling/doctype/party_specific_item/party_specific_item.js
index f7da90d..077b936 100644
--- a/erpnext/buying/doctype/supplier_item_group/supplier_item_group.js
+++ b/erpnext/selling/doctype/party_specific_item/party_specific_item.js
@@ -1,7 +1,7 @@
 // Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
 // For license information, please see license.txt
 
-frappe.ui.form.on('Supplier Item Group', {
+frappe.ui.form.on('Party Specific Item', {
 	// refresh: function(frm) {
 
 	// }
diff --git a/erpnext/selling/doctype/party_specific_item/party_specific_item.json b/erpnext/selling/doctype/party_specific_item/party_specific_item.json
new file mode 100644
index 0000000..32b5d47
--- /dev/null
+++ b/erpnext/selling/doctype/party_specific_item/party_specific_item.json
@@ -0,0 +1,77 @@
+{
+ "actions": [],
+ "creation": "2021-08-27 19:28:07.559978",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+  "party_type",
+  "party",
+  "column_break_3",
+  "restrict_based_on",
+  "based_on_value"
+ ],
+ "fields": [
+  {
+   "fieldname": "party_type",
+   "fieldtype": "Select",
+   "in_list_view": 1,
+   "label": "Party Type",
+   "options": "Customer\nSupplier",
+   "reqd": 1
+  },
+  {
+   "fieldname": "party",
+   "fieldtype": "Dynamic Link",
+   "in_list_view": 1,
+   "label": "Party Name",
+   "options": "party_type",
+   "reqd": 1
+  },
+  {
+   "fieldname": "restrict_based_on",
+   "fieldtype": "Select",
+   "in_list_view": 1,
+   "label": "Restrict Items Based On",
+   "options": "Item\nItem Group\nBrand",
+   "reqd": 1
+  },
+  {
+   "fieldname": "column_break_3",
+   "fieldtype": "Column Break"
+  },
+  {
+   "fieldname": "based_on_value",
+   "fieldtype": "Dynamic Link",
+   "in_list_view": 1,
+   "label": "Based On Value",
+   "options": "restrict_based_on",
+   "reqd": 1
+  }
+ ],
+ "index_web_pages_for_search": 1,
+ "links": [],
+ "modified": "2021-09-14 13:27:58.612334",
+ "modified_by": "Administrator",
+ "module": "Selling",
+ "name": "Party Specific Item",
+ "owner": "Administrator",
+ "permissions": [
+  {
+   "create": 1,
+   "delete": 1,
+   "email": 1,
+   "export": 1,
+   "print": 1,
+   "read": 1,
+   "report": 1,
+   "role": "System Manager",
+   "share": 1,
+   "write": 1
+  }
+ ],
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "title_field": "party",
+ "track_changes": 1
+}
\ No newline at end of file
diff --git a/erpnext/selling/doctype/party_specific_item/party_specific_item.py b/erpnext/selling/doctype/party_specific_item/party_specific_item.py
new file mode 100644
index 0000000..a408af5
--- /dev/null
+++ b/erpnext/selling/doctype/party_specific_item/party_specific_item.py
@@ -0,0 +1,19 @@
+# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+import frappe
+from frappe import _
+from frappe.model.document import Document
+
+
+class PartySpecificItem(Document):
+	def validate(self):
+		exists = frappe.db.exists({
+			'doctype': 'Party Specific Item',
+			'party_type': self.party_type,
+			'party': self.party,
+			'restrict_based_on': self.restrict_based_on,
+			'based_on': self.based_on_value,
+		})
+		if exists:
+			frappe.throw(_("This item filter has already been applied for the {0}").format(self.party_type))
diff --git a/erpnext/selling/doctype/party_specific_item/test_party_specific_item.py b/erpnext/selling/doctype/party_specific_item/test_party_specific_item.py
new file mode 100644
index 0000000..874a364
--- /dev/null
+++ b/erpnext/selling/doctype/party_specific_item/test_party_specific_item.py
@@ -0,0 +1,38 @@
+# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
+# See license.txt
+
+import unittest
+
+import frappe
+
+from erpnext.controllers.queries import item_query
+
+test_dependencies = ['Item', 'Customer', 'Supplier']
+
+def create_party_specific_item(**args):
+	psi = frappe.new_doc("Party Specific Item")
+	psi.party_type = args.get('party_type')
+	psi.party = args.get('party')
+	psi.restrict_based_on = args.get('restrict_based_on')
+	psi.based_on_value = args.get('based_on_value')
+	psi.insert()
+
+class TestPartySpecificItem(unittest.TestCase):
+	def setUp(self):
+		self.customer = frappe.get_last_doc("Customer")
+		self.supplier = frappe.get_last_doc("Supplier")
+		self.item = frappe.get_last_doc("Item")
+
+	def test_item_query_for_customer(self):
+		create_party_specific_item(party_type='Customer', party=self.customer.name, restrict_based_on='Item', based_on_value=self.item.name)
+		filters = {'is_sales_item': 1, 'customer': self.customer.name}
+		items = item_query(doctype= 'Item', txt= '', searchfield= 'name', start= 0, page_len= 20,filters=filters, as_dict= False)
+		for item in items:
+			self.assertEqual(item[0], self.item.name)
+
+	def test_item_query_for_supplier(self):
+		create_party_specific_item(party_type='Supplier', party=self.supplier.name, restrict_based_on='Item Group', based_on_value=self.item.item_group)
+		filters = {'supplier': self.supplier.name, 'is_purchase_item': 1}
+		items = item_query(doctype= 'Item', txt= '', searchfield= 'name', start= 0, page_len= 20,filters=filters, as_dict= False)
+		for item in items:
+			self.assertEqual(item[2], self.item.item_group)
diff --git a/erpnext/selling/sales_common.js b/erpnext/selling/sales_common.js
index 6a09109..ddd4c4e 100644
--- a/erpnext/selling/sales_common.js
+++ b/erpnext/selling/sales_common.js
@@ -63,7 +63,7 @@
 			this.frm.set_query("item_code", "items", function() {
 				return {
 					query: "erpnext.controllers.queries.item_query",
-					filters: {'is_sales_item': 1}
+					filters: {'is_sales_item': 1, 'customer': cur_frm.doc.customer}
 				}
 			});
 		}