Merge pull request #26525 from nabinhait/change-log-v13-7-0

chore: Added change log for v13.7.0
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.json b/erpnext/accounts/doctype/payment_entry/payment_entry.json
index 51f18a5..6f362c1 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.json
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.json
@@ -667,6 +667,7 @@
   {
    "fieldname": "base_paid_amount_after_tax",
    "fieldtype": "Currency",
+   "hidden": 1,
    "label": "Paid Amount After Tax (Company Currency)",
    "options": "Company:company:default_currency",
    "read_only": 1
@@ -693,21 +694,25 @@
    "depends_on": "eval:doc.received_amount && doc.payment_type != 'Internal Transfer'",
    "fieldname": "received_amount_after_tax",
    "fieldtype": "Currency",
+   "hidden": 1,
    "label": "Received Amount After Tax",
-   "options": "paid_to_account_currency"
+   "options": "paid_to_account_currency",
+   "read_only": 1
   },
   {
    "depends_on": "doc.received_amount",
    "fieldname": "base_received_amount_after_tax",
    "fieldtype": "Currency",
+   "hidden": 1,
    "label": "Received Amount After Tax (Company Currency)",
-   "options": "Company:company:default_currency"
+   "options": "Company:company:default_currency",
+   "read_only": 1
   }
  ],
  "index_web_pages_for_search": 1,
  "is_submittable": 1,
  "links": [],
- "modified": "2021-06-22 20:37:06.154206",
+ "modified": "2021-07-09 08:58:15.008761",
  "modified_by": "Administrator",
  "module": "Accounts",
  "name": "Payment Entry",
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py
index 0c21aae..7f665db 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.py
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py
@@ -404,9 +404,15 @@
 		if not self.advance_tax_account:
 			frappe.throw(_("Advance TDS account is mandatory for advance TDS deduction"))
 
-		reference_doclist = []
 		net_total = self.paid_amount
-		included_in_paid_amount = 0
+
+		for reference in self.get("references"):
+			net_total_for_tds = 0
+			if reference.reference_doctype == 'Purchase Order':
+				net_total_for_tds += flt(frappe.db.get_value('Purchase Order', reference.reference_name, 'net_total'))
+		
+			if net_total_for_tds:
+				net_total = net_total_for_tds
 
 		# Adding args as purchase invoice to get TDS amount
 		args = frappe._dict({
@@ -423,7 +429,7 @@
 			return
 
 		tax_withholding_details.update({
-			'included_in_paid_amount': included_in_paid_amount,
+			'add_deduct_tax': 'Add',
 			'cost_center': self.cost_center or erpnext.get_default_cost_center(self.company)
 		})
 
@@ -512,16 +518,19 @@
 		self.unallocated_amount = 0
 		if self.party:
 			total_deductions = sum(flt(d.amount) for d in self.get("deductions"))
+			included_taxes = self.get_included_taxes()
 			if self.payment_type == "Receive" \
-				and self.base_total_allocated_amount < self.base_received_amount_after_tax + total_deductions \
-				and self.total_allocated_amount < self.paid_amount_after_tax + (total_deductions / self.source_exchange_rate):
-				self.unallocated_amount = (self.received_amount_after_tax + total_deductions -
+				and self.base_total_allocated_amount < self.base_received_amount + total_deductions \
+				and self.total_allocated_amount < self.paid_amount + (total_deductions / self.source_exchange_rate):
+				self.unallocated_amount = (self.received_amount + total_deductions -
 					self.base_total_allocated_amount) / self.source_exchange_rate
+				self.unallocated_amount -= included_taxes
 			elif self.payment_type == "Pay" \
-				and self.base_total_allocated_amount < (self.base_paid_amount_after_tax - total_deductions) \
-				and self.total_allocated_amount < self.received_amount_after_tax + (total_deductions / self.target_exchange_rate):
-				self.unallocated_amount = (self.base_paid_amount_after_tax - (total_deductions +
+				and self.base_total_allocated_amount < (self.base_paid_amount - total_deductions) \
+				and self.total_allocated_amount < self.received_amount + (total_deductions / self.target_exchange_rate):
+				self.unallocated_amount = (self.base_paid_amount - (total_deductions +
 					self.base_total_allocated_amount)) / self.target_exchange_rate
+				self.unallocated_amount -= included_taxes
 
 	def set_difference_amount(self):
 		base_unallocated_amount = flt(self.unallocated_amount) * (flt(self.source_exchange_rate)
@@ -530,17 +539,29 @@
 		base_party_amount = flt(self.base_total_allocated_amount) + flt(base_unallocated_amount)
 
 		if self.payment_type == "Receive":
-			self.difference_amount = base_party_amount - self.base_received_amount_after_tax
+			self.difference_amount = base_party_amount - self.base_received_amount
 		elif self.payment_type == "Pay":
-			self.difference_amount = self.base_paid_amount_after_tax - base_party_amount
+			self.difference_amount = self.base_paid_amount - base_party_amount
 		else:
-			self.difference_amount = self.base_paid_amount_after_tax - flt(self.base_received_amount_after_tax)
+			self.difference_amount = self.base_paid_amount - flt(self.base_received_amount)
 
 		total_deductions = sum(flt(d.amount) for d in self.get("deductions"))
+		included_taxes = self.get_included_taxes()
 
-		self.difference_amount = flt(self.difference_amount - total_deductions,
+		self.difference_amount = flt(self.difference_amount - total_deductions - included_taxes,
 			self.precision("difference_amount"))
 
+	def get_included_taxes(self):
+		included_taxes = 0
+		for tax in self.get('taxes'):
+			if tax.included_in_paid_amount:
+				if tax.add_deduct_tax == 'Add':
+					included_taxes += tax.base_tax_amount
+				else:
+					included_taxes -= tax.base_tax_amount
+
+		return included_taxes
+
 	# Paid amount is auto allocated in the reference document by default.
 	# Clear the reference document which doesn't have allocated amount on validate so that form can be loaded fast
 	def clear_unallocated_reference_document_rows(self):
@@ -683,8 +704,8 @@
 					"account": self.paid_from,
 					"account_currency": self.paid_from_account_currency,
 					"against": self.party if self.payment_type=="Pay" else self.paid_to,
-					"credit_in_account_currency": self.paid_amount_after_tax,
-					"credit": self.base_paid_amount_after_tax,
+					"credit_in_account_currency": self.paid_amount,
+					"credit": self.base_paid_amount,
 					"cost_center": self.cost_center
 				}, item=self)
 			)
@@ -694,8 +715,8 @@
 					"account": self.paid_to,
 					"account_currency": self.paid_to_account_currency,
 					"against": self.party if self.payment_type=="Receive" else self.paid_from,
-					"debit_in_account_currency": self.received_amount_after_tax,
-					"debit": self.base_received_amount_after_tax,
+					"debit_in_account_currency": self.received_amount,
+					"debit": self.base_received_amount,
 					"cost_center": self.cost_center
 				}, item=self)
 			)
@@ -708,35 +729,42 @@
 
 			if self.payment_type in ('Pay', 'Internal Transfer'):
 				dr_or_cr = "debit" if d.add_deduct_tax == "Add" else "credit"
+				against = self.party or self.paid_from
 			elif self.payment_type == 'Receive':
 				dr_or_cr = "credit" if d.add_deduct_tax == "Add" else "debit"
+				against = self.party or self.paid_to
 
 			payment_or_advance_account = self.get_party_account_for_taxes()
+			tax_amount = d.tax_amount
+			base_tax_amount = d.base_tax_amount
+
+			if self.advance_tax_account:
+				tax_amount = -1 * tax_amount
+				base_tax_amount = -1 * base_tax_amount
 
 			gl_entries.append(
 				self.get_gl_dict({
 					"account": d.account_head,
-					"against": self.party if self.payment_type=="Receive" else self.paid_from,
-					dr_or_cr: d.base_tax_amount,
-					dr_or_cr + "_in_account_currency": d.base_tax_amount
+					"against": against,
+					dr_or_cr: tax_amount,
+					dr_or_cr + "_in_account_currency": base_tax_amount
 					if account_currency==self.company_currency
 					else d.tax_amount,
 					"cost_center": d.cost_center
 				}, account_currency, item=d))
 
 			#Intentionally use -1 to get net values in party account
-			gl_entries.append(
-				self.get_gl_dict({
-					"account": payment_or_advance_account,
-					"against": self.party if self.payment_type=="Receive" else self.paid_from,
-					dr_or_cr: -1 * d.base_tax_amount,
-					dr_or_cr + "_in_account_currency": -1*d.base_tax_amount
-					if account_currency==self.company_currency
-					else d.tax_amount,
-					"cost_center": self.cost_center,
-					"party_type": self.party_type,
-					"party": self.party
-				}, account_currency, item=d))
+			if not d.included_in_paid_amount or self.advance_tax_account:
+				gl_entries.append(
+					self.get_gl_dict({
+						"account": payment_or_advance_account,
+						"against": against,
+						dr_or_cr: -1 * tax_amount,
+						dr_or_cr + "_in_account_currency": -1 * base_tax_amount
+						if account_currency==self.company_currency
+						else d.tax_amount,
+						"cost_center": self.cost_center,
+					}, account_currency, item=d))
 
 	def add_deductions_gl_entries(self, gl_entries):
 		for d in self.get("deductions"):
@@ -760,9 +788,9 @@
 		if self.advance_tax_account:
 			return self.advance_tax_account
 		elif self.payment_type == 'Receive':
-			return self.paid_from
-		elif self.payment_type in ('Pay', 'Internal Transfer'):
 			return self.paid_to
+		elif self.payment_type in ('Pay', 'Internal Transfer'):
+			return self.paid_from
 
 	def update_advance_paid(self):
 		if self.payment_type in ("Receive", "Pay") and self.party:
@@ -1634,12 +1662,6 @@
 			if dt == "Employee Advance":
 				paid_amount = received_amount * doc.get('exchange_rate', 1)
 
-	if dt == "Purchase Order" and doc.apply_tds:
-		if party_account_currency == bank.account_currency:
-			paid_amount = received_amount = doc.base_net_total
-		else:
-			paid_amount = received_amount = doc.base_net_total * doc.get('exchange_rate', 1)
-
 	return paid_amount, received_amount
 
 def apply_early_payment_discount(paid_amount, received_amount, doc):
diff --git a/erpnext/accounts/report/tds_computation_summary/tds_computation_summary.py b/erpnext/accounts/report/tds_computation_summary/tds_computation_summary.py
index e15715d..6b9df41 100644
--- a/erpnext/accounts/report/tds_computation_summary/tds_computation_summary.py
+++ b/erpnext/accounts/report/tds_computation_summary/tds_computation_summary.py
@@ -75,7 +75,8 @@
 		select voucher_no, credit
 		from `tabGL Entry`
 		where party in (%s) and credit > 0
-			and company=%s and posting_date between %s and %s
+			and company=%s and is_cancelled = 0
+			and posting_date between %s and %s
 	""", (supplier, company, from_date, to_date), as_dict=1)
 
 	supplier_credit_amount = flt(sum(d.credit for d in entries))
diff --git a/erpnext/buying/doctype/supplier/supplier.js b/erpnext/buying/doctype/supplier/supplier.js
index 4ddc458..1766c2c 100644
--- a/erpnext/buying/doctype/supplier/supplier.js
+++ b/erpnext/buying/doctype/supplier/supplier.js
@@ -60,10 +60,23 @@
 				erpnext.utils.make_pricing_rule(frm.doc.doctype, frm.doc.name);
 			}, __('Create'));
 
+			frm.add_custom_button(__('Get Supplier Group Details'), function () {
+				frm.trigger("get_supplier_group_details");
+			}, __('Actions'));
+
 			// indicators
 			erpnext.utils.set_party_dashboard_indicators(frm);
 		}
 	},
+	get_supplier_group_details: function(frm) {
+		frappe.call({
+			method: "get_supplier_group_details",
+			doc: frm.doc,
+			callback: function() {
+				frm.refresh();
+			}
+		});
+	},
 
 	is_internal_supplier: function(frm) {
 		if (frm.doc.is_internal_supplier == 1) {
diff --git a/erpnext/buying/doctype/supplier/supplier.py b/erpnext/buying/doctype/supplier/supplier.py
index edeb135..fd16b23 100644
--- a/erpnext/buying/doctype/supplier/supplier.py
+++ b/erpnext/buying/doctype/supplier/supplier.py
@@ -51,6 +51,23 @@
 		validate_party_accounts(self)
 		self.validate_internal_supplier()
 
+	@frappe.whitelist()
+	def get_supplier_group_details(self):
+		doc = frappe.get_doc('Supplier Group', self.supplier_group)
+		self.payment_terms = ""
+		self.accounts = []
+
+		if doc.accounts:
+			for account in doc.accounts:
+				child = self.append('accounts')
+				child.company = account.company
+				child.account = account.account
+
+		if doc.payment_terms:
+			self.payment_terms = doc.payment_terms
+
+		self.save()
+
 	def validate_internal_supplier(self):
 		internal_supplier = frappe.db.get_value("Supplier",
 			{"is_internal_supplier": 1, "represents_company": self.represents_company, "name": ("!=", self.name)}, "name")
@@ -86,4 +103,4 @@
 						create_contact(supplier, 'Supplier',
 							doc.name, args.get('supplier_email_' + str(i)))
 				except frappe.NameError:
-					pass
\ No newline at end of file
+					pass
diff --git a/erpnext/buying/doctype/supplier/test_supplier.py b/erpnext/buying/doctype/supplier/test_supplier.py
index f9c8d35..8980466 100644
--- a/erpnext/buying/doctype/supplier/test_supplier.py
+++ b/erpnext/buying/doctype/supplier/test_supplier.py
@@ -13,6 +13,30 @@
 
 
 class TestSupplier(unittest.TestCase):
+    def test_get_supplier_group_details(self):
+        doc = frappe.new_doc("Supplier Group")
+        doc.supplier_group_name = "_Testing Supplier Group"
+        doc.payment_terms = "_Test Payment Term Template 3"
+        doc.accounts = []
+        test_account_details = {
+            "company": "_Test Company",
+            "account": "Creditors - _TC",
+        }
+        doc.append("accounts", test_account_details)
+        doc.save()
+        s_doc = frappe.new_doc("Supplier")
+        s_doc.supplier_name = "Testing Supplier"
+        s_doc.supplier_group = "_Testing Supplier Group"
+        s_doc.payment_terms = ""
+        s_doc.accounts = []
+        s_doc.insert()
+        s_doc.get_supplier_group_details()
+        self.assertEqual(s_doc.payment_terms, "_Test Payment Term Template 3")
+        self.assertEqual(s_doc.accounts[0].company, "_Test Company")
+        self.assertEqual(s_doc.accounts[0].account, "Creditors - _TC")
+        s_doc.delete()
+        doc.delete()
+
     def test_supplier_default_payment_terms(self):
         # Payment Term based on Days after invoice date
         frappe.db.set_value(
@@ -136,4 +160,4 @@
         return doc
 
     except frappe.DuplicateEntryError:
-        return frappe.get_doc("Supplier", args.supplier_name)
\ No newline at end of file
+        return frappe.get_doc("Supplier", args.supplier_name)
diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py
index 1c086e9..5d30b65 100644
--- a/erpnext/controllers/accounts_controller.py
+++ b/erpnext/controllers/accounts_controller.py
@@ -751,11 +751,11 @@
 					account_currency = get_account_currency(tax.account_head)
 
 					if self.doctype == "Purchase Invoice":
-						dr_or_cr = "credit" if tax.add_deduct_tax == "Add" else "debit"
-						rev_dr_cr = "debit" if tax.add_deduct_tax == "Add" else "credit"
-					else:
 						dr_or_cr = "debit" if tax.add_deduct_tax == "Add" else "credit"
 						rev_dr_cr = "credit" if tax.add_deduct_tax == "Add" else "debit"
+					else:
+						dr_or_cr = "credit" if tax.add_deduct_tax == "Add" else "debit"
+						rev_dr_cr = "debit" if tax.add_deduct_tax == "Add" else "credit"
 
 					party = self.supplier if self.doctype == "Purchase Invoice" else self.customer
 					unallocated_amount = tax.tax_amount - tax.allocated_amount
diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py
index c32a8a9..9da461f 100644
--- a/erpnext/manufacturing/doctype/bom/bom.py
+++ b/erpnext/manufacturing/doctype/bom/bom.py
@@ -713,7 +713,8 @@
 			"conversion_rate": 1, # Passed conversion rate as 1 purposefully, as conversion rate is applied at the end of the function
 			"conversion_factor": args.get("conversion_factor") or 1,
 			"plc_conversion_rate": 1,
-			"ignore_party": True
+			"ignore_party": True,
+			"ignore_conversion_rate": True
 		})
 		item_doc = frappe.get_cached_doc("Item", args.get("item_code"))
 		out = frappe._dict()
diff --git a/erpnext/manufacturing/doctype/work_order/test_work_order.py b/erpnext/manufacturing/doctype/work_order/test_work_order.py
index 68de0b2..bf1ccb7 100644
--- a/erpnext/manufacturing/doctype/work_order/test_work_order.py
+++ b/erpnext/manufacturing/doctype/work_order/test_work_order.py
@@ -513,6 +513,60 @@
 		work_order1.save()
 		self.assertEqual(work_order1.operations[0].time_in_mins, 40.0)
 
+	def test_batch_size_for_fg_item(self):
+		fg_item = "Test Batch Size Item For BOM 3"
+		rm1 = "Test Batch Size Item RM 1 For BOM 3"
+
+		frappe.db.set_value('Manufacturing Settings', None, 'make_serial_no_batch_from_work_order', 0)
+		for item in ["Test Batch Size Item For BOM 3", "Test Batch Size Item RM 1 For BOM 3"]:
+			item_args = {
+				"include_item_in_manufacturing": 1,
+				"is_stock_item": 1
+			}
+
+			if item == fg_item:
+				item_args['has_batch_no'] = 1
+				item_args['create_new_batch'] = 1
+				item_args['batch_number_series'] = 'TBSI3.#####'
+
+			make_item(item, item_args)
+
+		bom_name = frappe.db.get_value("BOM",
+			{"item": fg_item, "is_active": 1, "with_operations": 1}, "name")
+
+		if not bom_name:
+			bom = make_bom(item=fg_item, rate=1000, raw_materials = [rm1], do_not_save=True)
+			bom.save()
+			bom.submit()
+			bom_name = bom.name
+
+		work_order = make_wo_order_test_record(item=fg_item, skip_transfer=True, planned_start_date=now(), qty=1)
+		ste1 = frappe.get_doc(make_stock_entry(work_order.name, "Manufacture", 1))
+		for row in ste1.get('items'):
+			if row.is_finished_item:
+				self.assertEqual(row.item_code, fg_item)
+
+		work_order = make_wo_order_test_record(item=fg_item, skip_transfer=True, planned_start_date=now(), qty=1)
+		frappe.db.set_value('Manufacturing Settings', None, 'make_serial_no_batch_from_work_order', 1)
+		ste1 = frappe.get_doc(make_stock_entry(work_order.name, "Manufacture", 1))
+		for row in ste1.get('items'):
+			if row.is_finished_item:
+				self.assertEqual(row.item_code, fg_item)
+
+		work_order = make_wo_order_test_record(item=fg_item, skip_transfer=True, planned_start_date=now(),
+			qty=30, do_not_save = True)
+		work_order.batch_size = 10
+		work_order.insert()
+		work_order.submit()
+		self.assertEqual(work_order.has_batch_no, 1)
+		ste1 = frappe.get_doc(make_stock_entry(work_order.name, "Manufacture", 30))
+		for row in ste1.get('items'):
+			if row.is_finished_item:
+				self.assertEqual(row.item_code, fg_item)
+				self.assertEqual(row.qty, 10)
+
+		frappe.db.set_value('Manufacturing Settings', None, 'make_serial_no_batch_from_work_order', 0)
+
 	def test_partial_material_consumption(self):
 		frappe.db.set_value("Manufacturing Settings", None, "material_consumption", 1)
 		wo_order = make_wo_order_test_record(planned_start_date=now(), qty=4)
diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py
index 779ae42..0a8e532 100644
--- a/erpnext/manufacturing/doctype/work_order/work_order.py
+++ b/erpnext/manufacturing/doctype/work_order/work_order.py
@@ -239,7 +239,7 @@
 		self.create_serial_no_batch_no()
 
 	def on_submit(self):
-		if not self.wip_warehouse:
+		if not self.wip_warehouse and not self.skip_transfer:
 			frappe.throw(_("Work-in-Progress Warehouse is required before Submit"))
 		if not self.fg_warehouse:
 			frappe.throw(_("For Warehouse is required before Submit"))
diff --git a/erpnext/portal/product_configurator/utils.py b/erpnext/portal/product_configurator/utils.py
index d77eb2c..211b94a 100644
--- a/erpnext/portal/product_configurator/utils.py
+++ b/erpnext/portal/product_configurator/utils.py
@@ -2,6 +2,7 @@
 from frappe.utils import cint
 from erpnext.portal.product_configurator.item_variants_cache import ItemVariantsCacheManager
 from erpnext.shopping_cart.product_info import get_product_info_for_website
+from erpnext.setup.doctype.item_group.item_group import get_child_groups
 
 def get_field_filter_data():
 	product_settings = get_product_settings()
@@ -89,6 +90,7 @@
 def get_products_html_for_website(field_filters=None, attribute_filters=None):
 	field_filters = frappe.parse_json(field_filters)
 	attribute_filters = frappe.parse_json(attribute_filters)
+	set_item_group_filters(field_filters)
 
 	items = get_products_for_website(field_filters, attribute_filters)
 	html = ''.join(get_html_for_items(items))
@@ -98,6 +100,10 @@
 
 	return html
 
+def set_item_group_filters(field_filters):
+	if 'item_group' in field_filters:
+		field_filters['item_group'] = [ig[0] for ig in get_child_groups(field_filters['item_group'])]
+
 
 def get_item_codes_by_attributes(attribute_filters, template_item_code=None):
 	items = []
diff --git a/erpnext/public/js/controllers/taxes_and_totals.js b/erpnext/public/js/controllers/taxes_and_totals.js
index 1de9ec1..52efbb5 100644
--- a/erpnext/public/js/controllers/taxes_and_totals.js
+++ b/erpnext/public/js/controllers/taxes_and_totals.js
@@ -67,6 +67,8 @@
 
 	calculate_discount_amount: function(){
 		if (frappe.meta.get_docfield(this.frm.doc.doctype, "discount_amount")) {
+			this.calculate_item_values();
+			this.calculate_net_total();
 			this.set_discount_amount();
 			this.apply_discount_amount();
 		}
diff --git a/erpnext/regional/india/setup.py b/erpnext/regional/india/setup.py
index 5f9d5ed..9265460 100644
--- a/erpnext/regional/india/setup.py
+++ b/erpnext/regional/india/setup.py
@@ -12,7 +12,10 @@
 from frappe.utils import today
 
 def setup(company=None, patch=True):
-	setup_company_independent_fixtures(patch=patch)
+	# Company independent fixtures should be called only once at the first company setup
+	if frappe.db.count('Company', {'country': 'India'}) <=1:
+		setup_company_independent_fixtures(patch=patch)
+
 	if not patch:
 		make_fixtures(company)
 
@@ -122,10 +125,12 @@
 def make_property_setters(patch=False):
 	# GST rules do not allow for an invoice no. bigger than 16 characters
 	journal_entry_types = frappe.get_meta("Journal Entry").get_options("voucher_type").split("\n") + ['Reversal Of ITC']
+	sales_invoice_series = ['SINV-.YY.-', 'SRET-.YY.-', ''] + frappe.get_meta("Sales Invoice").get_options("naming_series").split("\n")
+	purchase_invoice_series = ['PINV-.YY.-', 'PRET-.YY.-', ''] + frappe.get_meta("Purchase Invoice").get_options("naming_series").split("\n")
 
 	if not patch:
-		make_property_setter('Sales Invoice', 'naming_series', 'options', 'SINV-.YY.-\nSRET-.YY.-', '')
-		make_property_setter('Purchase Invoice', 'naming_series', 'options', 'PINV-.YY.-\nPRET-.YY.-', '')
+		make_property_setter('Sales Invoice', 'naming_series', 'options', '\n'.join(sales_invoice_series), '')
+		make_property_setter('Purchase Invoice', 'naming_series', 'options', '\n'.join(purchase_invoice_series), '')
 		make_property_setter('Journal Entry', 'voucher_type', 'options', '\n'.join(journal_entry_types), '')
 
 def make_custom_fields(update=True):
@@ -786,7 +791,7 @@
 			doc.flags.ignore_mandatory = True
 			doc.insert()
 		else:
-			doc = frappe.get_doc("Tax Withholding Category", d.get("name"))
+			doc = frappe.get_doc("Tax Withholding Category", d.get("name"), for_update=True)
 
 			if accounts:
 				doc.append("accounts", accounts[0])
diff --git a/erpnext/regional/report/gstr_1/gstr_1.py b/erpnext/regional/report/gstr_1/gstr_1.py
index 1096159..cfcb8c3 100644
--- a/erpnext/regional/report/gstr_1/gstr_1.py
+++ b/erpnext/regional/report/gstr_1/gstr_1.py
@@ -584,7 +584,7 @@
 def get_json(filters, report_name, data):
 	filters = json.loads(filters)
 	report_data = json.loads(data)
-	gstin = get_company_gstin_number(filters["company"], filters["company_address"])
+	gstin = get_company_gstin_number(filters.get("company"), filters.get("company_address"))
 
 	fp = "%02d%s" % (getdate(filters["to_date"]).month, getdate(filters["to_date"]).year)
 
diff --git a/erpnext/selling/doctype/customer/customer.js b/erpnext/selling/doctype/customer/customer.js
index 825b170..2849466 100644
--- a/erpnext/selling/doctype/customer/customer.js
+++ b/erpnext/selling/doctype/customer/customer.js
@@ -130,6 +130,10 @@
 				erpnext.utils.make_pricing_rule(frm.doc.doctype, frm.doc.name);
 			}, __('Create'));
 
+			frm.add_custom_button(__('Get Customer Group Details'), function () {
+				frm.trigger("get_customer_group_details");
+			}, __('Actions'));
+
 			// indicator
 			erpnext.utils.set_party_dashboard_indicators(frm);
 
@@ -145,4 +149,15 @@
 		if(frm.doc.lead_name) frappe.model.clear_doc("Lead", frm.doc.lead_name);
 
 	},
-});
\ No newline at end of file
+	get_customer_group_details: function(frm) {
+		frappe.call({
+			method: "get_customer_group_details",
+			doc: frm.doc,
+			callback: function() {
+				frm.refresh();
+			}
+		});
+
+	}
+});
+
diff --git a/erpnext/selling/doctype/customer/customer.py b/erpnext/selling/doctype/customer/customer.py
index 818888c..3b62081 100644
--- a/erpnext/selling/doctype/customer/customer.py
+++ b/erpnext/selling/doctype/customer/customer.py
@@ -78,6 +78,29 @@
 			if sum(member.allocated_percentage or 0 for member in self.sales_team) != 100:
 				frappe.throw(_("Total contribution percentage should be equal to 100"))
 
+	@frappe.whitelist()
+	def get_customer_group_details(self):
+		doc = frappe.get_doc('Customer Group', self.customer_group)
+		self.accounts = self.credit_limits = []
+		self.payment_terms = self.default_price_list = ""
+
+		tables = [["accounts", "account"], ["credit_limits", "credit_limit"]]
+		fields = ["payment_terms", "default_price_list"]
+
+		for row in tables:
+			table, field = row[0], row[1]
+			if not doc.get(table): continue
+
+			for entry in doc.get(table):
+				child = self.append(table)
+				child.update({"company": entry.company, field: entry.get(field)})
+
+		for field in fields:
+			if not doc.get(field): continue
+			self.update({field: doc.get(field)})
+
+		self.save()
+
 	def check_customer_group_change(self):
 		frappe.flags.customer_group_changed = False
 
diff --git a/erpnext/selling/doctype/customer/test_customer.py b/erpnext/selling/doctype/customer/test_customer.py
index 7761aa7..b1a5b52 100644
--- a/erpnext/selling/doctype/customer/test_customer.py
+++ b/erpnext/selling/doctype/customer/test_customer.py
@@ -27,6 +27,42 @@
 	def tearDown(self):
 		set_credit_limit('_Test Customer', '_Test Company', 0)
 
+	def test_get_customer_group_details(self):
+		doc = frappe.new_doc("Customer Group")
+		doc.customer_group_name = "_Testing Customer Group"
+		doc.payment_terms = "_Test Payment Term Template 3"
+		doc.accounts = []
+		doc.default_price_list = "Standard Buying"
+		doc.credit_limits = []
+		test_account_details = {
+			"company": "_Test Company",
+			"account": "Creditors - _TC",
+		}
+		test_credit_limits = {
+			"company": "_Test Company",
+			"credit_limit": 350000
+		}
+		doc.append("accounts", test_account_details)
+		doc.append("credit_limits", test_credit_limits)
+		doc.insert()
+
+		c_doc = frappe.new_doc("Customer")
+		c_doc.customer_name = "Testing Customer"
+		c_doc.customer_group = "_Testing Customer Group"
+		c_doc.payment_terms = c_doc.default_price_list = ""
+		c_doc.accounts = c_doc.credit_limits= []
+		c_doc.insert()
+		c_doc.get_customer_group_details()
+		self.assertEqual(c_doc.payment_terms, "_Test Payment Term Template 3")
+
+		self.assertEqual(c_doc.accounts[0].company, "_Test Company")
+		self.assertEqual(c_doc.accounts[0].account, "Creditors - _TC")
+
+		self.assertEqual(c_doc.credit_limits[0].company, "_Test Company")
+		self.assertEqual(c_doc.credit_limits[0].credit_limit, 350000)
+		c_doc.delete()
+		doc.delete()
+
 	def test_party_details(self):
 		from erpnext.accounts.party import get_party_details
 
diff --git a/erpnext/selling/page/point_of_sale/pos_item_cart.js b/erpnext/selling/page/point_of_sale/pos_item_cart.js
index 38508c2..f7b2c1d 100644
--- a/erpnext/selling/page/point_of_sale/pos_item_cart.js
+++ b/erpnext/selling/page/point_of_sale/pos_item_cart.js
@@ -965,8 +965,23 @@
 		});
 	}
 
+	attach_refresh_field_event(frm) {
+		$(frm.wrapper).off('refresh-fields');
+		$(frm.wrapper).on('refresh-fields', () => {
+			if (frm.doc.items.length) {
+				frm.doc.items.forEach(item => {
+					this.update_item_html(item);
+				});
+			}
+			this.update_totals_section(frm);
+		});
+	}
+
 	load_invoice() {
 		const frm = this.events.get_frm();
+		
+		this.attach_refresh_field_event(frm);
+
 		this.fetch_customer_details(frm.doc.customer).then(() => {
 			this.events.customer_details_updated(this.customer_info);
 			this.update_customer_section();
diff --git a/erpnext/setup/doctype/company/company.py b/erpnext/setup/doctype/company/company.py
index 915e6a4..8755125 100644
--- a/erpnext/setup/doctype/company/company.py
+++ b/erpnext/setup/doctype/company/company.py
@@ -291,7 +291,7 @@
 		cash = frappe.db.get_value('Mode of Payment', {'type': 'Cash'}, 'name')
 		if cash and self.default_cash_account \
 			and not frappe.db.get_value('Mode of Payment Account', {'company': self.name, 'parent': cash}):
-			mode_of_payment = frappe.get_doc('Mode of Payment', cash)
+			mode_of_payment = frappe.get_doc('Mode of Payment', cash, for_update=True)
 			mode_of_payment.append('accounts', {
 				'company': self.name,
 				'default_account': self.default_cash_account
@@ -395,7 +395,7 @@
 
 @frappe.whitelist()
 def enqueue_replace_abbr(company, old, new):
-	kwargs = dict(company=company, old=old, new=new)
+	kwargs = dict(queue="long", company=company, old=old, new=new)
 	frappe.enqueue('erpnext.setup.doctype.company.company.replace_abbr', **kwargs)
 
 
diff --git a/erpnext/setup/doctype/item_group/item_group.py b/erpnext/setup/doctype/item_group/item_group.py
index 1c72ceb..5fcad00 100644
--- a/erpnext/setup/doctype/item_group/item_group.py
+++ b/erpnext/setup/doctype/item_group/item_group.py
@@ -87,8 +87,8 @@
 		if not field_filters:
 			field_filters = {}
 
-		# Ensure the query remains within current item group
-		field_filters['item_group'] = self.name
+		# Ensure the query remains within current item group & sub group
+		field_filters['item_group'] = [ig[0] for ig in get_child_groups(self.name)]
 
 		engine = ProductQuery()
 		context.items = engine.query(attribute_filters, field_filters, search, start, item_group=self.name)
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py
index 8f27ef4..c9838d7 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.py
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.py
@@ -529,7 +529,7 @@
 		scrap_items_cost = sum([flt(d.basic_amount) for d in self.get("items") if d.is_scrap_item])
 
 		# Get raw materials cost from BOM if multiple material consumption entries
-		if frappe.db.get_single_value("Manufacturing Settings", "material_consumption"):
+		if frappe.db.get_single_value("Manufacturing Settings", "material_consumption", cache=True):
 			bom_items = self.get_bom_raw_materials(finished_item_qty)
 			outgoing_items_cost = sum([flt(row.qty)*flt(row.rate) for row in bom_items.values()])
 
@@ -1090,13 +1090,13 @@
 			"is_finished_item": 1
 		}
 
-		if self.work_order and self.pro_doc.has_batch_no:
+		if self.work_order and self.pro_doc.has_batch_no and cint(frappe.db.get_single_value('Manufacturing Settings',
+			'make_serial_no_batch_from_work_order', cache=True)):
 			self.set_batchwise_finished_goods(args, item)
 		else:
-			self.add_finisged_goods(args, item)
+			self.add_finished_goods(args, item)
 
 	def set_batchwise_finished_goods(self, args, item):
-		qty = flt(self.fg_completed_qty)
 		filters = {
 			"reference_name": self.pro_doc.name,
 			"reference_doctype": self.pro_doc.doctype,
@@ -1105,7 +1105,17 @@
 
 		fields = ["qty_to_produce as qty", "produced_qty", "name"]
 
-		for row in frappe.get_all("Batch", filters = filters, fields = fields, order_by="creation asc"):
+		data = frappe.get_all("Batch", filters = filters, fields = fields, order_by="creation asc")
+
+		if not data:
+			self.add_finished_goods(args, item)
+		else:
+			self.add_batchwise_finished_good(data, args, item)
+
+	def add_batchwise_finished_good(self, data, args, item):
+		qty = flt(self.fg_completed_qty)
+
+		for row in data:
 			batch_qty = flt(row.qty) - flt(row.produced_qty)
 			if not batch_qty:
 				continue
@@ -1121,9 +1131,9 @@
 			args["qty"] = fg_qty
 			args["batch_no"] = row.name
 
-			self.add_finisged_goods(args, item)
+			self.add_finished_goods(args, item)
 
-	def add_finisged_goods(self, args, item):
+	def add_finished_goods(self, args, item):
 		self.add_to_stock_entry_detail({
 			item.name: args
 		}, bom_no = self.bom_no)
diff --git a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py
index 0febcb6..93482e8 100644
--- a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py
+++ b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py
@@ -89,17 +89,16 @@
 		if item_det.is_stock_item != 1:
 			frappe.throw(_("Item {0} must be a stock Item").format(self.item_code))
 
-		# check if batch number is required
-		if self.voucher_type != 'Stock Reconciliation':
-			if item_det.has_batch_no == 1:
-				batch_item = self.item_code if self.item_code == item_det.item_name else self.item_code + ":" +  item_det.item_name
-				if not self.batch_no:
-					frappe.throw(_("Batch number is mandatory for Item {0}").format(batch_item))
-				elif not frappe.db.get_value("Batch",{"item": self.item_code, "name": self.batch_no}):
-					frappe.throw(_("{0} is not a valid Batch Number for Item {1}").format(self.batch_no, batch_item))
+		# check if batch number is valid
+		if item_det.has_batch_no == 1:
+			batch_item = self.item_code if self.item_code == item_det.item_name else self.item_code + ":" + item_det.item_name
+			if not self.batch_no:
+				frappe.throw(_("Batch number is mandatory for Item {0}").format(batch_item))
+			elif not frappe.db.get_value("Batch",{"item": self.item_code, "name": self.batch_no}):
+				frappe.throw(_("{0} is not a valid Batch Number for Item {1}").format(self.batch_no, batch_item))
 
-			elif item_det.has_batch_no == 0 and self.batch_no and self.is_cancelled == 0:
-				frappe.throw(_("The Item {0} cannot have Batch").format(self.item_code))
+		elif item_det.has_batch_no == 0 and self.batch_no and self.is_cancelled == 0:
+			frappe.throw(_("The Item {0} cannot have Batch").format(self.item_code))
 
 		if item_det.has_variants:
 			frappe.throw(_("Stock cannot exist for Item {0} since has variants").format(self.item_code),
@@ -178,3 +177,4 @@
 
 	frappe.db.add_index("Stock Ledger Entry", ["voucher_no", "voucher_type"])
 	frappe.db.add_index("Stock Ledger Entry", ["batch_no", "item_code", "warehouse"])
+	frappe.db.add_index("Stock Ledger Entry", ["voucher_detail_no"])
diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js
index a01db80..349e59f 100644
--- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js
+++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js
@@ -17,6 +17,14 @@
 				}
 			}
 		});
+		frm.set_query("batch_no", "items", function(doc, cdt, cdn) {
+			var item = locals[cdt][cdn];
+			return {
+				filters: {
+					'item': item.item_code
+				}
+			};
+		});
 
 		if (frm.doc.company) {
 			erpnext.queries.setup_queries(frm, "Warehouse", function() {
diff --git a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py
index 84cdc49..c192582 100644
--- a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py
+++ b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py
@@ -16,6 +16,7 @@
 from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
 from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt
 
+
 class TestStockReconciliation(unittest.TestCase):
 	@classmethod
 	def setUpClass(self):
@@ -352,6 +353,26 @@
 		dn2.cancel()
 		pr1.cancel()
 
+	def test_valid_batch(self):
+		create_batch_item_with_batch("Testing Batch Item 1", "001")
+		create_batch_item_with_batch("Testing Batch Item 2", "002")
+		sr = create_stock_reconciliation(item_code="Testing Batch Item 1", qty=1, rate=100, batch_no="002"
+			, do_not_submit=True)
+		self.assertRaises(frappe.ValidationError, sr.submit)
+
+def create_batch_item_with_batch(item_name, batch_id):
+	batch_item_doc = create_item(item_name, is_stock_item=1)
+	if not batch_item_doc.has_batch_no:
+		batch_item_doc.has_batch_no = 1
+		batch_item_doc.create_new_batch = 1
+		batch_item_doc.save(ignore_permissions=True)
+
+	if not frappe.db.exists('Batch', batch_id):
+		b = frappe.new_doc('Batch')
+		b.item = item_name
+		b.batch_id = batch_id
+		b.save()
+
 def insert_existing_sle(warehouse):
 	from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
 
diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py
index ca174a3..4657700 100644
--- a/erpnext/stock/get_item_details.py
+++ b/erpnext/stock/get_item_details.py
@@ -441,7 +441,7 @@
 
 	if item_tax_templates is None:
 		item_tax_templates = {}
-	
+
 	if item_rates is None:
 		item_rates = {}
 
@@ -807,10 +807,14 @@
 def validate_conversion_rate(args, meta):
 	from erpnext.controllers.accounts_controller import validate_conversion_rate
 
-	if (not args.conversion_rate
-		and args.currency==frappe.get_cached_value('Company',  args.company,  "default_currency")):
+	company_currency = frappe.get_cached_value('Company',  args.company,  "default_currency")
+	if (not args.conversion_rate and args.currency==company_currency):
 		args.conversion_rate = 1.0
 
+	if (not args.ignore_conversion_rate and args.conversion_rate == 1 and args.currency!=company_currency):
+		args.conversion_rate = get_exchange_rate(args.currency,
+			company_currency, args.transaction_date, "for_buying") or 1.0
+
 	# validate currency conversion rate
 	validate_conversion_rate(args.currency, args.conversion_rate,
 		meta.get_label("conversion_rate"), args.company)
diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py
index 4e9c768..c15d1ed 100644
--- a/erpnext/stock/stock_ledger.py
+++ b/erpnext/stock/stock_ledger.py
@@ -6,13 +6,14 @@
 import erpnext
 import copy
 from frappe import _
-from frappe.utils import cint, flt, cstr, now, get_link_to_form
+from frappe.utils import cint, flt, cstr, now, get_link_to_form, getdate
 from frappe.model.meta import get_field_precision
 from erpnext.stock.utils import get_valuation_method, get_incoming_outgoing_rate_for_cancel
 from erpnext.stock.utils import get_bin
 import json
 from six import iteritems
 
+
 # future reposting
 class NegativeStockError(frappe.ValidationError): pass
 class SerialNoExistsInFutureTransaction(frappe.ValidationError):
@@ -130,7 +131,13 @@
 	if not args and voucher_type and voucher_no:
 		args = get_args_for_voucher(voucher_type, voucher_no)
 
-	distinct_item_warehouses = [(d.item_code, d.warehouse) for d in args]
+	distinct_item_warehouses = {}
+	for i, d in enumerate(args):
+		distinct_item_warehouses.setdefault((d.item_code, d.warehouse), frappe._dict({
+			"reposting_status": False,
+			"sle": d,
+			"args_idx": i
+		}))
 
 	i = 0
 	while i < len(args):
@@ -139,13 +146,21 @@
 			"warehouse": args[i].warehouse,
 			"posting_date": args[i].posting_date,
 			"posting_time": args[i].posting_time,
-			"creation": args[i].get("creation")
+			"creation": args[i].get("creation"),
+			"distinct_item_warehouses": distinct_item_warehouses
 		}, allow_negative_stock=allow_negative_stock, via_landed_cost_voucher=via_landed_cost_voucher)
 
-		for item_wh, new_sle in iteritems(obj.new_items):
-			if item_wh not in distinct_item_warehouses:
-				args.append(new_sle)
+		distinct_item_warehouses[(args[i].item_code, args[i].warehouse)].reposting_status = True
 
+		if obj.new_items_found:
+			for item_wh, data in iteritems(distinct_item_warehouses):
+				if ('args_idx' not in data and not data.reposting_status) or (data.sle_changed and data.reposting_status):
+					data.args_idx = len(args)
+					args.append(data.sle)
+				elif data.sle_changed and not data.reposting_status:
+					args[data.args_idx] = data.sle
+				
+				data.sle_changed = False
 		i += 1
 
 def get_args_for_voucher(voucher_type, voucher_no):
@@ -186,11 +201,12 @@
 		self.company = frappe.get_cached_value("Warehouse", self.args.warehouse, "company")
 		self.get_precision()
 		self.valuation_method = get_valuation_method(self.item_code)
-		self.new_items = {}
+
+		self.new_items_found = False
+		self.distinct_item_warehouses = args.get("distinct_item_warehouses", frappe._dict())
 
 		self.data = frappe._dict()
 		self.initialize_previous_data(self.args)
-
 		self.build()
 
 	def get_precision(self):
@@ -296,11 +312,29 @@
 		elif dependant_sle.item_code == self.item_code and dependant_sle.warehouse == self.args.warehouse:
 			return entries_to_fix
 		elif dependant_sle.item_code != self.item_code:
-			if (dependant_sle.item_code, dependant_sle.warehouse) not in self.new_items:
-				self.new_items[(dependant_sle.item_code, dependant_sle.warehouse)] = dependant_sle
+			self.update_distinct_item_warehouses(dependant_sle)
 			return entries_to_fix
 		elif dependant_sle.item_code == self.item_code and dependant_sle.warehouse in self.data:
 			return entries_to_fix
+		else:
+			return self.append_future_sle_for_dependant(dependant_sle, entries_to_fix)
+
+	def update_distinct_item_warehouses(self, dependant_sle):
+		key = (dependant_sle.item_code, dependant_sle.warehouse)
+		val = frappe._dict({
+			"sle": dependant_sle
+		})
+		if key not in self.distinct_item_warehouses:
+			self.distinct_item_warehouses[key] = val
+			self.new_items_found = True
+		else:
+			existing_sle_posting_date = self.distinct_item_warehouses[key].get("sle", {}).get("posting_date")
+			if getdate(dependant_sle.posting_date) < getdate(existing_sle_posting_date):
+				val.sle_changed = True
+				self.distinct_item_warehouses[key] = val
+				self.new_items_found = True
+
+	def append_future_sle_for_dependant(self, dependant_sle, entries_to_fix):
 		self.initialize_previous_data(dependant_sle)
 
 		args = self.data[dependant_sle.warehouse].previous_sle \
@@ -393,6 +427,7 @@
 		rate = 0
 		# Material Transfer, Repack, Manufacturing
 		if sle.voucher_type == "Stock Entry":
+			self.recalculate_amounts_in_stock_entry(sle.voucher_no)
 			rate = frappe.db.get_value("Stock Entry Detail", sle.voucher_detail_no, "valuation_rate")
 		# Sales and Purchase Return
 		elif sle.voucher_type in ("Purchase Receipt", "Purchase Invoice", "Delivery Note", "Sales Invoice"):
@@ -442,7 +477,11 @@
 		frappe.db.set_value("Stock Entry Detail", sle.voucher_detail_no, "basic_rate", outgoing_rate)
 
 		# Update outgoing item's rate, recalculate FG Item's rate and total incoming/outgoing amount
-		stock_entry = frappe.get_doc("Stock Entry", sle.voucher_no, for_update=True)
+		if not sle.dependant_sle_voucher_detail_no:
+			self.recalculate_amounts_in_stock_entry(sle.voucher_no)
+
+	def recalculate_amounts_in_stock_entry(self, voucher_no):
+		stock_entry = frappe.get_doc("Stock Entry", voucher_no, for_update=True)
 		stock_entry.calculate_rate_and_amount(reset_outgoing_rate=False, raise_error_if_no_rate=False)
 		stock_entry.db_update()
 		for d in stock_entry.items:
diff --git a/erpnext/templates/generators/item_group.html b/erpnext/templates/generators/item_group.html
index 393c3a4..9050cc3 100644
--- a/erpnext/templates/generators/item_group.html
+++ b/erpnext/templates/generators/item_group.html
@@ -9,7 +9,7 @@
 {% endblock %}
 
 {% block page_content %}
-<div class="item-group-content" itemscope itemtype="http://schema.org/Product">
+<div class="item-group-content" itemscope itemtype="http://schema.org/Product" data-item-group="{{ name }}">
 	<div class="item-group-slideshow">
 		{% if slideshow %}<!-- slideshow -->
 			{{ web_block(
@@ -127,15 +127,36 @@
 			</script>
 		</div>
 	</div>
-	<div class="row">
-		<div class="col-12">
+	<div class="row mt-6">
+		<div class="col-3">
+		</div>
+		<div class="col-9">
 			{% if frappe.form_dict.start|int > 0 %}
-			<button class="btn btn-outline-secondary btn-prev" data-start="{{ frappe.form_dict.start|int - page_length }}">{{ _("Prev") }}</button>
+			<button class="btn btn-outline-secondary btn-prev" data-start="{{ frappe.form_dict.start|int - page_length }}">
+				{{ _("Prev") }}
+			</button>
 			{% endif %}
 			{% if items|length >= page_length %}
-			<button class="btn btn-outline-secondary btn-next" data-start="{{ frappe.form_dict.start|int + page_length }}">{{ _("Next") }}</button>
+			<button class="btn btn-outline-secondary btn-next" data-start="{{ frappe.form_dict.start|int + page_length }}"
+				style="float: right;">
+				{{ _("Next") }}
+			</button>
 			{% endif %}
 		</div>
 	</div>
 </div>
+
+<script>
+	frappe.ready(() => {
+		$('.btn-prev, .btn-next').click((e) => {
+			const $btn = $(e.target);
+			$btn.prop('disabled', true);
+			const start = $btn.data('start');
+			let query_params = frappe.utils.get_query_params();
+			query_params.start = start;
+			let path = window.location.pathname + '?' + frappe.utils.get_url_from_dict(query_params);
+			window.location.href = path;
+		});
+	});
+</script>
 {% endblock %}
\ No newline at end of file
diff --git a/erpnext/www/all-products/index.js b/erpnext/www/all-products/index.js
index 0721056..1c641b5 100644
--- a/erpnext/www/all-products/index.js
+++ b/erpnext/www/all-products/index.js
@@ -124,6 +124,10 @@
 				attribute_filters: if_key_exists(attribute_filters)
 			};
 
+			const item_group = $(".item-group-content").data('item-group');
+			if (item_group) {
+				Object.assign(field_filters, { item_group });
+			}
 			return new Promise((resolve, reject) => {
 				frappe.call('erpnext.portal.product_configurator.utils.get_products_html_for_website', args)
 					.then(r => {