feat: allow to edit Stock Quantity in the Sales and Purchase Transactions (#36600)

feat: allow to edit Stock Quantity in the Sales and Purchase transactions
diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py
index c302ece..a76abe2 100644
--- a/erpnext/controllers/buying_controller.py
+++ b/erpnext/controllers/buying_controller.py
@@ -418,6 +418,10 @@
 					item.bom = None
 
 	def set_qty_as_per_stock_uom(self):
+		allow_to_edit_stock_qty = frappe.db.get_single_value(
+			"Stock Settings", "allow_to_edit_stock_uom_qty_for_purchase"
+		)
+
 		for d in self.get("items"):
 			if d.meta.get_field("stock_qty"):
 				# Check if item code is present
@@ -432,6 +436,11 @@
 						d.conversion_factor, d.precision("conversion_factor")
 					)
 
+				if allow_to_edit_stock_qty:
+					d.stock_qty = flt(d.stock_qty, d.precision("stock_qty"))
+					if d.get("received_stock_qty"):
+						d.received_stock_qty = flt(d.received_stock_qty, d.precision("received_stock_qty"))
+
 	def validate_purchase_return(self):
 		for d in self.get("items"):
 			if self.is_return and flt(d.rejected_qty) != 0:
diff --git a/erpnext/controllers/selling_controller.py b/erpnext/controllers/selling_controller.py
index 9771f60..9014662 100644
--- a/erpnext/controllers/selling_controller.py
+++ b/erpnext/controllers/selling_controller.py
@@ -194,11 +194,17 @@
 					frappe.throw(_("Maximum discount for Item {0} is {1}%").format(d.item_code, discount))
 
 	def set_qty_as_per_stock_uom(self):
+		allow_to_edit_stock_qty = frappe.db.get_single_value(
+			"Stock Settings", "allow_to_edit_stock_uom_qty_for_sales"
+		)
+
 		for d in self.get("items"):
 			if d.meta.get_field("stock_qty"):
 				if not d.conversion_factor:
 					frappe.throw(_("Row {0}: Conversion Factor is mandatory").format(d.idx))
 				d.stock_qty = flt(d.qty) * flt(d.conversion_factor)
+				if allow_to_edit_stock_qty:
+					d.stock_qty = flt(d.stock_qty, d.precision("stock_qty"))
 
 	def validate_selling_price(self):
 		def throw_message(idx, item_name, rate, ref_rate_field):
diff --git a/erpnext/public/js/controllers/buying.js b/erpnext/public/js/controllers/buying.js
index 54f0aad..0860d9c 100644
--- a/erpnext/public/js/controllers/buying.js
+++ b/erpnext/public/js/controllers/buying.js
@@ -9,6 +9,7 @@
 		erpnext.buying.BuyingController = class BuyingController extends erpnext.TransactionController {
 			setup() {
 				super.setup();
+				this.toggle_enable_for_stock_uom("allow_to_edit_stock_uom_qty_for_purchase");
 				this.frm.email_field = "contact_email";
 			}
 
diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js
index 975adc2..b0a9e40 100644
--- a/erpnext/public/js/controllers/transaction.js
+++ b/erpnext/public/js/controllers/transaction.js
@@ -224,6 +224,14 @@
 		}
 
 	}
+
+	toggle_enable_for_stock_uom(field) {
+		frappe.db.get_single_value('Stock Settings', field)
+		.then(value => {
+			this.frm.fields_dict["items"].grid.toggle_enable("stock_qty", value);
+		});
+	}
+
 	onload() {
 		var me = this;
 
@@ -1191,6 +1199,16 @@
 		]);
 	}
 
+	stock_qty(doc, cdt, cdn) {
+		let item = frappe.get_doc(cdt, cdn);
+		item.conversion_factor = 1.0;
+		if (item.stock_qty) {
+			item.conversion_factor = flt(item.stock_qty) / flt(item.qty);
+		}
+
+		refresh_field("conversion_factor", item.name, item.parentfield);
+	}
+
 	calculate_stock_uom_rate(doc, cdt, cdn) {
 		let item = frappe.get_doc(cdt, cdn);
 		item.stock_uom_rate = flt(item.rate)/flt(item.conversion_factor);
diff --git a/erpnext/public/js/utils/sales_common.js b/erpnext/public/js/utils/sales_common.js
index 89dcaa6..1d6daa5 100644
--- a/erpnext/public/js/utils/sales_common.js
+++ b/erpnext/public/js/utils/sales_common.js
@@ -8,6 +8,7 @@
 		erpnext.selling.SellingController = class SellingController extends erpnext.TransactionController {
 			setup() {
 				super.setup();
+				this.toggle_enable_for_stock_uom("allow_to_edit_stock_uom_qty_for_sales");
 				this.frm.email_field = "contact_email";
 			}
 
diff --git a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json
index d31fec5..5eb3656 100644
--- a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json
+++ b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json
@@ -34,8 +34,8 @@
   "sample_quantity",
   "tracking_section",
   "received_stock_qty",
-  "stock_qty",
   "col_break_tracking_section",
+  "stock_qty",
   "returned_qty",
   "rate_and_amount",
   "price_list_rate",
@@ -858,7 +858,8 @@
   },
   {
    "fieldname": "tracking_section",
-   "fieldtype": "Section Break"
+   "fieldtype": "Section Break",
+   "label": "Qty as Per Stock UOM"
   },
   {
    "fieldname": "col_break_tracking_section",
@@ -1060,7 +1061,7 @@
  "idx": 1,
  "istable": 1,
  "links": [],
- "modified": "2023-07-26 12:55:15.234477",
+ "modified": "2023-08-11 16:16:16.504549",
  "modified_by": "Administrator",
  "module": "Stock",
  "name": "Purchase Receipt Item",
diff --git a/erpnext/stock/doctype/stock_settings/stock_settings.json b/erpnext/stock/doctype/stock_settings/stock_settings.json
index 88b5575..4fbc0eb 100644
--- a/erpnext/stock/doctype/stock_settings/stock_settings.json
+++ b/erpnext/stock/doctype/stock_settings/stock_settings.json
@@ -18,6 +18,10 @@
   "auto_insert_price_list_rate_if_missing",
   "column_break_12",
   "update_existing_price_list_rate",
+  "conversion_factor_section",
+  "allow_to_edit_stock_uom_qty_for_sales",
+  "column_break_lznj",
+  "allow_to_edit_stock_uom_qty_for_purchase",
   "stock_validations_tab",
   "section_break_9",
   "over_delivery_receipt_allowance",
@@ -358,10 +362,6 @@
    "label": "Allow Partial Reservation"
   },
   {
-   "fieldname": "section_break_plhx",
-   "fieldtype": "Section Break"
-  },
-  {
    "fieldname": "column_break_mhzc",
    "fieldtype": "Column Break"
   },
@@ -400,6 +400,27 @@
    "fieldname": "auto_reserve_stock_for_sales_order",
    "fieldtype": "Check",
    "label": "Auto Reserve Stock for Sales Order"
+  },
+  {
+   "fieldname": "conversion_factor_section",
+   "fieldtype": "Section Break",
+   "label": "Stock UOM Quantity"
+  },
+  {
+   "fieldname": "column_break_lznj",
+   "fieldtype": "Column Break"
+  },
+  {
+   "default": "0",
+   "fieldname": "allow_to_edit_stock_uom_qty_for_sales",
+   "fieldtype": "Check",
+   "label": "Allow to Edit Stock UOM Qty for Sales Documents"
+  },
+  {
+   "default": "0",
+   "fieldname": "allow_to_edit_stock_uom_qty_for_purchase",
+   "fieldtype": "Check",
+   "label": "Allow to Edit Stock UOM Qty for Purchase Documents"
   }
  ],
  "icon": "icon-cog",
diff --git a/erpnext/stock/doctype/stock_settings/stock_settings.py b/erpnext/stock/doctype/stock_settings/stock_settings.py
index 9ad3c9d..c7afb10 100644
--- a/erpnext/stock/doctype/stock_settings/stock_settings.py
+++ b/erpnext/stock/doctype/stock_settings/stock_settings.py
@@ -56,6 +56,8 @@
 		self.validate_clean_description_html()
 		self.validate_pending_reposts()
 		self.validate_stock_reservation()
+		self.change_precision_for_for_sales()
+		self.change_precision_for_purchase()
 
 	def validate_warehouses(self):
 		warehouse_fields = ["default_warehouse", "sample_retention_warehouse"]
@@ -167,6 +169,56 @@
 	def on_update(self):
 		self.toggle_warehouse_field_for_inter_warehouse_transfer()
 
+	def change_precision_for_for_sales(self):
+		doc_before_save = self.get_doc_before_save()
+		if doc_before_save and (
+			doc_before_save.allow_to_edit_stock_uom_qty_for_sales
+			== self.allow_to_edit_stock_uom_qty_for_sales
+		):
+			return
+
+		if self.allow_to_edit_stock_uom_qty_for_sales:
+			doctypes = ["Sales Order Item", "Sales Invoice Item", "Delivery Note Item", "Quotation Item"]
+			self.make_property_setter_for_precision(doctypes)
+
+	def change_precision_for_purchase(self):
+		doc_before_save = self.get_doc_before_save()
+		if doc_before_save and (
+			doc_before_save.allow_to_edit_stock_uom_qty_for_purchase
+			== self.allow_to_edit_stock_uom_qty_for_purchase
+		):
+			return
+
+		if self.allow_to_edit_stock_uom_qty_for_purchase:
+			doctypes = [
+				"Purchase Order Item",
+				"Purchase Receipt Item",
+				"Purchase Invoice Item",
+				"Request for Quotation Item",
+				"Supplier Quotation Item",
+				"Material Request Item",
+			]
+			self.make_property_setter_for_precision(doctypes)
+
+	@staticmethod
+	def make_property_setter_for_precision(doctypes):
+		for doctype in doctypes:
+			if property_name := frappe.db.exists(
+				"Property Setter",
+				{"doc_type": doctype, "field_name": "conversion_factor", "property": "precision"},
+			):
+				frappe.db.set_value("Property Setter", property_name, "value", 9)
+				continue
+
+			make_property_setter(
+				doctype,
+				"conversion_factor",
+				"precision",
+				9,
+				"Float",
+				validate_fields_for_doctype=False,
+			)
+
 	def toggle_warehouse_field_for_inter_warehouse_transfer(self):
 		make_property_setter(
 			"Sales Invoice Item",