Merge branch 'develop' into fix-consolidation-precision-error
diff --git a/CODEOWNERS b/CODEOWNERS
index b6aadb3..e406f8f 100644
--- a/CODEOWNERS
+++ b/CODEOWNERS
@@ -14,8 +14,8 @@
 erpnext/buying/                 @rohitwaghchaure @s-aga-r
 erpnext/maintenance/            @rohitwaghchaure @s-aga-r
 erpnext/manufacturing/          @rohitwaghchaure @s-aga-r
-erpnext/quality_management/     @marination @rohitwaghchaure @s-aga-r
-erpnext/stock/                  @marination @rohitwaghchaure @s-aga-r
+erpnext/quality_management/     @rohitwaghchaure @s-aga-r
+erpnext/stock/                  @rohitwaghchaure @s-aga-r
 
 erpnext/crm/                    @NagariaHussain
 erpnext/education/              @rutwikhdev
diff --git a/erpnext/accounts/doctype/payment_ledger_entry/payment_ledger_entry.json b/erpnext/accounts/doctype/payment_ledger_entry/payment_ledger_entry.json
index 4596b00..22842ce 100644
--- a/erpnext/accounts/doctype/payment_ledger_entry/payment_ledger_entry.json
+++ b/erpnext/accounts/doctype/payment_ledger_entry/payment_ledger_entry.json
@@ -22,7 +22,8 @@
   "amount",
   "account_currency",
   "amount_in_account_currency",
-  "delinked"
+  "delinked",
+  "remarks"
  ],
  "fields": [
   {
@@ -136,12 +137,17 @@
    "fieldtype": "Link",
    "label": "Finance Book",
    "options": "Finance Book"
+  },
+  {
+   "fieldname": "remarks",
+   "fieldtype": "Text",
+   "label": "Remarks"
   }
  ],
  "in_create": 1,
  "index_web_pages_for_search": 1,
  "links": [],
- "modified": "2022-07-11 09:13:54.379168",
+ "modified": "2022-08-22 15:32:56.629430",
  "modified_by": "Administrator",
  "module": "Accounts",
  "name": "Payment Ledger Entry",
diff --git a/erpnext/accounts/doctype/process_deferred_accounting/process_deferred_accounting.py b/erpnext/accounts/doctype/process_deferred_accounting/process_deferred_accounting.py
index 8ec726b..1f88849 100644
--- a/erpnext/accounts/doctype/process_deferred_accounting/process_deferred_accounting.py
+++ b/erpnext/accounts/doctype/process_deferred_accounting/process_deferred_accounting.py
@@ -34,4 +34,4 @@
 			filters={"against_voucher_type": self.doctype, "against_voucher": self.name},
 		)
 
-		make_gl_entries(gl_entries=gl_entries, cancel=1)
+		make_gl_entries(gl_map=gl_entries, cancel=1)
diff --git a/erpnext/accounts/doctype/process_deferred_accounting/test_process_deferred_accounting.py b/erpnext/accounts/doctype/process_deferred_accounting/test_process_deferred_accounting.py
index 164ba6a..5a0aeb7 100644
--- a/erpnext/accounts/doctype/process_deferred_accounting/test_process_deferred_accounting.py
+++ b/erpnext/accounts/doctype/process_deferred_accounting/test_process_deferred_accounting.py
@@ -57,3 +57,16 @@
 		]
 
 		check_gl_entries(self, si.name, expected_gle, "2019-01-10")
+
+	def test_pda_submission_and_cancellation(self):
+		pda = frappe.get_doc(
+			dict(
+				doctype="Process Deferred Accounting",
+				posting_date="2019-01-01",
+				start_date="2019-01-01",
+				end_date="2019-01-31",
+				type="Income",
+			)
+		)
+		pda.submit()
+		pda.cancel()
diff --git a/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json b/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json
index b417c7d..7cddf12 100644
--- a/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json
+++ b/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json
@@ -282,7 +282,6 @@
    "label": "Discount (%) on Price List Rate with Margin",
    "oldfieldname": "adj_rate",
    "oldfieldtype": "Float",
-   "precision": "2",
    "print_hide": 1
   },
   {
@@ -846,7 +845,7 @@
  "idx": 1,
  "istable": 1,
  "links": [],
- "modified": "2022-06-17 05:33:15.335912",
+ "modified": "2022-08-26 12:06:31.205417",
  "modified_by": "Administrator",
  "module": "Accounts",
  "name": "Sales Invoice Item",
diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.js b/erpnext/accounts/report/accounts_receivable/accounts_receivable.js
index 0238711..0b4e577 100644
--- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.js
+++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.js
@@ -179,6 +179,11 @@
 			"hidden": 1
 		},
 		{
+			"fieldname": "show_remarks",
+			"label": __("Show Remarks"),
+			"fieldtype": "Check",
+		},
+		{
 			"fieldname": "customer_name",
 			"label": __("Customer Name"),
 			"fieldtype": "Data",
diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py
index 63242e8..3f504b1 100755
--- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py
+++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py
@@ -119,6 +119,7 @@
 					party_account=ple.account,
 					posting_date=ple.posting_date,
 					account_currency=ple.account_currency,
+					remarks=ple.remarks,
 					invoiced=0.0,
 					paid=0.0,
 					credit_note=0.0,
@@ -697,6 +698,7 @@
 				ple.account_currency,
 				ple.amount,
 				ple.amount_in_account_currency,
+				ple.remarks,
 			)
 			.where(ple.delinked == 0)
 			.where(Criterion.all(self.qb_selection_filter))
@@ -731,6 +733,7 @@
 	def prepare_conditions(self):
 		self.qb_selection_filter = []
 		party_type_field = scrub(self.party_type)
+		self.qb_selection_filter.append(self.ple.party_type == self.party_type)
 
 		self.add_common_filters(party_type_field=party_type_field)
 
@@ -974,6 +977,9 @@
 				options="Supplier Group",
 			)
 
+		if self.filters.show_remarks:
+			self.add_column(label=_("Remarks"), fieldname="remarks", fieldtype="Text", width=200),
+
 	def add_column(self, label, fieldname=None, fieldtype="Currency", options=None, width=120):
 		if not fieldname:
 			fieldname = scrub(label)
diff --git a/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py b/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py
index 98dbbf6..330e442 100644
--- a/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py
+++ b/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py
@@ -535,7 +535,11 @@
 		):
 			if account.account_name not in added_accounts:
 				accounts.append(account)
-				added_accounts.append(account.account_name)
+				if account.account_number:
+					account_key = account.account_number + "-" + account.account_name
+				else:
+					account_key = account.account_name
+				added_accounts.append(account_key)
 
 	return accounts
 
diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py
index 018e8f9..f61e8ac 100644
--- a/erpnext/accounts/utils.py
+++ b/erpnext/accounts/utils.py
@@ -1424,6 +1424,7 @@
 						"amount": dr_or_cr,
 						"amount_in_account_currency": dr_or_cr_account_currency,
 						"delinked": True if cancel else False,
+						"remarks": gle.remarks,
 					}
 				)
 
diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py
index 986b700..132840e 100644
--- a/erpnext/assets/doctype/asset/test_asset.py
+++ b/erpnext/assets/doctype/asset/test_asset.py
@@ -1454,12 +1454,14 @@
 	return item
 
 
-def set_depreciation_settings_in_company():
-	company = frappe.get_doc("Company", "_Test Company")
-	company.accumulated_depreciation_account = "_Test Accumulated Depreciations - _TC"
-	company.depreciation_expense_account = "_Test Depreciations - _TC"
-	company.disposal_account = "_Test Gain/Loss on Asset Disposal - _TC"
-	company.depreciation_cost_center = "_Test Cost Center - _TC"
+def set_depreciation_settings_in_company(company=None):
+	if not company:
+		company = "_Test Company"
+	company = frappe.get_doc("Company", company)
+	company.accumulated_depreciation_account = "_Test Accumulated Depreciations - " + company.abbr
+	company.depreciation_expense_account = "_Test Depreciations - " + company.abbr
+	company.disposal_account = "_Test Gain/Loss on Asset Disposal - " + company.abbr
+	company.depreciation_cost_center = "Main - " + company.abbr
 	company.save()
 
 	# Enable booking asset depreciation entry automatically
diff --git a/erpnext/assets/doctype/asset_repair/asset_repair.js b/erpnext/assets/doctype/asset_repair/asset_repair.js
index f5e4e72..f9ed2cc 100644
--- a/erpnext/assets/doctype/asset_repair/asset_repair.js
+++ b/erpnext/assets/doctype/asset_repair/asset_repair.js
@@ -76,7 +76,7 @@
 			'warehouse': frm.doc.warehouse,
 			'qty': item.consumed_quantity,
 			'serial_no': item.serial_no,
-			'company': frm.doc.company
+			'company': frm.doc.company,
 		};
 
 		frappe.call({
diff --git a/erpnext/assets/doctype/asset_repair/asset_repair.json b/erpnext/assets/doctype/asset_repair/asset_repair.json
index ba31898..accb5bf 100644
--- a/erpnext/assets/doctype/asset_repair/asset_repair.json
+++ b/erpnext/assets/doctype/asset_repair/asset_repair.json
@@ -238,7 +238,6 @@
    "no_copy": 1
   },
   {
-   "depends_on": "eval:!doc.__islocal",
    "fieldname": "purchase_invoice",
    "fieldtype": "Link",
    "label": "Purchase Invoice",
@@ -257,6 +256,7 @@
    "fieldname": "stock_entry",
    "fieldtype": "Link",
    "label": "Stock Entry",
+   "no_copy": 1,
    "options": "Stock Entry",
    "read_only": 1
   }
@@ -264,10 +264,11 @@
  "index_web_pages_for_search": 1,
  "is_submittable": 1,
  "links": [],
- "modified": "2021-06-25 13:14:38.307723",
+ "modified": "2022-08-16 15:55:25.023471",
  "modified_by": "Administrator",
  "module": "Assets",
  "name": "Asset Repair",
+ "naming_rule": "By \"Naming Series\" field",
  "owner": "Administrator",
  "permissions": [
   {
@@ -303,6 +304,7 @@
  ],
  "sort_field": "modified",
  "sort_order": "DESC",
+ "states": [],
  "title_field": "asset_name",
  "track_changes": 1,
  "track_seen": 1
diff --git a/erpnext/assets/doctype/asset_repair/asset_repair.py b/erpnext/assets/doctype/asset_repair/asset_repair.py
index 5bf6011..8758e9c 100644
--- a/erpnext/assets/doctype/asset_repair/asset_repair.py
+++ b/erpnext/assets/doctype/asset_repair/asset_repair.py
@@ -1,11 +1,11 @@
 # Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
 # For license information, please see license.txt
 
-
 import frappe
 from frappe import _
 from frappe.utils import add_months, cint, flt, getdate, time_diff_in_hours
 
+import erpnext
 from erpnext.accounts.general_ledger import make_gl_entries
 from erpnext.assets.doctype.asset.asset import get_asset_account
 from erpnext.controllers.accounts_controller import AccountsController
@@ -17,7 +17,7 @@
 		self.update_status()
 
 		if self.get("stock_items"):
-			self.set_total_value()
+			self.set_stock_items_cost()
 		self.calculate_total_repair_cost()
 
 	def update_status(self):
@@ -26,7 +26,7 @@
 		else:
 			self.asset_doc.set_status()
 
-	def set_total_value(self):
+	def set_stock_items_cost(self):
 		for item in self.get("stock_items"):
 			item.total_value = flt(item.valuation_rate) * flt(item.consumed_quantity)
 
@@ -66,6 +66,7 @@
 		if self.get("capitalize_repair_cost"):
 			self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry")
 			self.make_gl_entries(cancel=True)
+			self.db_set("stock_entry", None)
 			if (
 				frappe.db.get_value("Asset", self.asset, "calculate_depreciation")
 				and self.increase_in_asset_life
@@ -133,6 +134,7 @@
 					"qty": stock_item.consumed_quantity,
 					"basic_rate": stock_item.valuation_rate,
 					"serial_no": stock_item.serial_no,
+					"cost_center": self.cost_center,
 				},
 			)
 
@@ -142,72 +144,42 @@
 		self.db_set("stock_entry", stock_entry.name)
 
 	def increase_stock_quantity(self):
-		stock_entry = frappe.get_doc("Stock Entry", self.stock_entry)
-		stock_entry.flags.ignore_links = True
-		stock_entry.cancel()
+		if self.stock_entry:
+			stock_entry = frappe.get_doc("Stock Entry", self.stock_entry)
+			stock_entry.flags.ignore_links = True
+			stock_entry.cancel()
 
 	def make_gl_entries(self, cancel=False):
-		if flt(self.repair_cost) > 0:
+		if flt(self.total_repair_cost) > 0:
 			gl_entries = self.get_gl_entries()
 			make_gl_entries(gl_entries, cancel)
 
 	def get_gl_entries(self):
 		gl_entries = []
-		repair_and_maintenance_account = frappe.db.get_value(
-			"Company", self.company, "repair_and_maintenance_account"
-		)
+
 		fixed_asset_account = get_asset_account(
 			"fixed_asset_account", asset=self.asset, company=self.company
 		)
-		expense_account = (
+		self.get_gl_entries_for_repair_cost(gl_entries, fixed_asset_account)
+		self.get_gl_entries_for_consumed_items(gl_entries, fixed_asset_account)
+
+		return gl_entries
+
+	def get_gl_entries_for_repair_cost(self, gl_entries, fixed_asset_account):
+		if flt(self.repair_cost) <= 0:
+			return
+
+		pi_expense_account = (
 			frappe.get_doc("Purchase Invoice", self.purchase_invoice).items[0].expense_account
 		)
 
 		gl_entries.append(
 			self.get_gl_dict(
 				{
-					"account": expense_account,
-					"credit": self.repair_cost,
-					"credit_in_account_currency": self.repair_cost,
-					"against": repair_and_maintenance_account,
-					"voucher_type": self.doctype,
-					"voucher_no": self.name,
-					"cost_center": self.cost_center,
-					"posting_date": getdate(),
-					"company": self.company,
-				},
-				item=self,
-			)
-		)
-
-		if self.get("stock_consumption"):
-			# creating GL Entries for each row in Stock Items based on the Stock Entry created for it
-			stock_entry = frappe.get_doc("Stock Entry", self.stock_entry)
-			for item in stock_entry.items:
-				gl_entries.append(
-					self.get_gl_dict(
-						{
-							"account": item.expense_account,
-							"credit": item.amount,
-							"credit_in_account_currency": item.amount,
-							"against": repair_and_maintenance_account,
-							"voucher_type": self.doctype,
-							"voucher_no": self.name,
-							"cost_center": self.cost_center,
-							"posting_date": getdate(),
-							"company": self.company,
-						},
-						item=self,
-					)
-				)
-
-		gl_entries.append(
-			self.get_gl_dict(
-				{
 					"account": fixed_asset_account,
-					"debit": self.total_repair_cost,
-					"debit_in_account_currency": self.total_repair_cost,
-					"against": expense_account,
+					"debit": self.repair_cost,
+					"debit_in_account_currency": self.repair_cost,
+					"against": pi_expense_account,
 					"voucher_type": self.doctype,
 					"voucher_no": self.name,
 					"cost_center": self.cost_center,
@@ -220,7 +192,75 @@
 			)
 		)
 
-		return gl_entries
+		gl_entries.append(
+			self.get_gl_dict(
+				{
+					"account": pi_expense_account,
+					"credit": self.repair_cost,
+					"credit_in_account_currency": self.repair_cost,
+					"against": fixed_asset_account,
+					"voucher_type": self.doctype,
+					"voucher_no": self.name,
+					"cost_center": self.cost_center,
+					"posting_date": getdate(),
+					"company": self.company,
+				},
+				item=self,
+			)
+		)
+
+	def get_gl_entries_for_consumed_items(self, gl_entries, fixed_asset_account):
+		if not (self.get("stock_consumption") and self.get("stock_items")):
+			return
+
+		# creating GL Entries for each row in Stock Items based on the Stock Entry created for it
+		stock_entry = frappe.get_doc("Stock Entry", self.stock_entry)
+
+		default_expense_account = None
+		if not erpnext.is_perpetual_inventory_enabled(self.company):
+			default_expense_account = frappe.get_cached_value(
+				"Company", self.company, "default_expense_account"
+			)
+			if not default_expense_account:
+				frappe.throw(_("Please set default Expense Account in Company {0}").format(self.company))
+
+		for item in stock_entry.items:
+			if flt(item.amount) > 0:
+				gl_entries.append(
+					self.get_gl_dict(
+						{
+							"account": item.expense_account or default_expense_account,
+							"credit": item.amount,
+							"credit_in_account_currency": item.amount,
+							"against": fixed_asset_account,
+							"voucher_type": self.doctype,
+							"voucher_no": self.name,
+							"cost_center": self.cost_center,
+							"posting_date": getdate(),
+							"company": self.company,
+						},
+						item=self,
+					)
+				)
+
+				gl_entries.append(
+					self.get_gl_dict(
+						{
+							"account": fixed_asset_account,
+							"debit": item.amount,
+							"debit_in_account_currency": item.amount,
+							"against": item.expense_account or default_expense_account,
+							"voucher_type": self.doctype,
+							"voucher_no": self.name,
+							"cost_center": self.cost_center,
+							"posting_date": getdate(),
+							"against_voucher_type": "Stock Entry",
+							"against_voucher": self.stock_entry,
+							"company": self.company,
+						},
+						item=self,
+					)
+				)
 
 	def modify_depreciation_schedule(self):
 		for row in self.asset_doc.finance_books:
diff --git a/erpnext/assets/doctype/asset_repair/test_asset_repair.py b/erpnext/assets/doctype/asset_repair/test_asset_repair.py
index 4e7cf78..6e06f52 100644
--- a/erpnext/assets/doctype/asset_repair/test_asset_repair.py
+++ b/erpnext/assets/doctype/asset_repair/test_asset_repair.py
@@ -6,6 +6,7 @@
 import frappe
 from frappe.utils import flt, nowdate
 
+from erpnext.assets.doctype.asset.asset import get_asset_account
 from erpnext.assets.doctype.asset.test_asset import (
 	create_asset,
 	create_asset_data,
@@ -125,10 +126,109 @@
 		asset_repair = create_asset_repair(capitalize_repair_cost=1, submit=1)
 		self.assertTrue(asset_repair.purchase_invoice)
 
-	def test_gl_entries(self):
-		asset_repair = create_asset_repair(capitalize_repair_cost=1, submit=1)
-		gl_entry = frappe.get_last_doc("GL Entry")
-		self.assertEqual(asset_repair.name, gl_entry.voucher_no)
+	def test_gl_entries_with_perpetual_inventory(self):
+		set_depreciation_settings_in_company(company="_Test Company with perpetual inventory")
+
+		asset_category = frappe.get_doc("Asset Category", "Computers")
+		asset_category.append(
+			"accounts",
+			{
+				"company_name": "_Test Company with perpetual inventory",
+				"fixed_asset_account": "_Test Fixed Asset - TCP1",
+				"accumulated_depreciation_account": "_Test Accumulated Depreciations - TCP1",
+				"depreciation_expense_account": "_Test Depreciations - TCP1",
+			},
+		)
+		asset_category.save()
+
+		asset_repair = create_asset_repair(
+			capitalize_repair_cost=1,
+			stock_consumption=1,
+			warehouse="Stores - TCP1",
+			company="_Test Company with perpetual inventory",
+			submit=1,
+		)
+
+		gl_entries = frappe.db.sql(
+			"""
+			select
+				account,
+				sum(debit) as debit,
+				sum(credit) as credit
+			from `tabGL Entry`
+			where
+				voucher_type='Asset Repair'
+				and voucher_no=%s
+			group by
+				account
+		""",
+			asset_repair.name,
+			as_dict=1,
+		)
+
+		self.assertTrue(gl_entries)
+
+		fixed_asset_account = get_asset_account(
+			"fixed_asset_account", asset=asset_repair.asset, company=asset_repair.company
+		)
+		pi_expense_account = (
+			frappe.get_doc("Purchase Invoice", asset_repair.purchase_invoice).items[0].expense_account
+		)
+		stock_entry_expense_account = (
+			frappe.get_doc("Stock Entry", asset_repair.stock_entry).get("items")[0].expense_account
+		)
+
+		expected_values = {
+			fixed_asset_account: [asset_repair.total_repair_cost, 0],
+			pi_expense_account: [0, asset_repair.repair_cost],
+			stock_entry_expense_account: [0, 100],
+		}
+
+		for d in gl_entries:
+			self.assertEqual(expected_values[d.account][0], d.debit)
+			self.assertEqual(expected_values[d.account][1], d.credit)
+
+	def test_gl_entries_with_periodical_inventory(self):
+		frappe.db.set_value(
+			"Company", "_Test Company", "default_expense_account", "Cost of Goods Sold - _TC"
+		)
+		asset_repair = create_asset_repair(
+			capitalize_repair_cost=1,
+			stock_consumption=1,
+			submit=1,
+		)
+
+		gl_entries = frappe.db.sql(
+			"""
+			select
+				account,
+				sum(debit) as debit,
+				sum(credit) as credit
+			from `tabGL Entry`
+			where
+				voucher_type='Asset Repair'
+				and voucher_no=%s
+			group by
+				account
+		""",
+			asset_repair.name,
+			as_dict=1,
+		)
+
+		self.assertTrue(gl_entries)
+
+		fixed_asset_account = get_asset_account(
+			"fixed_asset_account", asset=asset_repair.asset, company=asset_repair.company
+		)
+		default_expense_account = frappe.get_cached_value(
+			"Company", asset_repair.company, "default_expense_account"
+		)
+
+		expected_values = {fixed_asset_account: [1100, 0], default_expense_account: [0, 1100]}
+
+		for d in gl_entries:
+			self.assertEqual(expected_values[d.account][0], d.debit)
+			self.assertEqual(expected_values[d.account][1], d.credit)
 
 	def test_increase_in_asset_life(self):
 		asset = create_asset(calculate_depreciation=1, submit=1)
@@ -160,7 +260,7 @@
 	if args.asset:
 		asset = args.asset
 	else:
-		asset = create_asset(is_existing_asset=1, submit=1)
+		asset = create_asset(is_existing_asset=1, submit=1, company=args.company)
 	asset_repair = frappe.new_doc("Asset Repair")
 	asset_repair.update(
 		{
@@ -192,7 +292,7 @@
 
 	if args.submit:
 		asset_repair.repair_status = "Completed"
-		asset_repair.cost_center = "_Test Cost Center - _TC"
+		asset_repair.cost_center = frappe.db.get_value("Company", asset.company, "cost_center")
 
 		if args.stock_consumption:
 			stock_entry = frappe.get_doc(
@@ -204,6 +304,8 @@
 					"t_warehouse": asset_repair.warehouse,
 					"item_code": asset_repair.stock_items[0].item_code,
 					"qty": asset_repair.stock_items[0].consumed_quantity,
+					"basic_rate": args.rate if args.get("rate") is not None else 100,
+					"cost_center": asset_repair.cost_center,
 				},
 			)
 			stock_entry.submit()
@@ -213,7 +315,13 @@
 			asset_repair.repair_cost = 1000
 			if asset.calculate_depreciation:
 				asset_repair.increase_in_asset_life = 12
-			asset_repair.purchase_invoice = make_purchase_invoice().name
+			pi = make_purchase_invoice(
+				company=asset.company,
+				expense_account=frappe.db.get_value("Company", asset.company, "default_expense_account"),
+				cost_center=asset_repair.cost_center,
+				warehouse=asset_repair.warehouse,
+			)
+			asset_repair.purchase_invoice = pi.name
 
 		asset_repair.submit()
 	return asset_repair
diff --git a/erpnext/buying/doctype/buying_settings/buying_settings.json b/erpnext/buying/doctype/buying_settings/buying_settings.json
index 6c18a46..aad2607 100644
--- a/erpnext/buying/doctype/buying_settings/buying_settings.json
+++ b/erpnext/buying/doctype/buying_settings/buying_settings.json
@@ -76,7 +76,7 @@
    "label": "Subcontracting Settings"
   },
   {
-   "default": "Material Transferred for Subcontract",
+   "default": "BOM",
    "fieldname": "backflush_raw_materials_of_subcontract_based_on",
    "fieldtype": "Select",
    "label": "Backflush Raw Materials of Subcontract Based On",
@@ -148,7 +148,7 @@
  "index_web_pages_for_search": 1,
  "issingle": 1,
  "links": [],
- "modified": "2022-05-31 19:40:26.103909",
+ "modified": "2022-09-01 18:01:34.994657",
  "modified_by": "Administrator",
  "module": "Buying",
  "name": "Buying Settings",
diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py
index b73b8ad..cbcccce 100644
--- a/erpnext/controllers/taxes_and_totals.py
+++ b/erpnext/controllers/taxes_and_totals.py
@@ -41,6 +41,7 @@
 		if self.doc.apply_discount_on == "Grand Total" and self.doc.get("is_cash_or_non_trade_discount"):
 			self.doc.grand_total -= self.doc.discount_amount
 			self.doc.base_grand_total -= self.doc.base_discount_amount
+			self.set_rounded_total()
 
 		self.calculate_shipping_charges()
 
diff --git a/erpnext/e_commerce/product_ui/search.js b/erpnext/e_commerce/product_ui/search.js
index 6192245..1688cc1 100644
--- a/erpnext/e_commerce/product_ui/search.js
+++ b/erpnext/e_commerce/product_ui/search.js
@@ -200,7 +200,7 @@
 			let thumbnail = res.thumbnail || '/assets/erpnext/images/ui-states/cart-empty-state.png';
 			html += `
 				<div class="dropdown-item" style="display: flex;">
-					<img class="item-thumb col-2" src=${thumbnail} />
+					<img class="item-thumb col-2" src=${encodeURI(thumbnail)} />
 					<div class="col-9" style="white-space: normal;">
 						<a href="/${res.route}">${res.web_item_name}</a><br>
 						<span class="brand-line">${res.brand ? "by " + res.brand : ""}</span>
@@ -241,4 +241,4 @@
 
 		this.category_container.html(html);
 	}
-};
\ No newline at end of file
+};
diff --git a/erpnext/e_commerce/redisearch_utils.py b/erpnext/e_commerce/redisearch_utils.py
index 1f649c7..87ca9bd 100644
--- a/erpnext/e_commerce/redisearch_utils.py
+++ b/erpnext/e_commerce/redisearch_utils.py
@@ -7,7 +7,9 @@
 from frappe import _
 from frappe.utils.redis_wrapper import RedisWrapper
 from redis import ResponseError
-from redisearch import AutoCompleter, Client, IndexDefinition, Suggestion, TagField, TextField
+from redis.commands.search.field import TagField, TextField
+from redis.commands.search.indexDefinition import IndexDefinition
+from redis.commands.search.suggestion import Suggestion
 
 WEBSITE_ITEM_INDEX = "website_items_index"
 WEBSITE_ITEM_KEY_PREFIX = "website_item:"
@@ -35,12 +37,9 @@
 def is_search_module_loaded():
 	try:
 		cache = frappe.cache()
-		out = cache.execute_command("MODULE LIST")
-
-		parsed_output = " ".join(
-			(" ".join([frappe.as_unicode(s) for s in o if not isinstance(s, int)]) for o in out)
-		)
-		return "search" in parsed_output
+		for module in cache.module_list():
+			if module.get(b"name") == b"search":
+				return True
 	except Exception:
 		return False  # handling older redis versions
 
@@ -58,18 +57,18 @@
 
 
 def make_key(key):
-	return "{0}|{1}".format(frappe.conf.db_name, key).encode("utf-8")
+	return frappe.cache().make_key(key)
 
 
 @if_redisearch_enabled
 def create_website_items_index():
 	"Creates Index Definition."
 
-	# CREATE index
-	client = Client(make_key(WEBSITE_ITEM_INDEX), conn=frappe.cache())
+	redis = frappe.cache()
+	index = redis.ft(WEBSITE_ITEM_INDEX)
 
 	try:
-		client.drop_index()  # drop if already exists
+		index.dropindex()  # drop if already exists
 	except ResponseError:
 		# will most likely raise a ResponseError if index does not exist
 		# ignore and create index
@@ -86,9 +85,10 @@
 	if "web_item_name" in idx_fields:
 		idx_fields.remove("web_item_name")
 
-	idx_fields = list(map(to_search_field, idx_fields))
+	idx_fields = [to_search_field(f) for f in idx_fields]
 
-	client.create_index(
+	# TODO: sortable?
+	index.create_index(
 		[TextField("web_item_name", sortable=True)] + idx_fields,
 		definition=idx_def,
 	)
@@ -119,8 +119,8 @@
 
 @if_redisearch_enabled
 def insert_to_name_ac(web_name, doc_name):
-	ac = AutoCompleter(make_key(WEBSITE_ITEM_NAME_AUTOCOMPLETE), conn=frappe.cache())
-	ac.add_suggestions(Suggestion(web_name, payload=doc_name))
+	ac = frappe.cache().ft()
+	ac.sugadd(WEBSITE_ITEM_NAME_AUTOCOMPLETE, Suggestion(web_name, payload=doc_name))
 
 
 def create_web_item_map(website_item_doc):
@@ -157,9 +157,8 @@
 @if_redisearch_enabled
 def delete_from_ac_dict(website_item_doc):
 	"""Removes this items's name from autocomplete dictionary"""
-	cache = frappe.cache()
-	name_ac = AutoCompleter(make_key(WEBSITE_ITEM_NAME_AUTOCOMPLETE), conn=cache)
-	name_ac.delete(website_item_doc.web_item_name)
+	ac = frappe.cache().ft()
+	ac.sugdel(website_item_doc.web_item_name)
 
 
 @if_redisearch_enabled
@@ -170,8 +169,6 @@
 	"""
 
 	cache = frappe.cache()
-	item_ac = AutoCompleter(make_key(WEBSITE_ITEM_NAME_AUTOCOMPLETE), conn=cache)
-	item_group_ac = AutoCompleter(make_key(WEBSITE_ITEM_CATEGORY_AUTOCOMPLETE), conn=cache)
 
 	# Delete both autocomplete dicts
 	try:
@@ -180,38 +177,43 @@
 	except Exception:
 		raise_redisearch_error()
 
-	create_items_autocomplete_dict(autocompleter=item_ac)
-	create_item_groups_autocomplete_dict(autocompleter=item_group_ac)
+	create_items_autocomplete_dict()
+	create_item_groups_autocomplete_dict()
 
 
 @if_redisearch_enabled
-def create_items_autocomplete_dict(autocompleter):
+def create_items_autocomplete_dict():
 	"Add items as suggestions in Autocompleter."
+
+	ac = frappe.cache().ft()
 	items = frappe.get_all(
 		"Website Item", fields=["web_item_name", "item_group"], filters={"published": 1}
 	)
-
 	for item in items:
-		autocompleter.add_suggestions(Suggestion(item.web_item_name))
+		ac.sugadd(WEBSITE_ITEM_NAME_AUTOCOMPLETE, Suggestion(item.web_item_name))
 
 
 @if_redisearch_enabled
-def create_item_groups_autocomplete_dict(autocompleter):
+def create_item_groups_autocomplete_dict():
 	"Add item groups with weightage as suggestions in Autocompleter."
+
 	published_item_groups = frappe.get_all(
 		"Item Group", fields=["name", "route", "weightage"], filters={"show_in_website": 1}
 	)
 	if not published_item_groups:
 		return
 
+	ac = frappe.cache().ft()
+
 	for item_group in published_item_groups:
 		payload = json.dumps({"name": item_group.name, "route": item_group.route})
-		autocompleter.add_suggestions(
+		ac.sugadd(
+			WEBSITE_ITEM_CATEGORY_AUTOCOMPLETE,
 			Suggestion(
 				string=item_group.name,
 				score=frappe.utils.flt(item_group.weightage) or 1.0,
 				payload=payload,  # additional info that can be retrieved later
-			)
+			),
 		)
 
 
diff --git a/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.py b/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.py
index 6d62aef..cac3f1f 100644
--- a/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.py
+++ b/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.py
@@ -236,7 +236,7 @@
 			AND l.is_term_loan =1
 			AND rs.payment_date <= %s
 			AND rs.is_accrued=0 {0}
-			AND rs.interest_amount > 0
+			AND rs.principal_amount > 0
 			AND l.status = 'Disbursed'
 			ORDER BY rs.payment_date""".format(
 			condition
diff --git a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py
index 29da988..018832c 100644
--- a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py
+++ b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py
@@ -735,6 +735,7 @@
 	)
 	amounts["pending_accrual_entries"] = pending_accrual_entries
 	amounts["unaccrued_interest"] = flt(unaccrued_interest, precision)
+	amounts["written_off_amount"] = flt(against_loan_doc.written_off_amount, precision)
 
 	if final_due_date:
 		amounts["due_date"] = final_due_date
diff --git a/erpnext/loan_management/doctype/process_loan_interest_accrual/process_loan_interest_accrual.py b/erpnext/loan_management/doctype/process_loan_interest_accrual/process_loan_interest_accrual.py
index 81464a3..25c72d9 100644
--- a/erpnext/loan_management/doctype/process_loan_interest_accrual/process_loan_interest_accrual.py
+++ b/erpnext/loan_management/doctype/process_loan_interest_accrual/process_loan_interest_accrual.py
@@ -57,7 +57,7 @@
 
 def process_loan_interest_accrual_for_term_loans(posting_date=None, loan_type=None, loan=None):
 
-	if not term_loan_accrual_pending(posting_date or nowdate()):
+	if not term_loan_accrual_pending(posting_date or nowdate(), loan=loan):
 		return
 
 	loan_process = frappe.new_doc("Process Loan Interest Accrual")
@@ -71,9 +71,12 @@
 	return loan_process.name
 
 
-def term_loan_accrual_pending(date):
-	pending_accrual = frappe.db.get_value(
-		"Repayment Schedule", {"payment_date": ("<=", date), "is_accrued": 0}
-	)
+def term_loan_accrual_pending(date, loan=None):
+	filters = {"payment_date": ("<=", date), "is_accrued": 0}
+
+	if loan:
+		filters.update({"parent": loan})
+
+	pending_accrual = frappe.db.get_value("Repayment Schedule", filters)
 
 	return pending_accrual
diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py
index 2cdf8d3..66d458b 100644
--- a/erpnext/manufacturing/doctype/production_plan/production_plan.py
+++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py
@@ -656,6 +656,8 @@
 			row.idx = idx + 1
 			self.append("sub_assembly_items", row)
 
+		self.set_default_supplier_for_subcontracting_order()
+
 	def set_sub_assembly_items_based_on_level(self, row, bom_data, manufacturing_type=None):
 		"Modify bom_data, set additional details."
 		for data in bom_data:
@@ -667,6 +669,32 @@
 				"Subcontract" if data.is_sub_contracted_item else "In House"
 			)
 
+	def set_default_supplier_for_subcontracting_order(self):
+		items = [
+			d.production_item for d in self.sub_assembly_items if d.type_of_manufacturing == "Subcontract"
+		]
+
+		if not items:
+			return
+
+		default_supplier = frappe._dict(
+			frappe.get_all(
+				"Item Default",
+				fields=["parent", "default_supplier"],
+				filters={"parent": ("in", items), "default_supplier": ("is", "set")},
+				as_list=1,
+			)
+		)
+
+		if not default_supplier:
+			return
+
+		for row in self.sub_assembly_items:
+			if row.type_of_manufacturing != "Subcontract":
+				continue
+
+			row.supplier = default_supplier.get(row.production_item)
+
 	def combine_subassembly_items(self, sub_assembly_items_store):
 		"Aggregate if same: Item, Warehouse, Inhouse/Outhouse Manu.g, BOM No."
 		key_wise_data = {}
diff --git a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py
index e2415ad..1d2d1bd 100644
--- a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py
+++ b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py
@@ -281,6 +281,31 @@
 		pln.reload()
 		pln.cancel()
 
+	def test_production_plan_subassembly_default_supplier(self):
+		from erpnext.manufacturing.doctype.bom.test_bom import create_nested_bom
+
+		bom_tree_1 = {"Test Laptop": {"Test Motherboard": {"Test Motherboard Wires": {}}}}
+		bom = create_nested_bom(bom_tree_1, prefix="")
+
+		item_doc = frappe.get_doc("Item", "Test Motherboard")
+		company = "_Test Company"
+
+		item_doc.is_sub_contracted_item = 1
+		for row in item_doc.item_defaults:
+			if row.company == company and not row.default_supplier:
+				row.default_supplier = "_Test Supplier"
+
+		if not item_doc.item_defaults:
+			item_doc.append("item_defaults", {"company": company, "default_supplier": "_Test Supplier"})
+
+		item_doc.save()
+
+		plan = create_production_plan(item_code="Test Laptop", use_multi_level_bom=1, do_not_submit=True)
+		plan.get_sub_assembly_items()
+		plan.set_default_supplier_for_subcontracting_order()
+
+		self.assertEqual(plan.sub_assembly_items[0].supplier, "_Test Supplier")
+
 	def test_production_plan_combine_subassembly(self):
 		"""
 		Test combining Sub assembly items belonging to the same BOM in Prod Plan.
diff --git a/erpnext/manufacturing/doctype/work_order/work_order_dashboard.py b/erpnext/manufacturing/doctype/work_order/work_order_dashboard.py
index 465460f..d0dcc55 100644
--- a/erpnext/manufacturing/doctype/work_order/work_order_dashboard.py
+++ b/erpnext/manufacturing/doctype/work_order/work_order_dashboard.py
@@ -7,6 +7,6 @@
 		"non_standard_fieldnames": {"Batch": "reference_name"},
 		"transactions": [
 			{"label": _("Transactions"), "items": ["Stock Entry", "Job Card", "Pick List"]},
-			{"label": _("Reference"), "items": ["Serial No", "Batch"]},
+			{"label": _("Reference"), "items": ["Serial No", "Batch", "Material Request"]},
 		],
 	}
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index d92353a..4729add 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -311,4 +311,5 @@
 erpnext.patches.v13_0.fix_number_and_frequency_for_monthly_depreciation
 erpnext.patches.v14_0.remove_hr_and_payroll_modules # 20-07-2022
 erpnext.patches.v14_0.fix_crm_no_of_employees
-erpnext.patches.v14_0.create_accounting_dimensions_in_subcontracting_doctypes
\ No newline at end of file
+erpnext.patches.v14_0.create_accounting_dimensions_in_subcontracting_doctypes
+erpnext.patches.v14_0.migrate_remarks_from_gl_to_payment_ledger
diff --git a/erpnext/patches/v13_0/add_doctype_to_sla.py b/erpnext/patches/v13_0/add_doctype_to_sla.py
index 5f5974f..2d3b0de 100644
--- a/erpnext/patches/v13_0/add_doctype_to_sla.py
+++ b/erpnext/patches/v13_0/add_doctype_to_sla.py
@@ -14,7 +14,8 @@
 
 	for sla in frappe.get_all("Service Level Agreement"):
 		agreement = frappe.get_doc("Service Level Agreement", sla.name)
-		agreement.document_type = "Issue"
+		agreement.db_set("document_type", "Issue")
+		agreement.reload()
 		agreement.apply_sla_for_resolution = 1
 		agreement.append("sla_fulfilled_on", {"status": "Resolved"})
 		agreement.append("sla_fulfilled_on", {"status": "Closed"})
diff --git a/erpnext/patches/v13_0/delete_old_sales_reports.py b/erpnext/patches/v13_0/delete_old_sales_reports.py
index b31c9d1..1b53da7 100644
--- a/erpnext/patches/v13_0/delete_old_sales_reports.py
+++ b/erpnext/patches/v13_0/delete_old_sales_reports.py
@@ -16,18 +16,18 @@
 			delete_auto_email_reports(report)
 			check_and_delete_linked_reports(report)
 
-			frappe.delete_doc("Report", report)
+			frappe.delete_doc("Report", report, force=True)
 
 
 def delete_auto_email_reports(report):
 	"""Check for one or multiple Auto Email Reports and delete"""
 	auto_email_reports = frappe.db.get_values("Auto Email Report", {"report": report}, ["name"])
 	for auto_email_report in auto_email_reports:
-		frappe.delete_doc("Auto Email Report", auto_email_report[0])
+		frappe.delete_doc("Auto Email Report", auto_email_report[0], force=True)
 
 
 def delete_links_from_desktop_icons(report):
 	"""Check for one or multiple Desktop Icons and delete"""
 	desktop_icons = frappe.db.get_values("Desktop Icon", {"_report": report}, ["name"])
 	for desktop_icon in desktop_icons:
-		frappe.delete_doc("Desktop Icon", desktop_icon[0])
+		frappe.delete_doc("Desktop Icon", desktop_icon[0], force=True)
diff --git a/erpnext/patches/v14_0/migrate_remarks_from_gl_to_payment_ledger.py b/erpnext/patches/v14_0/migrate_remarks_from_gl_to_payment_ledger.py
new file mode 100644
index 0000000..062d24b
--- /dev/null
+++ b/erpnext/patches/v14_0/migrate_remarks_from_gl_to_payment_ledger.py
@@ -0,0 +1,56 @@
+import frappe
+from frappe import qb
+from frappe.utils import create_batch
+
+
+def execute():
+	if frappe.reload_doc("accounts", "doctype", "payment_ledger_entry"):
+
+		gle = qb.DocType("GL Entry")
+		ple = qb.DocType("Payment Ledger Entry")
+
+		# get ple and their remarks from GL Entry
+		pl_entries = (
+			qb.from_(ple)
+			.left_join(gle)
+			.on(
+				(ple.account == gle.account)
+				& (ple.party_type == gle.party_type)
+				& (ple.party == gle.party)
+				& (ple.voucher_type == gle.voucher_type)
+				& (ple.voucher_no == gle.voucher_no)
+				& (ple.company == gle.company)
+			)
+			.select(
+				ple.company,
+				ple.account,
+				ple.party_type,
+				ple.party,
+				ple.voucher_type,
+				ple.voucher_no,
+				gle.remarks.as_("gle_remarks"),
+			)
+			.where((ple.delinked == 0) & (gle.is_cancelled == 0))
+			.run(as_dict=True)
+		)
+
+		if pl_entries:
+			# split into multiple batches, update and commit for each batch
+			batch_size = 1000
+			for batch in create_batch(pl_entries, batch_size):
+				for entry in batch:
+					query = (
+						qb.update(ple)
+						.set(ple.remarks, entry.gle_remarks)
+						.where(
+							(ple.company == entry.company)
+							& (ple.account == entry.account)
+							& (ple.party_type == entry.party_type)
+							& (ple.party == entry.party)
+							& (ple.voucher_type == entry.voucher_type)
+							& (ple.voucher_no == entry.voucher_no)
+						)
+					)
+					query.run()
+
+				frappe.db.commit()
diff --git a/erpnext/projects/doctype/task_type/task_type.json b/erpnext/projects/doctype/task_type/task_type.json
index 3254444..b04264e 100644
--- a/erpnext/projects/doctype/task_type/task_type.json
+++ b/erpnext/projects/doctype/task_type/task_type.json
@@ -1,127 +1,70 @@
 {
- "allow_copy": 0,
- "allow_events_in_timeline": 0,
- "allow_guest_to_view": 0,
- "allow_import": 0,
- "allow_rename": 0,
+ "actions": [],
  "autoname": "Prompt",
- "beta": 0,
  "creation": "2019-04-19 15:04:05.317138",
- "custom": 0,
- "docstatus": 0,
  "doctype": "DocType",
- "document_type": "",
- "editable_grid": 0,
  "engine": "InnoDB",
+ "field_order": [
+  "weight",
+  "description"
+ ],
  "fields": [
   {
-   "allow_bulk_edit": 0,
-   "allow_in_quick_entry": 0,
-   "allow_on_submit": 0,
-   "bold": 0,
-   "collapsible": 0,
-   "columns": 0,
-   "fetch_if_empty": 0,
    "fieldname": "weight",
    "fieldtype": "Float",
-   "hidden": 0,
-   "ignore_user_permissions": 0,
-   "ignore_xss_filter": 0,
-   "in_filter": 0,
-   "in_global_search": 0,
-   "in_list_view": 0,
-   "in_standard_filter": 0,
-   "label": "Weight",
-   "length": 0,
-   "no_copy": 0,
-   "permlevel": 0,
-   "precision": "",
-   "print_hide": 0,
-   "print_hide_if_no_value": 0,
-   "read_only": 0,
-   "remember_last_selected_value": 0,
-   "report_hide": 0,
-   "reqd": 0,
-   "search_index": 0,
-   "set_only_once": 0,
-   "translatable": 0,
-   "unique": 0
+   "label": "Weight"
   },
   {
-   "allow_bulk_edit": 0,
-   "allow_in_quick_entry": 0,
-   "allow_on_submit": 0,
-   "bold": 0,
-   "collapsible": 0,
-   "columns": 0,
-   "fetch_if_empty": 0,
    "fieldname": "description",
    "fieldtype": "Small Text",
-   "hidden": 0,
-   "ignore_user_permissions": 0,
-   "ignore_xss_filter": 0,
-   "in_filter": 0,
-   "in_global_search": 0,
-   "in_list_view": 0,
-   "in_standard_filter": 0,
-   "label": "Description",
-   "length": 0,
-   "no_copy": 0,
-   "permlevel": 0,
-   "precision": "",
-   "print_hide": 0,
-   "print_hide_if_no_value": 0,
-   "read_only": 0,
-   "remember_last_selected_value": 0,
-   "report_hide": 0,
-   "reqd": 0,
-   "search_index": 0,
-   "set_only_once": 0,
-   "translatable": 0,
-   "unique": 0
+   "label": "Description"
   }
  ],
- "has_web_view": 0,
- "hide_toolbar": 0,
- "idx": 0,
- "in_create": 0,
- "is_submittable": 0,
- "issingle": 0,
- "istable": 0,
- "max_attachments": 0,
- "modified": "2019-04-19 15:31:48.080164",
+ "links": [],
+ "modified": "2022-08-29 17:46:41.342979",
  "modified_by": "Administrator",
  "module": "Projects",
  "name": "Task Type",
- "name_case": "",
+ "naming_rule": "Set by user",
  "owner": "Administrator",
  "permissions": [
   {
-   "amend": 0,
-   "cancel": 0,
    "create": 1,
    "delete": 1,
    "email": 1,
    "export": 1,
-   "if_owner": 0,
-   "import": 0,
-   "permlevel": 0,
    "print": 1,
    "read": 1,
    "report": 1,
    "role": "System Manager",
-   "set_user_permissions": 0,
    "share": 1,
-   "submit": 0,
    "write": 1
+  },
+  {
+   "create": 1,
+   "delete": 1,
+   "email": 1,
+   "export": 1,
+   "print": 1,
+   "read": 1,
+   "report": 1,
+   "role": "Projects Manager",
+   "share": 1,
+   "write": 1
+  },
+  {
+   "email": 1,
+   "export": 1,
+   "print": 1,
+   "read": 1,
+   "report": 1,
+   "role": "Projects User",
+   "share": 1
   }
  ],
  "quick_entry": 1,
- "read_only": 0,
- "show_name_in_global_search": 0,
  "sort_field": "modified",
  "sort_order": "ASC",
- "track_changes": 1,
- "track_seen": 0,
- "track_views": 0
+ "states": [],
+ "track_changes": 1
 }
\ No newline at end of file
diff --git a/erpnext/selling/doctype/quotation/quotation.py b/erpnext/selling/doctype/quotation/quotation.py
index 863fbc4..96092b1 100644
--- a/erpnext/selling/doctype/quotation/quotation.py
+++ b/erpnext/selling/doctype/quotation/quotation.py
@@ -268,7 +268,7 @@
 
 def set_expired_status():
 	# filter out submitted non expired quotations whose validity has been ended
-	cond = "`tabQuotation`.docstatus = 1 and `tabQuotation`.status != 'Expired' and `tabQuotation`.valid_till < %s"
+	cond = "`tabQuotation`.docstatus = 1 and `tabQuotation`.status NOT IN ('Expired', 'Lost') and `tabQuotation`.valid_till < %s"
 	# check if those QUO have SO against it
 	so_against_quo = """
 		SELECT
diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py
index 8c03cb5..09a9652 100755
--- a/erpnext/selling/doctype/sales_order/sales_order.py
+++ b/erpnext/selling/doctype/sales_order/sales_order.py
@@ -892,6 +892,7 @@
 		target.additional_discount_percentage = 0.0
 		target.discount_amount = 0.0
 		target.inter_company_order_reference = ""
+		target.shipping_rule = ""
 
 		default_price_list = frappe.get_value("Supplier", supplier, "default_price_list")
 		if default_price_list:
@@ -1010,6 +1011,7 @@
 		target.additional_discount_percentage = 0.0
 		target.discount_amount = 0.0
 		target.inter_company_order_reference = ""
+		target.shipping_rule = ""
 		target.customer = ""
 		target.customer_name = ""
 		target.run_method("set_missing_values")
diff --git a/erpnext/setup/doctype/company/company.json b/erpnext/setup/doctype/company/company.json
index f34ec56..f087d99 100644
--- a/erpnext/setup/doctype/company/company.json
+++ b/erpnext/setup/doctype/company/company.json
@@ -85,7 +85,6 @@
   "depreciation_expense_account",
   "series_for_depreciation_entry",
   "expenses_included_in_asset_valuation",
-  "repair_and_maintenance_account",
   "column_break_40",
   "disposal_account",
   "depreciation_cost_center",
@@ -234,7 +233,6 @@
    "label": "Default Warehouse for Sales Return",
    "options": "Warehouse"
   },
-
   {
    "fieldname": "country",
    "fieldtype": "Link",
@@ -679,12 +677,6 @@
    "label": "Fixed Asset Defaults"
   },
   {
-   "fieldname": "repair_and_maintenance_account",
-   "fieldtype": "Link",
-   "label": "Repair and Maintenance Account",
-   "options": "Account"
-  },
-  {
    "fieldname": "section_break_28",
    "fieldtype": "Section Break",
    "label": "Chart of Accounts"
@@ -709,7 +701,7 @@
  "image_field": "company_logo",
  "is_tree": 1,
  "links": [],
- "modified": "2022-06-30 18:03:18.701314",
+ "modified": "2022-08-16 16:09:02.327724",
  "modified_by": "Administrator",
  "module": "Setup",
  "name": "Company",
diff --git a/erpnext/stock/doctype/item/item.js b/erpnext/stock/doctype/item/item.js
index 29b001f..7e1476d 100644
--- a/erpnext/stock/doctype/item/item.js
+++ b/erpnext/stock/doctype/item/item.js
@@ -562,7 +562,7 @@
 			let selected_attributes = {};
 			me.multiple_variant_dialog.$wrapper.find('.form-column').each((i, col) => {
 				if(i===0) return;
-				let attribute_name = $(col).find('label').html().trim();
+				let attribute_name = $(col).find('.control-label').html().trim();
 				selected_attributes[attribute_name] = [];
 				let checked_opts = $(col).find('.checkbox input');
 				checked_opts.each((i, opt) => {
diff --git a/erpnext/stock/doctype/material_request/material_request.json b/erpnext/stock/doctype/material_request/material_request.json
index cb46a6c..3593130 100644
--- a/erpnext/stock/doctype/material_request/material_request.json
+++ b/erpnext/stock/doctype/material_request/material_request.json
@@ -37,7 +37,8 @@
   "tc_name",
   "terms",
   "reference",
-  "job_card"
+  "job_card",
+  "work_order"
  ],
  "fields": [
   {
@@ -309,16 +310,24 @@
    "label": "Transfer Status",
    "options": "\nNot Started\nIn Transit\nCompleted",
    "read_only": 1
+  },
+  {
+   "fieldname": "work_order",
+   "fieldtype": "Link",
+   "label": "Work Order",
+   "options": "Work Order",
+   "read_only": 1
   }
  ],
  "icon": "fa fa-ticket",
  "idx": 70,
  "is_submittable": 1,
  "links": [],
- "modified": "2021-08-17 20:16:12.737743",
+ "modified": "2022-08-25 11:49:28.155048",
  "modified_by": "Administrator",
  "module": "Stock",
  "name": "Material Request",
+ "naming_rule": "By \"Naming Series\" field",
  "owner": "Administrator",
  "permissions": [
   {
@@ -386,5 +395,6 @@
  "show_name_in_global_search": 1,
  "sort_field": "modified",
  "sort_order": "DESC",
+ "states": [],
  "title_field": "title"
 }
\ No newline at end of file
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.js b/erpnext/stock/doctype/stock_entry/stock_entry.js
index e3a8438..1bbe570 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.js
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.js
@@ -174,6 +174,8 @@
 					if(!items.length) {
 						items = frm.doc.items;
 					}
+
+					mr.work_order = frm.doc.work_order;
 					items.forEach(function(item) {
 						var mr_item = frappe.model.add_child(mr, 'items');
 						mr_item.item_code = item.item_code;
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py
index f719c1e..d709522 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.py
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.py
@@ -116,6 +116,7 @@
 		self.validate_warehouse()
 		self.validate_work_order()
 		self.validate_bom()
+		self.validate_purchase_order()
 
 		if self.purpose in ("Manufacture", "Repack"):
 			self.mark_finished_and_scrap_items()
@@ -946,6 +947,19 @@
 				item_code = d.original_item or d.item_code
 				validate_bom_no(item_code, d.bom_no)
 
+	def validate_purchase_order(self):
+		if self.purpose == "Send to Subcontractor" and self.get("purchase_order"):
+			is_old_subcontracting_flow = frappe.db.get_value(
+				"Purchase Order", self.purchase_order, "is_old_subcontracting_flow"
+			)
+
+			if not is_old_subcontracting_flow:
+				frappe.throw(
+					_("Please select Subcontracting Order instead of Purchase Order {0}").format(
+						self.purchase_order
+					)
+				)
+
 	def mark_finished_and_scrap_items(self):
 		if any([d.item_code for d in self.items if (d.is_finished_item and d.t_warehouse)]):
 			return
@@ -2215,7 +2229,7 @@
 		return sorted(list(set(get_serial_nos(self.pro_doc.serial_no)) - set(used_serial_nos)))
 
 	def update_subcontracting_order_status(self):
-		if self.subcontracting_order and self.purpose == "Send to Subcontractor":
+		if self.subcontracting_order and self.purpose in ["Send to Subcontractor", "Material Transfer"]:
 			from erpnext.subcontracting.doctype.subcontracting_order.subcontracting_order import (
 				update_subcontracting_order_status,
 			)
@@ -2554,27 +2568,26 @@
 
 @frappe.whitelist()
 def get_items_from_subcontracting_order(source_name, target_doc=None):
-	sco = frappe.get_doc("Subcontracting Order", source_name)
+	def post_process(source, target):
+		target.stock_entry_type = target.purpose = "Send to Subcontractor"
+		target.subcontracting_order = source_name
 
-	if sco.docstatus == 1:
-		if target_doc and isinstance(target_doc, str):
-			target_doc = frappe.get_doc(json.loads(target_doc))
-
-		if target_doc.items:
-			target_doc.items = []
+		if target.items:
+			target.items = []
 
 		warehouses = {}
-		for item in sco.items:
+		for item in source.items:
 			warehouses[item.name] = item.warehouse
 
-		for item in sco.supplied_items:
-			target_doc.append(
+		for item in source.supplied_items:
+			target.append(
 				"items",
 				{
 					"s_warehouse": warehouses.get(item.reference_name),
-					"t_warehouse": sco.supplier_warehouse,
+					"t_warehouse": source.supplier_warehouse,
+					"subcontracted_item": item.main_item_code,
 					"item_code": item.rm_item_code,
-					"qty": item.required_qty,
+					"qty": max(item.required_qty - item.total_supplied_qty, 0),
 					"transfer_qty": item.required_qty,
 					"uom": item.stock_uom,
 					"stock_uom": item.stock_uom,
@@ -2582,6 +2595,23 @@
 				},
 			)
 
+	target_doc = get_mapped_doc(
+		"Subcontracting Order",
+		source_name,
+		{
+			"Subcontracting Order": {
+				"doctype": "Stock Entry",
+				"field_no_map": ["purchase_order"],
+				"validation": {
+					"docstatus": ["=", 1],
+				},
+			},
+		},
+		target_doc,
+		post_process,
+		ignore_child_tables=True,
+	)
+
 	return target_doc
 
 
diff --git a/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.js b/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.js
index c20f8ab..065ef39 100644
--- a/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.js
+++ b/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.js
@@ -107,9 +107,9 @@
 	get_materials_from_supplier: function (frm) {
 		let sco_rm_details = [];
 
-		if (frm.doc.supplied_items && (frm.doc.per_received == 100)) {
+		if (frm.doc.supplied_items && frm.doc.per_received > 0) {
 			frm.doc.supplied_items.forEach(d => {
-				if (d.total_supplied_qty && d.total_supplied_qty != d.consumed_qty) {
+				if (d.total_supplied_qty > 0 && d.total_supplied_qty != d.consumed_qty) {
 					sco_rm_details.push(d.name);
 				}
 			});
@@ -160,14 +160,11 @@
 		var me = this;
 
 		if (doc.docstatus == 1) {
-			if (doc.status != 'Completed') {
+			if (!['Closed', 'Completed'].includes(doc.status)) {
 				if (flt(doc.per_received) < 100) {
 					cur_frm.add_custom_button(__('Subcontracting Receipt'), this.make_subcontracting_receipt, __('Create'));
 					if (me.has_unsupplied_items()) {
-						cur_frm.add_custom_button(__('Material to Supplier'),
-							() => {
-								me.make_stock_entry();
-							}, __('Transfer'));
+						cur_frm.add_custom_button(__('Material to Supplier'), this.make_stock_entry, __('Transfer'));
 					}
 				}
 				cur_frm.page.set_inner_btn_group_as_primary(__('Create'));
@@ -195,120 +192,6 @@
 		transaction_controller.autofill_warehouse(child_table, warehouse_field, warehouse);
 	}
 
-	make_stock_entry() {
-		var items = $.map(cur_frm.doc.items, (d) => d.bom ? d.item_code : false);
-		var me = this;
-
-		if (items.length >= 1) {
-			me.raw_material_data = [];
-			me.show_dialog = 1;
-			let title = __('Transfer Material to Supplier');
-			let fields = [
-				{ fieldtype: 'Section Break', label: __('Raw Materials') },
-				{
-					fieldname: 'sub_con_rm_items', fieldtype: 'Table', label: __('Items'),
-					fields: [
-						{
-							fieldtype: 'Data',
-							fieldname: 'item_code',
-							label: __('Item'),
-							read_only: 1,
-							in_list_view: 1
-						},
-						{
-							fieldtype: 'Data',
-							fieldname: 'rm_item_code',
-							label: __('Raw Material'),
-							read_only: 1,
-							in_list_view: 1
-						},
-						{
-							fieldtype: 'Float',
-							read_only: 1,
-							fieldname: 'qty',
-							label: __('Quantity'),
-							in_list_view: 1
-						},
-						{
-							fieldtype: 'Data',
-							read_only: 1,
-							fieldname: 'warehouse',
-							label: __('Reserve Warehouse'),
-							in_list_view: 1
-						},
-						{
-							fieldtype: 'Float',
-							read_only: 1,
-							fieldname: 'rate',
-							label: __('Rate'),
-							hidden: 1
-						},
-						{
-							fieldtype: 'Float',
-							read_only: 1,
-							fieldname: 'amount',
-							label: __('Amount'),
-							hidden: 1
-						},
-						{
-							fieldtype: 'Link',
-							read_only: 1,
-							fieldname: 'uom',
-							label: __('UOM'),
-							hidden: 1
-						}
-					],
-					data: me.raw_material_data,
-					get_data: () => me.raw_material_data
-				}
-			];
-
-			me.dialog = new frappe.ui.Dialog({
-				title: title, fields: fields
-			});
-
-			if (me.frm.doc['supplied_items']) {
-				me.frm.doc['supplied_items'].forEach((item) => {
-					if (item.rm_item_code && item.main_item_code && item.required_qty - item.supplied_qty != 0) {
-						me.raw_material_data.push({
-							'name': item.name,
-							'item_code': item.main_item_code,
-							'rm_item_code': item.rm_item_code,
-							'item_name': item.rm_item_code,
-							'qty': item.required_qty - item.supplied_qty,
-							'warehouse': item.reserve_warehouse,
-							'rate': item.rate,
-							'amount': item.amount,
-							'stock_uom': item.stock_uom
-						});
-						me.dialog.fields_dict.sub_con_rm_items.grid.refresh();
-					}
-				});
-			}
-
-			me.dialog.get_field('sub_con_rm_items').check_all_rows();
-
-			me.dialog.show();
-			this.dialog.set_primary_action(__('Transfer'), () => {
-				me.values = me.dialog.get_values();
-				if (me.values) {
-					me.values.sub_con_rm_items.map((row, i) => {
-						if (!row.item_code || !row.rm_item_code || !row.warehouse || !row.qty || row.qty === 0) {
-							let row_id = i + 1;
-							frappe.throw(__('Item Code, warehouse and quantity are required on row {0}', [row_id]));
-						}
-					});
-					me.make_rm_stock_entry(me.dialog.fields_dict.sub_con_rm_items.grid.get_selected_children());
-					me.dialog.hide();
-				}
-			});
-		}
-
-		me.dialog.get_close_btn().on('click', () => {
-			me.dialog.hide();
-		});
-	}
-
 	has_unsupplied_items() {
 		return this.frm.doc['supplied_items'].some(item => item.required_qty > item.supplied_qty);
 	}
@@ -321,6 +204,15 @@
 		});
 	}
 
+	make_stock_entry() {
+		frappe.model.open_mapped_doc({
+			method: 'erpnext.stock.doctype.stock_entry.stock_entry.get_items_from_subcontracting_order',
+			source_name: cur_frm.doc.name,
+			freeze: true,
+			freeze_message: __('Creating Stock Entry ...')
+		});
+	}
+
 	make_rm_stock_entry(rm_items) {
 		frappe.call({
 			method: 'erpnext.controllers.subcontracting_controller.make_rm_stock_entry',
diff --git a/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.py b/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.py
index 156f027..e6de72d 100644
--- a/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.py
+++ b/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.py
@@ -153,7 +153,7 @@
 			else:
 				self.set_missing_values()
 
-	def update_status(self, status=None, update_modified=False):
+	def update_status(self, status=None, update_modified=True):
 		if self.docstatus >= 1 and not status:
 			if self.docstatus == 1:
 				if self.status == "Draft":
@@ -162,6 +162,10 @@
 					status = "Completed"
 				elif self.per_received > 0 and self.per_received < 100:
 					status = "Partially Received"
+					for item in self.supplied_items:
+						if item.returned_qty:
+							status = "Closed"
+							break
 				else:
 					total_required_qty = total_supplied_qty = 0
 					for item in self.supplied_items:
@@ -176,7 +180,10 @@
 			elif self.docstatus == 2:
 				status = "Cancelled"
 
-			frappe.db.set_value("Subcontracting Order", self.name, "status", status, update_modified)
+		if status:
+			frappe.db.set_value(
+				"Subcontracting Order", self.name, "status", status, update_modified=update_modified
+			)
 
 
 @frappe.whitelist()
diff --git a/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order_list.js b/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order_list.js
index 650419c..aab2fc9 100644
--- a/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order_list.js
+++ b/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order_list.js
@@ -10,6 +10,7 @@
 			"Completed": "green",
 			"Partial Material Transferred": "purple",
 			"Material Transferred": "blue",
+			"Closed": "red",
 		};
 		return [__(doc.status), status_colors[doc.status], "status,=," + doc.status];
 	},
diff --git a/erpnext/subcontracting/doctype/subcontracting_order/test_subcontracting_order.py b/erpnext/subcontracting/doctype/subcontracting_order/test_subcontracting_order.py
index 94bb38e..098242a 100644
--- a/erpnext/subcontracting/doctype/subcontracting_order/test_subcontracting_order.py
+++ b/erpnext/subcontracting/doctype/subcontracting_order/test_subcontracting_order.py
@@ -7,7 +7,10 @@
 from frappe.tests.utils import FrappeTestCase
 
 from erpnext.buying.doctype.purchase_order.purchase_order import get_mapped_subcontracting_order
-from erpnext.controllers.subcontracting_controller import make_rm_stock_entry
+from erpnext.controllers.subcontracting_controller import (
+	get_materials_from_supplier,
+	make_rm_stock_entry,
+)
 from erpnext.controllers.tests.test_subcontracting_controller import (
 	get_rm_items,
 	get_subcontracting_order,
@@ -89,6 +92,16 @@
 		sco.load_from_db()
 		self.assertEqual(sco.status, "Partially Received")
 
+		# Closed
+		ste = get_materials_from_supplier(sco.name, [d.name for d in sco.supplied_items])
+		ste.save()
+		ste.submit()
+		sco.load_from_db()
+		self.assertEqual(sco.status, "Closed")
+		ste.cancel()
+		sco.load_from_db()
+		self.assertEqual(sco.status, "Partially Received")
+
 		# Completed
 		scr = make_subcontracting_receipt(sco.name)
 		scr.save()
diff --git a/erpnext/subcontracting/doctype/subcontracting_order_supplied_item/subcontracting_order_supplied_item.json b/erpnext/subcontracting/doctype/subcontracting_order_supplied_item/subcontracting_order_supplied_item.json
index a206a21..8f7128b 100644
--- a/erpnext/subcontracting/doctype/subcontracting_order_supplied_item/subcontracting_order_supplied_item.json
+++ b/erpnext/subcontracting/doctype/subcontracting_order_supplied_item/subcontracting_order_supplied_item.json
@@ -150,8 +150,7 @@
             "label": "Returned Qty",
             "no_copy": 1,
             "print_hide": 1,
-            "read_only": 1,
-            "hidden": 1
+            "read_only": 1
         },
         {
             "fieldname": "total_supplied_qty",
@@ -166,7 +165,7 @@
     "hide_toolbar": 1,
     "istable": 1,
     "links": [],
-    "modified": "2022-04-07 12:58:28.208847",
+    "modified": "2022-08-26 16:04:56.125951",
     "modified_by": "Administrator",
     "module": "Subcontracting",
     "name": "Subcontracting Order Supplied Item",
diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.json b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.json
index 84e9554..5cd4e63 100644
--- a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.json
+++ b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.json
@@ -369,7 +369,7 @@
             "in_standard_filter": 1,
             "label": "Status",
             "no_copy": 1,
-            "options": "\nDraft\nCompleted\nReturn\nReturn Issued\nCancelled",
+            "options": "\nDraft\nCompleted\nReturn\nReturn Issued\nCancelled\nClosed",
             "print_hide": 1,
             "print_width": "150px",
             "read_only": 1,
@@ -628,7 +628,7 @@
     "in_create": 1,
     "is_submittable": 1,
     "links": [],
-    "modified": "2022-08-22 17:30:40.827517",
+    "modified": "2022-08-26 21:02:26.353870",
     "modified_by": "Administrator",
     "module": "Subcontracting",
     "name": "Subcontracting Receipt",
diff --git a/erpnext/templates/pages/product_search.py b/erpnext/templates/pages/product_search.py
index 0768cc3..f40fd47 100644
--- a/erpnext/templates/pages/product_search.py
+++ b/erpnext/templates/pages/product_search.py
@@ -5,14 +5,13 @@
 
 import frappe
 from frappe.utils import cint, cstr
-from redisearch import AutoCompleter, Client, Query
+from redis.commands.search.query import Query
 
 from erpnext.e_commerce.redisearch_utils import (
 	WEBSITE_ITEM_CATEGORY_AUTOCOMPLETE,
 	WEBSITE_ITEM_INDEX,
 	WEBSITE_ITEM_NAME_AUTOCOMPLETE,
 	is_redisearch_enabled,
-	make_key,
 )
 from erpnext.e_commerce.shopping_cart.product_info import set_product_info_for_website
 from erpnext.setup.doctype.item_group.item_group import get_item_for_list_in_html
@@ -88,15 +87,17 @@
 	if not query:
 		return search_results
 
-	red = frappe.cache()
+	redis = frappe.cache()
 	query = clean_up_query(query)
 
 	# TODO: Check perf/correctness with Suggestions & Query vs only Query
 	# TODO: Use Levenshtein Distance in Query (max=3)
-	ac = AutoCompleter(make_key(WEBSITE_ITEM_NAME_AUTOCOMPLETE), conn=red)
-	client = Client(make_key(WEBSITE_ITEM_INDEX), conn=red)
-	suggestions = ac.get_suggestions(
-		query, num=limit, fuzzy=fuzzy_search and len(query) > 3  # Fuzzy on length < 3 can be real slow
+	redisearch = redis.ft(WEBSITE_ITEM_INDEX)
+	suggestions = redisearch.sugget(
+		WEBSITE_ITEM_NAME_AUTOCOMPLETE,
+		query,
+		num=limit,
+		fuzzy=fuzzy_search and len(query) > 3,
 	)
 
 	# Build a query
@@ -106,8 +107,8 @@
 		query_string += f"|('{clean_up_query(s.string)}')"
 
 	q = Query(query_string)
+	results = redisearch.search(q)
 
-	results = client.search(q)
 	search_results["results"] = list(map(convert_to_dict, results.docs))
 	search_results["results"] = sorted(
 		search_results["results"], key=lambda k: frappe.utils.cint(k["ranking"]), reverse=True
@@ -141,8 +142,8 @@
 	if not query:
 		return search_results
 
-	ac = AutoCompleter(make_key(WEBSITE_ITEM_CATEGORY_AUTOCOMPLETE), conn=frappe.cache())
-	suggestions = ac.get_suggestions(query, num=10, with_payloads=True)
+	ac = frappe.cache().ft()
+	suggestions = ac.sugget(WEBSITE_ITEM_CATEGORY_AUTOCOMPLETE, query, num=10, with_payloads=True)
 
 	results = [json.loads(s.payload) for s in suggestions]
 
diff --git a/pyproject.toml b/pyproject.toml
index 5acfd39..c61f1a5 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -12,7 +12,6 @@
     "pycountry~=20.7.3",
     "python-stdnum~=1.16",
     "Unidecode~=1.2.0",
-    "redisearch~=2.1.0",
 
     # integration dependencies
     "gocardless-pro~=1.22.0",
@@ -21,6 +20,9 @@
     "python-youtube~=0.8.0",
     "taxjar~=1.9.2",
     "tweepy~=3.10.0",
+
+    # Not used directly - required by PyQRCode for PNG generation
+    "pypng~=0.20220715.0",
 ]
 
 [build-system]