Merge branch 'develop' into refactor/stock/report/incorrect-stock-value
diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py
index 601fc87..52efd33 100644
--- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py
+++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py
@@ -14,6 +14,7 @@
 	QueryPaymentLedger,
 	get_outstanding_invoices,
 	reconcile_against_document,
+	update_reference_in_payment_entry,
 )
 from erpnext.controllers.accounts_controller import get_advance_payment_entries
 
@@ -212,6 +213,23 @@
 			inv.currency = entry.get("currency")
 			inv.outstanding_amount = flt(entry.get("outstanding_amount"))
 
+	def get_difference_amount(self, allocated_entry):
+		if allocated_entry.get("reference_type") != "Payment Entry":
+			return
+
+		dr_or_cr = (
+			"credit_in_account_currency"
+			if erpnext.get_party_account_type(self.party_type) == "Receivable"
+			else "debit_in_account_currency"
+		)
+
+		row = self.get_payment_details(allocated_entry, dr_or_cr)
+
+		doc = frappe.get_doc(allocated_entry.reference_type, allocated_entry.reference_name)
+		update_reference_in_payment_entry(row, doc, do_not_save=True)
+
+		return doc.difference_amount
+
 	@frappe.whitelist()
 	def allocate_entries(self, args):
 		self.validate_entries()
@@ -227,12 +245,16 @@
 					res = self.get_allocated_entry(pay, inv, pay["amount"])
 					inv["outstanding_amount"] = flt(inv.get("outstanding_amount")) - flt(pay.get("amount"))
 					pay["amount"] = 0
+
+				res.difference_amount = self.get_difference_amount(res)
+
 				if pay.get("amount") == 0:
 					entries.append(res)
 					break
 				elif inv.get("outstanding_amount") == 0:
 					entries.append(res)
 					continue
+
 			else:
 				break
 
diff --git a/erpnext/accounts/report/gross_and_net_profit_report/gross_and_net_profit_report.py b/erpnext/accounts/report/gross_and_net_profit_report/gross_and_net_profit_report.py
index 9d56678..cd5f366 100644
--- a/erpnext/accounts/report/gross_and_net_profit_report/gross_and_net_profit_report.py
+++ b/erpnext/accounts/report/gross_and_net_profit_report/gross_and_net_profit_report.py
@@ -155,7 +155,6 @@
 	for d in data:
 		for period in period_list:
 			key = period if consolidated else period.key
-			d[key] = totals[d["account"]]
 			d["total"] = totals[d["account"]]
 	return data
 
diff --git a/erpnext/crm/report/opportunity_summary_by_sales_stage/opportunity_summary_by_sales_stage.js b/erpnext/crm/report/opportunity_summary_by_sales_stage/opportunity_summary_by_sales_stage.js
index 116db2f..7cd1710 100644
--- a/erpnext/crm/report/opportunity_summary_by_sales_stage/opportunity_summary_by_sales_stage.js
+++ b/erpnext/crm/report/opportunity_summary_by_sales_stage/opportunity_summary_by_sales_stage.js
@@ -44,7 +44,7 @@
 		},
 		{
 			fieldname: "opportunity_source",
-			label: __("Oppoturnity Source"),
+			label: __("Opportunity Source"),
 			fieldtype: "Link",
 			options: "Lead Source",
 		},
@@ -62,4 +62,4 @@
 			default: frappe.defaults.get_user_default("Company")
 		}
 	]
-};
\ No newline at end of file
+};
diff --git a/erpnext/manufacturing/report/job_card_summary/job_card_summary.py b/erpnext/manufacturing/report/job_card_summary/job_card_summary.py
index 5083b73..63c2d97 100644
--- a/erpnext/manufacturing/report/job_card_summary/job_card_summary.py
+++ b/erpnext/manufacturing/report/job_card_summary/job_card_summary.py
@@ -85,8 +85,8 @@
 		open_job_cards.append(periodic_data.get("Open").get(d))
 		completed.append(periodic_data.get("Completed").get(d))
 
-	datasets.append({"name": "Open", "values": open_job_cards})
-	datasets.append({"name": "Completed", "values": completed})
+	datasets.append({"name": _("Open"), "values": open_job_cards})
+	datasets.append({"name": _("Completed"), "values": completed})
 
 	chart = {"data": {"labels": labels, "datasets": datasets}, "type": "bar"}
 
diff --git a/erpnext/manufacturing/report/work_order_summary/work_order_summary.py b/erpnext/manufacturing/report/work_order_summary/work_order_summary.py
index 2368bfd..41ffcbb 100644
--- a/erpnext/manufacturing/report/work_order_summary/work_order_summary.py
+++ b/erpnext/manufacturing/report/work_order_summary/work_order_summary.py
@@ -83,6 +83,7 @@
 	for d in data:
 		status_wise_data[d.status] += 1
 
+	labels = [_(label) for label in labels]
 	values = [status_wise_data[label] for label in labels]
 
 	chart = {
@@ -95,7 +96,7 @@
 
 
 def get_chart_based_on_age(data):
-	labels = ["0-30 Days", "30-60 Days", "60-90 Days", "90 Above"]
+	labels = [_("0-30 Days"), _("30-60 Days"), _("60-90 Days"), _("90 Above")]
 
 	age_wise_data = {"0-30 Days": 0, "30-60 Days": 0, "60-90 Days": 0, "90 Above": 0}
 
@@ -135,8 +136,8 @@
 		pending.append(periodic_data.get("Pending").get(d))
 		completed.append(periodic_data.get("Completed").get(d))
 
-	datasets.append({"name": "Pending", "values": pending})
-	datasets.append({"name": "Completed", "values": completed})
+	datasets.append({"name": _("Pending"), "values": pending})
+	datasets.append({"name": _("Completed"), "values": completed})
 
 	chart = {
 		"data": {"labels": labels, "datasets": datasets},
diff --git a/erpnext/projects/report/project_summary/project_summary.py b/erpnext/projects/report/project_summary/project_summary.py
index 606c0c2..7a35fd2 100644
--- a/erpnext/projects/report/project_summary/project_summary.py
+++ b/erpnext/projects/report/project_summary/project_summary.py
@@ -91,9 +91,9 @@
 		"data": {
 			"labels": labels[:30],
 			"datasets": [
-				{"name": "Overdue", "values": overdue[:30]},
-				{"name": "Completed", "values": completed[:30]},
-				{"name": "Total Tasks", "values": total[:30]},
+				{"name": _("Overdue"), "values": overdue[:30]},
+				{"name": _("Completed"), "values": completed[:30]},
+				{"name": _("Total Tasks"), "values": total[:30]},
 			],
 		},
 		"type": "bar",
diff --git a/erpnext/public/js/help_links.js b/erpnext/public/js/help_links.js
index b643cca..1c3f43e 100644
--- a/erpnext/public/js/help_links.js
+++ b/erpnext/public/js/help_links.js
@@ -671,7 +671,7 @@
 		label: "Item Valuation",
 		url:
 			docsUrl +
-			"user/manual/en/stock/articles/item-valuation-fifo-and-moving-average",
+			"user/manual/en/stock/articles/calculation-of-valuation-rate-in-fifo-and-moving-average",
 	},
 ];
 
diff --git a/erpnext/public/js/utils/barcode_scanner.js b/erpnext/public/js/utils/barcode_scanner.js
index a6bff2c..83b108b 100644
--- a/erpnext/public/js/utils/barcode_scanner.js
+++ b/erpnext/public/js/utils/barcode_scanner.js
@@ -21,6 +21,11 @@
 		this.items_table_name = opts.items_table_name || "items";
 		this.items_table = this.frm.doc[this.items_table_name];
 
+		// optional sound name to play when scan either fails or passes.
+		// see https://frappeframework.com/docs/v14/user/en/python-api/hooks#sounds
+		this.success_sound = opts.play_success_sound;
+		this.fail_sound = opts.play_fail_sound;
+
 		// any API that takes `search_value` as input and returns dictionary as follows
 		// {
 		//     item_code: "HORSESHOE", // present if any item was found
@@ -54,19 +59,24 @@
 					if (!data || Object.keys(data).length === 0) {
 						this.show_alert(__("Cannot find Item with this Barcode"), "red");
 						this.clean_up();
+						this.play_fail_sound();
 						reject();
 						return;
 					}
 
 					me.update_table(data).then(row => {
-						row ? resolve(row) : reject();
+						this.play_success_sound();
+						resolve(row);
+					}).catch(() => {
+						this.play_fail_sound();
+						reject();
 					});
 				});
 		});
 	}
 
 	update_table(data) {
-		return new Promise(resolve => {
+		return new Promise((resolve, reject) => {
 			let cur_grid = this.frm.fields_dict[this.items_table_name].grid;
 
 			const {item_code, barcode, batch_no, serial_no, uom} = data;
@@ -77,6 +87,7 @@
 				if (this.dont_allow_new_row) {
 					this.show_alert(__("Maximum quantity scanned for item {0}.", [item_code]), "red");
 					this.clean_up();
+					reject();
 					return;
 				}
 
@@ -88,6 +99,7 @@
 
 			if (this.is_duplicate_serial_no(row, serial_no)) {
 				this.clean_up();
+				reject();
 				return;
 			}
 
@@ -219,6 +231,14 @@
 		return this.items_table.find((d) => !d.item_code);
 	}
 
+	play_success_sound() {
+		this.success_sound && frappe.utils.play_sound(this.success_sound);
+	}
+
+	play_fail_sound() {
+		this.fail_sound && frappe.utils.play_sound(this.fail_sound);
+	}
+
 	clean_up() {
 		this.scan_barcode_field.set_value("");
 		refresh_field(this.items_table_name);
diff --git a/erpnext/stock/report/item_price_stock/item_price_stock.py b/erpnext/stock/report/item_price_stock/item_price_stock.py
index 15218e6..1b07f59 100644
--- a/erpnext/stock/report/item_price_stock/item_price_stock.py
+++ b/erpnext/stock/report/item_price_stock/item_price_stock.py
@@ -62,22 +62,28 @@
 
 
 def get_item_price_qty_data(filters):
-	conditions = ""
-	if filters.get("item_code"):
-		conditions += "where a.item_code=%(item_code)s"
+	item_price = frappe.qb.DocType("Item Price")
+	bin = frappe.qb.DocType("Bin")
 
-	item_results = frappe.db.sql(
-		"""select a.item_code, a.item_name, a.name as price_list_name,
-		a.brand as brand, b.warehouse as warehouse, b.actual_qty as actual_qty
-		from `tabItem Price` a left join `tabBin` b
-		ON a.item_code = b.item_code
-		{conditions}""".format(
-			conditions=conditions
-		),
-		filters,
-		as_dict=1,
+	query = (
+		frappe.qb.from_(item_price)
+		.left_join(bin)
+		.on(item_price.item_code == bin.item_code)
+		.select(
+			item_price.item_code,
+			item_price.item_name,
+			item_price.name.as_("price_list_name"),
+			item_price.brand.as_("brand"),
+			bin.warehouse.as_("warehouse"),
+			bin.actual_qty.as_("actual_qty"),
+		)
 	)
 
+	if filters.get("item_code"):
+		query = query.where(item_price.item_code == filters.get("item_code"))
+
+	item_results = query.run(as_dict=True)
+
 	price_list_names = list(set(item.price_list_name for item in item_results))
 
 	buying_price_map = get_price_map(price_list_names, buying=1)