Merge pull request #30911 from alyf-de/de-translate-employee

fix: German translations for Employee doctype
diff --git a/.eslintrc b/.eslintrc
index 46fb354..276d6ff 100644
--- a/.eslintrc
+++ b/.eslintrc
@@ -5,7 +5,7 @@
 		"es6": true
 	},
 	"parserOptions": {
-		"ecmaVersion": 9,
+		"ecmaVersion": 11,
 		"sourceType": "module"
 	},
 	"extends": "eslint:recommended",
diff --git a/.github/stale.yml b/.github/stale.yml
index 1c2dcf3..fbf6447 100644
--- a/.github/stale.yml
+++ b/.github/stale.yml
@@ -25,7 +25,7 @@
     ready. Thank you for contributing.
 
 issues:
-  daysUntilStale: 60
+  daysUntilStale: 90
   daysUntilClose: 7
   exemptLabels:
     - valid
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py
index 7257e6d..a3a7be2 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.py
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py
@@ -346,6 +346,12 @@
 								)
 							)
 
+						if ref_doc.doctype == "Purchase Invoice" and ref_doc.get("on_hold"):
+							frappe.throw(
+								_("{0} {1} is on hold").format(d.reference_doctype, d.reference_name),
+								title=_("Invalid Invoice"),
+							)
+
 					if ref_doc.docstatus != 1:
 						frappe.throw(_("{0} {1} must be submitted").format(d.reference_doctype, d.reference_name))
 
diff --git a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py
index 5b70b51..a8211c8 100644
--- a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py
+++ b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py
@@ -743,6 +743,21 @@
 			flt(payment_entry.total_taxes_and_charges, 2), flt(10 / payment_entry.target_exchange_rate, 2)
 		)
 
+	def test_payment_entry_against_onhold_purchase_invoice(self):
+		pi = make_purchase_invoice()
+
+		pe = get_payment_entry("Purchase Invoice", pi.name, bank_account="_Test Bank USD - _TC")
+		pe.reference_no = "1"
+		pe.reference_date = "2016-01-01"
+
+		# block invoice after creating payment entry
+		# since `get_payment_entry` will not attach blocked invoice to payment
+		pi.block_invoice()
+		with self.assertRaises(frappe.ValidationError) as err:
+			pe.save()
+
+		self.assertTrue("is on hold" in str(err.exception).lower())
+
 
 def create_payment_entry(**args):
 	payment_entry = frappe.new_doc("Payment Entry")
diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
index a0c0ecc..b8154dd 100644
--- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
@@ -3140,6 +3140,39 @@
 		si.reload()
 		self.assertTrue(si.items[0].serial_no)
 
+	def test_sales_invoice_with_disabled_account(self):
+		try:
+			account = frappe.get_doc("Account", "VAT 5% - _TC")
+			account.disabled = 1
+			account.save()
+
+			si = create_sales_invoice(do_not_save=True)
+			si.posting_date = add_days(getdate(), 1)
+			si.taxes = []
+
+			si.append(
+				"taxes",
+				{
+					"charge_type": "On Net Total",
+					"account_head": "VAT 5% - _TC",
+					"cost_center": "Main - _TC",
+					"description": "VAT @ 5.0",
+					"rate": 9,
+				},
+			)
+			si.save()
+
+			with self.assertRaises(frappe.ValidationError) as err:
+				si.submit()
+
+			self.assertTrue(
+				"Cannot create accounting entries against disabled accounts" in str(err.exception)
+			)
+
+		finally:
+			account.disabled = 0
+			account.save()
+
 	def test_gain_loss_with_advance_entry(self):
 		from erpnext.accounts.doctype.journal_entry.test_journal_entry import make_journal_entry
 
diff --git a/erpnext/accounts/general_ledger.py b/erpnext/accounts/general_ledger.py
index 89034eb..1598d91 100644
--- a/erpnext/accounts/general_ledger.py
+++ b/erpnext/accounts/general_ledger.py
@@ -31,6 +31,7 @@
 	if gl_map:
 		if not cancel:
 			validate_accounting_period(gl_map)
+			validate_disabled_accounts(gl_map)
 			gl_map = process_gl_map(gl_map, merge_entries)
 			if gl_map and len(gl_map) > 1:
 				save_entries(gl_map, adv_adj, update_outstanding, from_repost)
@@ -45,6 +46,26 @@
 			make_reverse_gl_entries(gl_map, adv_adj=adv_adj, update_outstanding=update_outstanding)
 
 
+def validate_disabled_accounts(gl_map):
+	accounts = [d.account for d in gl_map if d.account]
+
+	Account = frappe.qb.DocType("Account")
+
+	disabled_accounts = (
+		frappe.qb.from_(Account)
+		.where(Account.name.isin(accounts) & Account.disabled == 1)
+		.select(Account.name, Account.disabled)
+	).run(as_dict=True)
+
+	if disabled_accounts:
+		account_list = "<br>"
+		account_list += ", ".join([frappe.bold(d.name) for d in disabled_accounts])
+		frappe.throw(
+			_("Cannot create accounting entries against disabled accounts: {0}").format(account_list),
+			title=_("Disabled Account Selected"),
+		)
+
+
 def validate_accounting_period(gl_map):
 	accounting_periods = frappe.db.sql(
 		""" SELECT
diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.py b/erpnext/buying/doctype/purchase_order/purchase_order.py
index 5860c4c..9189f18 100644
--- a/erpnext/buying/doctype/purchase_order/purchase_order.py
+++ b/erpnext/buying/doctype/purchase_order/purchase_order.py
@@ -637,6 +637,8 @@
 						}
 					}
 					stock_entry.add_to_stock_entry_detail(items_dict)
+
+		stock_entry.set_missing_values()
 		return stock_entry.as_dict()
 	else:
 		frappe.throw(_("No Items selected for transfer"))
@@ -724,7 +726,7 @@
 			add_items_in_ste(ste_doc, value, value.qty, po_details)
 
 	ste_doc.set_stock_entry_type()
-	ste_doc.calculate_rate_and_amount()
+	ste_doc.set_missing_values()
 
 	return ste_doc
 
diff --git a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.js b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.js
index e7049fd..4e29ee5 100644
--- a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.js
+++ b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.js
@@ -31,7 +31,7 @@
 		if (frm.doc.docstatus === 1) {
 
 			frm.add_custom_button(__('Supplier Quotation'),
-				function(){ frm.trigger("make_suppplier_quotation") }, __("Create"));
+				function(){ frm.trigger("make_supplier_quotation") }, __("Create"));
 
 
 			frm.add_custom_button(__("Send Emails to Suppliers"), function() {
@@ -87,16 +87,24 @@
 
 	},
 
-	make_suppplier_quotation: function(frm) {
+	make_supplier_quotation: function(frm) {
 		var doc = frm.doc;
 		var dialog = new frappe.ui.Dialog({
 			title: __("Create Supplier Quotation"),
 			fields: [
-				{	"fieldtype": "Select", "label": __("Supplier"),
+				{	"fieldtype": "Link",
+					"label": __("Supplier"),
 					"fieldname": "supplier",
-					"options": doc.suppliers.map(d => d.supplier),
+					"options": 'Supplier',
 					"reqd": 1,
-					"default": doc.suppliers.length === 1 ? doc.suppliers[0].supplier_name : "" },
+					get_query: () => {
+						return {
+							filters: [
+								["Supplier", "name", "in", frm.doc.suppliers.map((row) => {return row.supplier;})]
+							]
+						}
+					}
+				}
 			],
 			primary_action_label: __("Create"),
 			primary_action: (args) => {
diff --git a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.json b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.json
index 4993df9..083cab7 100644
--- a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.json
+++ b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.json
@@ -32,7 +32,9 @@
   "terms",
   "printing_settings",
   "select_print_heading",
-  "letter_head"
+  "letter_head",
+  "more_info",
+  "opportunity"
  ],
  "fields": [
   {
@@ -194,6 +196,23 @@
    "print_hide": 1
   },
   {
+   "collapsible": 1,
+   "fieldname": "more_info",
+   "fieldtype": "Section Break",
+   "label": "More Information",
+   "oldfieldtype": "Section Break",
+   "options": "fa fa-file-text",
+   "print_hide": 1
+  },
+  {
+   "fieldname": "opportunity",
+   "fieldtype": "Link",
+   "label": "Opportunity",
+   "options": "Opportunity",
+   "print_hide": 1,
+   "read_only": 1
+  },
+  {
    "fieldname": "status",
    "fieldtype": "Select",
    "label": "Status",
@@ -258,7 +277,7 @@
  "index_web_pages_for_search": 1,
  "is_submittable": 1,
  "links": [],
- "modified": "2021-11-24 17:47:49.909000",
+ "modified": "2022-04-06 17:47:49.909000",
  "modified_by": "Administrator",
  "module": "Buying",
  "name": "Request for Quotation",
@@ -327,4 +346,4 @@
  "show_name_in_global_search": 1,
  "sort_field": "modified",
  "sort_order": "DESC"
-}
\ No newline at end of file
+}
diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py
index 78645e0..46013bb 100644
--- a/erpnext/controllers/accounts_controller.py
+++ b/erpnext/controllers/accounts_controller.py
@@ -2451,11 +2451,21 @@
 			parent_doctype, parent_doctype_name, child_doctype, child_docname, item_row
 		)
 
-	def validate_quantity(child_item, d):
-		if parent_doctype == "Sales Order" and flt(d.get("qty")) < flt(child_item.delivered_qty):
+	def validate_quantity(child_item, new_data):
+		if not flt(new_data.get("qty")):
+			frappe.throw(
+				_("Row # {0}: Quantity for Item {1} cannot be zero").format(
+					new_data.get("idx"), frappe.bold(new_data.get("item_code"))
+				),
+				title=_("Invalid Qty"),
+			)
+
+		if parent_doctype == "Sales Order" and flt(new_data.get("qty")) < flt(child_item.delivered_qty):
 			frappe.throw(_("Cannot set quantity less than delivered quantity"))
 
-		if parent_doctype == "Purchase Order" and flt(d.get("qty")) < flt(child_item.received_qty):
+		if parent_doctype == "Purchase Order" and flt(new_data.get("qty")) < flt(
+			child_item.received_qty
+		):
 			frappe.throw(_("Cannot set quantity less than received quantity"))
 
 	data = json.loads(trans_items)
diff --git a/erpnext/controllers/queries.py b/erpnext/controllers/queries.py
index abe9977..eeb5a7f 100644
--- a/erpnext/controllers/queries.py
+++ b/erpnext/controllers/queries.py
@@ -162,6 +162,7 @@
 				{account_type_condition}
 				AND is_group = 0
 				AND company = %(company)s
+				AND disabled = %(disabled)s
 				AND (account_currency = %(currency)s or ifnull(account_currency, '') = '')
 				AND `{searchfield}` LIKE %(txt)s
 				{mcond}
@@ -175,6 +176,7 @@
 			dict(
 				account_types=filters.get("account_type"),
 				company=filters.get("company"),
+				disabled=filters.get("disabled", 0),
 				currency=company_currency,
 				txt="%{}%".format(txt),
 				offset=start,
diff --git a/erpnext/crm/doctype/opportunity/opportunity_dashboard.py b/erpnext/crm/doctype/opportunity/opportunity_dashboard.py
index d4c5e02..d3a10bc 100644
--- a/erpnext/crm/doctype/opportunity/opportunity_dashboard.py
+++ b/erpnext/crm/doctype/opportunity/opportunity_dashboard.py
@@ -2,6 +2,6 @@
 	return {
 		"fieldname": "opportunity",
 		"transactions": [
-			{"items": ["Quotation", "Supplier Quotation"]},
+			{"items": ["Quotation", "Request for Quotation", "Supplier Quotation"]},
 		],
 	}
diff --git a/erpnext/manufacturing/doctype/job_card/job_card.py b/erpnext/manufacturing/doctype/job_card/job_card.py
index bf4f82f..a98fc94 100644
--- a/erpnext/manufacturing/doctype/job_card/job_card.py
+++ b/erpnext/manufacturing/doctype/job_card/job_card.py
@@ -764,8 +764,6 @@
 		pending_fg_qty = flt(source.get("for_quantity", 0)) - flt(source.get("transferred_qty", 0))
 		target.fg_completed_qty = pending_fg_qty if pending_fg_qty > 0 else 0
 
-		target.set_transfer_qty()
-		target.calculate_rate_and_amount()
 		target.set_missing_values()
 		target.set_stock_entry_type()
 
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index d5b1592..1fef240 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -369,4 +369,5 @@
 erpnext.patches.v13_0.change_default_item_manufacturer_fieldtype
 erpnext.patches.v14_0.discount_accounting_separation
 erpnext.patches.v14_0.delete_employee_transfer_property_doctype
-erpnext.patches.v13_0.create_accounting_dimensions_in_orders
\ No newline at end of file
+erpnext.patches.v13_0.create_accounting_dimensions_in_orders
+erpnext.patches.v13_0.set_per_billed_in_return_delivery_note
\ No newline at end of file
diff --git a/erpnext/patches/v13_0/create_accounting_dimensions_in_orders.py b/erpnext/patches/v13_0/create_accounting_dimensions_in_orders.py
index 8a3f1d0..7e6e820 100644
--- a/erpnext/patches/v13_0/create_accounting_dimensions_in_orders.py
+++ b/erpnext/patches/v13_0/create_accounting_dimensions_in_orders.py
@@ -33,7 +33,7 @@
 				"insert_after": insert_after_field,
 			}
 
-			create_custom_field(doctype, df, ignore_validate=False)
+			create_custom_field(doctype, df, ignore_validate=True)
 			frappe.clear_cache(doctype=doctype)
 
 		count += 1
diff --git a/erpnext/patches/v13_0/set_per_billed_in_return_delivery_note.py b/erpnext/patches/v13_0/set_per_billed_in_return_delivery_note.py
new file mode 100644
index 0000000..a4d7012
--- /dev/null
+++ b/erpnext/patches/v13_0/set_per_billed_in_return_delivery_note.py
@@ -0,0 +1,29 @@
+# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+import frappe
+
+
+def execute():
+	dn = frappe.qb.DocType("Delivery Note")
+	dn_item = frappe.qb.DocType("Delivery Note Item")
+
+	dn_list = (
+		frappe.qb.from_(dn)
+		.inner_join(dn_item)
+		.on(dn.name == dn_item.parent)
+		.select(dn.name)
+		.where(dn.docstatus == 1)
+		.where(dn.is_return == 1)
+		.where(dn.per_billed < 100)
+		.where(dn_item.returned_qty > 0)
+		.run(as_dict=True)
+	)
+
+	frappe.qb.update(dn_item).inner_join(dn).on(dn.name == dn_item.parent).set(
+		dn_item.returned_qty, 0
+	).where(dn.is_return == 1).where(dn_item.returned_qty > 0).run()
+
+	for d in dn_list:
+		dn_doc = frappe.get_doc("Delivery Note", d.get("name"))
+		dn_doc.run_method("update_billing_status")
diff --git a/erpnext/public/js/controllers/accounts.js b/erpnext/public/js/controllers/accounts.js
index 84c7176..c1fe72b 100644
--- a/erpnext/public/js/controllers/accounts.js
+++ b/erpnext/public/js/controllers/accounts.js
@@ -27,7 +27,8 @@
 					query: "erpnext.controllers.queries.tax_account_query",
 					filters: {
 						"account_type": account_type,
-						"company": doc.company
+						"company": doc.company,
+						"disabled": 0
 					}
 				}
 			});
diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js
index 767221e..c3812f3 100644
--- a/erpnext/public/js/controllers/transaction.js
+++ b/erpnext/public/js/controllers/transaction.js
@@ -1384,12 +1384,15 @@
 		var me = this;
 		var args = this._get_args(item);
 		if (!(args.items && args.items.length)) {
-			if(calculate_taxes_and_totals) me.calculate_taxes_and_totals();
+			if (calculate_taxes_and_totals) me.calculate_taxes_and_totals();
 			return;
 		}
 
 		// Target doc created from a mapped doc
 		if (this.frm.doc.__onload && this.frm.doc.__onload.ignore_price_list) {
+			// Calculate totals even though pricing rule is not applied.
+			// `apply_pricing_rule` is triggered due to change in data which most likely contributes to Total.
+			if (calculate_taxes_and_totals) me.calculate_taxes_and_totals();
 			return;
 		}
 
diff --git a/erpnext/public/js/utils/barcode_scanner.js b/erpnext/public/js/utils/barcode_scanner.js
index f72b85c..3ae1234 100644
--- a/erpnext/public/js/utils/barcode_scanner.js
+++ b/erpnext/public/js/utils/barcode_scanner.js
@@ -10,6 +10,12 @@
 		this.serial_no_field = opts.serial_no_field || "serial_no";
 		this.batch_no_field = opts.batch_no_field || "batch_no";
 		this.qty_field = opts.qty_field || "qty";
+		// field name on row which defines max quantity to be scanned e.g. picklist
+		this.max_qty_field = opts.max_qty_field;
+		// scanner won't add a new row if this flag is set.
+		this.dont_allow_new_row = opts.dont_allow_new_row;
+		// scanner will ask user to type the quantity instead of incrementing by 1
+		this.prompt_qty = opts.prompt_qty;
 
 		this.items_table_name = opts.items_table_name || "items";
 		this.items_table = this.frm.doc[this.items_table_name];
@@ -42,10 +48,7 @@
 			.then((r) => {
 				const data = r && r.message;
 				if (!data || Object.keys(data).length === 0) {
-					frappe.show_alert({
-						message: __("Cannot find Item with this Barcode"),
-						indicator: "red",
-					});
+					this.show_alert(__("Cannot find Item with this Barcode"), "red");
 					this.clean_up();
 					return;
 				}
@@ -56,22 +59,18 @@
 
 	update_table(data) {
 		let cur_grid = this.frm.fields_dict[this.items_table_name].grid;
-		let row = null;
 
 		const {item_code, barcode, batch_no, serial_no} = data;
 
-		// Check if batch is scanned and table has batch no field
-		let batch_no_scan =
-			Boolean(batch_no) && frappe.meta.has_field(cur_grid.doctype, this.batch_no_field);
-
-		if (batch_no_scan) {
-			row = this.get_batch_row_to_modify(batch_no);
-		} else {
-			// serial or barcode scan
-			row = this.get_row_to_modify_on_scan(item_code);
-		}
+		let row = this.get_row_to_modify_on_scan(item_code, batch_no);
 
 		if (!row) {
+			if (this.dont_allow_new_row) {
+				this.show_alert(__("Maximum quantity scanned for item {0}.", [item_code]), "red");
+				this.clean_up();
+				return;
+			}
+
 			// add new row if new item/batch is scanned
 			row = frappe.model.add_child(this.frm.doc, cur_grid.doctype, this.items_table_name);
 			// trigger any row add triggers defined on child table.
@@ -83,9 +82,10 @@
 			return;
 		}
 
-		this.show_scan_message(row.idx, row.item_code);
 		this.set_selector_trigger_flag(row, data);
-		this.set_item(row, item_code);
+		this.set_item(row, item_code).then(qty => {
+			this.show_scan_message(row.idx, row.item_code, qty);
+		});
 		this.set_serial_no(row, serial_no);
 		this.set_batch_no(row, batch_no);
 		this.set_barcode(row, barcode);
@@ -106,9 +106,23 @@
 	}
 
 	set_item(row, item_code) {
-		const item_data = { item_code: item_code };
-		item_data[this.qty_field] = (row[this.qty_field] || 0) + 1;
-		frappe.model.set_value(row.doctype, row.name, item_data);
+		return new Promise(resolve => {
+			const increment = (value = 1) => {
+				const item_data = {item_code: item_code};
+				item_data[this.qty_field] = Number((row[this.qty_field] || 0)) + Number(value);
+				frappe.model.set_value(row.doctype, row.name, item_data);
+			};
+
+			if (this.prompt_qty) {
+				frappe.prompt(__("Please enter quantity for item {0}", [item_code]), ({value}) => {
+					increment(value);
+					resolve(value);
+				});
+			} else {
+				increment();
+				resolve();
+			}
+		});
 	}
 
 	set_serial_no(row, serial_no) {
@@ -137,53 +151,42 @@
 		}
 	}
 
-	show_scan_message(idx, exist = null) {
+	show_scan_message(idx, exist = null, qty = 1) {
 		// show new row or qty increase toast
 		if (exist) {
-			frappe.show_alert(
-				{
-					message: __("Row #{0}: Qty increased by 1", [idx]),
-					indicator: "green",
-				},
-				5
-			);
+			this.show_alert(__("Row #{0}: Qty increased by {1}", [idx, qty]), "green");
 		} else {
-			frappe.show_alert(
-				{
-					message: __("Row #{0}: Item added", [idx]),
-					indicator: "green",
-				},
-				5
-			);
+			this.show_alert(__("Row #{0}: Item added", [idx]), "green")
 		}
 	}
 
 	is_duplicate_serial_no(row, serial_no) {
-		const is_duplicate = !!serial_no && !!row[this.serial_no_field]
-			&& row[this.serial_no_field].includes(serial_no);
+		const is_duplicate = row[this.serial_no_field]?.includes(serial_no);
 
 		if (is_duplicate) {
-			frappe.show_alert(
-				{
-					message: __("Serial No {0} is already added", [serial_no]),
-					indicator: "orange",
-				},
-				5
-			);
+			this.show_alert(__("Serial No {0} is already added", [serial_no]), "orange");
 		}
 		return is_duplicate;
 	}
 
-	get_batch_row_to_modify(batch_no) {
-		// get row if batch already exists in table
-		const existing_batch_row = this.items_table.find((d) => d.batch_no === batch_no);
-		return existing_batch_row || this.get_existing_blank_row();
-	}
+	get_row_to_modify_on_scan(item_code, batch_no) {
+		let cur_grid = this.frm.fields_dict[this.items_table_name].grid;
 
-	get_row_to_modify_on_scan(item_code) {
-		// get an existing item row to increment or blank row to modify
-		const existing_item_row = this.items_table.find((d) => d.item_code === item_code);
-		return existing_item_row || this.get_existing_blank_row();
+		// Check if batch is scanned and table has batch no field
+		let is_batch_no_scan = batch_no && frappe.meta.has_field(cur_grid.doctype, this.batch_no_field);
+		let check_max_qty = this.max_qty_field && frappe.meta.has_field(cur_grid.doctype, this.max_qty_field);
+
+		const matching_row = (row) => {
+			const item_match = row.item_code == item_code;
+			const batch_match = row.batch_no == batch_no;
+			const qty_in_limit = flt(row[this.qty_field]) < flt(row[this.max_qty_field]);
+
+			return item_match
+				&& (!is_batch_no_scan || batch_match)
+				&& (!check_max_qty || qty_in_limit)
+		}
+
+		return this.items_table.find(matching_row) || this.get_existing_blank_row();
 	}
 
 	get_existing_blank_row() {
@@ -194,4 +197,7 @@
 		this.scan_barcode_field.set_value("");
 		refresh_field(this.items_table_name);
 	}
+	show_alert(msg, indicator, duration=3) {
+		frappe.show_alert({message: msg, indicator: indicator}, duration);
+	}
 };
diff --git a/erpnext/stock/doctype/bin/bin.py b/erpnext/stock/doctype/bin/bin.py
index 1bfce66..bc235d9 100644
--- a/erpnext/stock/doctype/bin/bin.py
+++ b/erpnext/stock/doctype/bin/bin.py
@@ -64,13 +64,18 @@
 		se = frappe.qb.DocType("Stock Entry")
 		se_item = frappe.qb.DocType("Stock Entry Detail")
 
+		if frappe.db.field_exists("Stock Entry", "is_return"):
+			qty_field = (
+				Case().when(se.is_return == 1, se_item.transfer_qty * -1).else_(se_item.transfer_qty)
+			)
+		else:
+			qty_field = se_item.transfer_qty
+
 		materials_transferred = (
 			frappe.qb.from_(se)
 			.from_(se_item)
 			.from_(po)
-			.select(
-				Sum(Case().when(se.is_return == 1, se_item.transfer_qty * -1).else_(se_item.transfer_qty))
-			)
+			.select(Sum(qty_field))
 			.where(
 				(se.docstatus == 1)
 				& (se.purpose == "Send to Subcontractor")
diff --git a/erpnext/stock/doctype/delivery_note/test_delivery_note.py b/erpnext/stock/doctype/delivery_note/test_delivery_note.py
index f97e7ca..0738bfb 100644
--- a/erpnext/stock/doctype/delivery_note/test_delivery_note.py
+++ b/erpnext/stock/doctype/delivery_note/test_delivery_note.py
@@ -962,6 +962,44 @@
 
 		automatically_fetch_payment_terms(enable=0)
 
+	def test_returned_qty_in_return_dn(self):
+		# SO ---> SI ---> DN
+		#                 |
+		#                 |---> DN(Partial Sales Return) ---> SI(Credit Note)
+		#                 |
+		#                 |---> DN(Partial Sales Return) ---> SI(Credit Note)
+
+		from erpnext.accounts.doctype.sales_invoice.sales_invoice import make_delivery_note
+		from erpnext.selling.doctype.sales_order.sales_order import make_sales_invoice
+
+		so = make_sales_order(qty=10)
+		si = make_sales_invoice(so.name)
+		si.insert()
+		si.submit()
+		dn = make_delivery_note(si.name)
+		dn.insert()
+		dn.submit()
+		self.assertEqual(dn.items[0].returned_qty, 0)
+		self.assertEqual(dn.per_billed, 100)
+
+		from erpnext.stock.doctype.delivery_note.delivery_note import make_sales_invoice
+
+		dn1 = create_delivery_note(is_return=1, return_against=dn.name, qty=-3)
+		si1 = make_sales_invoice(dn1.name)
+		si1.insert()
+		si1.submit()
+		dn1.reload()
+		self.assertEqual(dn1.items[0].returned_qty, 0)
+		self.assertEqual(dn1.per_billed, 100)
+
+		dn2 = create_delivery_note(is_return=1, return_against=dn.name, qty=-4)
+		si2 = make_sales_invoice(dn2.name)
+		si2.insert()
+		si2.submit()
+		dn2.reload()
+		self.assertEqual(dn2.items[0].returned_qty, 0)
+		self.assertEqual(dn2.per_billed, 100)
+
 
 def create_delivery_note(**args):
 	dn = frappe.new_doc("Delivery Note")
diff --git a/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json b/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json
index e2eb2a4..2d7abc8 100644
--- a/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json
+++ b/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json
@@ -737,7 +737,9 @@
    "depends_on": "returned_qty",
    "fieldname": "returned_qty",
    "fieldtype": "Float",
-   "label": "Returned Qty in Stock UOM"
+   "label": "Returned Qty in Stock UOM",
+   "no_copy": 1,
+   "read_only": 1
   },
   {
    "fieldname": "incoming_rate",
@@ -778,7 +780,7 @@
  "index_web_pages_for_search": 1,
  "istable": 1,
  "links": [],
- "modified": "2022-03-31 18:36:24.671913",
+ "modified": "2022-05-02 12:09:39.610075",
  "modified_by": "Administrator",
  "module": "Stock",
  "name": "Delivery Note Item",
diff --git a/erpnext/stock/doctype/material_request/material_request.py b/erpnext/stock/doctype/material_request/material_request.py
index a70ff17..c998629 100644
--- a/erpnext/stock/doctype/material_request/material_request.py
+++ b/erpnext/stock/doctype/material_request/material_request.py
@@ -597,7 +597,7 @@
 		if source.material_request_type == "Customer Provided":
 			target.purpose = "Material Receipt"
 
-		target.run_method("calculate_rate_and_amount")
+		target.set_missing_values()
 		target.set_stock_entry_type()
 		target.set_job_card_data()
 
diff --git a/erpnext/stock/doctype/pick_list/pick_list.js b/erpnext/stock/doctype/pick_list/pick_list.js
index 13b74b5..799406c 100644
--- a/erpnext/stock/doctype/pick_list/pick_list.js
+++ b/erpnext/stock/doctype/pick_list/pick_list.js
@@ -158,6 +158,19 @@
 				get_query_filters: get_query_filters
 			});
 		});
+	},
+	scan_barcode: (frm) => {
+		const opts = {
+			frm,
+			items_table_name: 'locations',
+			qty_field: 'picked_qty',
+			max_qty_field: 'qty',
+			dont_allow_new_row: true,
+			prompt_qty: frm.doc.prompt_qty,
+			serial_no_field: "not_supported",  // doesn't make sense for picklist without a separate field.
+		};
+		const barcode_scanner = new erpnext.utils.BarcodeScanner(opts);
+		barcode_scanner.process_scan();
 	}
 });
 
diff --git a/erpnext/stock/doctype/pick_list/pick_list.json b/erpnext/stock/doctype/pick_list/pick_list.json
index e984c08..ff20909 100644
--- a/erpnext/stock/doctype/pick_list/pick_list.json
+++ b/erpnext/stock/doctype/pick_list/pick_list.json
@@ -17,6 +17,11 @@
   "parent_warehouse",
   "get_item_locations",
   "section_break_6",
+  "scan_barcode",
+  "column_break_13",
+  "scan_mode",
+  "prompt_qty",
+  "section_break_15",
   "locations",
   "amended_from",
   "print_settings_section",
@@ -36,6 +41,7 @@
    "fieldtype": "Column Break"
   },
   {
+   "depends_on": "eval:!doc.docstatus",
    "fieldname": "section_break_6",
    "fieldtype": "Section Break"
   },
@@ -126,11 +132,38 @@
    "fieldtype": "Check",
    "label": "Group Same Items",
    "print_hide": 1
+  },
+  {
+   "fieldname": "section_break_15",
+   "fieldtype": "Section Break"
+  },
+  {
+   "fieldname": "scan_barcode",
+   "fieldtype": "Data",
+   "label": "Scan Barcode",
+   "options": "Barcode"
+  },
+  {
+   "fieldname": "column_break_13",
+   "fieldtype": "Column Break"
+  },
+  {
+   "default": "0",
+   "description": "If checked, picked qty won't automatically be fulfilled on submit of pick list.",
+   "fieldname": "scan_mode",
+   "fieldtype": "Check",
+   "label": "Scan Mode"
+  },
+  {
+   "default": "0",
+   "fieldname": "prompt_qty",
+   "fieldtype": "Check",
+   "label": "Prompt Qty"
   }
  ],
  "is_submittable": 1,
  "links": [],
- "modified": "2022-04-21 07:56:40.646473",
+ "modified": "2022-05-11 09:09:53.029312",
  "modified_by": "Administrator",
  "module": "Stock",
  "name": "Pick List",
diff --git a/erpnext/stock/doctype/pick_list/pick_list.py b/erpnext/stock/doctype/pick_list/pick_list.py
index 70d2f23..7dc3ba0 100644
--- a/erpnext/stock/doctype/pick_list/pick_list.py
+++ b/erpnext/stock/doctype/pick_list/pick_list.py
@@ -41,8 +41,15 @@
 	def before_submit(self):
 		update_sales_orders = set()
 		for item in self.locations:
-			# if the user has not entered any picked qty, set it to stock_qty, before submit
-			if item.picked_qty == 0:
+			if self.scan_mode and item.picked_qty < item.stock_qty:
+				frappe.throw(
+					_(
+						"Row {0} picked quantity is less than the required quantity, additional {1} {2} required."
+					).format(item.idx, item.stock_qty - item.picked_qty, item.stock_uom),
+					title=_("Pick List Incomplete"),
+				)
+			elif not self.scan_mode and item.picked_qty == 0:
+				# if the user has not entered any picked qty, set it to stock_qty, before submit
 				item.picked_qty = item.stock_qty
 
 			if item.sales_order_item:
@@ -672,8 +679,7 @@
 	else:
 		stock_entry = update_stock_entry_items_with_no_reference(pick_list, stock_entry)
 
-	stock_entry.set_actual_qty()
-	stock_entry.calculate_rate_and_amount()
+	stock_entry.set_missing_values()
 
 	return stock_entry.as_dict()
 
diff --git a/erpnext/stock/doctype/pick_list_item/pick_list_item.json b/erpnext/stock/doctype/pick_list_item/pick_list_item.json
index a96ebfc..a6f8c0d 100644
--- a/erpnext/stock/doctype/pick_list_item/pick_list_item.json
+++ b/erpnext/stock/doctype/pick_list_item/pick_list_item.json
@@ -202,4 +202,4 @@
  "sort_order": "DESC",
  "states": [],
  "track_changes": 1
-}
\ No newline at end of file
+}
diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
index ec0e809..fcf0cd1 100644
--- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
+++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
@@ -1036,6 +1036,7 @@
 	def set_missing_values(source, target):
 		target.stock_entry_type = "Material Transfer"
 		target.purpose = "Material Transfer"
+		target.set_missing_values()
 
 	doclist = get_mapped_doc(
 		"Purchase Receipt",
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.js b/erpnext/stock/doctype/stock_entry/stock_entry.js
index 1df56ef..540ad18 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.js
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.js
@@ -470,7 +470,9 @@
 				},
 				callback: function(r) {
 					if (!r.exc) {
-						$.extend(child, r.message);
+						["actual_qty", "basic_rate"].forEach((field) => {
+							frappe.model.set_value(cdt, cdn, field, (r.message[field] || 0.0));
+						});
 						frm.events.calculate_basic_amount(frm, child);
 					}
 				}
@@ -1057,8 +1059,8 @@
 
 function check_should_not_attach_bom_items(bom_no) {
   return (
-    bom_no === undefined ||
-    (erpnext.stock.bom && erpnext.stock.bom.name === bom_no)
+	bom_no === undefined ||
+	(erpnext.stock.bom && erpnext.stock.bom.name === bom_no)
   );
 }
 
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py
index 27a6eaf..2a7354d 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.py
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.py
@@ -2197,6 +2197,12 @@
 
 		return sorted(list(set(get_serial_nos(self.pro_doc.serial_no)) - set(used_serial_nos)))
 
+	def set_missing_values(self):
+		"Updates rate and availability of all the items of mapped doc."
+		self.set_transfer_qty()
+		self.set_actual_qty()
+		self.calculate_rate_and_amount()
+
 
 @frappe.whitelist()
 def move_sample_to_retention_warehouse(company, items):
@@ -2246,6 +2252,7 @@
 def make_stock_in_entry(source_name, target_doc=None):
 	def set_missing_values(source, target):
 		target.set_stock_entry_type()
+		target.set_missing_values()
 
 	def update_item(source_doc, target_doc, source_parent):
 		target_doc.t_warehouse = ""
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry_utils.py b/erpnext/stock/doctype/stock_entry/stock_entry_utils.py
index b3df728..c5c0cef 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry_utils.py
+++ b/erpnext/stock/doctype/stock_entry/stock_entry_utils.py
@@ -132,6 +132,7 @@
 	)
 
 	s.set_stock_entry_type()
+
 	if not args.do_not_save:
 		s.insert()
 		if not args.do_not_submit:
diff --git a/erpnext/stock/doctype/stock_entry/test_stock_entry.py b/erpnext/stock/doctype/stock_entry/test_stock_entry.py
index b9c57c1..71baf9f 100644
--- a/erpnext/stock/doctype/stock_entry/test_stock_entry.py
+++ b/erpnext/stock/doctype/stock_entry/test_stock_entry.py
@@ -1424,6 +1424,25 @@
 
 		self.assertRaises(frappe.ValidationError, se.save)
 
+	def test_mapped_stock_entry(self):
+		"Check if rate and stock details are populated in mapped SE given warehouse."
+		from erpnext.stock.doctype.purchase_receipt.purchase_receipt import make_stock_entry
+		from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt
+
+		item_code = "_TestMappedItem"
+		create_item(item_code, is_stock_item=True)
+
+		pr = make_purchase_receipt(
+			item_code=item_code, qty=2, rate=100, company="_Test Company", warehouse="Stores - _TC"
+		)
+
+		mapped_se = make_stock_entry(pr.name)
+
+		self.assertEqual(mapped_se.items[0].s_warehouse, "Stores - _TC")
+		self.assertEqual(mapped_se.items[0].actual_qty, 2)
+		self.assertEqual(mapped_se.items[0].basic_rate, 100)
+		self.assertEqual(mapped_se.items[0].basic_amount, 200)
+
 
 def make_serialized_item(**args):
 	args = frappe._dict(args)
diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.json b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.json
index e545b8e..9a85431 100644
--- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.json
+++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.json
@@ -170,6 +170,7 @@
    "options": "Warehouse"
   },
   {
+   "depends_on": "eval:!doc.docstatus",
    "fieldname": "section_break_22",
    "fieldtype": "Section Break"
   },
@@ -182,7 +183,7 @@
  "idx": 1,
  "is_submittable": 1,
  "links": [],
- "modified": "2022-03-27 08:57:47.161959",
+ "modified": "2022-05-11 09:10:26.327652",
  "modified_by": "Administrator",
  "module": "Stock",
  "name": "Stock Reconciliation",
diff --git a/erpnext/stock/report/fifo_queue_vs_qty_after_transaction_comparison/__init__.py b/erpnext/stock/report/fifo_queue_vs_qty_after_transaction_comparison/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/stock/report/fifo_queue_vs_qty_after_transaction_comparison/__init__.py
diff --git a/erpnext/stock/report/fifo_queue_vs_qty_after_transaction_comparison/fifo_queue_vs_qty_after_transaction_comparison.js b/erpnext/stock/report/fifo_queue_vs_qty_after_transaction_comparison/fifo_queue_vs_qty_after_transaction_comparison.js
new file mode 100644
index 0000000..0b8f496
--- /dev/null
+++ b/erpnext/stock/report/fifo_queue_vs_qty_after_transaction_comparison/fifo_queue_vs_qty_after_transaction_comparison.js
@@ -0,0 +1,53 @@
+// Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+/* eslint-disable */
+
+const DIFFERNCE_FIELD_NAMES = [
+	"fifo_qty_diff",
+	"fifo_value_diff",
+];
+
+frappe.query_reports["FIFO Queue vs Qty After Transaction Comparison"] = {
+	"filters": [
+		{
+			"fieldname": "item_code",
+			"fieldtype": "Link",
+			"label": "Item",
+			"options": "Item",
+			get_query: function() {
+				return {
+					filters: {is_stock_item: 1, has_serial_no: 0}
+				}
+			}
+		},
+		{
+			"fieldname": "item_group",
+			"fieldtype": "Link",
+			"label": "Item Group",
+			"options": "Item Group",
+		},
+		{
+			"fieldname": "warehouse",
+			"fieldtype": "Link",
+			"label": "Warehouse",
+			"options": "Warehouse",
+		},
+		{
+			"fieldname": "from_date",
+			"fieldtype": "Date",
+			"label": "From Posting Date",
+		},
+		{
+			"fieldname": "to_date",
+			"fieldtype": "Date",
+			"label": "From Posting Date",
+		}
+	],
+	formatter (value, row, column, data, default_formatter) {
+		value = default_formatter(value, row, column, data);
+		if (DIFFERNCE_FIELD_NAMES.includes(column.fieldname) && Math.abs(data[column.fieldname]) > 0.001) {
+			value = "<span style='color:red'>" + value + "</span>";
+		}
+		return value;
+	},
+};
diff --git a/erpnext/stock/report/fifo_queue_vs_qty_after_transaction_comparison/fifo_queue_vs_qty_after_transaction_comparison.json b/erpnext/stock/report/fifo_queue_vs_qty_after_transaction_comparison/fifo_queue_vs_qty_after_transaction_comparison.json
new file mode 100644
index 0000000..5e958aa
--- /dev/null
+++ b/erpnext/stock/report/fifo_queue_vs_qty_after_transaction_comparison/fifo_queue_vs_qty_after_transaction_comparison.json
@@ -0,0 +1,27 @@
+{
+ "add_total_row": 0,
+ "columns": [],
+ "creation": "2022-05-11 04:09:13.460652",
+ "disable_prepared_report": 0,
+ "disabled": 0,
+ "docstatus": 0,
+ "doctype": "Report",
+ "filters": [],
+ "idx": 0,
+ "is_standard": "Yes",
+ "letter_head": "abc",
+ "modified": "2022-05-11 04:09:20.232177",
+ "modified_by": "Administrator",
+ "module": "Stock",
+ "name": "FIFO Queue vs Qty After Transaction Comparison",
+ "owner": "Administrator",
+ "prepared_report": 0,
+ "ref_doctype": "Stock Ledger Entry",
+ "report_name": "FIFO Queue vs Qty After Transaction Comparison",
+ "report_type": "Script Report",
+ "roles": [
+  {
+   "role": "Administrator"
+  }
+ ]
+}
\ No newline at end of file
diff --git a/erpnext/stock/report/fifo_queue_vs_qty_after_transaction_comparison/fifo_queue_vs_qty_after_transaction_comparison.py b/erpnext/stock/report/fifo_queue_vs_qty_after_transaction_comparison/fifo_queue_vs_qty_after_transaction_comparison.py
new file mode 100644
index 0000000..9e14033
--- /dev/null
+++ b/erpnext/stock/report/fifo_queue_vs_qty_after_transaction_comparison/fifo_queue_vs_qty_after_transaction_comparison.py
@@ -0,0 +1,212 @@
+# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+import json
+
+import frappe
+from frappe import _
+from frappe.utils import flt
+from frappe.utils.nestedset import get_descendants_of
+
+SLE_FIELDS = (
+	"name",
+	"item_code",
+	"warehouse",
+	"posting_date",
+	"posting_time",
+	"creation",
+	"voucher_type",
+	"voucher_no",
+	"actual_qty",
+	"qty_after_transaction",
+	"stock_queue",
+	"batch_no",
+	"stock_value",
+	"valuation_rate",
+)
+
+
+def execute(filters=None):
+	columns = get_columns()
+	data = get_data(filters)
+	return columns, data
+
+
+def get_data(filters):
+	if not any([filters.warehouse, filters.item_code, filters.item_group]):
+		frappe.throw(_("Any one of following filters required: warehouse, Item Code, Item Group"))
+	sles = get_stock_ledger_entries(filters)
+	return find_first_bad_queue(sles)
+
+
+def get_stock_ledger_entries(filters):
+
+	sle_filters = {"is_cancelled": 0}
+
+	if filters.warehouse:
+		children = get_descendants_of("Warehouse", filters.warehouse)
+		sle_filters["warehouse"] = ("in", children + [filters.warehouse])
+
+	if filters.item_code:
+		sle_filters["item_code"] = filters.item_code
+	elif filters.get("item_group"):
+		item_group = filters.get("item_group")
+		children = get_descendants_of("Item Group", item_group)
+		item_group_filter = {"item_group": ("in", children + [item_group])}
+		sle_filters["item_code"] = (
+			"in",
+			frappe.get_all("Item", filters=item_group_filter, pluck="name", order_by=None),
+		)
+
+	if filters.from_date:
+		sle_filters["posting_date"] = (">=", filters.from_date)
+	if filters.to_date:
+		sle_filters["posting_date"] = ("<=", filters.to_date)
+
+	return frappe.get_all(
+		"Stock Ledger Entry",
+		fields=SLE_FIELDS,
+		filters=sle_filters,
+		order_by="timestamp(posting_date, posting_time), creation",
+	)
+
+
+def find_first_bad_queue(sles):
+	item_warehouse_sles = {}
+	for sle in sles:
+		item_warehouse_sles.setdefault((sle.item_code, sle.warehouse), []).append(sle)
+
+	data = []
+
+	for _item_wh, sles in item_warehouse_sles.items():
+		for idx, sle in enumerate(sles):
+			queue = json.loads(sle.stock_queue or "[]")
+
+			sle.fifo_queue_qty = 0.0
+			sle.fifo_stock_value = 0.0
+			for qty, rate in queue:
+				sle.fifo_queue_qty += flt(qty)
+				sle.fifo_stock_value += flt(qty) * flt(rate)
+
+			sle.fifo_qty_diff = sle.qty_after_transaction - sle.fifo_queue_qty
+			sle.fifo_value_diff = sle.stock_value - sle.fifo_stock_value
+
+			if sle.batch_no:
+				sle.use_batchwise_valuation = frappe.db.get_value(
+					"Batch", sle.batch_no, "use_batchwise_valuation", cache=True
+				)
+
+			if abs(sle.fifo_qty_diff) > 0.001 or abs(sle.fifo_value_diff) > 0.1:
+				if idx:
+					data.append(sles[idx - 1])
+				data.append(sle)
+				data.append({})
+				break
+
+	return data
+
+
+def get_columns():
+	return [
+		{
+			"fieldname": "name",
+			"fieldtype": "Link",
+			"label": _("Stock Ledger Entry"),
+			"options": "Stock Ledger Entry",
+		},
+		{
+			"fieldname": "item_code",
+			"fieldtype": "Link",
+			"label": _("Item Code"),
+			"options": "Item",
+		},
+		{
+			"fieldname": "warehouse",
+			"fieldtype": "Link",
+			"label": _("Warehouse"),
+			"options": "Warehouse",
+		},
+		{
+			"fieldname": "posting_date",
+			"fieldtype": "Data",
+			"label": _("Posting Date"),
+		},
+		{
+			"fieldname": "posting_time",
+			"fieldtype": "Data",
+			"label": _("Posting Time"),
+		},
+		{
+			"fieldname": "creation",
+			"fieldtype": "Data",
+			"label": _("Creation"),
+		},
+		{
+			"fieldname": "voucher_type",
+			"fieldtype": "Link",
+			"label": _("Voucher Type"),
+			"options": "DocType",
+		},
+		{
+			"fieldname": "voucher_no",
+			"fieldtype": "Dynamic Link",
+			"label": _("Voucher No"),
+			"options": "voucher_type",
+		},
+		{
+			"fieldname": "batch_no",
+			"fieldtype": "Link",
+			"label": _("Batch"),
+			"options": "Batch",
+		},
+		{
+			"fieldname": "use_batchwise_valuation",
+			"fieldtype": "Check",
+			"label": _("Batchwise Valuation"),
+		},
+		{
+			"fieldname": "actual_qty",
+			"fieldtype": "Float",
+			"label": _("Qty Change"),
+		},
+		{
+			"fieldname": "qty_after_transaction",
+			"fieldtype": "Float",
+			"label": _("(A) Qty After Transaction"),
+		},
+		{
+			"fieldname": "stock_queue",
+			"fieldtype": "Data",
+			"label": _("FIFO/LIFO Queue"),
+		},
+		{
+			"fieldname": "fifo_queue_qty",
+			"fieldtype": "Float",
+			"label": _("(C) Total qty in queue"),
+		},
+		{
+			"fieldname": "fifo_qty_diff",
+			"fieldtype": "Float",
+			"label": _("A - C"),
+		},
+		{
+			"fieldname": "stock_value",
+			"fieldtype": "Float",
+			"label": _("(D) Balance Stock Value"),
+		},
+		{
+			"fieldname": "fifo_stock_value",
+			"fieldtype": "Float",
+			"label": _("(E) Balance Stock Value in Queue"),
+		},
+		{
+			"fieldname": "fifo_value_diff",
+			"fieldtype": "Float",
+			"label": _("D - E"),
+		},
+		{
+			"fieldname": "valuation_rate",
+			"fieldtype": "Float",
+			"label": _("(H) Valuation Rate"),
+		},
+	]
diff --git a/erpnext/stock/report/stock_analytics/stock_analytics.py b/erpnext/stock/report/stock_analytics/stock_analytics.py
index da0776b..89ca9d9 100644
--- a/erpnext/stock/report/stock_analytics/stock_analytics.py
+++ b/erpnext/stock/report/stock_analytics/stock_analytics.py
@@ -1,6 +1,7 @@
 # Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
 # For license information, please see license.txt
 import datetime
+from typing import List
 
 import frappe
 from frappe import _, scrub
@@ -148,18 +149,26 @@
 	                        - Warehouse A : bal_qty/value
 	                        - Warehouse B : bal_qty/value
 	"""
+
+	expected_ranges = get_period_date_ranges(filters)
+	expected_periods = []
+	for _start_date, end_date in expected_ranges:
+		expected_periods.append(get_period(end_date, filters))
+
 	periodic_data = {}
 	for d in entry:
 		period = get_period(d.posting_date, filters)
 		bal_qty = 0
 
+		fill_intermediate_periods(periodic_data, d.item_code, period, expected_periods)
+
 		# if period against item does not exist yet, instantiate it
 		# insert existing balance dict against period, and add/subtract to it
 		if periodic_data.get(d.item_code) and not periodic_data.get(d.item_code).get(period):
 			previous_balance = periodic_data[d.item_code]["balance"].copy()
 			periodic_data[d.item_code][period] = previous_balance
 
-		if d.voucher_type == "Stock Reconciliation":
+		if d.voucher_type == "Stock Reconciliation" and not d.batch_no:
 			if periodic_data.get(d.item_code) and periodic_data.get(d.item_code).get("balance").get(
 				d.warehouse
 			):
@@ -186,6 +195,36 @@
 	return periodic_data
 
 
+def fill_intermediate_periods(
+	periodic_data, item_code: str, current_period: str, all_periods: List[str]
+) -> None:
+	"""There might be intermediate periods where no stock ledger entry exists, copy previous previous data.
+
+	Previous data is ONLY copied if period falls in report range and before period being processed currently.
+
+	args:
+	        current_period: process till this period (exclusive)
+	        all_periods: all periods expected in report via filters
+	        periodic_data: report's periodic data
+	        item_code: item_code being processed
+	"""
+
+	previous_period_data = None
+	for period in all_periods:
+		if period == current_period:
+			return
+
+		if (
+			periodic_data.get(item_code)
+			and not periodic_data.get(item_code).get(period)
+			and previous_period_data
+		):
+			# This period should exist since it's in report range, assign previous period data
+			periodic_data[item_code][period] = previous_period_data.copy()
+
+		previous_period_data = periodic_data.get(item_code, {}).get(period)
+
+
 def get_data(filters):
 	data = []
 	items = get_items(filters)
@@ -194,6 +233,8 @@
 	periodic_data = get_periodic_data(sle, filters)
 	ranges = get_period_date_ranges(filters)
 
+	today = getdate()
+
 	for dummy, item_data in item_details.items():
 		row = {
 			"name": item_data.name,
@@ -202,14 +243,15 @@
 			"uom": item_data.stock_uom,
 			"brand": item_data.brand,
 		}
-		total = 0
-		for dummy, end_date in ranges:
+		previous_period_value = 0.0
+		for start_date, end_date in ranges:
 			period = get_period(end_date, filters)
 			period_data = periodic_data.get(item_data.name, {}).get(period)
-			amount = sum(period_data.values()) if period_data else 0
-			row[scrub(period)] = amount
-			total += amount
-		row["total"] = total
+			if period_data:
+				row[scrub(period)] = previous_period_value = sum(period_data.values())
+			else:
+				row[scrub(period)] = previous_period_value if today >= start_date else None
+
 		data.append(row)
 
 	return data
diff --git a/erpnext/stock/report/stock_analytics/test_stock_analytics.py b/erpnext/stock/report/stock_analytics/test_stock_analytics.py
index f6c98f9..dd8f8d8 100644
--- a/erpnext/stock/report/stock_analytics/test_stock_analytics.py
+++ b/erpnext/stock/report/stock_analytics/test_stock_analytics.py
@@ -1,13 +1,59 @@
 import datetime
 
+import frappe
 from frappe import _dict
 from frappe.tests.utils import FrappeTestCase
+from frappe.utils.data import add_to_date, get_datetime, getdate, nowdate
 
 from erpnext.accounts.utils import get_fiscal_year
-from erpnext.stock.report.stock_analytics.stock_analytics import get_period_date_ranges
+from erpnext.stock.doctype.item.test_item import make_item
+from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
+from erpnext.stock.report.stock_analytics.stock_analytics import execute, get_period_date_ranges
+
+
+def stock_analytics(filters):
+	col, data, *_ = execute(filters)
+	return col, data
 
 
 class TestStockAnalyticsReport(FrappeTestCase):
+	def setUp(self) -> None:
+		self.item = make_item().name
+		self.warehouse = "_Test Warehouse - _TC"
+
+	def assert_single_item_report(self, movement, expected_buckets):
+		self.generate_stock(movement)
+		filters = _dict(
+			range="Monthly",
+			from_date=movement[0][1].replace(day=1),
+			to_date=movement[-1][1].replace(day=28),
+			value_quantity="Quantity",
+			company="_Test Company",
+			item_code=self.item,
+		)
+
+		cols, data = stock_analytics(filters)
+
+		self.assertEqual(len(data), 1)
+		row = frappe._dict(data[0])
+		self.assertEqual(row.name, self.item)
+		self.compare_analytics_row(row, cols, expected_buckets)
+
+	def generate_stock(self, movement):
+		for qty, posting_date in movement:
+			args = {"item": self.item, "qty": abs(qty), "posting_date": posting_date}
+			args["to_warehouse" if qty > 0 else "from_warehouse"] = self.warehouse
+			make_stock_entry(**args)
+
+	def compare_analytics_row(self, report_row, columns, expected_buckets):
+		# last (N) cols will be monthly data
+		no_of_buckets = len(expected_buckets)
+		month_cols = [col["fieldname"] for col in columns[-no_of_buckets:]]
+
+		actual_buckets = [report_row.get(col) for col in month_cols]
+
+		self.assertEqual(actual_buckets, expected_buckets)
+
 	def test_get_period_date_ranges(self):
 
 		filters = _dict(range="Monthly", from_date="2020-12-28", to_date="2021-02-06")
@@ -33,3 +79,38 @@
 		]
 
 		self.assertEqual(ranges, expected_ranges)
+
+	def test_basic_report_functionality(self):
+		"""Stock analytics report generates balance "as of" periods based on
+		user defined ranges. Check that this behaviour is correct."""
+
+		# create stock movement in 3 months at 15th of month
+		today = getdate()
+		movement = [
+			(10, add_to_date(today, months=0).replace(day=15)),
+			(-5, add_to_date(today, months=1).replace(day=15)),
+			(10, add_to_date(today, months=2).replace(day=15)),
+		]
+		self.assert_single_item_report(movement, [10, 5, 15])
+
+	def test_empty_month_in_between(self):
+		today = getdate()
+		movement = [
+			(100, add_to_date(today, months=0).replace(day=15)),
+			(-50, add_to_date(today, months=1).replace(day=15)),
+			# Skip a month
+			(20, add_to_date(today, months=3).replace(day=15)),
+		]
+		self.assert_single_item_report(movement, [100, 50, 50, 70])
+
+	def test_multi_month_missings(self):
+		today = getdate()
+		movement = [
+			(100, add_to_date(today, months=0).replace(day=15)),
+			(-50, add_to_date(today, months=1).replace(day=15)),
+			# Skip a month
+			(20, add_to_date(today, months=3).replace(day=15)),
+			# Skip another month
+			(-10, add_to_date(today, months=5).replace(day=15)),
+		]
+		self.assert_single_item_report(movement, [100, 50, 50, 70, 70, 60])
diff --git a/erpnext/stock/report/test_reports.py b/erpnext/stock/report/test_reports.py
index 55b9104..d118d8e 100644
--- a/erpnext/stock/report/test_reports.py
+++ b/erpnext/stock/report/test_reports.py
@@ -65,6 +65,8 @@
 	("Delayed Item Report", {"based_on": "Delivery Note"}),
 	("Stock Ageing", {"range1": 30, "range2": 60, "range3": 90, "_optional": True}),
 	("Stock Ledger Invariant Check", {"warehouse": "_Test Warehouse - _TC", "item": "_Test Item"}),
+	("FIFO Queue vs Qty After Transaction Comparison", {"warehouse": "_Test Warehouse - _TC"}),
+	("FIFO Queue vs Qty After Transaction Comparison", {"item_group": "All Item Groups"}),
 ]
 
 OPTIONAL_FILTERS = {
diff --git a/erpnext/translations/ru.csv b/erpnext/translations/ru.csv
index 6ca3344..fb56ff6 100644
--- a/erpnext/translations/ru.csv
+++ b/erpnext/translations/ru.csv
@@ -1357,7 +1357,7 @@
 "Item Price appears multiple times based on Price List, Supplier/Customer, Currency, Item, UOM, Qty and Dates.","Цена товара отображается несколько раз на основе Прайс-листа, Поставщика / Клиента, Валюты, Предмет, UOM, Кол-во и Даты.",
 Item Price updated for {0} in Price List {1},Цена продукта {0} обновлена в прайс-листе {1},
 Item Row {0}: {1} {2} does not exist in above '{1}' table,Элемент Row {0}: {1} {2} не существует в таблице «{1}»,
-Item Tax Row {0} must have account of type Tax or Income or Expense or Chargeable,"Строка налога {0} должен иметь счет типа Налога, Доход, Расходов или Облагаемый налогом,"
+Item Tax Row {0} must have account of type Tax or Income or Expense or Chargeable,"Строка налога {0} должен иметь счет типа Налога, Доход, Расходов или Облагаемый налогом",
 Item Template,Шаблон продукта,
 Item Variant Settings,Параметры модификации продкута,
 Item Variant {0} already exists with same attributes,Модификация продукта {0} с этими атрибутами уже существует,