From 2d6640ac61fc8fdf51649b54f9b55dbce879b2eb Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 18 Nov 2025 17:05:47 +0530 Subject: [PATCH] fix: add condition for allow negative stock in pos (backport #50369) (#50600) Co-authored-by: Logesh Periyasamy fix: add condition for allow negative stock in pos (#50369) --- .../doctype/pos_invoice/pos_invoice.py | 21 ++++++++++++------- .../page/point_of_sale/point_of_sale.py | 4 ++-- .../page/point_of_sale/pos_controller.js | 4 ++++ 3 files changed, 19 insertions(+), 10 deletions(-) diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py index 0196b2b6189..80d75bfddc3 100644 --- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py +++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py @@ -189,6 +189,9 @@ class POSInvoice(SalesInvoice): super().__init__(*args, **kwargs) def validate(self): + if not self.customer: + frappe.throw(_("Please select Customer first")) + if not cint(self.is_pos): frappe.throw( _("POS Invoice should have the field {0} checked.").format(frappe.bold(_("Include Payment"))) @@ -345,14 +348,14 @@ class POSInvoice(SalesInvoice): ): return - from erpnext.stock.stock_ledger import is_negative_stock_allowed - for d in self.get("items"): if not d.serial_and_batch_bundle: - if is_negative_stock_allowed(item_code=d.item_code): - return + available_stock, is_stock_item, is_negative_stock_allowed = get_stock_availability( + d.item_code, d.warehouse + ) - available_stock, is_stock_item = get_stock_availability(d.item_code, d.warehouse) + if is_negative_stock_allowed: + continue item_code, warehouse, _qty = ( frappe.bold(d.item_code), @@ -760,20 +763,22 @@ class POSInvoice(SalesInvoice): @frappe.whitelist() def get_stock_availability(item_code, warehouse): + from erpnext.stock.stock_ledger import is_negative_stock_allowed + if frappe.db.get_value("Item", item_code, "is_stock_item"): is_stock_item = True bin_qty = get_bin_qty(item_code, warehouse) pos_sales_qty = get_pos_reserved_qty(item_code, warehouse) - return bin_qty - pos_sales_qty, is_stock_item + return bin_qty - pos_sales_qty, is_stock_item, is_negative_stock_allowed(item_code=item_code) else: is_stock_item = True if frappe.db.exists("Product Bundle", {"name": item_code, "disabled": 0}): - return get_bundle_availability(item_code, warehouse), is_stock_item + return get_bundle_availability(item_code, warehouse), is_stock_item, False else: is_stock_item = False # Is a service item or non_stock item - return 0, is_stock_item + return 0, is_stock_item, False def get_bundle_availability(bundle_item_code, warehouse): diff --git a/erpnext/selling/page/point_of_sale/point_of_sale.py b/erpnext/selling/page/point_of_sale/point_of_sale.py index d8de762dcd3..df488dafeb0 100644 --- a/erpnext/selling/page/point_of_sale/point_of_sale.py +++ b/erpnext/selling/page/point_of_sale/point_of_sale.py @@ -55,7 +55,7 @@ def search_by_term(search_term, warehouse, price_list): } ) - item_stock_qty, is_stock_item = get_stock_availability(item_code, warehouse) + item_stock_qty, is_stock_item, is_negative_stock_allowed = get_stock_availability(item_code, warehouse) item_stock_qty = item_stock_qty // item.get("conversion_factor", 1) item.update({"actual_qty": item_stock_qty}) @@ -198,7 +198,7 @@ def get_items(start, page_length, price_list, item_group, pos_profile, search_te current_date = frappe.utils.today() for item in items_data: - item.actual_qty, _ = get_stock_availability(item.item_code, warehouse) + item.actual_qty, _, is_negative_stock_allowed = get_stock_availability(item.item_code, warehouse) item_prices = frappe.get_all( "Item Price", diff --git a/erpnext/selling/page/point_of_sale/pos_controller.js b/erpnext/selling/page/point_of_sale/pos_controller.js index 5e3218a67a2..6506ba047d0 100644 --- a/erpnext/selling/page/point_of_sale/pos_controller.js +++ b/erpnext/selling/page/point_of_sale/pos_controller.js @@ -759,12 +759,16 @@ erpnext.PointOfSale.Controller = class { const resp = (await this.get_available_stock(item_row.item_code, warehouse)).message; const available_qty = resp[0]; const is_stock_item = resp[1]; + const is_negative_stock_allowed = resp[2]; frappe.dom.unfreeze(); const bold_uom = item_row.stock_uom.bold(); const bold_item_code = item_row.item_code.bold(); const bold_warehouse = warehouse.bold(); const bold_available_qty = available_qty.toString().bold(); + + if (is_negative_stock_allowed) return; + if (!(available_qty > 0)) { if (is_stock_item) { frappe.model.clear_doc(item_row.doctype, item_row.name);