From dd4769ecf1f760b10f94124c94ab30e323f356ca Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Wed, 20 Sep 2023 18:09:03 +0530 Subject: [PATCH] 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 --- erpnext/controllers/buying_controller.py | 9 ++++ erpnext/controllers/selling_controller.py | 6 +++ erpnext/public/js/controllers/buying.js | 1 + erpnext/public/js/controllers/transaction.js | 18 +++++++ erpnext/public/js/utils/sales_common.js | 1 + .../purchase_receipt_item.json | 7 +-- .../stock_settings/stock_settings.json | 29 +++++++++-- .../doctype/stock_settings/stock_settings.py | 52 +++++++++++++++++++ 8 files changed, 116 insertions(+), 7 deletions(-) diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py index c302ece596..a76abe2154 100644 --- a/erpnext/controllers/buying_controller.py +++ b/erpnext/controllers/buying_controller.py @@ -418,6 +418,10 @@ class BuyingController(SubcontractingController): 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 @@ class BuyingController(SubcontractingController): 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 9771f60ceb..901466267b 100644 --- a/erpnext/controllers/selling_controller.py +++ b/erpnext/controllers/selling_controller.py @@ -194,11 +194,17 @@ class SellingController(StockController): 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 54f0aadb1d..0860d9c667 100644 --- a/erpnext/public/js/controllers/buying.js +++ b/erpnext/public/js/controllers/buying.js @@ -9,6 +9,7 @@ erpnext.buying = { 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 975adc2ace..b0a9e405cd 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -224,6 +224,14 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe } } + + 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 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe ]); } + 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 89dcaa6485..1d6daa554b 100644 --- a/erpnext/public/js/utils/sales_common.js +++ b/erpnext/public/js/utils/sales_common.js @@ -8,6 +8,7 @@ erpnext.sales_common = { 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 d31fec54c6..5eb3656c00 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 88b5575a37..4fbc0eb43a 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", @@ -357,10 +361,6 @@ "fieldtype": "Check", "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 9ad3c9db28..c7afb105b1 100644 --- a/erpnext/stock/doctype/stock_settings/stock_settings.py +++ b/erpnext/stock/doctype/stock_settings/stock_settings.py @@ -56,6 +56,8 @@ class StockSettings(Document): 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 @@ class StockSettings(Document): 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",