Merge pull request #33680 from rohitwaghchaure/refactor-picked-qty

refactor: picked qty in sales order item
This commit is contained in:
rohitwaghchaure 2023-01-16 23:31:56 +05:30 committed by GitHub
commit d6915df81d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -4,7 +4,7 @@
import json import json
from collections import OrderedDict, defaultdict from collections import OrderedDict, defaultdict
from itertools import groupby from itertools import groupby
from typing import Dict, List, Set from typing import Dict, List
import frappe import frappe
from frappe import _ from frappe import _
@ -41,7 +41,9 @@ class PickList(Document):
) )
def before_submit(self): def before_submit(self):
update_sales_orders = set() self.validate_picked_items()
def validate_picked_items(self):
for item in self.locations: for item in self.locations:
if self.scan_mode and item.picked_qty < item.stock_qty: if self.scan_mode and item.picked_qty < item.stock_qty:
frappe.throw( frappe.throw(
@ -50,17 +52,14 @@ class PickList(Document):
).format(item.idx, item.stock_qty - item.picked_qty, item.stock_uom), ).format(item.idx, item.stock_qty - item.picked_qty, item.stock_uom),
title=_("Pick List Incomplete"), title=_("Pick List Incomplete"),
) )
elif not self.scan_mode and item.picked_qty == 0:
if not self.scan_mode and item.picked_qty == 0:
# if the user has not entered any picked qty, set it to stock_qty, before submit # if the user has not entered any picked qty, set it to stock_qty, before submit
item.picked_qty = item.stock_qty item.picked_qty = item.stock_qty
if item.sales_order_item:
# update the picked_qty in SO Item
self.update_sales_order_item(item, item.picked_qty, item.item_code)
update_sales_orders.add(item.sales_order)
if not frappe.get_cached_value("Item", item.item_code, "has_serial_no"): if not frappe.get_cached_value("Item", item.item_code, "has_serial_no"):
continue continue
if not item.serial_no: if not item.serial_no:
frappe.throw( frappe.throw(
_("Row #{0}: {1} does not have any available serial numbers in {2}").format( _("Row #{0}: {1} does not have any available serial numbers in {2}").format(
@ -68,8 +67,8 @@ class PickList(Document):
), ),
title=_("Serial Nos Required"), title=_("Serial Nos Required"),
) )
if len(item.serial_no.split("\n")) == item.picked_qty:
continue if len(item.serial_no.split("\n")) != item.picked_qty:
frappe.throw( frappe.throw(
_( _(
"For item {0} at row {1}, count of serial numbers does not match with the picked quantity" "For item {0} at row {1}, count of serial numbers does not match with the picked quantity"
@ -77,49 +76,87 @@ class PickList(Document):
title=_("Quantity Mismatch"), title=_("Quantity Mismatch"),
) )
def on_submit(self):
self.update_bundle_picked_qty() self.update_bundle_picked_qty()
self.update_sales_order_picking_status(update_sales_orders) self.update_reference_qty()
self.update_sales_order_picking_status()
def before_cancel(self):
"""Deduct picked qty on cancelling pick list"""
updated_sales_orders = set()
for item in self.get("locations"):
if item.sales_order_item:
self.update_sales_order_item(item, -1 * item.picked_qty, item.item_code)
updated_sales_orders.add(item.sales_order)
def on_cancel(self):
self.update_bundle_picked_qty() self.update_bundle_picked_qty()
self.update_sales_order_picking_status(updated_sales_orders) self.update_reference_qty()
self.update_sales_order_picking_status()
def update_sales_order_item(self, item, picked_qty, item_code): def update_reference_qty(self):
item_table = "Sales Order Item" if not item.product_bundle_item else "Packed Item" packed_items = []
stock_qty_field = "stock_qty" if not item.product_bundle_item else "qty" so_items = []
already_picked, actual_qty = frappe.db.get_value( for item in self.locations:
item_table, if item.product_bundle_item:
item.sales_order_item, packed_items.append(item.sales_order_item)
["picked_qty", stock_qty_field], elif item.sales_order_item:
for_update=True, so_items.append(item.sales_order_item)
if packed_items:
self.update_packed_items_qty(packed_items)
if so_items:
self.update_sales_order_item_qty(so_items)
def update_packed_items_qty(self, packed_items):
picked_items = get_picked_items_qty(packed_items)
self.validate_picked_qty(picked_items)
picked_qty = frappe._dict()
for d in picked_items:
picked_qty[d.sales_order_item] = d.picked_qty
for packed_item in packed_items:
frappe.db.set_value(
"Packed Item",
packed_item,
"picked_qty",
flt(picked_qty.get(packed_item)),
update_modified=False,
) )
if self.docstatus == 1: def update_sales_order_item_qty(self, so_items):
if (((already_picked + picked_qty) / actual_qty) * 100) > ( picked_items = get_picked_items_qty(so_items)
100 + flt(frappe.db.get_single_value("Stock Settings", "over_delivery_receipt_allowance")) self.validate_picked_qty(picked_items)
):
picked_qty = frappe._dict()
for d in picked_items:
picked_qty[d.sales_order_item] = d.picked_qty
for so_item in so_items:
frappe.db.set_value(
"Sales Order Item",
so_item,
"picked_qty",
flt(picked_qty.get(so_item)),
update_modified=False,
)
def update_sales_order_picking_status(self) -> None:
sales_orders = []
for row in self.locations:
if row.sales_order and row.sales_order not in sales_orders:
sales_orders.append(row.sales_order)
for sales_order in sales_orders:
frappe.get_doc("Sales Order", sales_order, for_update=True).update_picking_status()
def validate_picked_qty(self, data):
over_delivery_receipt_allowance = 100 + flt(
frappe.db.get_single_value("Stock Settings", "over_delivery_receipt_allowance")
)
for row in data:
if (row.picked_qty / row.stock_qty) * 100 > over_delivery_receipt_allowance:
frappe.throw( frappe.throw(
_( _(
"You are picking more than required quantity for {}. Check if there is any other pick list created for {}" f"You are picking more than required quantity for the item {row.item_code}. Check if there is any other pick list created for the sales order {row.sales_order}."
).format(item_code, item.sales_order) )
) )
frappe.db.set_value(item_table, item.sales_order_item, "picked_qty", already_picked + picked_qty)
@staticmethod
def update_sales_order_picking_status(sales_orders: Set[str]) -> None:
for sales_order in sales_orders:
if sales_order:
frappe.get_doc("Sales Order", sales_order, for_update=True).update_picking_status()
@frappe.whitelist() @frappe.whitelist()
def set_item_locations(self, save=False): def set_item_locations(self, save=False):
@ -309,6 +346,31 @@ class PickList(Document):
return int(flt(min(possible_bundles), precision or 6)) return int(flt(min(possible_bundles), precision or 6))
def get_picked_items_qty(items) -> List[Dict]:
return frappe.db.sql(
f"""
SELECT
sales_order_item,
item_code,
sales_order,
SUM(stock_qty) AS stock_qty,
SUM(picked_qty) AS picked_qty
FROM
`tabPick List Item`
WHERE
sales_order_item IN (
{", ".join(frappe.db.escape(d) for d in items)}
)
AND docstatus = 1
GROUP BY
sales_order_item,
sales_order
FOR UPDATE
""",
as_dict=1,
)
def validate_item_locations(pick_list): def validate_item_locations(pick_list):
if not pick_list.locations: if not pick_list.locations:
frappe.throw(_("Add items in the Item Locations table")) frappe.throw(_("Add items in the Item Locations table"))