Merge pull request #34257 from deepeshgarg007/opening_entry

feat: Closing balance for period closing and reporting
diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py
index e1d58a0..b5b7ba8 100644
--- a/erpnext/assets/doctype/asset/asset.py
+++ b/erpnext/assets/doctype/asset/asset.py
@@ -474,17 +474,21 @@
 	@erpnext.allow_regional
 	def get_depreciation_amount(self, depreciable_value, fb_row):
 		if fb_row.depreciation_method in ("Straight Line", "Manual"):
-			# if the Depreciation Schedule is being prepared for the first time
-			if not self.flags.increase_in_asset_life:
-				depreciation_amount = (
-					flt(self.gross_purchase_amount) - flt(fb_row.expected_value_after_useful_life)
-				) / flt(fb_row.total_number_of_depreciations)
-
-			# if the Depreciation Schedule is being modified after Asset Repair
-			else:
+			# if the Depreciation Schedule is being modified after Asset Repair due to increase in asset life and value
+			if self.flags.increase_in_asset_life:
 				depreciation_amount = (
 					flt(fb_row.value_after_depreciation) - flt(fb_row.expected_value_after_useful_life)
 				) / (date_diff(self.to_date, self.available_for_use_date) / 365)
+			# if the Depreciation Schedule is being modified after Asset Repair due to increase in asset value
+			elif self.flags.increase_in_asset_value_due_to_repair:
+				depreciation_amount = (
+					flt(fb_row.value_after_depreciation) - flt(fb_row.expected_value_after_useful_life)
+				) / flt(fb_row.total_number_of_depreciations)
+			# if the Depreciation Schedule is being prepared for the first time
+			else:
+				depreciation_amount = (
+					flt(self.gross_purchase_amount) - flt(fb_row.expected_value_after_useful_life)
+				) / flt(fb_row.total_number_of_depreciations)
 		else:
 			depreciation_amount = flt(depreciable_value * (flt(fb_row.rate_of_depreciation) / 100))
 
@@ -617,11 +621,22 @@
 			return 200.0 / args.get("total_number_of_depreciations")
 
 		if args.get("depreciation_method") == "Written Down Value":
-			if args.get("rate_of_depreciation") and on_validate:
+			if (
+				args.get("rate_of_depreciation")
+				and on_validate
+				and not self.flags.increase_in_asset_value_due_to_repair
+			):
 				return args.get("rate_of_depreciation")
 
-			value = flt(args.get("expected_value_after_useful_life")) / flt(self.gross_purchase_amount)
+			if self.flags.increase_in_asset_value_due_to_repair:
+				value = flt(args.get("expected_value_after_useful_life")) / flt(
+					args.get("value_after_depreciation")
+				)
+			else:
+				value = flt(args.get("expected_value_after_useful_life")) / flt(self.gross_purchase_amount)
+
 			depreciation_rate = math.pow(value, 1.0 / flt(args.get("total_number_of_depreciations"), 2))
+
 			return flt((100 * (1 - depreciation_rate)), float_precision)
 
 	def get_pro_rata_amt(self, row, depreciation_amount, from_date, to_date):
diff --git a/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py b/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py
index 5912329..d23edfa 100644
--- a/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py
+++ b/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py
@@ -460,6 +460,16 @@
 
 		new_asset_depr_schedule_doc = frappe.copy_doc(current_asset_depr_schedule_doc)
 
+		if asset_doc.flags.increase_in_asset_value_due_to_repair and row.depreciation_method in (
+			"Written Down Value",
+			"Double Declining Balance",
+		):
+			new_rate_of_depreciation = flt(
+				asset_doc.get_depreciation_rate(row), row.precision("rate_of_depreciation")
+			)
+			row.rate_of_depreciation = new_rate_of_depreciation
+			new_asset_depr_schedule_doc.rate_of_depreciation = new_rate_of_depreciation
+
 		new_asset_depr_schedule_doc.make_depr_schedule(asset_doc, row, date_of_disposal)
 		new_asset_depr_schedule_doc.set_accumulated_depreciation(row, date_of_disposal, date_of_return)
 
diff --git a/erpnext/assets/doctype/asset_repair/asset_repair.py b/erpnext/assets/doctype/asset_repair/asset_repair.py
index a7172a7..a913ee4 100644
--- a/erpnext/assets/doctype/asset_repair/asset_repair.py
+++ b/erpnext/assets/doctype/asset_repair/asset_repair.py
@@ -43,53 +43,57 @@
 	def before_submit(self):
 		self.check_repair_status()
 
-		if self.get("stock_consumption") or self.get("capitalize_repair_cost"):
-			self.increase_asset_value()
-		if self.get("stock_consumption"):
-			self.check_for_stock_items_and_warehouse()
-			self.decrease_stock_quantity()
-		if self.get("capitalize_repair_cost"):
-			self.make_gl_entries()
-			if (
-				frappe.db.get_value("Asset", self.asset, "calculate_depreciation")
-				and self.increase_in_asset_life
-			):
-				self.modify_depreciation_schedule()
+		self.asset_doc.flags.increase_in_asset_value_due_to_repair = False
 
-		notes = _(
-			"This schedule was created when Asset {0} was repaired through Asset Repair {1}."
-		).format(
-			get_link_to_form(self.asset_doc.doctype, self.asset_doc.name),
-			get_link_to_form(self.doctype, self.name),
-		)
-		self.asset_doc.flags.ignore_validate_update_after_submit = True
-		make_new_active_asset_depr_schedules_and_cancel_current_ones(self.asset_doc, notes)
-		self.asset_doc.save()
+		if self.get("stock_consumption") or self.get("capitalize_repair_cost"):
+			self.asset_doc.flags.increase_in_asset_value_due_to_repair = True
+
+			self.increase_asset_value()
+
+			if self.get("stock_consumption"):
+				self.check_for_stock_items_and_warehouse()
+				self.decrease_stock_quantity()
+			if self.get("capitalize_repair_cost"):
+				self.make_gl_entries()
+				if self.asset_doc.calculate_depreciation and self.increase_in_asset_life:
+					self.modify_depreciation_schedule()
+
+			notes = _(
+				"This schedule was created when Asset {0} was repaired through Asset Repair {1}."
+			).format(
+				get_link_to_form(self.asset_doc.doctype, self.asset_doc.name),
+				get_link_to_form(self.doctype, self.name),
+			)
+			self.asset_doc.flags.ignore_validate_update_after_submit = True
+			make_new_active_asset_depr_schedules_and_cancel_current_ones(self.asset_doc, notes)
+			self.asset_doc.save()
 
 	def before_cancel(self):
 		self.asset_doc = frappe.get_doc("Asset", self.asset)
 
-		if self.get("stock_consumption") or self.get("capitalize_repair_cost"):
-			self.decrease_asset_value()
-		if self.get("stock_consumption"):
-			self.increase_stock_quantity()
-		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
-			):
-				self.revert_depreciation_schedule_on_cancellation()
+		self.asset_doc.flags.increase_in_asset_value_due_to_repair = False
 
-		notes = _("This schedule was created when Asset {0}'s Asset Repair {1} was cancelled.").format(
-			get_link_to_form(self.asset_doc.doctype, self.asset_doc.name),
-			get_link_to_form(self.doctype, self.name),
-		)
-		self.asset_doc.flags.ignore_validate_update_after_submit = True
-		make_new_active_asset_depr_schedules_and_cancel_current_ones(self.asset_doc, notes)
-		self.asset_doc.save()
+		if self.get("stock_consumption") or self.get("capitalize_repair_cost"):
+			self.asset_doc.flags.increase_in_asset_value_due_to_repair = True
+
+			self.decrease_asset_value()
+
+			if self.get("stock_consumption"):
+				self.increase_stock_quantity()
+			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 self.asset_doc.calculate_depreciation and self.increase_in_asset_life:
+					self.revert_depreciation_schedule_on_cancellation()
+
+			notes = _("This schedule was created when Asset {0}'s Asset Repair {1} was cancelled.").format(
+				get_link_to_form(self.asset_doc.doctype, self.asset_doc.name),
+				get_link_to_form(self.doctype, self.name),
+			)
+			self.asset_doc.flags.ignore_validate_update_after_submit = True
+			make_new_active_asset_depr_schedules_and_cancel_current_ones(self.asset_doc, notes)
+			self.asset_doc.save()
 
 	def after_delete(self):
 		frappe.get_doc("Asset", self.asset).set_status()
diff --git a/erpnext/buying/doctype/supplier/supplier.py b/erpnext/buying/doctype/supplier/supplier.py
index 120b2f8..01b5c8f 100644
--- a/erpnext/buying/doctype/supplier/supplier.py
+++ b/erpnext/buying/doctype/supplier/supplier.py
@@ -125,18 +125,9 @@
 
 	def on_trash(self):
 		if self.supplier_primary_contact:
-			frappe.db.sql(
-				"""
-				UPDATE `tabSupplier`
-				SET
-					supplier_primary_contact=null,
-					supplier_primary_address=null,
-					mobile_no=null,
-					email_id=null,
-					primary_address=null
-				WHERE name=%(name)s""",
-				{"name": self.name},
-			)
+			self.db_set("supplier_primary_contact", None)
+		if self.supplier_primary_address:
+			self.db_set("supplier_primary_address", None)
 
 		delete_contact_and_address("Supplier", self.name)
 
diff --git a/erpnext/manufacturing/doctype/job_card/job_card.js b/erpnext/manufacturing/doctype/job_card/job_card.js
index 619e6bd..5305db3 100644
--- a/erpnext/manufacturing/doctype/job_card/job_card.js
+++ b/erpnext/manufacturing/doctype/job_card/job_card.js
@@ -83,7 +83,7 @@
 			// and if stock mvt for WIP is required
 			if (frm.doc.work_order) {
 				frappe.db.get_value('Work Order', frm.doc.work_order, ['skip_transfer', 'status'], (result) => {
-					if (result.skip_transfer === 1 || result.status == 'In Process') {
+					if (result.skip_transfer === 1 || result.status == 'In Process' || frm.doc.transferred_qty > 0) {
 						frm.trigger("prepare_timer_buttons");
 					}
 				});
diff --git a/erpnext/public/js/templates/crm_notes.html b/erpnext/public/js/templates/crm_notes.html
index fddeb1c..53df933 100644
--- a/erpnext/public/js/templates/crm_notes.html
+++ b/erpnext/public/js/templates/crm_notes.html
@@ -17,7 +17,7 @@
 								{{ frappe.avatar(notes[i].added_by) }}
 							</div>
 							<div class="col-xs-10">
-								<div class="mr-2 title font-weight-bold">
+								<div class="mr-2 title font-weight-bold ellipsis" title="{{ strip_html(notes[i].added_by) }}">
 									{{ strip_html(notes[i].added_by) }}
 								</div>
 								<div class="time small text-muted">
diff --git a/erpnext/selling/doctype/customer/customer.py b/erpnext/selling/doctype/customer/customer.py
index d9dab33..baa316f 100644
--- a/erpnext/selling/doctype/customer/customer.py
+++ b/erpnext/selling/doctype/customer/customer.py
@@ -272,18 +272,9 @@
 
 	def on_trash(self):
 		if self.customer_primary_contact:
-			frappe.db.sql(
-				"""
-				UPDATE `tabCustomer`
-				SET
-					customer_primary_contact=null,
-					customer_primary_address=null,
-					mobile_no=null,
-					email_id=null,
-					primary_address=null
-				WHERE name=%(name)s""",
-				{"name": self.name},
-			)
+			self.db_set("customer_primary_contact", None)
+		if self.customer_primary_address:
+			self.db_set("customer_primary_address", None)
 
 		delete_contact_and_address("Customer", self.name)
 		if self.lead_name:
diff --git a/erpnext/selling/report/sales_partner_target_variance_based_on_item_group/item_group_wise_sales_target_variance.py b/erpnext/selling/report/sales_partner_target_variance_based_on_item_group/item_group_wise_sales_target_variance.py
index f34f3e3..7d28f2b 100644
--- a/erpnext/selling/report/sales_partner_target_variance_based_on_item_group/item_group_wise_sales_target_variance.py
+++ b/erpnext/selling/report/sales_partner_target_variance_based_on_item_group/item_group_wise_sales_target_variance.py
@@ -44,20 +44,30 @@
 
 	if not sales_users_data:
 		return
-	sales_users, item_groups = [], []
+	sales_users = []
+	sales_user_wise_item_groups = {}
 
 	for d in sales_users_data:
 		if d.parent not in sales_users:
 			sales_users.append(d.parent)
 
-		if d.item_group not in item_groups:
-			item_groups.append(d.item_group)
+		sales_user_wise_item_groups.setdefault(d.parent, [])
+		if d.item_group:
+			sales_user_wise_item_groups[d.parent].append(d.item_group)
 
 	date_field = "transaction_date" if filters.get("doctype") == "Sales Order" else "posting_date"
 
-	actual_data = get_actual_data(filters, item_groups, sales_users, date_field, sales_field)
+	actual_data = get_actual_data(filters, sales_users, date_field, sales_field)
 
-	return prepare_data(filters, sales_users_data, actual_data, date_field, period_list, sales_field)
+	return prepare_data(
+		filters,
+		sales_users_data,
+		sales_user_wise_item_groups,
+		actual_data,
+		date_field,
+		period_list,
+		sales_field,
+	)
 
 
 def get_columns(filters, period_list, partner_doctype):
@@ -142,7 +152,15 @@
 	return columns
 
 
-def prepare_data(filters, sales_users_data, actual_data, date_field, period_list, sales_field):
+def prepare_data(
+	filters,
+	sales_users_data,
+	sales_user_wise_item_groups,
+	actual_data,
+	date_field,
+	period_list,
+	sales_field,
+):
 	rows = {}
 
 	target_qty_amt_field = "target_qty" if filters.get("target_on") == "Quantity" else "target_amount"
@@ -173,9 +191,9 @@
 			for r in actual_data:
 				if (
 					r.get(sales_field) == d.parent
-					and r.item_group == d.item_group
 					and period.from_date <= r.get(date_field)
 					and r.get(date_field) <= period.to_date
+					and (not sales_user_wise_item_groups.get(d.parent) or r.item_group == d.item_group)
 				):
 					details[p_key] += r.get(qty_or_amount_field, 0)
 					details[variance_key] = details.get(p_key) - details.get(target_key)
@@ -186,7 +204,7 @@
 	return rows
 
 
-def get_actual_data(filters, item_groups, sales_users_or_territory_data, date_field, sales_field):
+def get_actual_data(filters, sales_users_or_territory_data, date_field, sales_field):
 	fiscal_year = get_fiscal_year(fiscal_year=filters.get("fiscal_year"), as_dict=1)
 	dates = [fiscal_year.year_start_date, fiscal_year.year_end_date]
 
@@ -213,7 +231,6 @@
 		WHERE
 			`tab{child_doc}`.parent = `tab{parent_doc}`.name
 			and `tab{parent_doc}`.docstatus = 1 and {cond}
-			and `tab{child_doc}`.item_group in ({item_groups})
 			and `tab{parent_doc}`.{date_field} between %s and %s""".format(
 			cond=cond,
 			date_field=date_field,
@@ -221,9 +238,8 @@
 			child_table=child_table,
 			parent_doc=filters.get("doctype"),
 			child_doc=filters.get("doctype") + " Item",
-			item_groups=",".join(["%s"] * len(item_groups)),
 		),
-		tuple(sales_users_or_territory_data + item_groups + dates),
+		tuple(sales_users_or_territory_data + dates),
 		as_dict=1,
 	)
 
diff --git a/erpnext/selling/report/sales_person_target_variance_based_on_item_group/sales_person_target_variance_based_on_item_group.py b/erpnext/selling/report/sales_person_target_variance_based_on_item_group/sales_person_target_variance_based_on_item_group.py
index dda2466..8207122 100644
--- a/erpnext/selling/report/sales_person_target_variance_based_on_item_group/sales_person_target_variance_based_on_item_group.py
+++ b/erpnext/selling/report/sales_person_target_variance_based_on_item_group/sales_person_target_variance_based_on_item_group.py
@@ -8,6 +8,4 @@
 
 
 def execute(filters=None):
-	data = []
-
 	return get_data_column(filters, "Sales Person")