mirror of
https://github.com/frappe/erpnext.git
synced 2025-12-03 18:35:36 +00:00
fix(pos): add negative stock validation for product bundle
This commit is contained in:
@@ -20,6 +20,7 @@ from erpnext.accounts.party import get_due_date, get_party_account
|
||||
from erpnext.controllers.queries import item_query as _item_query
|
||||
from erpnext.controllers.sales_and_purchase_return import get_sales_invoice_item_from_consolidated_invoice
|
||||
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
|
||||
from erpnext.stock.stock_ledger import is_negative_stock_allowed
|
||||
|
||||
|
||||
class POSInvoice(SalesInvoice):
|
||||
@@ -395,32 +396,66 @@ class POSInvoice(SalesInvoice):
|
||||
|
||||
for d in self.get("items"):
|
||||
if not d.serial_and_batch_bundle:
|
||||
available_stock, is_stock_item, is_negative_stock_allowed = get_stock_availability(
|
||||
d.item_code, d.warehouse
|
||||
)
|
||||
if frappe.db.exists("Product Bundle", d.item_code):
|
||||
(
|
||||
availability,
|
||||
is_stock_item,
|
||||
is_negative_stock_allowed,
|
||||
) = get_product_bundle_stock_availability(d.item_code, d.warehouse, d.stock_qty)
|
||||
|
||||
else:
|
||||
availability, is_stock_item, is_negative_stock_allowed = get_stock_availability(
|
||||
d.item_code, d.warehouse
|
||||
)
|
||||
|
||||
if is_negative_stock_allowed:
|
||||
continue
|
||||
|
||||
item_code, warehouse, _qty = (
|
||||
frappe.bold(d.item_code),
|
||||
frappe.bold(d.warehouse),
|
||||
frappe.bold(d.qty),
|
||||
)
|
||||
if is_stock_item and flt(available_stock) <= 0:
|
||||
frappe.throw(
|
||||
_("Row #{}: Item Code: {} is not available under warehouse {}.").format(
|
||||
d.idx, item_code, warehouse
|
||||
),
|
||||
title=_("Item Unavailable"),
|
||||
)
|
||||
elif is_stock_item and flt(available_stock) < flt(d.stock_qty):
|
||||
frappe.throw(
|
||||
_("Row #{}: Stock quantity not enough for Item Code: {} under warehouse {}.").format(
|
||||
d.idx, item_code, warehouse
|
||||
),
|
||||
title=_("Item Unavailable"),
|
||||
)
|
||||
if isinstance(availability, list):
|
||||
error_msgs = []
|
||||
for item in availability:
|
||||
if flt(item["available"]) < flt(item["required"]):
|
||||
error_msgs.append(
|
||||
_("<li>Packed Item {0}: Required {1}, Available {2}</li>").format(
|
||||
frappe.bold(item["item_code"]),
|
||||
frappe.bold(flt(item["required"], 2)),
|
||||
frappe.bold(flt(item["available"], 2)),
|
||||
)
|
||||
)
|
||||
|
||||
if error_msgs:
|
||||
frappe.throw(
|
||||
_(
|
||||
"<b>Row #{0}:</b> Bundle {1} in warehouse {2} has insufficient packed items:<br><div style='margin-top: 15px;'><ul style='line-height: 0.8;'>{3}</ul></div>"
|
||||
).format(
|
||||
d.idx,
|
||||
frappe.bold(d.item_code),
|
||||
frappe.bold(d.warehouse),
|
||||
"<br>".join(error_msgs),
|
||||
),
|
||||
title=_("Insufficient Stock for Product Bundle Items"),
|
||||
)
|
||||
|
||||
else:
|
||||
item_code, warehouse = frappe.bold(d.item_code), frappe.bold(d.warehouse)
|
||||
if is_stock_item and flt(availability) <= 0:
|
||||
frappe.throw(
|
||||
_("Row #{0}: Item {1} has no stock in warehouse {2}.").format(
|
||||
d.idx, item_code, warehouse
|
||||
),
|
||||
title=_("Item Out of Stock"),
|
||||
)
|
||||
elif is_stock_item and flt(availability) < flt(d.stock_qty):
|
||||
frappe.throw(
|
||||
_("Row #{0}: Item {1} in warehouse {2}: Available {3}, Needed {4}.").format(
|
||||
d.idx,
|
||||
item_code,
|
||||
warehouse,
|
||||
frappe.bold(flt(availability, 2)),
|
||||
frappe.bold(flt(d.stock_qty, 2)),
|
||||
),
|
||||
title=_("Insufficient Stock"),
|
||||
)
|
||||
|
||||
def validate_is_pos_using_sales_invoice(self):
|
||||
self.invoice_type_in_pos = frappe.db.get_single_value("POS Settings", "invoice_type")
|
||||
@@ -858,8 +893,6 @@ 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)
|
||||
@@ -876,6 +909,26 @@ def get_stock_availability(item_code, warehouse):
|
||||
return 0, is_stock_item, False
|
||||
|
||||
|
||||
def get_product_bundle_stock_availability(item_code, warehouse, item_qty):
|
||||
is_stock_item = True
|
||||
bundle = frappe.get_doc("Product Bundle", item_code)
|
||||
availabilities = []
|
||||
for bundle_item in bundle.items:
|
||||
if frappe.get_value("Item", bundle_item.item_code, "is_stock_item"):
|
||||
bin_qty = get_bin_qty(bundle_item.item_code, warehouse)
|
||||
reserved_qty = get_pos_reserved_qty(bundle_item.item_code, warehouse)
|
||||
available = bin_qty - reserved_qty
|
||||
availabilities.append(
|
||||
{
|
||||
"item_code": bundle_item.item_code,
|
||||
"required": bundle_item.qty * item_qty,
|
||||
"available": available,
|
||||
}
|
||||
)
|
||||
|
||||
return availabilities, is_stock_item, is_negative_stock_allowed(item_code=item_code)
|
||||
|
||||
|
||||
def get_bundle_availability(bundle_item_code, warehouse):
|
||||
product_bundle = frappe.get_doc("Product Bundle", bundle_item_code)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user