Merge pull request #38290 from s-aga-r/FIX-36704

feat: auto create PR on SCR submission
diff --git a/erpnext/accounts/report/sales_register/sales_register.js b/erpnext/accounts/report/sales_register/sales_register.js
index 1a41172..4578ac3 100644
--- a/erpnext/accounts/report/sales_register/sales_register.js
+++ b/erpnext/accounts/report/sales_register/sales_register.js
@@ -23,6 +23,12 @@
 			"options": "Customer"
 		},
 		{
+			"fieldname":"customer_group",
+			"label": __("Customer Group"),
+			"fieldtype": "Link",
+			"options": "Customer Group"
+		},
+		{
 			"fieldname":"company",
 			"label": __("Company"),
 			"fieldtype": "Link",
diff --git a/erpnext/accounts/report/sales_register/sales_register.py b/erpnext/accounts/report/sales_register/sales_register.py
index 0ba7186..ec6dd72 100644
--- a/erpnext/accounts/report/sales_register/sales_register.py
+++ b/erpnext/accounts/report/sales_register/sales_register.py
@@ -449,6 +449,9 @@
 	if filters.get("customer"):
 		query = query.where(si.customer == filters.customer)
 
+	if filters.get("customer_group"):
+		query = query.where(si.customer_group == filters.customer_group)
+
 	query = get_conditions(filters, query, "Sales Invoice")
 	query = apply_common_conditions(
 		filters, query, doctype="Sales Invoice", child_doctype="Sales Invoice Item"
diff --git a/erpnext/accounts/report/tds_computation_summary/tds_computation_summary.py b/erpnext/accounts/report/tds_computation_summary/tds_computation_summary.py
index 82f97f1..2b5566f 100644
--- a/erpnext/accounts/report/tds_computation_summary/tds_computation_summary.py
+++ b/erpnext/accounts/report/tds_computation_summary/tds_computation_summary.py
@@ -1,7 +1,7 @@
 import frappe
 from frappe import _
 
-from erpnext.accounts.report.tds_payable_monthly.tds_payable_monthly import (
+from erpnext.accounts.report.tax_withholding_details.tax_withholding_details import (
 	get_result,
 	get_tds_docs,
 )
diff --git a/erpnext/assets/doctype/asset/asset.js b/erpnext/assets/doctype/asset/asset.js
index d378fbd..58fd6d4 100644
--- a/erpnext/assets/doctype/asset/asset.js
+++ b/erpnext/assets/doctype/asset/asset.js
@@ -214,30 +214,43 @@
 		})
 	},
 
-	render_depreciation_schedule_view: function(frm, depr_schedule) {
+	render_depreciation_schedule_view: function(frm, asset_depr_schedule_doc) {
 		let wrapper = $(frm.fields_dict["depreciation_schedule_view"].wrapper).empty();
 
 		let data = [];
 
-		depr_schedule.forEach((sch) => {
+		asset_depr_schedule_doc.depreciation_schedule.forEach((sch) => {
 			const row = [
 				sch['idx'],
 				frappe.format(sch['schedule_date'], { fieldtype: 'Date' }),
 				frappe.format(sch['depreciation_amount'], { fieldtype: 'Currency' }),
 				frappe.format(sch['accumulated_depreciation_amount'], { fieldtype: 'Currency' }),
-				sch['journal_entry'] || ''
+				sch['journal_entry'] || '',
 			];
+
+			if (asset_depr_schedule_doc.shift_based) {
+				row.push(sch['shift']);
+			}
+
 			data.push(row);
 		});
 
+		let columns = [
+			{name: __("No."), editable: false, resizable: false, format: value => value, width: 60},
+			{name: __("Schedule Date"), editable: false, resizable: false, width: 270},
+			{name: __("Depreciation Amount"), editable: false, resizable: false, width: 164},
+			{name: __("Accumulated Depreciation Amount"), editable: false, resizable: false, width: 164},
+		];
+
+		if (asset_depr_schedule_doc.shift_based) {
+			columns.push({name: __("Journal Entry"), editable: false, resizable: false, format: value => `<a href="/app/journal-entry/${value}">${value}</a>`, width: 245});
+			columns.push({name: __("Shift"), editable: false, resizable: false, width: 59});
+		} else {
+			columns.push({name: __("Journal Entry"), editable: false, resizable: false, format: value => `<a href="/app/journal-entry/${value}">${value}</a>`, width: 304});
+		}
+
 		let datatable = new frappe.DataTable(wrapper.get(0), {
-			columns: [
-				{name: __("No."), editable: false, resizable: false, format: value => value, width: 60},
-				{name: __("Schedule Date"), editable: false, resizable: false, width: 270},
-				{name: __("Depreciation Amount"), editable: false, resizable: false, width: 164},
-				{name: __("Accumulated Depreciation Amount"), editable: false, resizable: false, width: 164},
-				{name: __("Journal Entry"), editable: false, resizable: false, format: value => `<a href="/app/journal-entry/${value}">${value}</a>`, width: 304}
-			],
+			columns: columns,
 			data: data,
 			layout: "fluid",
 			serialNoColumn: false,
@@ -272,8 +285,8 @@
 				asset_values.push(flt(frm.doc.gross_purchase_amount - frm.doc.opening_accumulated_depreciation, precision('gross_purchase_amount')));
 			}
 
-			let depr_schedule = (await frappe.call(
-				"erpnext.assets.doctype.asset_depreciation_schedule.asset_depreciation_schedule.get_depr_schedule",
+			let asset_depr_schedule_doc = (await frappe.call(
+				"erpnext.assets.doctype.asset_depreciation_schedule.asset_depreciation_schedule.get_asset_depr_schedule_doc",
 				{
 					asset_name: frm.doc.name,
 					status: "Active",
@@ -281,7 +294,7 @@
 				}
 			)).message;
 
-			$.each(depr_schedule || [], function(i, v) {
+			$.each(asset_depr_schedule_doc.depreciation_schedule || [], function(i, v) {
 				x_intervals.push(frappe.format(v.schedule_date, { fieldtype: 'Date' }));
 				var asset_value = flt(frm.doc.gross_purchase_amount - v.accumulated_depreciation_amount, precision('gross_purchase_amount'));
 				if(v.journal_entry) {
@@ -296,7 +309,7 @@
 			});
 
 			frm.toggle_display(["depreciation_schedule_view"], 1);
-			frm.events.render_depreciation_schedule_view(frm, depr_schedule);
+			frm.events.render_depreciation_schedule_view(frm, asset_depr_schedule_doc);
 		} else {
 			if(frm.doc.opening_accumulated_depreciation) {
 				x_intervals.push(frappe.format(frm.doc.creation.split(" ")[0], { fieldtype: 'Date' }));
diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py
index 7b7953b..d1f03f2 100644
--- a/erpnext/assets/doctype/asset/asset.py
+++ b/erpnext/assets/doctype/asset/asset.py
@@ -825,6 +825,7 @@
 				"total_number_of_depreciations": d.total_number_of_depreciations,
 				"frequency_of_depreciation": d.frequency_of_depreciation,
 				"daily_prorata_based": d.daily_prorata_based,
+				"shift_based": d.shift_based,
 				"salvage_value_percentage": d.salvage_value_percentage,
 				"expected_value_after_useful_life": flt(gross_purchase_amount)
 				* flt(d.salvage_value_percentage / 100),
diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py
index ca2b980..dc80aa5 100644
--- a/erpnext/assets/doctype/asset/test_asset.py
+++ b/erpnext/assets/doctype/asset/test_asset.py
@@ -1000,7 +1000,11 @@
 			},
 		)
 
-		depreciation_amount = get_depreciation_amount(asset, 100000, asset.finance_books[0])
+		asset_depr_schedule_doc = get_asset_depr_schedule_doc(asset.name, "Active")
+
+		depreciation_amount = get_depreciation_amount(
+			asset_depr_schedule_doc, asset, 100000, asset.finance_books[0]
+		)
 		self.assertEqual(depreciation_amount, 30000)
 
 	def test_make_depr_schedule(self):
@@ -1732,6 +1736,7 @@
 				"expected_value_after_useful_life": args.expected_value_after_useful_life or 0,
 				"depreciation_start_date": args.depreciation_start_date,
 				"daily_prorata_based": args.daily_prorata_based or 0,
+				"shift_based": args.shift_based or 0,
 			},
 		)
 
diff --git a/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.js b/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.js
index 3d2dff1..c99297d 100644
--- a/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.js
+++ b/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.js
@@ -8,11 +8,13 @@
 	},
 
 	make_schedules_editable: function(frm) {
-		var is_editable = frm.doc.depreciation_method == "Manual" ? true : false;
+		var is_manual_hence_editable = frm.doc.depreciation_method === "Manual" ? true : false;
+		var is_shift_hence_editable = frm.doc.shift_based ? true : false;
 
-		frm.toggle_enable("depreciation_schedule", is_editable);
-		frm.fields_dict["depreciation_schedule"].grid.toggle_enable("schedule_date", is_editable);
-		frm.fields_dict["depreciation_schedule"].grid.toggle_enable("depreciation_amount", is_editable);
+		frm.toggle_enable("depreciation_schedule", is_manual_hence_editable || is_shift_hence_editable);
+		frm.fields_dict["depreciation_schedule"].grid.toggle_enable("schedule_date", is_manual_hence_editable);
+		frm.fields_dict["depreciation_schedule"].grid.toggle_enable("depreciation_amount", is_manual_hence_editable);
+		frm.fields_dict["depreciation_schedule"].grid.toggle_enable("shift", is_shift_hence_editable);
 	}
 });
 
diff --git a/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.json b/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.json
index 8d8b463..be35914 100644
--- a/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.json
+++ b/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.json
@@ -20,6 +20,7 @@
   "total_number_of_depreciations",
   "rate_of_depreciation",
   "daily_prorata_based",
+  "shift_based",
   "column_break_8",
   "frequency_of_depreciation",
   "expected_value_after_useful_life",
@@ -184,12 +185,20 @@
    "label": "Depreciate based on daily pro-rata",
    "print_hide": 1,
    "read_only": 1
+  },
+  {
+   "default": "0",
+   "depends_on": "eval:doc.depreciation_method == \"Straight Line\"",
+   "fieldname": "shift_based",
+   "fieldtype": "Check",
+   "label": "Depreciate based on shifts",
+   "read_only": 1
   }
  ],
  "index_web_pages_for_search": 1,
  "is_submittable": 1,
  "links": [],
- "modified": "2023-11-03 21:32:15.021796",
+ "modified": "2023-11-29 00:57:00.461998",
  "modified_by": "Administrator",
  "module": "Assets",
  "name": "Asset Depreciation Schedule",
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 7305691..6e390ce 100644
--- a/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py
+++ b/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py
@@ -26,6 +26,7 @@
 			self.prepare_draft_asset_depr_schedule_data_from_asset_name_and_fb_name(
 				self.asset, self.finance_book
 			)
+		self.update_shift_depr_schedule()
 
 	def validate(self):
 		self.validate_another_asset_depr_schedule_does_not_exist()
@@ -73,6 +74,16 @@
 	def on_cancel(self):
 		self.db_set("status", "Cancelled")
 
+	def update_shift_depr_schedule(self):
+		if not self.shift_based or self.docstatus != 0:
+			return
+
+		asset_doc = frappe.get_doc("Asset", self.asset)
+		fb_row = asset_doc.finance_books[self.finance_book_id - 1]
+
+		self.make_depr_schedule(asset_doc, fb_row)
+		self.set_accumulated_depreciation(asset_doc, fb_row)
+
 	def prepare_draft_asset_depr_schedule_data_from_asset_name_and_fb_name(self, asset_name, fb_name):
 		asset_doc = frappe.get_doc("Asset", asset_name)
 
@@ -154,13 +165,14 @@
 		self.rate_of_depreciation = row.rate_of_depreciation
 		self.expected_value_after_useful_life = row.expected_value_after_useful_life
 		self.daily_prorata_based = row.daily_prorata_based
+		self.shift_based = row.shift_based
 		self.status = "Draft"
 
 	def make_depr_schedule(
 		self,
 		asset_doc,
 		row,
-		date_of_disposal,
+		date_of_disposal=None,
 		update_asset_finance_book_row=True,
 		value_after_depreciation=None,
 	):
@@ -181,6 +193,8 @@
 		num_of_depreciations_completed = 0
 		depr_schedule = []
 
+		self.schedules_before_clearing = self.get("depreciation_schedule")
+
 		for schedule in self.get("depreciation_schedule"):
 			if schedule.journal_entry:
 				num_of_depreciations_completed += 1
@@ -246,6 +260,7 @@
 				prev_depreciation_amount = 0
 
 			depreciation_amount = get_depreciation_amount(
+				self,
 				asset_doc,
 				value_after_depreciation,
 				row,
@@ -282,10 +297,7 @@
 				)
 
 				if depreciation_amount > 0:
-					self.add_depr_schedule_row(
-						date_of_disposal,
-						depreciation_amount,
-					)
+					self.add_depr_schedule_row(date_of_disposal, depreciation_amount, n)
 
 				break
 
@@ -369,10 +381,7 @@
 				skip_row = True
 
 			if flt(depreciation_amount, asset_doc.precision("gross_purchase_amount")) > 0:
-				self.add_depr_schedule_row(
-					schedule_date,
-					depreciation_amount,
-				)
+				self.add_depr_schedule_row(schedule_date, depreciation_amount, n)
 
 	# to ensure that final accumulated depreciation amount is accurate
 	def get_adjusted_depreciation_amount(
@@ -394,16 +403,22 @@
 	def get_depreciation_amount_for_first_row(self):
 		return self.get("depreciation_schedule")[0].depreciation_amount
 
-	def add_depr_schedule_row(
-		self,
-		schedule_date,
-		depreciation_amount,
-	):
+	def add_depr_schedule_row(self, schedule_date, depreciation_amount, schedule_idx):
+		if self.shift_based:
+			shift = (
+				self.schedules_before_clearing[schedule_idx].shift
+				if self.schedules_before_clearing and len(self.schedules_before_clearing) > schedule_idx
+				else frappe.get_cached_value("Asset Shift Factor", {"default": 1}, "shift_name")
+			)
+		else:
+			shift = None
+
 		self.append(
 			"depreciation_schedule",
 			{
 				"schedule_date": schedule_date,
 				"depreciation_amount": depreciation_amount,
+				"shift": shift,
 			},
 		)
 
@@ -445,6 +460,7 @@
 				and i == max(straight_line_idx) - 1
 				and not date_of_disposal
 				and not date_of_return
+				and not row.shift_based
 			):
 				depreciation_amount += flt(
 					value_after_depreciation - flt(row.expected_value_after_useful_life),
@@ -527,6 +543,7 @@
 
 
 def get_depreciation_amount(
+	asset_depr_schedule,
 	asset,
 	depreciable_value,
 	fb_row,
@@ -537,7 +554,7 @@
 ):
 	if fb_row.depreciation_method in ("Straight Line", "Manual"):
 		return get_straight_line_or_manual_depr_amount(
-			asset, fb_row, schedule_idx, number_of_pending_depreciations
+			asset_depr_schedule, asset, fb_row, schedule_idx, number_of_pending_depreciations
 		)
 	else:
 		rate_of_depreciation = get_updated_rate_of_depreciation_for_wdv_and_dd(
@@ -559,8 +576,11 @@
 
 
 def get_straight_line_or_manual_depr_amount(
-	asset, row, schedule_idx, number_of_pending_depreciations
+	asset_depr_schedule, asset, row, schedule_idx, number_of_pending_depreciations
 ):
+	if row.shift_based:
+		return get_shift_depr_amount(asset_depr_schedule, asset, row, schedule_idx)
+
 	# if the Depreciation Schedule is being modified after Asset Repair due to increase in asset life and value
 	if asset.flags.increase_in_asset_life:
 		return (flt(row.value_after_depreciation) - flt(row.expected_value_after_useful_life)) / (
@@ -655,6 +675,41 @@
 			) / flt(row.total_number_of_depreciations - asset.number_of_depreciations_booked)
 
 
+def get_shift_depr_amount(asset_depr_schedule, asset, row, schedule_idx):
+	if asset_depr_schedule.get("__islocal") and not asset.flags.shift_allocation:
+		return (
+			flt(asset.gross_purchase_amount)
+			- flt(asset.opening_accumulated_depreciation)
+			- flt(row.expected_value_after_useful_life)
+		) / flt(row.total_number_of_depreciations - asset.number_of_depreciations_booked)
+
+	asset_shift_factors_map = get_asset_shift_factors_map()
+	shift = (
+		asset_depr_schedule.schedules_before_clearing[schedule_idx].shift
+		if len(asset_depr_schedule.schedules_before_clearing) > schedule_idx
+		else None
+	)
+	shift_factor = asset_shift_factors_map.get(shift) if shift else 0
+
+	shift_factors_sum = sum(
+		flt(asset_shift_factors_map.get(schedule.shift))
+		for schedule in asset_depr_schedule.schedules_before_clearing
+	)
+
+	return (
+		(
+			flt(asset.gross_purchase_amount)
+			- flt(asset.opening_accumulated_depreciation)
+			- flt(row.expected_value_after_useful_life)
+		)
+		/ flt(shift_factors_sum)
+	) * shift_factor
+
+
+def get_asset_shift_factors_map():
+	return dict(frappe.db.get_all("Asset Shift Factor", ["shift_name", "shift_factor"], as_list=True))
+
+
 def get_wdv_or_dd_depr_amount(
 	depreciable_value,
 	rate_of_depreciation,
@@ -803,7 +858,12 @@
 
 
 def get_temp_asset_depr_schedule_doc(
-	asset_doc, row, date_of_disposal=None, date_of_return=None, update_asset_finance_book_row=False
+	asset_doc,
+	row,
+	date_of_disposal=None,
+	date_of_return=None,
+	update_asset_finance_book_row=False,
+	new_depr_schedule=None,
 ):
 	current_asset_depr_schedule_doc = get_asset_depr_schedule_doc(
 		asset_doc.name, "Active", row.finance_book
@@ -818,6 +878,21 @@
 
 	temp_asset_depr_schedule_doc = frappe.copy_doc(current_asset_depr_schedule_doc)
 
+	if new_depr_schedule:
+		temp_asset_depr_schedule_doc.depreciation_schedule = []
+
+		for schedule in new_depr_schedule:
+			temp_asset_depr_schedule_doc.append(
+				"depreciation_schedule",
+				{
+					"schedule_date": schedule.schedule_date,
+					"depreciation_amount": schedule.depreciation_amount,
+					"accumulated_depreciation_amount": schedule.accumulated_depreciation_amount,
+					"journal_entry": schedule.journal_entry,
+					"shift": schedule.shift,
+				},
+			)
+
 	temp_asset_depr_schedule_doc.prepare_draft_asset_depr_schedule_data(
 		asset_doc,
 		row,
@@ -839,6 +914,7 @@
 	return asset_depr_schedule_doc.get("depreciation_schedule")
 
 
+@frappe.whitelist()
 def get_asset_depr_schedule_doc(asset_name, status, finance_book=None):
 	asset_depr_schedule_name = get_asset_depr_schedule_name(asset_name, status, finance_book)
 
diff --git a/erpnext/assets/doctype/asset_finance_book/asset_finance_book.json b/erpnext/assets/doctype/asset_finance_book/asset_finance_book.json
index e597d5f..25ae7a4 100644
--- a/erpnext/assets/doctype/asset_finance_book/asset_finance_book.json
+++ b/erpnext/assets/doctype/asset_finance_book/asset_finance_book.json
@@ -9,6 +9,7 @@
   "depreciation_method",
   "total_number_of_depreciations",
   "daily_prorata_based",
+  "shift_based",
   "column_break_5",
   "frequency_of_depreciation",
   "depreciation_start_date",
@@ -97,12 +98,19 @@
    "fieldname": "daily_prorata_based",
    "fieldtype": "Check",
    "label": "Depreciate based on daily pro-rata"
+  },
+  {
+   "default": "0",
+   "depends_on": "eval:doc.depreciation_method == \"Straight Line\"",
+   "fieldname": "shift_based",
+   "fieldtype": "Check",
+   "label": "Depreciate based on shifts"
   }
  ],
  "index_web_pages_for_search": 1,
  "istable": 1,
  "links": [],
- "modified": "2023-11-03 21:30:24.266601",
+ "modified": "2023-11-29 00:57:07.579777",
  "modified_by": "Administrator",
  "module": "Assets",
  "name": "Asset Finance Book",
diff --git a/erpnext/assets/doctype/asset_shift_allocation/__init__.py b/erpnext/assets/doctype/asset_shift_allocation/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/assets/doctype/asset_shift_allocation/__init__.py
diff --git a/erpnext/assets/doctype/asset_shift_allocation/asset_shift_allocation.js b/erpnext/assets/doctype/asset_shift_allocation/asset_shift_allocation.js
new file mode 100644
index 0000000..54df693
--- /dev/null
+++ b/erpnext/assets/doctype/asset_shift_allocation/asset_shift_allocation.js
@@ -0,0 +1,14 @@
+// Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+frappe.ui.form.on('Asset Shift Allocation', {
+	onload: function(frm) {
+		frm.events.make_schedules_editable(frm);
+	},
+
+	make_schedules_editable: function(frm) {
+		frm.toggle_enable("depreciation_schedule", true);
+		frm.fields_dict["depreciation_schedule"].grid.toggle_enable("schedule_date", false);
+		frm.fields_dict["depreciation_schedule"].grid.toggle_enable("depreciation_amount", false);
+		frm.fields_dict["depreciation_schedule"].grid.toggle_enable("shift", true);
+	}
+});
diff --git a/erpnext/assets/doctype/asset_shift_allocation/asset_shift_allocation.json b/erpnext/assets/doctype/asset_shift_allocation/asset_shift_allocation.json
new file mode 100644
index 0000000..89fa298
--- /dev/null
+++ b/erpnext/assets/doctype/asset_shift_allocation/asset_shift_allocation.json
@@ -0,0 +1,111 @@
+{
+ "actions": [],
+ "autoname": "naming_series:",
+ "creation": "2023-11-24 15:07:44.652133",
+ "doctype": "DocType",
+ "engine": "InnoDB",
+ "field_order": [
+  "section_break_esaa",
+  "asset",
+  "naming_series",
+  "column_break_tdae",
+  "finance_book",
+  "amended_from",
+  "depreciation_schedule_section",
+  "depreciation_schedule"
+ ],
+ "fields": [
+  {
+   "fieldname": "section_break_esaa",
+   "fieldtype": "Section Break"
+  },
+  {
+   "fieldname": "amended_from",
+   "fieldtype": "Link",
+   "label": "Amended From",
+   "no_copy": 1,
+   "options": "Asset Shift Allocation",
+   "print_hide": 1,
+   "read_only": 1,
+   "search_index": 1
+  },
+  {
+   "fieldname": "asset",
+   "fieldtype": "Link",
+   "in_list_view": 1,
+   "label": "Asset",
+   "options": "Asset",
+   "reqd": 1
+  },
+  {
+   "fieldname": "column_break_tdae",
+   "fieldtype": "Column Break"
+  },
+  {
+   "fieldname": "finance_book",
+   "fieldtype": "Link",
+   "label": "Finance Book",
+   "options": "Finance Book"
+  },
+  {
+   "depends_on": "eval:!doc.__islocal",
+   "fieldname": "depreciation_schedule_section",
+   "fieldtype": "Section Break",
+   "label": "Depreciation Schedule"
+  },
+  {
+   "fieldname": "depreciation_schedule",
+   "fieldtype": "Table",
+   "label": "Depreciation Schedule",
+   "options": "Depreciation Schedule"
+  },
+  {
+   "fieldname": "naming_series",
+   "fieldtype": "Select",
+   "label": "Naming Series",
+   "options": "ACC-ASA-.YYYY.-",
+   "reqd": 1
+  }
+ ],
+ "index_web_pages_for_search": 1,
+ "is_submittable": 1,
+ "links": [],
+ "modified": "2023-11-29 04:05:04.683518",
+ "modified_by": "Administrator",
+ "module": "Assets",
+ "name": "Asset Shift Allocation",
+ "naming_rule": "By \"Naming Series\" field",
+ "owner": "Administrator",
+ "permissions": [
+  {
+   "create": 1,
+   "delete": 1,
+   "email": 1,
+   "export": 1,
+   "print": 1,
+   "read": 1,
+   "report": 1,
+   "role": "System Manager",
+   "share": 1,
+   "write": 1
+  },
+  {
+   "amend": 1,
+   "cancel": 1,
+   "create": 1,
+   "delete": 1,
+   "email": 1,
+   "export": 1,
+   "print": 1,
+   "read": 1,
+   "report": 1,
+   "role": "Accounts User",
+   "share": 1,
+   "submit": 1,
+   "write": 1
+  }
+ ],
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "states": []
+}
\ No newline at end of file
diff --git a/erpnext/assets/doctype/asset_shift_allocation/asset_shift_allocation.py b/erpnext/assets/doctype/asset_shift_allocation/asset_shift_allocation.py
new file mode 100644
index 0000000..d419ef4
--- /dev/null
+++ b/erpnext/assets/doctype/asset_shift_allocation/asset_shift_allocation.py
@@ -0,0 +1,262 @@
+# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+import frappe
+from frappe import _
+from frappe.model.document import Document
+from frappe.utils import (
+	add_months,
+	cint,
+	flt,
+	get_last_day,
+	get_link_to_form,
+	is_last_day_of_the_month,
+)
+
+from erpnext.assets.doctype.asset_activity.asset_activity import add_asset_activity
+from erpnext.assets.doctype.asset_depreciation_schedule.asset_depreciation_schedule import (
+	get_asset_depr_schedule_doc,
+	get_asset_shift_factors_map,
+	get_temp_asset_depr_schedule_doc,
+)
+
+
+class AssetShiftAllocation(Document):
+	def after_insert(self):
+		self.fetch_and_set_depr_schedule()
+
+	def validate(self):
+		self.asset_depr_schedule_doc = get_asset_depr_schedule_doc(
+			self.asset, "Active", self.finance_book
+		)
+
+		self.validate_invalid_shift_change()
+		self.update_depr_schedule()
+
+	def on_submit(self):
+		self.create_new_asset_depr_schedule()
+
+	def fetch_and_set_depr_schedule(self):
+		if self.asset_depr_schedule_doc:
+			if self.asset_depr_schedule_doc.shift_based:
+				for schedule in self.asset_depr_schedule_doc.get("depreciation_schedule"):
+					self.append(
+						"depreciation_schedule",
+						{
+							"schedule_date": schedule.schedule_date,
+							"depreciation_amount": schedule.depreciation_amount,
+							"accumulated_depreciation_amount": schedule.accumulated_depreciation_amount,
+							"journal_entry": schedule.journal_entry,
+							"shift": schedule.shift,
+						},
+					)
+
+				self.flags.ignore_validate = True
+				self.save()
+			else:
+				frappe.throw(
+					_(
+						"Asset Depreciation Schedule for Asset {0} and Finance Book {1} is not using shift based depreciation"
+					).format(self.asset, self.finance_book)
+				)
+		else:
+			frappe.throw(
+				_("Asset Depreciation Schedule not found for Asset {0} and Finance Book {1}").format(
+					self.asset, self.finance_book
+				)
+			)
+
+	def validate_invalid_shift_change(self):
+		if not self.get("depreciation_schedule") or self.docstatus == 1:
+			return
+
+		for i, sch in enumerate(self.depreciation_schedule):
+			if (
+				sch.journal_entry and self.asset_depr_schedule_doc.depreciation_schedule[i].shift != sch.shift
+			):
+				frappe.throw(
+					_(
+						"Row {0}: Shift cannot be changed since the depreciation has already been processed"
+					).format(i)
+				)
+
+	def update_depr_schedule(self):
+		if not self.get("depreciation_schedule") or self.docstatus == 1:
+			return
+
+		self.allocate_shift_diff_in_depr_schedule()
+
+		asset_doc = frappe.get_doc("Asset", self.asset)
+		fb_row = asset_doc.finance_books[self.asset_depr_schedule_doc.finance_book_id - 1]
+
+		asset_doc.flags.shift_allocation = True
+
+		temp_depr_schedule = get_temp_asset_depr_schedule_doc(
+			asset_doc, fb_row, new_depr_schedule=self.depreciation_schedule
+		).get("depreciation_schedule")
+
+		self.depreciation_schedule = []
+
+		for schedule in temp_depr_schedule:
+			self.append(
+				"depreciation_schedule",
+				{
+					"schedule_date": schedule.schedule_date,
+					"depreciation_amount": schedule.depreciation_amount,
+					"accumulated_depreciation_amount": schedule.accumulated_depreciation_amount,
+					"journal_entry": schedule.journal_entry,
+					"shift": schedule.shift,
+				},
+			)
+
+	def allocate_shift_diff_in_depr_schedule(self):
+		asset_shift_factors_map = get_asset_shift_factors_map()
+		reverse_asset_shift_factors_map = {
+			asset_shift_factors_map[k]: k for k in asset_shift_factors_map
+		}
+
+		original_shift_factors_sum = sum(
+			flt(asset_shift_factors_map.get(schedule.shift))
+			for schedule in self.asset_depr_schedule_doc.depreciation_schedule
+		)
+
+		new_shift_factors_sum = sum(
+			flt(asset_shift_factors_map.get(schedule.shift)) for schedule in self.depreciation_schedule
+		)
+
+		diff = new_shift_factors_sum - original_shift_factors_sum
+
+		if diff > 0:
+			for i, schedule in reversed(list(enumerate(self.depreciation_schedule))):
+				if diff <= 0:
+					break
+
+				shift_factor = flt(asset_shift_factors_map.get(schedule.shift))
+
+				if shift_factor <= diff:
+					self.depreciation_schedule.pop()
+					diff -= shift_factor
+				else:
+					try:
+						self.depreciation_schedule[i].shift = reverse_asset_shift_factors_map.get(
+							shift_factor - diff
+						)
+						diff = 0
+					except Exception:
+						frappe.throw(_("Could not auto update shifts. Shift with shift factor {0} needed.")).format(
+							shift_factor - diff
+						)
+		elif diff < 0:
+			shift_factors = list(asset_shift_factors_map.values())
+			desc_shift_factors = sorted(shift_factors, reverse=True)
+			depr_schedule_len_diff = self.asset_depr_schedule_doc.total_number_of_depreciations - len(
+				self.depreciation_schedule
+			)
+			subsets_result = []
+
+			if depr_schedule_len_diff > 0:
+				num_rows_to_add = depr_schedule_len_diff
+
+				while not subsets_result and num_rows_to_add > 0:
+					find_subsets_with_sum(shift_factors, num_rows_to_add, abs(diff), [], subsets_result)
+					if subsets_result:
+						break
+					num_rows_to_add -= 1
+
+				if subsets_result:
+					for i in range(num_rows_to_add):
+						schedule_date = add_months(
+							self.depreciation_schedule[-1].schedule_date,
+							cint(self.asset_depr_schedule_doc.frequency_of_depreciation),
+						)
+
+						if is_last_day_of_the_month(self.depreciation_schedule[-1].schedule_date):
+							schedule_date = get_last_day(schedule_date)
+
+						self.append(
+							"depreciation_schedule",
+							{
+								"schedule_date": schedule_date,
+								"shift": reverse_asset_shift_factors_map.get(subsets_result[0][i]),
+							},
+						)
+
+			if depr_schedule_len_diff <= 0 or not subsets_result:
+				for i, schedule in reversed(list(enumerate(self.depreciation_schedule))):
+					diff = abs(diff)
+
+					if diff <= 0:
+						break
+
+					shift_factor = flt(asset_shift_factors_map.get(schedule.shift))
+
+					if shift_factor <= diff:
+						for sf in desc_shift_factors:
+							if sf - shift_factor <= diff:
+								self.depreciation_schedule[i].shift = reverse_asset_shift_factors_map.get(sf)
+								diff -= sf - shift_factor
+								break
+					else:
+						try:
+							self.depreciation_schedule[i].shift = reverse_asset_shift_factors_map.get(
+								shift_factor + diff
+							)
+							diff = 0
+						except Exception:
+							frappe.throw(_("Could not auto update shifts. Shift with shift factor {0} needed.")).format(
+								shift_factor + diff
+							)
+
+	def create_new_asset_depr_schedule(self):
+		new_asset_depr_schedule_doc = frappe.copy_doc(self.asset_depr_schedule_doc)
+
+		new_asset_depr_schedule_doc.depreciation_schedule = []
+
+		for schedule in self.depreciation_schedule:
+			new_asset_depr_schedule_doc.append(
+				"depreciation_schedule",
+				{
+					"schedule_date": schedule.schedule_date,
+					"depreciation_amount": schedule.depreciation_amount,
+					"accumulated_depreciation_amount": schedule.accumulated_depreciation_amount,
+					"journal_entry": schedule.journal_entry,
+					"shift": schedule.shift,
+				},
+			)
+
+		notes = _(
+			"This schedule was created when Asset {0}'s shifts were adjusted through Asset Shift Allocation {1}."
+		).format(
+			get_link_to_form("Asset", self.asset),
+			get_link_to_form(self.doctype, self.name),
+		)
+
+		new_asset_depr_schedule_doc.notes = notes
+
+		self.asset_depr_schedule_doc.flags.should_not_cancel_depreciation_entries = True
+		self.asset_depr_schedule_doc.cancel()
+
+		new_asset_depr_schedule_doc.submit()
+
+		add_asset_activity(
+			self.asset,
+			_("Asset's depreciation schedule updated after Asset Shift Allocation {0}").format(
+				get_link_to_form(self.doctype, self.name)
+			),
+		)
+
+
+def find_subsets_with_sum(numbers, k, target_sum, current_subset, result):
+	if k == 0 and target_sum == 0:
+		result.append(current_subset.copy())
+		return
+	if k <= 0 or target_sum <= 0 or not numbers:
+		return
+
+	# Include the current number in the subset
+	find_subsets_with_sum(
+		numbers, k - 1, target_sum - numbers[0], current_subset + [numbers[0]], result
+	)
+
+	# Exclude the current number from the subset
+	find_subsets_with_sum(numbers[1:], k, target_sum, current_subset, result)
diff --git a/erpnext/assets/doctype/asset_shift_allocation/test_asset_shift_allocation.py b/erpnext/assets/doctype/asset_shift_allocation/test_asset_shift_allocation.py
new file mode 100644
index 0000000..8d00a24
--- /dev/null
+++ b/erpnext/assets/doctype/asset_shift_allocation/test_asset_shift_allocation.py
@@ -0,0 +1,113 @@
+# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and Contributors
+# See license.txt
+
+import frappe
+from frappe.tests.utils import FrappeTestCase
+from frappe.utils import cstr
+
+from erpnext.assets.doctype.asset.test_asset import create_asset
+from erpnext.assets.doctype.asset_depreciation_schedule.asset_depreciation_schedule import (
+	get_depr_schedule,
+)
+
+
+class TestAssetShiftAllocation(FrappeTestCase):
+	@classmethod
+	def setUpClass(cls):
+		create_asset_shift_factors()
+
+	@classmethod
+	def tearDownClass(cls):
+		frappe.db.rollback()
+
+	def test_asset_shift_allocation(self):
+		asset = create_asset(
+			calculate_depreciation=1,
+			available_for_use_date="2023-01-01",
+			purchase_date="2023-01-01",
+			gross_purchase_amount=120000,
+			depreciation_start_date="2023-01-31",
+			total_number_of_depreciations=12,
+			frequency_of_depreciation=1,
+			shift_based=1,
+			submit=1,
+		)
+
+		expected_schedules = [
+			["2023-01-31", 10000.0, 10000.0, "Single"],
+			["2023-02-28", 10000.0, 20000.0, "Single"],
+			["2023-03-31", 10000.0, 30000.0, "Single"],
+			["2023-04-30", 10000.0, 40000.0, "Single"],
+			["2023-05-31", 10000.0, 50000.0, "Single"],
+			["2023-06-30", 10000.0, 60000.0, "Single"],
+			["2023-07-31", 10000.0, 70000.0, "Single"],
+			["2023-08-31", 10000.0, 80000.0, "Single"],
+			["2023-09-30", 10000.0, 90000.0, "Single"],
+			["2023-10-31", 10000.0, 100000.0, "Single"],
+			["2023-11-30", 10000.0, 110000.0, "Single"],
+			["2023-12-31", 10000.0, 120000.0, "Single"],
+		]
+
+		schedules = [
+			[cstr(d.schedule_date), d.depreciation_amount, d.accumulated_depreciation_amount, d.shift]
+			for d in get_depr_schedule(asset.name, "Active")
+		]
+
+		self.assertEqual(schedules, expected_schedules)
+
+		asset_shift_allocation = frappe.get_doc(
+			{"doctype": "Asset Shift Allocation", "asset": asset.name}
+		).insert()
+
+		schedules = [
+			[cstr(d.schedule_date), d.depreciation_amount, d.accumulated_depreciation_amount, d.shift]
+			for d in asset_shift_allocation.get("depreciation_schedule")
+		]
+
+		self.assertEqual(schedules, expected_schedules)
+
+		asset_shift_allocation = frappe.get_doc("Asset Shift Allocation", asset_shift_allocation.name)
+		asset_shift_allocation.depreciation_schedule[4].shift = "Triple"
+		asset_shift_allocation.save()
+
+		schedules = [
+			[cstr(d.schedule_date), d.depreciation_amount, d.accumulated_depreciation_amount, d.shift]
+			for d in asset_shift_allocation.get("depreciation_schedule")
+		]
+
+		expected_schedules = [
+			["2023-01-31", 10000.0, 10000.0, "Single"],
+			["2023-02-28", 10000.0, 20000.0, "Single"],
+			["2023-03-31", 10000.0, 30000.0, "Single"],
+			["2023-04-30", 10000.0, 40000.0, "Single"],
+			["2023-05-31", 20000.0, 60000.0, "Triple"],
+			["2023-06-30", 10000.0, 70000.0, "Single"],
+			["2023-07-31", 10000.0, 80000.0, "Single"],
+			["2023-08-31", 10000.0, 90000.0, "Single"],
+			["2023-09-30", 10000.0, 100000.0, "Single"],
+			["2023-10-31", 10000.0, 110000.0, "Single"],
+			["2023-11-30", 10000.0, 120000.0, "Single"],
+		]
+
+		self.assertEqual(schedules, expected_schedules)
+
+		asset_shift_allocation.submit()
+
+		schedules = [
+			[cstr(d.schedule_date), d.depreciation_amount, d.accumulated_depreciation_amount, d.shift]
+			for d in get_depr_schedule(asset.name, "Active")
+		]
+
+		self.assertEqual(schedules, expected_schedules)
+
+
+def create_asset_shift_factors():
+	shifts = [
+		{"doctype": "Asset Shift Factor", "shift_name": "Half", "shift_factor": 0.5, "default": 0},
+		{"doctype": "Asset Shift Factor", "shift_name": "Single", "shift_factor": 1, "default": 1},
+		{"doctype": "Asset Shift Factor", "shift_name": "Double", "shift_factor": 1.5, "default": 0},
+		{"doctype": "Asset Shift Factor", "shift_name": "Triple", "shift_factor": 2, "default": 0},
+	]
+
+	for s in shifts:
+		frappe.get_doc(s).insert()
diff --git a/erpnext/assets/doctype/asset_shift_factor/__init__.py b/erpnext/assets/doctype/asset_shift_factor/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/assets/doctype/asset_shift_factor/__init__.py
diff --git a/erpnext/assets/doctype/asset_shift_factor/asset_shift_factor.js b/erpnext/assets/doctype/asset_shift_factor/asset_shift_factor.js
new file mode 100644
index 0000000..88887fe
--- /dev/null
+++ b/erpnext/assets/doctype/asset_shift_factor/asset_shift_factor.js
@@ -0,0 +1,8 @@
+// Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+
+// frappe.ui.form.on("Asset Shift Factor", {
+// 	refresh(frm) {
+
+// 	},
+// });
diff --git a/erpnext/assets/doctype/asset_shift_factor/asset_shift_factor.json b/erpnext/assets/doctype/asset_shift_factor/asset_shift_factor.json
new file mode 100644
index 0000000..fd04ffc
--- /dev/null
+++ b/erpnext/assets/doctype/asset_shift_factor/asset_shift_factor.json
@@ -0,0 +1,74 @@
+{
+ "actions": [],
+ "allow_rename": 1,
+ "autoname": "field:shift_name",
+ "creation": "2023-11-27 18:16:03.980086",
+ "doctype": "DocType",
+ "engine": "InnoDB",
+ "field_order": [
+  "shift_name",
+  "shift_factor",
+  "default"
+ ],
+ "fields": [
+  {
+   "fieldname": "shift_name",
+   "fieldtype": "Data",
+   "in_list_view": 1,
+   "label": "Shift Name",
+   "reqd": 1,
+   "unique": 1
+  },
+  {
+   "fieldname": "shift_factor",
+   "fieldtype": "Float",
+   "in_list_view": 1,
+   "label": "Shift Factor",
+   "reqd": 1
+  },
+  {
+   "default": "0",
+   "fieldname": "default",
+   "fieldtype": "Check",
+   "in_list_view": 1,
+   "label": "Default"
+  }
+ ],
+ "index_web_pages_for_search": 1,
+ "links": [],
+ "modified": "2023-11-29 04:04:24.272872",
+ "modified_by": "Administrator",
+ "module": "Assets",
+ "name": "Asset Shift Factor",
+ "naming_rule": "By fieldname",
+ "owner": "Administrator",
+ "permissions": [
+  {
+   "create": 1,
+   "delete": 1,
+   "email": 1,
+   "export": 1,
+   "print": 1,
+   "read": 1,
+   "report": 1,
+   "role": "System Manager",
+   "share": 1,
+   "write": 1
+  },
+  {
+   "create": 1,
+   "delete": 1,
+   "email": 1,
+   "export": 1,
+   "print": 1,
+   "read": 1,
+   "report": 1,
+   "role": "Accounts User",
+   "share": 1,
+   "write": 1
+  }
+ ],
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "states": []
+}
\ No newline at end of file
diff --git a/erpnext/assets/doctype/asset_shift_factor/asset_shift_factor.py b/erpnext/assets/doctype/asset_shift_factor/asset_shift_factor.py
new file mode 100644
index 0000000..4c275ce
--- /dev/null
+++ b/erpnext/assets/doctype/asset_shift_factor/asset_shift_factor.py
@@ -0,0 +1,24 @@
+# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+import frappe
+from frappe import _
+from frappe.model.document import Document
+
+
+class AssetShiftFactor(Document):
+	def validate(self):
+		self.validate_default()
+
+	def validate_default(self):
+		if self.default:
+			existing_default_shift_factor = frappe.db.get_value(
+				"Asset Shift Factor", {"default": 1}, "name"
+			)
+
+			if existing_default_shift_factor:
+				frappe.throw(
+					_("Asset Shift Factor {0} is set as default currently. Please change it first.").format(
+						frappe.bold(existing_default_shift_factor)
+					)
+				)
diff --git a/erpnext/assets/doctype/asset_shift_factor/test_asset_shift_factor.py b/erpnext/assets/doctype/asset_shift_factor/test_asset_shift_factor.py
new file mode 100644
index 0000000..7507367
--- /dev/null
+++ b/erpnext/assets/doctype/asset_shift_factor/test_asset_shift_factor.py
@@ -0,0 +1,9 @@
+# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and Contributors
+# See license.txt
+
+# import frappe
+from frappe.tests.utils import FrappeTestCase
+
+
+class TestAssetShiftFactor(FrappeTestCase):
+	pass
diff --git a/erpnext/assets/doctype/depreciation_schedule/depreciation_schedule.json b/erpnext/assets/doctype/depreciation_schedule/depreciation_schedule.json
index 884e0c6..ef706e8 100644
--- a/erpnext/assets/doctype/depreciation_schedule/depreciation_schedule.json
+++ b/erpnext/assets/doctype/depreciation_schedule/depreciation_schedule.json
@@ -12,6 +12,7 @@
   "column_break_3",
   "accumulated_depreciation_amount",
   "journal_entry",
+  "shift",
   "make_depreciation_entry"
  ],
  "fields": [
@@ -57,11 +58,17 @@
    "fieldname": "make_depreciation_entry",
    "fieldtype": "Button",
    "label": "Make Depreciation Entry"
+  },
+  {
+   "fieldname": "shift",
+   "fieldtype": "Link",
+   "label": "Shift",
+   "options": "Asset Shift Factor"
   }
  ],
  "istable": 1,
  "links": [],
- "modified": "2023-07-26 12:56:48.718736",
+ "modified": "2023-11-27 18:28:35.325376",
  "modified_by": "Administrator",
  "module": "Assets",
  "name": "Depreciation Schedule",
diff --git a/erpnext/controllers/tests/test_subcontracting_controller.py b/erpnext/controllers/tests/test_subcontracting_controller.py
index 6b61ae9..47762ac 100644
--- a/erpnext/controllers/tests/test_subcontracting_controller.py
+++ b/erpnext/controllers/tests/test_subcontracting_controller.py
@@ -1001,6 +1001,7 @@
 		"Subcontracted Item SA5": {},
 		"Subcontracted Item SA6": {},
 		"Subcontracted Item SA7": {},
+		"Subcontracted Item SA8": {},
 	}
 
 	for item, properties in sub_contracted_items.items():
@@ -1020,6 +1021,7 @@
 		},
 		"Subcontracted SRM Item 4": {"has_serial_no": 1, "serial_no_series": "SRII.####"},
 		"Subcontracted SRM Item 5": {"has_serial_no": 1, "serial_no_series": "SRIID.####"},
+		"Subcontracted SRM Item 8": {},
 	}
 
 	for item, properties in raw_materials.items():
@@ -1043,6 +1045,7 @@
 		"Subcontracted Service Item 5": {},
 		"Subcontracted Service Item 6": {},
 		"Subcontracted Service Item 7": {},
+		"Subcontracted Service Item 8": {},
 	}
 
 	for item, properties in service_items.items():
@@ -1066,6 +1069,7 @@
 		"Subcontracted Item SA5": ["Subcontracted SRM Item 5"],
 		"Subcontracted Item SA6": ["Subcontracted SRM Item 3"],
 		"Subcontracted Item SA7": ["Subcontracted SRM Item 1"],
+		"Subcontracted Item SA8": ["Subcontracted SRM Item 8"],
 	}
 
 	for item_code, raw_materials in boms.items():
diff --git a/erpnext/crm/doctype/lead/lead.py b/erpnext/crm/doctype/lead/lead.py
index fdec88d..d22cc55 100644
--- a/erpnext/crm/doctype/lead/lead.py
+++ b/erpnext/crm/doctype/lead/lead.py
@@ -36,6 +36,15 @@
 	def before_insert(self):
 		self.contact_doc = None
 		if frappe.db.get_single_value("CRM Settings", "auto_creation_of_contact"):
+			if self.source == "Existing Customer" and self.customer:
+				contact = frappe.db.get_value(
+					"Dynamic Link",
+					{"link_doctype": "Customer", "link_name": self.customer},
+					"parent",
+				)
+				if contact:
+					self.contact_doc = frappe.get_doc("Contact", contact)
+					return
 			self.contact_doc = self.create_contact()
 
 	def after_insert(self):
diff --git a/erpnext/patches/v15_0/create_asset_depreciation_schedules_from_assets.py b/erpnext/patches/v15_0/create_asset_depreciation_schedules_from_assets.py
index 9a2a39f..793497b 100644
--- a/erpnext/patches/v15_0/create_asset_depreciation_schedules_from_assets.py
+++ b/erpnext/patches/v15_0/create_asset_depreciation_schedules_from_assets.py
@@ -86,6 +86,7 @@
 			afb.frequency_of_depreciation,
 			afb.rate_of_depreciation,
 			afb.expected_value_after_useful_life,
+			afb.shift_based,
 		)
 		.where(asset.docstatus < 2)
 		.orderby(afb.idx)
diff --git a/erpnext/public/js/communication.js b/erpnext/public/js/communication.js
index 7ce8b09..f205d88 100644
--- a/erpnext/public/js/communication.js
+++ b/erpnext/public/js/communication.js
@@ -13,7 +13,7 @@
 				frappe.confirm(__(confirm_msg, [__("Issue")]), () => {
 					frm.trigger('make_issue_from_communication');
 				})
-			}, "Create");
+			}, __("Create"));
 		}
 
 		if(!in_list(["Lead", "Opportunity"], frm.doc.reference_doctype)) {
diff --git a/erpnext/public/js/utils/sales_common.js b/erpnext/public/js/utils/sales_common.js
index 1d6daa5..5514963 100644
--- a/erpnext/public/js/utils/sales_common.js
+++ b/erpnext/public/js/utils/sales_common.js
@@ -369,6 +369,11 @@
 					}
 				}
 			}
+
+			coupon_code() {
+				this.frm.set_value("discount_amount", 0);
+				this.frm.set_value("additional_discount_percentage", 0);
+			}
 		};
 	}
 }
diff --git a/erpnext/regional/report/uae_vat_201/uae_vat_201.py b/erpnext/regional/report/uae_vat_201/uae_vat_201.py
index 59ef58b..6ef21e5 100644
--- a/erpnext/regional/report/uae_vat_201/uae_vat_201.py
+++ b/erpnext/regional/report/uae_vat_201/uae_vat_201.py
@@ -141,7 +141,7 @@
 		return frappe.db.sql(
 			"""
 			select
-				s.vat_emirate as emirate, sum(i.base_amount) as total, sum(i.tax_amount)
+				s.vat_emirate as emirate, sum(i.base_net_amount) as total, sum(i.tax_amount)
 			from
 				`tabSales Invoice Item` i inner join `tabSales Invoice` s
 			on
@@ -356,7 +356,7 @@
 			frappe.db.sql(
 				"""
 			select
-				sum(i.base_amount) as total
+				sum(i.base_net_amount) as total
 			from
 				`tabSales Invoice Item` i inner join `tabSales Invoice` s
 			on
@@ -383,7 +383,7 @@
 			frappe.db.sql(
 				"""
 			select
-				sum(i.base_amount) as total
+				sum(i.base_net_amount) as total
 			from
 				`tabSales Invoice Item` i inner join `tabSales Invoice` s
 			on
diff --git a/erpnext/stock/doctype/delivery_note/test_delivery_note.py b/erpnext/stock/doctype/delivery_note/test_delivery_note.py
index 137c352..9465574 100644
--- a/erpnext/stock/doctype/delivery_note/test_delivery_note.py
+++ b/erpnext/stock/doctype/delivery_note/test_delivery_note.py
@@ -1247,6 +1247,25 @@
 		dn.reload()
 		self.assertFalse(dn.items[0].target_warehouse)
 
+	def test_serial_no_status(self):
+		from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt
+
+		item = make_item(
+			"Test Serial Item For Status",
+			{"has_serial_no": 1, "is_stock_item": 1, "serial_no_series": "TESTSERIAL.#####"},
+		)
+
+		item_code = item.name
+		pi = make_purchase_receipt(qty=1, item_code=item.name)
+		pi.reload()
+		serial_no = get_serial_nos_from_bundle(pi.items[0].serial_and_batch_bundle)
+
+		self.assertEqual(frappe.db.get_value("Serial No", serial_no, "status"), "Active")
+
+		dn = create_delivery_note(qty=1, item_code=item_code, serial_no=serial_no)
+		dn.reload()
+		self.assertEqual(frappe.db.get_value("Serial No", serial_no, "status"), "Delivered")
+
 
 def create_delivery_note(**args):
 	dn = frappe.new_doc("Delivery Note")
diff --git a/erpnext/stock/doctype/serial_no/serial_no.js b/erpnext/stock/doctype/serial_no/serial_no.js
index 9d5555e..1cb9fd1 100644
--- a/erpnext/stock/doctype/serial_no/serial_no.js
+++ b/erpnext/stock/doctype/serial_no/serial_no.js
@@ -18,3 +18,22 @@
 frappe.ui.form.on("Serial No", "refresh", function(frm) {
 	frm.toggle_enable("item_code", frm.doc.__islocal);
 });
+
+
+frappe.ui.form.on("Serial No", {
+	refresh(frm) {
+		frm.trigger("view_ledgers")
+	},
+
+	view_ledgers(frm) {
+		frm.add_custom_button(__("View Ledgers"), () => {
+			frappe.route_options = {
+				"item_code": frm.doc.item_code,
+				"serial_no": frm.doc.name,
+				"posting_date": frappe.datetime.now_date(),
+				"posting_time": frappe.datetime.now_time()
+			};
+			frappe.set_route("query-report", "Serial No Ledger");
+		}).addClass('btn-primary');
+	}
+})
\ No newline at end of file
diff --git a/erpnext/stock/doctype/serial_no/serial_no.json b/erpnext/stock/doctype/serial_no/serial_no.json
index ed1b0af..b4ece00 100644
--- a/erpnext/stock/doctype/serial_no/serial_no.json
+++ b/erpnext/stock/doctype/serial_no/serial_no.json
@@ -269,7 +269,7 @@
    "in_list_view": 1,
    "in_standard_filter": 1,
    "label": "Status",
-   "options": "\nActive\nInactive\nExpired",
+   "options": "\nActive\nInactive\nDelivered\nExpired",
    "read_only": 1
   },
   {
@@ -280,7 +280,7 @@
  "icon": "fa fa-barcode",
  "idx": 1,
  "links": [],
- "modified": "2023-04-16 15:58:46.139887",
+ "modified": "2023-11-28 15:37:59.489945",
  "modified_by": "Administrator",
  "module": "Stock",
  "name": "Serial No",
diff --git a/erpnext/stock/report/serial_no_ledger/serial_no_ledger.py b/erpnext/stock/report/serial_no_ledger/serial_no_ledger.py
index 7212b92..ae12fbb 100644
--- a/erpnext/stock/report/serial_no_ledger/serial_no_ledger.py
+++ b/erpnext/stock/report/serial_no_ledger/serial_no_ledger.py
@@ -36,21 +36,27 @@
 			"fieldtype": "Link",
 			"fieldname": "company",
 			"options": "Company",
-			"width": 150,
+			"width": 120,
 		},
 		{
 			"label": _("Warehouse"),
 			"fieldtype": "Link",
 			"fieldname": "warehouse",
 			"options": "Warehouse",
-			"width": 150,
+			"width": 120,
+		},
+		{
+			"label": _("Status"),
+			"fieldtype": "Data",
+			"fieldname": "status",
+			"width": 120,
 		},
 		{
 			"label": _("Serial No"),
 			"fieldtype": "Link",
 			"fieldname": "serial_no",
 			"options": "Serial No",
-			"width": 150,
+			"width": 130,
 		},
 		{
 			"label": _("Valuation Rate"),
@@ -58,6 +64,12 @@
 			"fieldname": "valuation_rate",
 			"width": 150,
 		},
+		{
+			"label": _("Qty"),
+			"fieldtype": "Float",
+			"fieldname": "qty",
+			"width": 150,
+		},
 	]
 
 	return columns
@@ -83,12 +95,16 @@
 				"posting_time": row.posting_time,
 				"voucher_type": row.voucher_type,
 				"voucher_no": row.voucher_no,
+				"status": "Active" if row.actual_qty > 0 else "Delivered",
 				"company": row.company,
 				"warehouse": row.warehouse,
+				"qty": 1 if row.actual_qty > 0 else -1,
 			}
 		)
 
-		serial_nos = bundle_wise_serial_nos.get(row.serial_and_batch_bundle, [])
+		serial_nos = [{"serial_no": row.serial_no, "valuation_rate": row.valuation_rate}]
+		if row.serial_and_batch_bundle:
+			serial_nos = bundle_wise_serial_nos.get(row.serial_and_batch_bundle, [])
 
 		for index, bundle_data in enumerate(serial_nos):
 			if index == 0:
diff --git a/erpnext/stock/serial_batch_bundle.py b/erpnext/stock/serial_batch_bundle.py
index da98455..de28be1 100644
--- a/erpnext/stock/serial_batch_bundle.py
+++ b/erpnext/stock/serial_batch_bundle.py
@@ -255,11 +255,15 @@
 		if not serial_nos:
 			return
 
+		status = "Inactive"
+		if self.sle.actual_qty < 0:
+			status = "Delivered"
+
 		sn_table = frappe.qb.DocType("Serial No")
 		(
 			frappe.qb.update(sn_table)
 			.set(sn_table.warehouse, warehouse)
-			.set(sn_table.status, "Active" if warehouse else "Inactive")
+			.set(sn_table.status, "Active" if warehouse else status)
 			.where(sn_table.name.isin(serial_nos))
 		).run()
 
diff --git a/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.py b/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.py
index 33a2061..6a65846 100644
--- a/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.py
+++ b/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.py
@@ -8,7 +8,7 @@
 
 from erpnext.buying.doctype.purchase_order.purchase_order import is_subcontracting_order_created
 from erpnext.controllers.subcontracting_controller import SubcontractingController
-from erpnext.stock.stock_balance import get_ordered_qty, update_bin_qty
+from erpnext.stock.stock_balance import update_bin_qty
 from erpnext.stock.utils import get_bin
 
 
@@ -114,7 +114,32 @@
 			):
 				item_wh_list.append([item.item_code, item.warehouse])
 		for item_code, warehouse in item_wh_list:
-			update_bin_qty(item_code, warehouse, {"ordered_qty": get_ordered_qty(item_code, warehouse)})
+			update_bin_qty(
+				item_code, warehouse, {"ordered_qty": self.get_ordered_qty(item_code, warehouse)}
+			)
+
+	@staticmethod
+	def get_ordered_qty(item_code, warehouse):
+		table = frappe.qb.DocType("Subcontracting Order")
+		child = frappe.qb.DocType("Subcontracting Order Item")
+
+		query = (
+			frappe.qb.from_(table)
+			.inner_join(child)
+			.on(table.name == child.parent)
+			.select((child.qty - child.received_qty) * child.conversion_factor)
+			.where(
+				(table.docstatus == 1)
+				& (child.item_code == item_code)
+				& (child.warehouse == warehouse)
+				& (child.qty > child.received_qty)
+				& (table.status != "Completed")
+			)
+		)
+
+		query = query.run()
+
+		return flt(query[0][0]) if query else 0
 
 	def update_reserved_qty_for_subcontracting(self):
 		for item in self.supplied_items:
diff --git a/erpnext/subcontracting/doctype/subcontracting_order/test_subcontracting_order.py b/erpnext/subcontracting/doctype/subcontracting_order/test_subcontracting_order.py
index 22fdc13..3557858 100644
--- a/erpnext/subcontracting/doctype/subcontracting_order/test_subcontracting_order.py
+++ b/erpnext/subcontracting/doctype/subcontracting_order/test_subcontracting_order.py
@@ -6,6 +6,7 @@
 
 import frappe
 from frappe.tests.utils import FrappeTestCase
+from frappe.utils import flt
 
 from erpnext.buying.doctype.purchase_order.purchase_order import get_mapped_subcontracting_order
 from erpnext.controllers.subcontracting_controller import (
@@ -566,6 +567,67 @@
 		self.assertEqual(sco.status, "Closed")
 		self.assertEqual(sco.supplied_items[0].returned_qty, 5)
 
+	def test_ordered_qty_for_subcontracting_order(self):
+		service_items = [
+			{
+				"warehouse": "_Test Warehouse - _TC",
+				"item_code": "Subcontracted Service Item 8",
+				"qty": 10,
+				"rate": 100,
+				"fg_item": "Subcontracted Item SA8",
+				"fg_item_qty": 10,
+			},
+		]
+
+		ordered_qty = frappe.db.get_value(
+			"Bin",
+			filters={"warehouse": "_Test Warehouse - _TC", "item_code": "Subcontracted Item SA8"},
+			fieldname="ordered_qty",
+		)
+		ordered_qty = flt(ordered_qty)
+
+		sco = get_subcontracting_order(service_items=service_items)
+		sco.reload()
+
+		new_ordered_qty = frappe.db.get_value(
+			"Bin",
+			filters={"warehouse": "_Test Warehouse - _TC", "item_code": "Subcontracted Item SA8"},
+			fieldname="ordered_qty",
+		)
+		new_ordered_qty = flt(new_ordered_qty)
+
+		self.assertEqual(ordered_qty + 10, new_ordered_qty)
+
+		for row in sco.supplied_items:
+			make_stock_entry(
+				target="_Test Warehouse 1 - _TC",
+				item_code=row.rm_item_code,
+				qty=row.required_qty,
+				basic_rate=100,
+			)
+
+		scr = make_subcontracting_receipt(sco.name)
+		scr.submit()
+
+		new_ordered_qty = frappe.db.get_value(
+			"Bin",
+			filters={"warehouse": "_Test Warehouse - _TC", "item_code": "Subcontracted Item SA8"},
+			fieldname="ordered_qty",
+		)
+
+		self.assertEqual(ordered_qty, new_ordered_qty)
+
+		scr.reload()
+		scr.cancel()
+
+		new_ordered_qty = frappe.db.get_value(
+			"Bin",
+			filters={"warehouse": "_Test Warehouse - _TC", "item_code": "Subcontracted Item SA8"},
+			fieldname="ordered_qty",
+		)
+
+		self.assertEqual(ordered_qty + 10, new_ordered_qty)
+
 
 def create_subcontracting_order(**args):
 	args = frappe._dict(args)
diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py
index 33e5c3a..6df9129 100644
--- a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py
+++ b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py
@@ -97,12 +97,12 @@
 		)
 		self.update_status_updater_args()
 		self.update_prevdoc_status()
-		self.update_stock_ledger()
-		self.make_gl_entries_on_cancel()
-		self.repost_future_sle_and_gle()
 		self.delete_auto_created_batches()
 		self.set_consumed_qty_in_subcontract_order()
 		self.set_subcontracting_order_status()
+		self.update_stock_ledger()
+		self.make_gl_entries_on_cancel()
+		self.repost_future_sle_and_gle()
 		self.update_status()
 
 	def validate_items_qty(self):
diff --git a/erpnext/support/doctype/warranty_claim/warranty_claim.js b/erpnext/support/doctype/warranty_claim/warranty_claim.js
index 358768e..10cb37f 100644
--- a/erpnext/support/doctype/warranty_claim/warranty_claim.js
+++ b/erpnext/support/doctype/warranty_claim/warranty_claim.js
@@ -4,93 +4,67 @@
 frappe.provide("erpnext.support");
 
 frappe.ui.form.on("Warranty Claim", {
-	setup: function(frm) {
-		frm.set_query('contact_person', erpnext.queries.contact_query);
-		frm.set_query('customer_address', erpnext.queries.address_query);
-		frm.set_query('customer', erpnext.queries.customer);
+	setup: (frm) => {
+		frm.set_query("contact_person", erpnext.queries.contact_query);
+		frm.set_query("customer_address", erpnext.queries.address_query);
+		frm.set_query("customer", erpnext.queries.customer);
 
-		frm.add_fetch('serial_no', 'item_code', 'item_code');
-		frm.add_fetch('serial_no', 'item_name', 'item_name');
-		frm.add_fetch('serial_no', 'description', 'description');
-		frm.add_fetch('serial_no', 'maintenance_status', 'warranty_amc_status');
-		frm.add_fetch('serial_no', 'warranty_expiry_date', 'warranty_expiry_date');
-		frm.add_fetch('serial_no', 'amc_expiry_date', 'amc_expiry_date');
-		frm.add_fetch('serial_no', 'customer', 'customer');
-		frm.add_fetch('serial_no', 'customer_name', 'customer_name');
-		frm.add_fetch('item_code', 'item_name', 'item_name');
-		frm.add_fetch('item_code', 'description', 'description');
+		frm.set_query("serial_no", () => {
+			let filters = {
+				company: frm.doc.company,
+			};
+
+			if (frm.doc.item_code) {
+				filters["item_code"] = frm.doc.item_code;
+			}
+
+			return { filters: filters };
+		});
+
+		frm.set_query("item_code", () => {
+			return {
+				filters: {
+					disabled: 0,
+				},
+			};
+		});
 	},
-	onload: function(frm) {
-		if(!frm.doc.status) {
-			frm.set_value('status', 'Open');
+
+	onload: (frm) => {
+		if (!frm.doc.status) {
+			frm.set_value("status", "Open");
 		}
 	},
-	customer: function(frm) {
+
+	refresh: (frm) => {
+		frappe.dynamic_link = {
+			doc: frm.doc,
+			fieldname: "customer",
+			doctype: "Customer",
+		};
+
+		if (
+			!frm.doc.__islocal &&
+			["Open", "Work In Progress"].includes(frm.doc.status)
+		) {
+			frm.add_custom_button(__("Maintenance Visit"), () => {
+				frappe.model.open_mapped_doc({
+					method: "erpnext.support.doctype.warranty_claim.warranty_claim.make_maintenance_visit",
+					frm: frm,
+				});
+			});
+		}
+	},
+
+	customer: (frm) => {
 		erpnext.utils.get_party_details(frm);
 	},
-	customer_address: function(frm) {
+
+	customer_address: (frm) => {
 		erpnext.utils.get_address_display(frm);
 	},
-	contact_person: function(frm) {
+
+	contact_person: (frm) => {
 		erpnext.utils.get_contact_details(frm);
-	}
+	},
 });
-
-erpnext.support.WarrantyClaim = class WarrantyClaim extends frappe.ui.form.Controller {
-	refresh() {
-		frappe.dynamic_link = {doc: this.frm.doc, fieldname: 'customer', doctype: 'Customer'}
-
-		if(!cur_frm.doc.__islocal &&
-			(cur_frm.doc.status=='Open' || cur_frm.doc.status == 'Work In Progress')) {
-			cur_frm.add_custom_button(__('Maintenance Visit'),
-				this.make_maintenance_visit);
-		}
-	}
-
-	make_maintenance_visit() {
-		frappe.model.open_mapped_doc({
-			method: "erpnext.support.doctype.warranty_claim.warranty_claim.make_maintenance_visit",
-			frm: cur_frm
-		})
-	}
-};
-
-extend_cscript(cur_frm.cscript, new erpnext.support.WarrantyClaim({frm: cur_frm}));
-
-cur_frm.fields_dict['serial_no'].get_query = function(doc, cdt, cdn) {
-	var cond = [];
-	var filter = [
-		['Serial No', 'docstatus', '!=', 2]
-	];
-	if(doc.item_code) {
-		cond = ['Serial No', 'item_code', '=', doc.item_code];
-		filter.push(cond);
-	}
-	if(doc.customer) {
-		cond = ['Serial No', 'customer', '=', doc.customer];
-		filter.push(cond);
-	}
-	return{
-		filters:filter
-	}
-}
-
-cur_frm.fields_dict['item_code'].get_query = function(doc, cdt, cdn) {
-	if(doc.serial_no) {
-		return{
-			doctype: "Serial No",
-			fields: "item_code",
-			filters:{
-				name: doc.serial_no
-			}
-		}
-	}
-	else{
-		return{
-			filters:[
-				['Item', 'docstatus', '!=', 2],
-				['Item', 'disabled', '=', 0]
-			]
-		}
-	}
-};
diff --git a/erpnext/support/doctype/warranty_claim/warranty_claim.json b/erpnext/support/doctype/warranty_claim/warranty_claim.json
index 01d9b01..9af2b46 100644
--- a/erpnext/support/doctype/warranty_claim/warranty_claim.json
+++ b/erpnext/support/doctype/warranty_claim/warranty_claim.json
@@ -92,7 +92,8 @@
    "fieldname": "serial_no",
    "fieldtype": "Link",
    "label": "Serial No",
-   "options": "Serial No"
+   "options": "Serial No",
+   "search_index": 1
   },
   {
    "fieldname": "customer",
@@ -128,6 +129,8 @@
    "options": "fa fa-ticket"
   },
   {
+   "fetch_from": "serial_no.item_code",
+   "fetch_if_empty": 1,
    "fieldname": "item_code",
    "fieldtype": "Link",
    "in_list_view": 1,
@@ -140,6 +143,7 @@
   },
   {
    "depends_on": "eval:doc.item_code",
+   "fetch_from": "item_code.item_name",
    "fieldname": "item_name",
    "fieldtype": "Data",
    "label": "Item Name",
@@ -149,6 +153,7 @@
   },
   {
    "depends_on": "eval:doc.item_code",
+   "fetch_from": "item_code.description",
    "fieldname": "description",
    "fieldtype": "Small Text",
    "label": "Description",
@@ -164,17 +169,24 @@
    "width": "50%"
   },
   {
+   "fetch_from": "serial_no.maintenance_status",
+   "fetch_if_empty": 1,
    "fieldname": "warranty_amc_status",
    "fieldtype": "Select",
    "label": "Warranty / AMC Status",
-   "options": "\nUnder Warranty\nOut of Warranty\nUnder AMC\nOut of AMC"
+   "options": "\nUnder Warranty\nOut of Warranty\nUnder AMC\nOut of AMC",
+   "search_index": 1
   },
   {
+   "fetch_from": "serial_no.warranty_expiry_date",
+   "fetch_if_empty": 1,
    "fieldname": "warranty_expiry_date",
    "fieldtype": "Date",
    "label": "Warranty Expiry Date"
   },
   {
+   "fetch_from": "serial_no.amc_expiry_date",
+   "fetch_if_empty": 1,
    "fieldname": "amc_expiry_date",
    "fieldtype": "Date",
    "label": "AMC Expiry Date"
@@ -225,6 +237,7 @@
   {
    "bold": 1,
    "depends_on": "customer",
+   "fetch_from": "customer.customer_name",
    "fieldname": "customer_name",
    "fieldtype": "Data",
    "in_global_search": 1,
@@ -366,7 +379,7 @@
  "icon": "fa fa-bug",
  "idx": 1,
  "links": [],
- "modified": "2023-06-03 16:17:07.694449",
+ "modified": "2023-11-28 17:30:35.676410",
  "modified_by": "Administrator",
  "module": "Support",
  "name": "Warranty Claim",