Merge branch 'develop' into fix/github-issue/28766
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py
index 79fab64..26192ec 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.py
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py
@@ -1758,6 +1758,8 @@
 	pe.setup_party_account_field()
 	pe.set_missing_values()
 
+	update_accounting_dimensions(pe, doc)
+
 	if party_account and bank:
 		pe.set_exchange_rate(ref_doc=reference_doc)
 		pe.set_amounts()
@@ -1775,6 +1777,18 @@
 	return pe
 
 
+def update_accounting_dimensions(pe, doc):
+	"""
+	Updates accounting dimensions in Payment Entry based on the accounting dimensions in the reference document
+	"""
+	from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
+		get_accounting_dimensions,
+	)
+
+	for dimension in get_accounting_dimensions():
+		pe.set(dimension, doc.get(dimension))
+
+
 def get_bank_cash_account(doc, bank_account):
 	bank = get_default_bank_cash_account(
 		doc.company, "Bank", mode_of_payment=doc.get("mode_of_payment"), account=bank_account
diff --git a/erpnext/accounts/doctype/payment_request/payment_request.json b/erpnext/accounts/doctype/payment_request/payment_request.json
index 2f3516e..381f3fb 100644
--- a/erpnext/accounts/doctype/payment_request/payment_request.json
+++ b/erpnext/accounts/doctype/payment_request/payment_request.json
@@ -32,6 +32,10 @@
   "iban",
   "branch_code",
   "swift_number",
+  "accounting_dimensions_section",
+  "cost_center",
+  "dimension_col_break",
+  "project",
   "recipient_and_message",
   "print_format",
   "email_to",
@@ -362,13 +366,35 @@
    "label": "Payment Channel",
    "options": "\nEmail\nPhone",
    "read_only": 1
+  },
+  {
+   "collapsible": 1,
+   "fieldname": "accounting_dimensions_section",
+   "fieldtype": "Section Break",
+   "label": "Accounting Dimensions"
+  },
+  {
+   "fieldname": "cost_center",
+   "fieldtype": "Link",
+   "label": "Cost Center",
+   "options": "Cost Center"
+  },
+  {
+   "fieldname": "dimension_col_break",
+   "fieldtype": "Column Break"
+  },
+  {
+   "fieldname": "project",
+   "fieldtype": "Link",
+   "label": "Project",
+   "options": "Project"
   }
  ],
  "in_create": 1,
  "index_web_pages_for_search": 1,
  "is_submittable": 1,
  "links": [],
- "modified": "2022-09-30 16:19:43.680025",
+ "modified": "2022-12-21 16:56:40.115737",
  "modified_by": "Administrator",
  "module": "Accounts",
  "name": "Payment Request",
diff --git a/erpnext/accounts/doctype/payment_request/payment_request.py b/erpnext/accounts/doctype/payment_request/payment_request.py
index fc93801..4fc12db 100644
--- a/erpnext/accounts/doctype/payment_request/payment_request.py
+++ b/erpnext/accounts/doctype/payment_request/payment_request.py
@@ -10,6 +10,9 @@
 from frappe.utils import flt, get_url, nowdate
 from frappe.utils.background_jobs import enqueue
 
+from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
+	get_accounting_dimensions,
+)
 from erpnext.accounts.doctype.payment_entry.payment_entry import (
 	get_company_defaults,
 	get_payment_entry,
@@ -270,6 +273,17 @@
 			}
 		)
 
+		# Update dimensions
+		payment_entry.update(
+			{
+				"cost_center": self.get("cost_center"),
+				"project": self.get("project"),
+			}
+		)
+
+		for dimension in get_accounting_dimensions():
+			payment_entry.update({dimension: self.get(dimension)})
+
 		if payment_entry.difference_amount:
 			company_details = get_company_defaults(ref_doc.company)
 
@@ -449,6 +463,17 @@
 			}
 		)
 
+		# Update dimensions
+		pr.update(
+			{
+				"cost_center": ref_doc.get("cost_center"),
+				"project": ref_doc.get("project"),
+			}
+		)
+
+		for dimension in get_accounting_dimensions():
+			pr.update({dimension: ref_doc.get(dimension)})
+
 		if args.order_type == "Shopping Cart" or args.mute_email:
 			pr.flags.mute_email = True
 
diff --git a/erpnext/accounts/doctype/pricing_rule/utils.py b/erpnext/accounts/doctype/pricing_rule/utils.py
index 3989f8a..1ce780e 100644
--- a/erpnext/accounts/doctype/pricing_rule/utils.py
+++ b/erpnext/accounts/doctype/pricing_rule/utils.py
@@ -252,10 +252,15 @@
 
 	if args.get("doctype") in [
 		"Quotation",
+		"Quotation Item",
 		"Sales Order",
+		"Sales Order Item",
 		"Delivery Note",
+		"Delivery Note Item",
 		"Sales Invoice",
+		"Sales Invoice Item",
 		"POS Invoice",
+		"POS Invoice Item",
 	]:
 		conditions += """ and ifnull(`tabPricing Rule`.selling, 0) = 1"""
 	else:
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 62c3ced..35d19ed 100644
--- a/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json
+++ b/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json
@@ -890,7 +890,7 @@
  "idx": 1,
  "istable": 1,
  "links": [],
- "modified": "2022-11-02 12:53:12.693217",
+ "modified": "2022-12-28 16:17:33.484531",
  "modified_by": "Administrator",
  "module": "Accounts",
  "name": "Sales Invoice Item",
diff --git a/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py b/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py
index c04b9c7..d34c213 100644
--- a/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py
+++ b/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py
@@ -53,9 +53,6 @@
 	item_details = get_item_details()
 
 	for d in item_list:
-		if not d.stock_qty:
-			continue
-
 		item_record = item_details.get(d.item_code)
 
 		purchase_receipt = None
@@ -94,7 +91,7 @@
 				"expense_account": expense_account,
 				"stock_qty": d.stock_qty,
 				"stock_uom": d.stock_uom,
-				"rate": d.base_net_amount / d.stock_qty,
+				"rate": d.base_net_amount / d.stock_qty if d.stock_qty else d.base_net_amount,
 				"amount": d.base_net_amount,
 			}
 		)
diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.py b/erpnext/buying/doctype/purchase_order/purchase_order.py
index 4c10b48..5a4168a 100644
--- a/erpnext/buying/doctype/purchase_order/purchase_order.py
+++ b/erpnext/buying/doctype/purchase_order/purchase_order.py
@@ -207,31 +207,36 @@
 				)
 
 	def validate_fg_item_for_subcontracting(self):
-		if self.is_subcontracted and not self.is_old_subcontracting_flow:
+		if self.is_subcontracted:
+			if not self.is_old_subcontracting_flow:
+				for item in self.items:
+					if not item.fg_item:
+						frappe.throw(
+							_("Row #{0}: Finished Good Item is not specified for service item {1}").format(
+								item.idx, item.item_code
+							)
+						)
+					else:
+						if not frappe.get_value("Item", item.fg_item, "is_sub_contracted_item"):
+							frappe.throw(
+								_(
+									"Row #{0}: Finished Good Item {1} must be a sub-contracted item for service item {2}"
+								).format(item.idx, item.fg_item, item.item_code)
+							)
+						elif not frappe.get_value("Item", item.fg_item, "default_bom"):
+							frappe.throw(
+								_("Row #{0}: Default BOM not found for FG Item {1}").format(item.idx, item.fg_item)
+							)
+					if not item.fg_item_qty:
+						frappe.throw(
+							_("Row #{0}: Finished Good Item Qty is not specified for service item {0}").format(
+								item.idx, item.item_code
+							)
+						)
+		else:
 			for item in self.items:
-				if not item.fg_item:
-					frappe.throw(
-						_("Row #{0}: Finished Good Item is not specified for service item {1}").format(
-							item.idx, item.item_code
-						)
-					)
-				else:
-					if not frappe.get_value("Item", item.fg_item, "is_sub_contracted_item"):
-						frappe.throw(
-							_(
-								"Row #{0}: Finished Good Item {1} must be a sub-contracted item for service item {2}"
-							).format(item.idx, item.fg_item, item.item_code)
-						)
-					elif not frappe.get_value("Item", item.fg_item, "default_bom"):
-						frappe.throw(
-							_("Row #{0}: Default BOM not found for FG Item {1}").format(item.idx, item.fg_item)
-						)
-				if not item.fg_item_qty:
-					frappe.throw(
-						_("Row #{0}: Finished Good Item Qty is not specified for service item {0}").format(
-							item.idx, item.item_code
-						)
-					)
+				item.set("fg_item", None)
+				item.set("fg_item_qty", 0)
 
 	def get_schedule_dates(self):
 		for d in self.get("items"):
diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py
index 334a2d8..788dc49 100644
--- a/erpnext/controllers/accounts_controller.py
+++ b/erpnext/controllers/accounts_controller.py
@@ -584,7 +584,12 @@
 					if bool(uom) != bool(stock_uom):  # xor
 						item.stock_uom = item.uom = uom or stock_uom
 
-					item.conversion_factor = get_uom_conv_factor(item.get("uom"), item.get("stock_uom"))
+					# UOM cannot be zero so substitute as 1
+					item.conversion_factor = (
+						get_uom_conv_factor(item.get("uom"), item.get("stock_uom"))
+						or item.get("conversion_factor")
+						or 1
+					)
 
 			if self.doctype == "Purchase Invoice":
 				self.set_expense_account(for_validate)
diff --git a/erpnext/erpnext_integrations/doctype/exotel_settings/exotel_settings.json b/erpnext/erpnext_integrations/doctype/exotel_settings/exotel_settings.json
index 72f47b5..0d42ca8 100644
--- a/erpnext/erpnext_integrations/doctype/exotel_settings/exotel_settings.json
+++ b/erpnext/erpnext_integrations/doctype/exotel_settings/exotel_settings.json
@@ -1,4 +1,5 @@
 {
+ "actions": [],
  "creation": "2019-05-21 07:41:53.536536",
  "doctype": "DocType",
  "engine": "InnoDB",
@@ -7,10 +8,14 @@
   "section_break_2",
   "account_sid",
   "api_key",
-  "api_token"
+  "api_token",
+  "section_break_6",
+  "map_custom_field_to_doctype",
+  "target_doctype"
  ],
  "fields": [
   {
+   "default": "0",
    "fieldname": "enabled",
    "fieldtype": "Check",
    "label": "Enabled"
@@ -18,7 +23,8 @@
   {
    "depends_on": "enabled",
    "fieldname": "section_break_2",
-   "fieldtype": "Section Break"
+   "fieldtype": "Section Break",
+   "label": "Credentials"
   },
   {
    "fieldname": "account_sid",
@@ -34,10 +40,31 @@
    "fieldname": "api_key",
    "fieldtype": "Data",
    "label": "API Key"
+  },
+  {
+   "depends_on": "enabled",
+   "fieldname": "section_break_6",
+   "fieldtype": "Section Break",
+   "label": "Custom Field"
+  },
+  {
+   "default": "0",
+   "fieldname": "map_custom_field_to_doctype",
+   "fieldtype": "Check",
+   "label": "Map Custom Field to DocType"
+  },
+  {
+   "depends_on": "map_custom_field_to_doctype",
+   "fieldname": "target_doctype",
+   "fieldtype": "Link",
+   "label": "Target DocType",
+   "mandatory_depends_on": "map_custom_field_to_doctype",
+   "options": "DocType"
   }
  ],
  "issingle": 1,
- "modified": "2019-05-22 06:25:18.026997",
+ "links": [],
+ "modified": "2022-12-14 17:24:50.176107",
  "modified_by": "Administrator",
  "module": "ERPNext Integrations",
  "name": "Exotel Settings",
@@ -57,5 +84,6 @@
  "quick_entry": 1,
  "sort_field": "modified",
  "sort_order": "ASC",
+ "states": [],
  "track_changes": 1
 }
\ No newline at end of file
diff --git a/erpnext/erpnext_integrations/exotel_integration.py b/erpnext/erpnext_integrations/exotel_integration.py
index fd0f783..0d40667 100644
--- a/erpnext/erpnext_integrations/exotel_integration.py
+++ b/erpnext/erpnext_integrations/exotel_integration.py
@@ -72,6 +72,24 @@
 		return frappe.get_doc("Call Log", call_log_id)
 
 
+def map_custom_field(call_payload, call_log):
+	field_value = call_payload.get("CustomField")
+
+	if not field_value:
+		return call_log
+
+	settings = get_exotel_settings()
+	target_doctype = settings.target_doctype
+	mapping_enabled = settings.map_custom_field_to_doctype
+
+	if not mapping_enabled or not target_doctype:
+		return call_log
+
+	call_log.append("links", {"link_doctype": target_doctype, "link_name": field_value})
+
+	return call_log
+
+
 def create_call_log(call_payload):
 	call_log = frappe.new_doc("Call Log")
 	call_log.id = call_payload.get("CallSid")
@@ -79,6 +97,7 @@
 	call_log.medium = call_payload.get("To")
 	call_log.status = "Ringing"
 	setattr(call_log, "from", call_payload.get("CallFrom"))
+	map_custom_field(call_payload, call_log)
 	call_log.save(ignore_permissions=True)
 	frappe.db.commit()
 	return call_log
@@ -93,10 +112,10 @@
 
 
 @frappe.whitelist()
-def make_a_call(from_number, to_number, caller_id):
+def make_a_call(from_number, to_number, caller_id, **kwargs):
 	endpoint = get_exotel_endpoint("Calls/connect.json?details=true")
 	response = requests.post(
-		endpoint, data={"From": from_number, "To": to_number, "CallerId": caller_id}
+		endpoint, data={"From": from_number, "To": to_number, "CallerId": caller_id, **kwargs}
 	)
 
 	return response.json()
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index 0aad1d3..2420a23 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -318,4 +318,5 @@
 erpnext.patches.v14_0.create_accounting_dimensions_for_asset_capitalization
 erpnext.patches.v14_0.update_partial_tds_fields
 erpnext.patches.v14_0.create_incoterms_and_migrate_shipment
-erpnext.patches.v14_0.setup_clear_repost_logs
\ No newline at end of file
+erpnext.patches.v14_0.setup_clear_repost_logs
+erpnext.patches.v14_0.create_accounting_dimensions_for_payment_request
\ No newline at end of file
diff --git a/erpnext/patches/v14_0/create_accounting_dimensions_for_payment_request.py b/erpnext/patches/v14_0/create_accounting_dimensions_for_payment_request.py
new file mode 100644
index 0000000..bede419
--- /dev/null
+++ b/erpnext/patches/v14_0/create_accounting_dimensions_for_payment_request.py
@@ -0,0 +1,31 @@
+import frappe
+from frappe.custom.doctype.custom_field.custom_field import create_custom_field
+
+
+def execute():
+	accounting_dimensions = frappe.db.get_all(
+		"Accounting Dimension", fields=["fieldname", "label", "document_type", "disabled"]
+	)
+
+	if not accounting_dimensions:
+		return
+
+	doctype = "Payment Request"
+
+	for d in accounting_dimensions:
+		field = frappe.db.get_value("Custom Field", {"dt": doctype, "fieldname": d.fieldname})
+
+		if field:
+			continue
+
+		df = {
+			"fieldname": d.fieldname,
+			"label": d.label,
+			"fieldtype": "Link",
+			"options": d.document_type,
+			"insert_after": "accounting_dimensions_section",
+		}
+
+		create_custom_field(doctype, df, ignore_validate=True)
+
+	frappe.clear_cache(doctype=doctype)
diff --git a/erpnext/selling/doctype/customer/customer.py b/erpnext/selling/doctype/customer/customer.py
index 12ecb01..d9dab33 100644
--- a/erpnext/selling/doctype/customer/customer.py
+++ b/erpnext/selling/doctype/customer/customer.py
@@ -737,7 +737,7 @@
 		qb.from_(con)
 		.join(dlink)
 		.on(con.name == dlink.parent)
-		.select(con.name, con.full_name, con.email_id)
+		.select(con.name, con.email_id)
 		.where((dlink.link_name == customer) & (con.name.like(f"%{txt}%")))
 		.run()
 	)
diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py
index efe1f8e..7c0601e 100755
--- a/erpnext/selling/doctype/sales_order/sales_order.py
+++ b/erpnext/selling/doctype/sales_order/sales_order.py
@@ -1024,6 +1024,15 @@
 	]
 	items_to_map = list(set(items_to_map))
 
+	def is_drop_ship_order(target):
+		drop_ship = True
+		for item in target.items:
+			if not item.delivered_by_supplier:
+				drop_ship = False
+				break
+
+		return drop_ship
+
 	def set_missing_values(source, target):
 		target.supplier = ""
 		target.apply_discount_on = ""
@@ -1031,6 +1040,14 @@
 		target.discount_amount = 0.0
 		target.inter_company_order_reference = ""
 		target.shipping_rule = ""
+
+		if is_drop_ship_order(target):
+			target.customer = source.customer
+			target.customer_name = source.customer_name
+			target.shipping_address = source.shipping_address_name
+		else:
+			target.customer = target.customer_name = target.shipping_address = None
+
 		target.run_method("set_missing_values")
 		target.run_method("calculate_taxes_and_totals")
 
@@ -1057,11 +1074,9 @@
 					"contact_email",
 					"contact_person",
 					"taxes_and_charges",
+					"shipping_address",
 					"terms",
 				],
-				"field_map": [
-					["shipping_address_name", "shipping_address"],
-				],
 				"validation": {"docstatus": ["=", 1]},
 			},
 			"Sales Order Item": {
diff --git a/erpnext/stock/dashboard/item_dashboard.js b/erpnext/stock/dashboard/item_dashboard.js
index 6e7622c..1be528f 100644
--- a/erpnext/stock/dashboard/item_dashboard.js
+++ b/erpnext/stock/dashboard/item_dashboard.js
@@ -102,6 +102,9 @@
 			args: args,
 			callback: function (r) {
 				me.render(r.message);
+				if(me.after_refresh) {
+					me.after_refresh();
+				}
 			}
 		});
 	}
diff --git a/erpnext/stock/doctype/pick_list/pick_list.js b/erpnext/stock/doctype/pick_list/pick_list.js
index 799406c..8213adb 100644
--- a/erpnext/stock/doctype/pick_list/pick_list.js
+++ b/erpnext/stock/doctype/pick_list/pick_list.js
@@ -51,7 +51,15 @@
 		if (!(frm.doc.locations && frm.doc.locations.length)) {
 			frappe.msgprint(__('Add items in the Item Locations table'));
 		} else {
-			frm.call('set_item_locations', {save: save});
+			frappe.call({
+				method: "set_item_locations",
+				doc: frm.doc,
+				args: {
+					"save": save,
+				},
+				freeze: 1,
+				freeze_message: __("Setting Item Locations..."),
+			});
 		}
 	},
 	get_item_locations: (frm) => {
diff --git a/erpnext/stock/doctype/pick_list/pick_list.py b/erpnext/stock/doctype/pick_list/pick_list.py
index aff5e05..953fca7 100644
--- a/erpnext/stock/doctype/pick_list/pick_list.py
+++ b/erpnext/stock/doctype/pick_list/pick_list.py
@@ -135,6 +135,7 @@
 
 		# reset
 		self.delete_key("locations")
+		updated_locations = frappe._dict()
 		for item_doc in items:
 			item_code = item_doc.item_code
 
@@ -155,7 +156,26 @@
 			for row in locations:
 				location = item_doc.as_dict()
 				location.update(row)
-				self.append("locations", location)
+				key = (
+					location.item_code,
+					location.warehouse,
+					location.uom,
+					location.batch_no,
+					location.serial_no,
+					location.sales_order_item or location.material_request_item,
+				)
+
+				if key not in updated_locations:
+					updated_locations.setdefault(key, location)
+				else:
+					updated_locations[key].qty += location.qty
+					updated_locations[key].stock_qty += location.stock_qty
+
+		for location in updated_locations.values():
+			if location.picked_qty > location.stock_qty:
+				location.picked_qty = location.stock_qty
+
+			self.append("locations", location)
 
 		# If table is empty on update after submit, set stock_qty, picked_qty to 0 so that indicator is red
 		# and give feedback to the user. This is to avoid empty Pick Lists.
@@ -441,7 +461,7 @@
 			sle.`batch_no`,
 			sle.`item_code`
 		HAVING `qty` > 0
-		ORDER BY IFNULL(batch.`expiry_date`, '2200-01-01'), batch.`creation`
+		ORDER BY IFNULL(batch.`expiry_date`, '2200-01-01'), batch.`creation`, sle.`batch_no`, sle.`warehouse`
 	""".format(
 			warehouse_condition=warehouse_condition
 		),
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.js b/erpnext/stock/doctype/stock_entry/stock_entry.js
index b910244..d4b4efa 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.js
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.js
@@ -112,6 +112,10 @@
 			}
 		});
 		attach_bom_items(frm.doc.bom_no);
+
+		if(!check_should_not_attach_bom_items(frm.doc.bom_no)) {
+			erpnext.accounts.dimensions.update_dimension(frm, frm.doctype);
+		}
 	},
 
 	setup_quality_inspection: function(frm) {
@@ -326,7 +330,11 @@
 		}
 
 		frm.trigger("setup_quality_inspection");
-		attach_bom_items(frm.doc.bom_no)
+		attach_bom_items(frm.doc.bom_no);
+
+		if(!check_should_not_attach_bom_items(frm.doc.bom_no)) {
+			erpnext.accounts.dimensions.update_dimension(frm, frm.doctype);
+		}
 	},
 
 	before_save: function(frm) {
@@ -939,7 +947,10 @@
 				method: "get_items",
 				callback: function(r) {
 					if(!r.exc) refresh_field("items");
-					if(me.frm.doc.bom_no) attach_bom_items(me.frm.doc.bom_no)
+					if(me.frm.doc.bom_no) {
+						attach_bom_items(me.frm.doc.bom_no);
+						erpnext.accounts.dimensions.update_dimension(me.frm, me.frm.doctype);
+					}
 				}
 			});
 		}