From b95a49e4c2096ecd8d18a7de9b4261fbad79c144 Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Mon, 27 Mar 2023 10:46:07 +0530 Subject: [PATCH] fix: validate DN against SRE --- .../doctype/delivery_note/delivery_note.py | 74 +++++++++++++++++++ .../stock_reservation_entry.py | 31 ++++++++ 2 files changed, 105 insertions(+) diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.py b/erpnext/stock/doctype/delivery_note/delivery_note.py index d6d51af886..55a49757fa 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/delivery_note.py @@ -147,6 +147,8 @@ class DeliveryNote(SellingController): if not self.installation_status: self.installation_status = "Not Installed" + + self.validate_against_sre() self.reset_default_field_value("set_warehouse", "items", "warehouse") def validate_with_previous_doc(self): @@ -282,6 +284,78 @@ class DeliveryNote(SellingController): if item.against_sre: update_delivered_qty(item.doctype, item.against_sre) + def validate_against_sre(self): + from erpnext.stock.doctype.stock_reservation_entry.stock_reservation_entry import ( + get_stock_reservation_entry_for_items, + has_reserved_stock, + ) + + sre_details = get_stock_reservation_entry_for_items(self.items) + + for item in self.items: + if item.against_sre: + sre = sre_details[item.against_sre] + + # SRE `docstatus` should be `1` (submitted) + if sre.docstatus == 0: + frappe.throw( + _("Row #{0}: Stock Reservation Entry {1} is not submitted").format( + item.idx, item.against_sre + ) + ) + elif sre.docstatus == 2: + frappe.throw( + _("Row #{0}: Stock Reservation Entry {0} is cancelled").format(item.idx, item.against_sre) + ) + + # SRE `status` should not be `Delivered` + if sre.status == "Delivered": + frappe.throw( + _("Row #{0}: Cannot deliver more against Stock Reservation Entry {1}").format( + item.idx, item.against_sre + ) + ) + + for field in ( + "item_code", + "warehouse", + ("against_sales_order", "voucher_no"), + ("so_detail", "voucher_detail_no"), + ): + item_field = sre_field = None + + if isinstance(field, tuple): + item_field, sre_field = field[0], field[1] + else: + item_field = sre_field = field + + if item.get(item_field) != sre.get(sre_field): + frappe.throw( + _("Row #{0}: {1} {2} does not match with Stock Reservation Entry {3}").format( + item.idx, + frappe.get_meta(item.doctype).get_label(item_field), + item.get(item_field), + item.against_sre, + ) + ) + + max_delivered_qty = (sre.reserved_qty - sre.delivered_qty) / item.conversion_factor + if item.qty > max_delivered_qty: + frappe.throw( + _("Row #{0}: Cannot deliver more than {1} {2} against Stock Reservation Entry {3}").format( + item.idx, max_delivered_qty, item.uom, item.against_sre + ) + ) + elif item.against_sales_order: + if not item.so_detail: + frappe.throw(_("Row #{0}: Sales Order Item reference is required").format(item.idx)) + elif has_reserved_stock("Sales Order", item.against_sales_order, item.so_detail): + frappe.throw( + _("Row #{0}: Cannot deliver against Sales Order {1} without Stock Reservation Entry").format( + item.idx, item.against_sales_order + ) + ) + def check_credit_limit(self): from erpnext.selling.doctype.customer.customer import check_credit_limit diff --git a/erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.py b/erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.py index 82eebb4978..3c5b621401 100644 --- a/erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.py +++ b/erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.py @@ -131,3 +131,34 @@ def update_delivered_qty(doctype, sre_name, sre_field="against_sre", qty_field=" sre_doc.delivered_qty = delivered_qty sre_doc.db_update() sre_doc.update_status() + + +def get_stock_reservation_entry_for_items(items, sre_field="against_sre"): + sre_details = {} + + sre_list = [item.get(sre_field) for item in items if item.get(sre_field)] + + if sre_list: + sre = frappe.qb.DocType("Stock Reservation Entry") + sre_data = ( + frappe.qb.from_(sre) + .select( + sre.name, + sre.status, + sre.docstatus, + sre.item_code, + sre.warehouse, + sre.voucher_type, + sre.voucher_no, + sre.voucher_detail_no, + sre.reserved_qty, + sre.delivered_qty, + sre.stock_uom, + ) + .where(sre.name.isin(sre_list)) + .orderby(sre.creation) + ).run(as_dict=True) + + sre_details = {d.name: d for d in sre_data} + + return sre_details