Merge pull request #37185 from s-aga-r/REFACTOR-SRE

refactor(minor): Stock Reservation Entry
This commit is contained in:
s-aga-r 2023-09-21 21:46:09 +05:30 committed by GitHub
commit c679d54852
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 87 additions and 65 deletions

View File

@ -3,7 +3,7 @@ from frappe.model.db_query import DatabaseQuery
from frappe.utils import cint, flt from frappe.utils import cint, flt
from erpnext.stock.doctype.stock_reservation_entry.stock_reservation_entry import ( from erpnext.stock.doctype.stock_reservation_entry.stock_reservation_entry import (
get_sre_reserved_qty_for_item_and_warehouse as get_reserved_stock, get_sre_reserved_qty_for_items_and_warehouses as get_reserved_stock_details,
) )
@ -61,7 +61,10 @@ def get_data(
limit_page_length=21, limit_page_length=21,
) )
sre_reserved_stock_details = get_reserved_stock(item_code, warehouse) item_code_list = [item_code] if item_code else [i.item_code for i in items]
warehouse_list = [warehouse] if warehouse else [i.warehouse for i in items]
sre_reserved_stock_details = get_reserved_stock_details(item_code_list, warehouse_list)
precision = cint(frappe.db.get_single_value("System Settings", "float_precision")) precision = cint(frappe.db.get_single_value("System Settings", "float_precision"))
for item in items: for item in items:
@ -75,7 +78,8 @@ def get_data(
"reserved_qty_for_production": flt(item.reserved_qty_for_production, precision), "reserved_qty_for_production": flt(item.reserved_qty_for_production, precision),
"reserved_qty_for_sub_contract": flt(item.reserved_qty_for_sub_contract, precision), "reserved_qty_for_sub_contract": flt(item.reserved_qty_for_sub_contract, precision),
"actual_qty": flt(item.actual_qty, precision), "actual_qty": flt(item.actual_qty, precision),
"reserved_stock": sre_reserved_stock_details, "reserved_stock": flt(sre_reserved_stock_details.get((item.item_code, item.warehouse))),
} }
) )
return items return items

View File

@ -346,7 +346,7 @@ class StockReconciliation(StockController):
"""Raises an exception if there is any reserved stock for the items in the Stock Reconciliation.""" """Raises an exception if there is any reserved stock for the items in the Stock Reconciliation."""
from erpnext.stock.doctype.stock_reservation_entry.stock_reservation_entry import ( from erpnext.stock.doctype.stock_reservation_entry.stock_reservation_entry import (
get_sre_reserved_qty_for_item_and_warehouse as get_sre_reserved_qty_details, get_sre_reserved_qty_for_items_and_warehouses as get_sre_reserved_qty_details,
) )
item_code_list, warehouse_list = [], [] item_code_list, warehouse_list = [], []

View File

@ -1,42 +1,42 @@
// Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors // Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt // For license information, please see license.txt
frappe.ui.form.on("Stock Reservation Entry", { frappe.ui.form.on('Stock Reservation Entry', {
refresh(frm) { refresh(frm) {
frm.trigger("set_queries"); frm.trigger('set_queries');
frm.trigger("toggle_read_only_fields"); frm.trigger('toggle_read_only_fields');
frm.trigger("hide_rate_related_fields"); frm.trigger('hide_rate_related_fields');
frm.trigger("hide_primary_action_button"); frm.trigger('hide_primary_action_button');
frm.trigger("make_sb_entries_warehouse_read_only"); frm.trigger('make_sb_entries_warehouse_read_only');
}, },
has_serial_no(frm) { has_serial_no(frm) {
frm.trigger("toggle_read_only_fields"); frm.trigger('toggle_read_only_fields');
}, },
has_batch_no(frm) { has_batch_no(frm) {
frm.trigger("toggle_read_only_fields"); frm.trigger('toggle_read_only_fields');
}, },
warehouse(frm) { warehouse(frm) {
if (frm.doc.warehouse) { if (frm.doc.warehouse) {
frm.doc.sb_entries.forEach((row) => { frm.doc.sb_entries.forEach((row) => {
frappe.model.set_value(row.doctype, row.name, "warehouse", frm.doc.warehouse); frappe.model.set_value(row.doctype, row.name, 'warehouse', frm.doc.warehouse);
}); });
} }
}, },
set_queries(frm) { set_queries(frm) {
frm.set_query("warehouse", () => { frm.set_query('warehouse', () => {
return { return {
filters: { filters: {
"is_group": 0, 'is_group': 0,
"company": frm.doc.company, 'company': frm.doc.company,
} }
}; };
}); });
frm.set_query("serial_no", "sb_entries", function(doc, cdt, cdn) { frm.set_query('serial_no', 'sb_entries', function(doc, cdt, cdn) {
var selected_serial_nos = doc.sb_entries.map(row => { var selected_serial_nos = doc.sb_entries.map(row => {
return row.serial_no; return row.serial_no;
}); });
@ -45,16 +45,16 @@ frappe.ui.form.on("Stock Reservation Entry", {
filters: { filters: {
item_code: doc.item_code, item_code: doc.item_code,
warehouse: row.warehouse, warehouse: row.warehouse,
status: "Active", status: 'Active',
name: ["not in", selected_serial_nos], name: ['not in', selected_serial_nos],
} }
} }
}); });
frm.set_query("batch_no", "sb_entries", function(doc, cdt, cdn) { frm.set_query('batch_no', 'sb_entries', function(doc, cdt, cdn) {
let filters = { let filters = {
item: doc.item_code, item: doc.item_code,
batch_qty: [">", 0], batch_qty: ['>', 0],
disabled: 0, disabled: 0,
} }
@ -63,7 +63,7 @@ frappe.ui.form.on("Stock Reservation Entry", {
return row.batch_no; return row.batch_no;
}); });
filters.name = ["not in", selected_batch_nos]; filters.name = ['not in', selected_batch_nos];
} }
return { filters: filters } return { filters: filters }
@ -74,37 +74,37 @@ frappe.ui.form.on("Stock Reservation Entry", {
if (frm.doc.has_serial_no) { if (frm.doc.has_serial_no) {
frm.doc.sb_entries.forEach(row => { frm.doc.sb_entries.forEach(row => {
if (row.qty !== 1) { if (row.qty !== 1) {
frappe.model.set_value(row.doctype, row.name, "qty", 1); frappe.model.set_value(row.doctype, row.name, 'qty', 1);
} }
}) })
} }
frm.fields_dict.sb_entries.grid.update_docfield_property( frm.fields_dict.sb_entries.grid.update_docfield_property(
"serial_no", "read_only", !frm.doc.has_serial_no 'serial_no', 'read_only', !frm.doc.has_serial_no
); );
frm.fields_dict.sb_entries.grid.update_docfield_property( frm.fields_dict.sb_entries.grid.update_docfield_property(
"batch_no", "read_only", !frm.doc.has_batch_no 'batch_no', 'read_only', !frm.doc.has_batch_no
); );
// Qty will always be 1 for Serial No. // Qty will always be 1 for Serial No.
frm.fields_dict.sb_entries.grid.update_docfield_property( frm.fields_dict.sb_entries.grid.update_docfield_property(
"qty", "read_only", frm.doc.has_serial_no 'qty', 'read_only', frm.doc.has_serial_no
); );
frm.set_df_property("sb_entries", "allow_on_submit", frm.doc.against_pick_list ? 0 : 1); frm.set_df_property('sb_entries', 'allow_on_submit', frm.doc.against_pick_list ? 0 : 1);
}, },
hide_rate_related_fields(frm) { hide_rate_related_fields(frm) {
["incoming_rate", "outgoing_rate", "stock_value_difference", "is_outward", "stock_queue"].forEach(field => { ['incoming_rate', 'outgoing_rate', 'stock_value_difference', 'is_outward', 'stock_queue'].forEach(field => {
frm.fields_dict.sb_entries.grid.update_docfield_property( frm.fields_dict.sb_entries.grid.update_docfield_property(
field, "hidden", 1 field, 'hidden', 1
); );
}); });
}, },
hide_primary_action_button(frm) { hide_primary_action_button(frm) {
// Hide "Amend" button on cancelled document // Hide 'Amend' button on cancelled document
if (frm.doc.docstatus == 2) { if (frm.doc.docstatus == 2) {
frm.page.btn_primary.hide() frm.page.btn_primary.hide()
} }
@ -112,15 +112,15 @@ frappe.ui.form.on("Stock Reservation Entry", {
make_sb_entries_warehouse_read_only(frm) { make_sb_entries_warehouse_read_only(frm) {
frm.fields_dict.sb_entries.grid.update_docfield_property( frm.fields_dict.sb_entries.grid.update_docfield_property(
"warehouse", "read_only", 1 'warehouse', 'read_only', 1
); );
}, },
}); });
frappe.ui.form.on("Serial and Batch Entry", { frappe.ui.form.on('Serial and Batch Entry', {
sb_entries_add(frm, cdt, cdn) { sb_entries_add(frm, cdt, cdn) {
if (frm.doc.warehouse) { if (frm.doc.warehouse) {
frappe.model.set_value(cdt, cdn, "warehouse", frm.doc.warehouse); frappe.model.set_value(cdt, cdn, 'warehouse', frm.doc.warehouse);
} }
}, },
}); });

View File

@ -14,7 +14,7 @@ class StockReservationEntry(Document):
self.validate_amended_doc() self.validate_amended_doc()
self.validate_mandatory() self.validate_mandatory()
self.validate_for_group_warehouse() self.validate_group_warehouse()
validate_disabled_warehouse(self.warehouse) validate_disabled_warehouse(self.warehouse)
validate_warehouse_company(self.warehouse, self.company) validate_warehouse_company(self.warehouse, self.company)
self.validate_uom_is_integer() self.validate_uom_is_integer()
@ -74,7 +74,7 @@ class StockReservationEntry(Document):
msg = _("{0} is required").format(self.meta.get_label(d)) msg = _("{0} is required").format(self.meta.get_label(d))
frappe.throw(msg) frappe.throw(msg)
def validate_for_group_warehouse(self) -> None: def validate_group_warehouse(self) -> None:
"""Raises an exception if `Warehouse` is a Group Warehouse.""" """Raises an exception if `Warehouse` is a Group Warehouse."""
if frappe.get_cached_value("Warehouse", self.warehouse, "is_group"): if frappe.get_cached_value("Warehouse", self.warehouse, "is_group"):
@ -544,10 +544,36 @@ def get_available_serial_nos_to_reserve(
return available_serial_nos_list return available_serial_nos_list
def get_sre_reserved_qty_for_item_and_warehouse( def get_sre_reserved_qty_for_item_and_warehouse(item_code: str, warehouse: str = None) -> float:
item_code: str | list, warehouse: str | list = None """Returns current `Reserved Qty` for Item and Warehouse combination."""
) -> float | dict:
"""Returns `Reserved Qty` for Item and Warehouse combination OR a dict like {("item_code", "warehouse"): "reserved_qty", ... }.""" sre = frappe.qb.DocType("Stock Reservation Entry")
query = (
frappe.qb.from_(sre)
.select(Sum(sre.reserved_qty - sre.delivered_qty).as_("reserved_qty"))
.where(
(sre.docstatus == 1)
& (sre.item_code == item_code)
& (sre.status.notin(["Delivered", "Cancelled"]))
)
.groupby(sre.item_code, sre.warehouse)
)
if warehouse:
query = query.where(sre.warehouse == warehouse)
reserved_qty = query.run(as_list=True)
return flt(reserved_qty[0][0]) if reserved_qty else 0.0
def get_sre_reserved_qty_for_items_and_warehouses(
item_code_list: list, warehouse_list: list = None
) -> dict:
"""Returns a dict like {("item_code", "warehouse"): "reserved_qty", ... }."""
if not item_code_list:
return {}
sre = frappe.qb.DocType("Stock Reservation Entry") sre = frappe.qb.DocType("Stock Reservation Entry")
query = ( query = (
@ -557,29 +583,20 @@ def get_sre_reserved_qty_for_item_and_warehouse(
sre.warehouse, sre.warehouse,
Sum(sre.reserved_qty - sre.delivered_qty).as_("reserved_qty"), Sum(sre.reserved_qty - sre.delivered_qty).as_("reserved_qty"),
) )
.where((sre.docstatus == 1) & (sre.status.notin(["Delivered", "Cancelled"]))) .where(
(sre.docstatus == 1)
& sre.item_code.isin(item_code_list)
& (sre.status.notin(["Delivered", "Cancelled"]))
)
.groupby(sre.item_code, sre.warehouse) .groupby(sre.item_code, sre.warehouse)
) )
query = ( if warehouse_list:
query.where(sre.item_code.isin(item_code)) query = query.where(sre.warehouse.isin(warehouse_list))
if isinstance(item_code, list)
else query.where(sre.item_code == item_code)
)
if warehouse:
query = (
query.where(sre.warehouse.isin(warehouse))
if isinstance(warehouse, list)
else query.where(sre.warehouse == warehouse)
)
data = query.run(as_dict=True) data = query.run(as_dict=True)
if isinstance(item_code, str) and isinstance(warehouse, str): return {(d["item_code"], d["warehouse"]): d["reserved_qty"] for d in data} if data else {}
return data[0]["reserved_qty"] if data else 0.0
else:
return {(d["item_code"], d["warehouse"]): d["reserved_qty"] for d in data} if data else {}
def get_sre_reserved_qty_details_for_voucher(voucher_type: str, voucher_no: str) -> dict: def get_sre_reserved_qty_details_for_voucher(voucher_type: str, voucher_no: str) -> dict:
@ -711,7 +728,7 @@ def get_serial_batch_entries_for_voucher(sre_name: str) -> list[dict]:
).run(as_dict=True) ).run(as_dict=True)
def get_ssb_bundle_for_voucher(sre: dict) -> object | None: def get_ssb_bundle_for_voucher(sre: dict) -> object:
"""Returns a new `Serial and Batch Bundle` against the provided SRE.""" """Returns a new `Serial and Batch Bundle` against the provided SRE."""
sb_entries = get_serial_batch_entries_for_voucher(sre["name"]) sb_entries = get_serial_batch_entries_for_voucher(sre["name"])

View File

@ -4,13 +4,14 @@
frappe.listview_settings['Stock Reservation Entry'] = { frappe.listview_settings['Stock Reservation Entry'] = {
get_indicator: function (doc) { get_indicator: function (doc) {
const status_colors = { const status_colors = {
'Draft': 'red', 'Draft': 'red',
'Partially Reserved': 'orange', 'Partially Reserved': 'orange',
'Reserved': 'blue', 'Reserved': 'blue',
'Partially Delivered': 'purple', 'Partially Delivered': 'purple',
'Delivered': 'green', 'Delivered': 'green',
'Cancelled': 'red', 'Cancelled': 'red',
}; };
return [__(doc.status), status_colors[doc.status], 'status,=,' + doc.status];
return [__(doc.status), status_colors[doc.status], 'status,=,' + doc.status];
}, },
}; };

View File

@ -165,7 +165,7 @@ class StockBalanceReport(object):
def get_sre_reserved_qty_details(self) -> dict: def get_sre_reserved_qty_details(self) -> dict:
from erpnext.stock.doctype.stock_reservation_entry.stock_reservation_entry import ( from erpnext.stock.doctype.stock_reservation_entry.stock_reservation_entry import (
get_sre_reserved_qty_for_item_and_warehouse as get_reserved_qty_details, get_sre_reserved_qty_for_items_and_warehouses as get_reserved_qty_details,
) )
item_code_list, warehouse_list = [], [] item_code_list, warehouse_list = [], []