feat: reserve stock for SO on PR submission

This commit is contained in:
s-aga-r 2023-10-19 14:48:13 +05:30
parent 188175be84
commit 64497c9228
4 changed files with 62 additions and 35 deletions

View File

@ -541,7 +541,7 @@ class SalesOrder(SellingController):
create_stock_reservation_entries_for_so_items as create_stock_reservation_entries, create_stock_reservation_entries_for_so_items as create_stock_reservation_entries,
) )
create_stock_reservation_entries(so=self, items_details=items_details, notify=notify) create_stock_reservation_entries(sales_order=self, items_details=items_details, notify=notify)
@frappe.whitelist() @frappe.whitelist()
def cancel_stock_reservation_entries(self, sre_list=None, notify=True) -> None: def cancel_stock_reservation_entries(self, sre_list=None, notify=True) -> None:

View File

@ -242,7 +242,7 @@ class PickList(Document):
for so, locations in so_details.items(): for so, locations in so_details.items():
so_doc = frappe.get_doc("Sales Order", so) so_doc = frappe.get_doc("Sales Order", so)
create_stock_reservation_entries_for_so_items( create_stock_reservation_entries_for_so_items(
so=so_doc, items_details=locations, against_pick_list=True, notify=notify sales_order=so_doc, items_details=locations, against_pick_list=True, notify=notify
) )
@frappe.whitelist() @frappe.whitelist()

View File

@ -264,6 +264,7 @@ class PurchaseReceipt(BuyingController):
self.make_gl_entries() self.make_gl_entries()
self.repost_future_sle_and_gle() self.repost_future_sle_and_gle()
self.set_consumed_qty_in_subcontract_order() self.set_consumed_qty_in_subcontract_order()
self.reserve_stock_for_sales_order()
def check_next_docstatus(self): def check_next_docstatus(self):
submit_rv = frappe.db.sql( submit_rv = frappe.db.sql(
@ -829,6 +830,31 @@ class PurchaseReceipt(BuyingController):
self.load_from_db() self.load_from_db()
def reserve_stock_for_sales_order(self):
if self.is_return or not cint(
frappe.db.get_single_value("Stock Settings", "auto_reserve_stock_for_sales_order_on_purchase")
):
return
self.reload() # reload to get the Serial and Batch Bundle Details
so_items_details_map = {}
for item in self.items:
if item.sales_order and item.sales_order_item:
item_details = {
"name": item.sales_order_item,
"item_code": item.item_code,
"warehouse": item.warehouse,
"qty_to_reserve": item.stock_qty,
"serial_and_batch_bundle": item.get("serial_and_batch_bundle"),
}
so_items_details_map.setdefault(item.sales_order, []).append(item_details)
if so_items_details_map:
for so, items_details in so_items_details_map.items():
so_doc = frappe.get_doc("Sales Order", so)
so_doc.create_stock_reservation_entries(items_details)
def update_billed_amount_based_on_po(po_details, update_modified=True): def update_billed_amount_based_on_po(po_details, update_modified=True):
po_billed_amt_details = get_billed_amount_against_po(po_details) po_billed_amt_details = get_billed_amount_against_po(po_details)

View File

@ -761,7 +761,7 @@ def has_reserved_stock(voucher_type: str, voucher_no: str, voucher_detail_no: st
def create_stock_reservation_entries_for_so_items( def create_stock_reservation_entries_for_so_items(
so: object, sales_order: object,
items_details: list[dict] = None, items_details: list[dict] = None,
against_pick_list: bool = False, against_pick_list: bool = False,
notify=True, notify=True,
@ -771,15 +771,17 @@ def create_stock_reservation_entries_for_so_items(
from erpnext.selling.doctype.sales_order.sales_order import get_unreserved_qty from erpnext.selling.doctype.sales_order.sales_order import get_unreserved_qty
if not against_pick_list and ( if not against_pick_list and (
so.get("_action") == "submit" sales_order.get("_action") == "submit"
and so.set_warehouse and sales_order.set_warehouse
and cint(frappe.get_cached_value("Warehouse", so.set_warehouse, "is_group")) and cint(frappe.get_cached_value("Warehouse", sales_order.set_warehouse, "is_group"))
): ):
return frappe.msgprint( return frappe.msgprint(
_("Stock cannot be reserved in the group warehouse {0}.").format(frappe.bold(so.set_warehouse)) _("Stock cannot be reserved in the group warehouse {0}.").format(
frappe.bold(sales_order.set_warehouse)
)
) )
validate_stock_reservation_settings(so) validate_stock_reservation_settings(sales_order)
allow_partial_reservation = frappe.db.get_single_value( allow_partial_reservation = frappe.db.get_single_value(
"Stock Settings", "allow_partial_reservation" "Stock Settings", "allow_partial_reservation"
@ -787,29 +789,28 @@ def create_stock_reservation_entries_for_so_items(
items = [] items = []
if items_details: if items_details:
item_field = "sales_order_item" if against_pick_list else "name"
for item in items_details: for item in items_details:
so_item = frappe.get_doc( so_item = frappe.get_doc("Sales Order Item", item.get(item_field))
"Sales Order Item", item.get("sales_order_item") if against_pick_list else item.get("name")
)
so_item.reserve_stock = 1
so_item.warehouse = item.get("warehouse") so_item.warehouse = item.get("warehouse")
so_item.qty_to_reserve = ( so_item.qty_to_reserve = (
item.get("picked_qty") - item.get("stock_reserved_qty", 0) item.get("picked_qty") - item.get("stock_reserved_qty", 0)
if against_pick_list if against_pick_list
else (flt(item.get("qty_to_reserve")) * flt(so_item.conversion_factor, 1)) else (flt(item.get("qty_to_reserve")) * flt(so_item.conversion_factor, 1))
) )
so_item.serial_and_batch_bundle = item.get("serial_and_batch_bundle")
if against_pick_list: if against_pick_list:
so_item.pick_list = item.get("parent") so_item.pick_list = item.get("parent")
so_item.pick_list_item = item.get("name") so_item.pick_list_item = item.get("name")
so_item.pick_list_sbb = item.get("serial_and_batch_bundle")
items.append(so_item) items.append(so_item)
sre_count = 0 sre_count = 0
reserved_qty_details = get_sre_reserved_qty_details_for_voucher("Sales Order", so.name) reserved_qty_details = get_sre_reserved_qty_details_for_voucher("Sales Order", sales_order.name)
for item in items if items_details else so.get("items"): for item in items if items_details else sales_order.get("items"):
# Skip if `Reserved Stock` is not checked for the item. # Skip if `Reserved Stock` is not checked for the item.
if not item.get("reserve_stock"): if not item.get("reserve_stock"):
continue continue
@ -817,9 +818,9 @@ def create_stock_reservation_entries_for_so_items(
# Stock should be reserved from the Pick List if has Picked Qty. # Stock should be reserved from the Pick List if has Picked Qty.
if not against_pick_list and flt(item.picked_qty) > 0: if not against_pick_list and flt(item.picked_qty) > 0:
frappe.throw( frappe.throw(
_( _("Row #{0}: Item {1} has been picked, please reserve stock from the Pick List.").format(
"Row #{0}: Item {1} has been picked, please create a Stock Reservation from the Pick List." item.idx, frappe.bold(item.item_code)
).format(item.idx, frappe.bold(item.item_code)) )
) )
is_stock_item, has_serial_no, has_batch_no = frappe.get_cached_value( is_stock_item, has_serial_no, has_batch_no = frappe.get_cached_value(
@ -915,33 +916,33 @@ def create_stock_reservation_entries_for_so_items(
sre.warehouse = item.warehouse sre.warehouse = item.warehouse
sre.has_serial_no = has_serial_no sre.has_serial_no = has_serial_no
sre.has_batch_no = has_batch_no sre.has_batch_no = has_batch_no
sre.voucher_type = so.doctype sre.voucher_type = sales_order.doctype
sre.voucher_no = so.name sre.voucher_no = sales_order.name
sre.voucher_detail_no = item.name sre.voucher_detail_no = item.name
sre.available_qty = available_qty_to_reserve sre.available_qty = available_qty_to_reserve
sre.voucher_qty = item.stock_qty sre.voucher_qty = item.stock_qty
sre.reserved_qty = qty_to_be_reserved sre.reserved_qty = qty_to_be_reserved
sre.company = so.company sre.company = sales_order.company
sre.stock_uom = item.stock_uom sre.stock_uom = item.stock_uom
sre.project = so.project sre.project = sales_order.project
if against_pick_list: if against_pick_list:
sre.against_pick_list = item.pick_list sre.against_pick_list = item.pick_list
sre.against_pick_list_item = item.pick_list_item sre.against_pick_list_item = item.pick_list_item
if item.pick_list_sbb: if item.serial_and_batch_bundle:
sbb = frappe.get_doc("Serial and Batch Bundle", item.pick_list_sbb) sbb = frappe.get_doc("Serial and Batch Bundle", item.serial_and_batch_bundle)
sre.reservation_based_on = "Serial and Batch" sre.reservation_based_on = "Serial and Batch"
for entry in sbb.entries: for entry in sbb.entries:
sre.append( sre.append(
"sb_entries", "sb_entries",
{ {
"serial_no": entry.serial_no, "serial_no": entry.serial_no,
"batch_no": entry.batch_no, "batch_no": entry.batch_no,
"qty": 1 if has_serial_no else abs(entry.qty), "qty": 1 if has_serial_no else abs(entry.qty),
"warehouse": entry.warehouse, "warehouse": entry.warehouse,
}, },
) )
sre.save() sre.save()
sre.submit() sre.submit()