mirror of
https://github.com/frappe/erpnext.git
synced 2026-06-07 07:02:54 +00:00
refactor: remove Against Stock Reservation Entry field from DN Item
This commit is contained in:
@@ -622,42 +622,7 @@ def make_project(source_name, target_doc=None):
|
|||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def make_delivery_note(source_name, target_doc=None, skip_item_mapping=False):
|
def make_delivery_note(source_name, target_doc=None, skip_item_mapping=False):
|
||||||
from erpnext.stock.doctype.stock_reservation_entry.stock_reservation_entry import (
|
|
||||||
get_stock_reservation_entries_for_voucher,
|
|
||||||
has_reserved_stock,
|
|
||||||
)
|
|
||||||
|
|
||||||
def set_missing_values(source, target):
|
def set_missing_values(source, target):
|
||||||
if not target.items and has_reserved_stock("Sales Order", source_name):
|
|
||||||
sre_list = get_stock_reservation_entries_for_voucher("Sales Order", source_name)
|
|
||||||
sre_dict = {d.pop("voucher_detail_no"): d for d in sre_list}
|
|
||||||
|
|
||||||
for item in source.get("items"):
|
|
||||||
if item.name in sre_dict:
|
|
||||||
reserved_qty, delivered_qty, warehouse = (
|
|
||||||
sre_dict[item.name]["reserved_qty"],
|
|
||||||
sre_dict[item.name]["delivered_qty"],
|
|
||||||
sre_dict[item.name]["warehouse"],
|
|
||||||
)
|
|
||||||
qty_to_deliver = (reserved_qty - delivered_qty) / item.conversion_factor
|
|
||||||
|
|
||||||
row = frappe.new_doc("Delivery Note Item")
|
|
||||||
row.against_sales_order = source.name
|
|
||||||
row.against_sre = sre_dict[item.name]["name"]
|
|
||||||
row.so_detail = item.name
|
|
||||||
row.item_code = item.item_code
|
|
||||||
row.item_name = item.item_name
|
|
||||||
row.description = item.description
|
|
||||||
row.qty = qty_to_deliver
|
|
||||||
row.stock_uom = item.stock_uom
|
|
||||||
row.uom = item.uom
|
|
||||||
row.conversion_factor = item.conversion_factor
|
|
||||||
|
|
||||||
if not frappe.get_cached_value("Warehouse", warehouse, "is_group"):
|
|
||||||
row.warehouse = warehouse
|
|
||||||
|
|
||||||
target.append("items", row)
|
|
||||||
|
|
||||||
target.run_method("set_missing_values")
|
target.run_method("set_missing_values")
|
||||||
target.run_method("set_po_nos")
|
target.run_method("set_po_nos")
|
||||||
target.run_method("calculate_taxes_and_totals")
|
target.run_method("calculate_taxes_and_totals")
|
||||||
@@ -686,9 +651,6 @@ def make_delivery_note(source_name, target_doc=None, skip_item_mapping=False):
|
|||||||
or item_group.get("buying_cost_center")
|
or item_group.get("buying_cost_center")
|
||||||
)
|
)
|
||||||
|
|
||||||
if has_reserved_stock("Sales Order", source_name):
|
|
||||||
skip_item_mapping = True
|
|
||||||
|
|
||||||
mapper = {
|
mapper = {
|
||||||
"Sales Order": {"doctype": "Delivery Note", "validation": {"docstatus": ["=", 1]}},
|
"Sales Order": {"doctype": "Delivery Note", "validation": {"docstatus": ["=", 1]}},
|
||||||
"Sales Taxes and Charges": {"doctype": "Sales Taxes and Charges", "add_if_empty": True},
|
"Sales Taxes and Charges": {"doctype": "Sales Taxes and Charges", "add_if_empty": True},
|
||||||
|
|||||||
@@ -77,19 +77,6 @@ frappe.ui.form.on("Delivery Note", {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
frm.set_query("against_sre", "items", (doc, cdt, cdn) => {
|
|
||||||
var row = locals[cdt][cdn];
|
|
||||||
return {
|
|
||||||
filters: {
|
|
||||||
"docstatus": 1,
|
|
||||||
"status": ["not in", ["Delivered", "Cancelled"]],
|
|
||||||
"voucher_type": "Sales Order",
|
|
||||||
"voucher_no": row.against_sales_order,
|
|
||||||
"voucher_detail_no": row.so_detail,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
frm.set_df_property('packed_items', 'cannot_add_rows', true);
|
frm.set_df_property('packed_items', 'cannot_add_rows', true);
|
||||||
frm.set_df_property('packed_items', 'cannot_delete_rows', true);
|
frm.set_df_property('packed_items', 'cannot_delete_rows', true);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -148,7 +148,7 @@ class DeliveryNote(SellingController):
|
|||||||
if not self.installation_status:
|
if not self.installation_status:
|
||||||
self.installation_status = "Not Installed"
|
self.installation_status = "Not Installed"
|
||||||
|
|
||||||
self.validate_against_sre()
|
self.validate_against_stock_reservation()
|
||||||
self.reset_default_field_value("set_warehouse", "items", "warehouse")
|
self.reset_default_field_value("set_warehouse", "items", "warehouse")
|
||||||
|
|
||||||
def validate_with_previous_doc(self):
|
def validate_with_previous_doc(self):
|
||||||
@@ -262,8 +262,6 @@ class DeliveryNote(SellingController):
|
|||||||
self.update_prevdoc_status()
|
self.update_prevdoc_status()
|
||||||
self.update_billing_status()
|
self.update_billing_status()
|
||||||
|
|
||||||
self.update_stock_reservation_entry()
|
|
||||||
|
|
||||||
# Updating stock ledger should always be called after updating prevdoc status,
|
# Updating stock ledger should always be called after updating prevdoc status,
|
||||||
# because updating reserved qty in bin depends upon updated delivered qty in SO
|
# because updating reserved qty in bin depends upon updated delivered qty in SO
|
||||||
self.update_stock_ledger()
|
self.update_stock_ledger()
|
||||||
@@ -275,105 +273,87 @@ class DeliveryNote(SellingController):
|
|||||||
self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry", "Repost Item Valuation")
|
self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry", "Repost Item Valuation")
|
||||||
|
|
||||||
def update_stock_reservation_entry(self):
|
def update_stock_reservation_entry(self):
|
||||||
if not self.is_return:
|
if self.is_return or self._action != "submit":
|
||||||
from erpnext.stock.doctype.stock_reservation_entry.stock_reservation_entry import (
|
return
|
||||||
update_sre_delivered_qty,
|
|
||||||
)
|
|
||||||
|
|
||||||
for item in self.get("items"):
|
|
||||||
if item.against_sre:
|
|
||||||
update_sre_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_entries_for_items,
|
|
||||||
has_reserved_stock,
|
|
||||||
)
|
|
||||||
|
|
||||||
sre_details = get_stock_reservation_entries_for_items(self.items)
|
|
||||||
|
|
||||||
for item in self.items:
|
for item in self.items:
|
||||||
if item.against_sre:
|
if not item.against_sales_order or not item.so_detail:
|
||||||
sre = sre_details[item.against_sre]
|
continue
|
||||||
|
|
||||||
# SRE `docstatus` should be `1` (submitted)
|
sre_list = frappe.db.get_all(
|
||||||
if sre.docstatus == 0:
|
"Stock Reservation Entry",
|
||||||
frappe.throw(
|
{
|
||||||
_("Row #{0}: Stock Reservation Entry {1} is not submitted").format(
|
"docstatus": 1,
|
||||||
item.idx, item.against_sre
|
"voucher_type": "Sales Order",
|
||||||
)
|
"voucher_no": item.against_sales_order,
|
||||||
)
|
"voucher_detail_no": item.so_detail,
|
||||||
elif sre.docstatus == 2:
|
"warehouse": item.warehouse,
|
||||||
frappe.throw(
|
"status": ["not in", ["Delivered", "Cancelled"]],
|
||||||
_("Row #{0}: Stock Reservation Entry {0} is cancelled").format(item.idx, item.against_sre)
|
},
|
||||||
)
|
order_by="creation",
|
||||||
|
)
|
||||||
|
|
||||||
# SRE `status` should not be `Delivered`
|
if not sre_list:
|
||||||
if sre.status == "Delivered":
|
continue
|
||||||
frappe.throw(
|
|
||||||
_("Row #{0}: Cannot deliver more against Stock Reservation Entry {1}").format(
|
|
||||||
item.idx, item.against_sre
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
if not frappe.get_cached_value("Warehouse", sre.warehouse, "is_group"):
|
available_qty_to_deliver = item.stock_qty
|
||||||
if item.warehouse != sre.warehouse:
|
for sre in sre_list:
|
||||||
|
if available_qty_to_deliver <= 0:
|
||||||
|
break
|
||||||
|
|
||||||
|
sre_doc = frappe.get_doc("Stock Reservation Entry", sre)
|
||||||
|
qty_to_be_deliver = min(sre_doc.reserved_qty - sre_doc.delivered_qty, available_qty_to_deliver)
|
||||||
|
sre_doc.delivered_qty += qty_to_be_deliver
|
||||||
|
sre_doc.db_update()
|
||||||
|
sre_doc.update_status()
|
||||||
|
|
||||||
|
available_qty_to_deliver -= qty_to_be_deliver
|
||||||
|
|
||||||
|
def validate_against_stock_reservation(self):
|
||||||
|
from erpnext.stock.doctype.stock_reservation_entry.stock_reservation_entry import (
|
||||||
|
get_sre_reserved_qty_details_for_voucher_detail_no,
|
||||||
|
)
|
||||||
|
|
||||||
|
if self.is_return:
|
||||||
|
return
|
||||||
|
|
||||||
|
for item in self.items:
|
||||||
|
if not item.against_sales_order or not item.so_detail:
|
||||||
|
continue
|
||||||
|
|
||||||
|
sre_data = get_sre_reserved_qty_details_for_voucher_detail_no(
|
||||||
|
"Sales Order", item.against_sales_order, item.so_detail
|
||||||
|
)
|
||||||
|
|
||||||
|
if not sre_data:
|
||||||
|
continue
|
||||||
|
|
||||||
|
is_group_warehouse = frappe.get_cached_value("Warehouse", sre_data[0], "is_group")
|
||||||
|
|
||||||
|
if not item.warehouse:
|
||||||
|
if not is_group_warehouse:
|
||||||
|
item.warehouse = sre_data[0]
|
||||||
|
else:
|
||||||
|
frappe.throw(_("Row #{0}: Warehouse is mandatory").format(item.idx, item.item_code))
|
||||||
|
else:
|
||||||
|
if not is_group_warehouse:
|
||||||
|
if item.warehouse != sre_data[0]:
|
||||||
frappe.throw(
|
frappe.throw(
|
||||||
_("Row #{0}: Warehouse {1} does not match with Stock Reservation Entry {2}").format(
|
_("Row #{0}: Stock is reserved for Warehouse {1}").format(item.idx, sre_data[0]),
|
||||||
item.idx, item.warehouse, item.against_sre
|
title="Stock Reservation Warehouse Mismatch",
|
||||||
)
|
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
from erpnext.stock.doctype.warehouse.warehouse import get_child_warehouses
|
from erpnext.stock.doctype.warehouse.warehouse import get_child_warehouses
|
||||||
|
|
||||||
warehouses = get_child_warehouses(sre.warehouse)
|
warehouses = get_child_warehouses(sre_data[0])
|
||||||
|
|
||||||
if item.warehouse not in warehouses:
|
if item.warehouse not in warehouses:
|
||||||
frappe.throw(
|
frappe.throw(
|
||||||
_("Row #{0}: Warehouse {1} should be a child of Warehouse {2}").format(
|
_(
|
||||||
item.idx, item.warehouse, sre.warehouse
|
"Row #{0}: Stock is reserved for Group Warehouse {1}, please select its child Warehouse"
|
||||||
)
|
).format(item.idx, sre_data[0]),
|
||||||
|
title="Stock Reservation Group Warehouse",
|
||||||
)
|
)
|
||||||
|
|
||||||
for field in (
|
|
||||||
"item_code",
|
|
||||||
("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):
|
def check_credit_limit(self):
|
||||||
from erpnext.selling.doctype.customer.customer import check_credit_limit
|
from erpnext.selling.doctype.customer.customer import check_credit_limit
|
||||||
|
|
||||||
|
|||||||
@@ -76,7 +76,6 @@
|
|||||||
"si_detail",
|
"si_detail",
|
||||||
"dn_detail",
|
"dn_detail",
|
||||||
"pick_list_item",
|
"pick_list_item",
|
||||||
"against_sre",
|
|
||||||
"section_break_40",
|
"section_break_40",
|
||||||
"batch_no",
|
"batch_no",
|
||||||
"serial_no",
|
"serial_no",
|
||||||
@@ -833,22 +832,13 @@
|
|||||||
"fieldname": "material_request_item",
|
"fieldname": "material_request_item",
|
||||||
"fieldtype": "Data",
|
"fieldtype": "Data",
|
||||||
"label": "Material Request Item"
|
"label": "Material Request Item"
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldname": "against_sre",
|
|
||||||
"fieldtype": "Link",
|
|
||||||
"label": "Against Stock Reservation Entry",
|
|
||||||
"no_copy": 1,
|
|
||||||
"options": "Stock Reservation Entry",
|
|
||||||
"print_hide": 1,
|
|
||||||
"read_only": 1
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"idx": 1,
|
"idx": 1,
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2023-03-26 16:53:08.283469",
|
"modified": "2023-03-30 23:27:30.943175",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Stock",
|
"module": "Stock",
|
||||||
"name": "Delivery Note Item",
|
"name": "Delivery Note Item",
|
||||||
|
|||||||
@@ -152,6 +152,24 @@ def get_stock_reservation_entries_for_voucher(
|
|||||||
return query.run(as_dict=True)
|
return query.run(as_dict=True)
|
||||||
|
|
||||||
|
|
||||||
|
def get_sre_reserved_qty_details_for_voucher_detail_no(
|
||||||
|
voucher_type: str, voucher_no: str, voucher_detail_no: str
|
||||||
|
) -> list:
|
||||||
|
sre = frappe.qb.DocType("Stock Reservation Entry")
|
||||||
|
return (
|
||||||
|
frappe.qb.from_(sre)
|
||||||
|
.select(sre.warehouse, (Sum(sre.reserved_qty) - Sum(sre.delivered_qty)).as_("reserved_qty"))
|
||||||
|
.where(
|
||||||
|
(sre.docstatus == 1)
|
||||||
|
& (sre.voucher_type == voucher_type)
|
||||||
|
& (sre.voucher_no == voucher_no)
|
||||||
|
& (sre.voucher_detail_no == voucher_detail_no)
|
||||||
|
& (sre.status.notin(["Delivered", "Cancelled"]))
|
||||||
|
)
|
||||||
|
.groupby(sre.warehouse)
|
||||||
|
).run(as_list=True)[0]
|
||||||
|
|
||||||
|
|
||||||
def has_reserved_stock(voucher_type: str, voucher_no: str, voucher_detail_no: str = None) -> bool:
|
def has_reserved_stock(voucher_type: str, voucher_no: str, voucher_detail_no: str = None) -> bool:
|
||||||
if get_stock_reservation_entries_for_voucher(
|
if get_stock_reservation_entries_for_voucher(
|
||||||
voucher_type, voucher_no, voucher_detail_no, fields=["name"]
|
voucher_type, voucher_no, voucher_detail_no, fields=["name"]
|
||||||
@@ -161,56 +179,6 @@ def has_reserved_stock(voucher_type: str, voucher_no: str, voucher_detail_no: st
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def update_sre_delivered_qty(
|
|
||||||
doctype: str, sre_name: str, sre_field: str = "against_sre", qty_field: str = "stock_qty"
|
|
||||||
) -> None:
|
|
||||||
table = frappe.qb.DocType(doctype)
|
|
||||||
delivered_qty = (
|
|
||||||
frappe.qb.from_(table)
|
|
||||||
.select(Sum(table[qty_field]))
|
|
||||||
.where((table.docstatus == 1) & (table[sre_field] == sre_name))
|
|
||||||
).run(as_list=True)[0][0] or 0.0
|
|
||||||
|
|
||||||
sre_doc = frappe.get_doc("Stock Reservation Entry", sre_name)
|
|
||||||
sre_doc.delivered_qty = delivered_qty
|
|
||||||
sre_doc.db_update()
|
|
||||||
sre_doc.update_status()
|
|
||||||
|
|
||||||
|
|
||||||
def get_stock_reservation_entries_for_items(
|
|
||||||
items: list[dict | object], sre_field: str = "against_sre"
|
|
||||||
) -> dict[dict]:
|
|
||||||
sre_details = {}
|
|
||||||
|
|
||||||
if items:
|
|
||||||
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
|
|
||||||
|
|
||||||
|
|
||||||
def get_sre_reserved_qty_details(item_code: str | list, warehouse: str | list) -> dict:
|
def get_sre_reserved_qty_details(item_code: str | list, warehouse: str | list) -> dict:
|
||||||
sre_details = {}
|
sre_details = {}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user