mirror of
https://github.com/frappe/erpnext.git
synced 2026-06-01 11:19:09 +00:00
feat: Putaway Rule
This commit is contained in:
@@ -349,7 +349,9 @@ def close_or_unclose_purchase_orders(names, status):
|
|||||||
frappe.local.message_log = []
|
frappe.local.message_log = []
|
||||||
|
|
||||||
def set_missing_values(source, target):
|
def set_missing_values(source, target):
|
||||||
|
from erpnext.stock.doctype.putaway_rule.putaway_rule import apply_putaway_rule
|
||||||
target.ignore_pricing_rule = 1
|
target.ignore_pricing_rule = 1
|
||||||
|
target.items = apply_putaway_rule(target.items, target.company)
|
||||||
target.run_method("set_missing_values")
|
target.run_method("set_missing_values")
|
||||||
target.run_method("calculate_taxes_and_totals")
|
target.run_method("calculate_taxes_and_totals")
|
||||||
|
|
||||||
|
|||||||
@@ -55,7 +55,7 @@
|
|||||||
"reqd": 1
|
"reqd": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"default": "item_code.stock_uom",
|
"fetch_from": "item_code.stock_uom",
|
||||||
"fieldname": "stock_uom",
|
"fieldname": "stock_uom",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"label": "Stock UOM",
|
"label": "Stock UOM",
|
||||||
@@ -86,7 +86,7 @@
|
|||||||
],
|
],
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2020-11-10 17:06:27.151335",
|
"modified": "2020-11-12 11:20:52.765163",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Stock",
|
"module": "Stock",
|
||||||
"name": "Putaway Rule",
|
"name": "Putaway Rule",
|
||||||
|
|||||||
@@ -5,9 +5,11 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
import frappe
|
import frappe
|
||||||
import copy
|
import copy
|
||||||
|
from collections import defaultdict
|
||||||
from frappe import _
|
from frappe import _
|
||||||
from frappe.utils import flt
|
from frappe.utils import flt, nowdate
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
|
from erpnext.stock.utils import get_stock_balance
|
||||||
|
|
||||||
class PutawayRule(Document):
|
class PutawayRule(Document):
|
||||||
def validate(self):
|
def validate(self):
|
||||||
@@ -35,59 +37,97 @@ class PutawayRule(Document):
|
|||||||
title=_("Invalid Warehouse"))
|
title=_("Invalid Warehouse"))
|
||||||
|
|
||||||
def validate_capacity(self):
|
def validate_capacity(self):
|
||||||
# check if capacity is lesser than current balance in warehouse
|
balance_qty = get_stock_balance(self.item_code, self.warehouse, nowdate())
|
||||||
pass
|
if flt(self.capacity) < flt(balance_qty):
|
||||||
|
frappe.throw(_("Warehouse Capacity for Item '{0}' must be greater than the existing stock level of {1} qty.")
|
||||||
|
.format(self.item_code, frappe.bold(balance_qty)), title=_("Insufficient Capacity"))
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_ordered_putaway_rules(item_code, company, qty):
|
def get_ordered_putaway_rules(item_code, company):
|
||||||
"""Returns an ordered list of putaway rules to apply on an item."""
|
"""Returns an ordered list of putaway rules to apply on an item."""
|
||||||
|
rules = frappe.get_all("Putaway Rule", fields=["name", "capacity", "priority", "warehouse"],
|
||||||
|
filters={"item_code": item_code, "company": company, "disable": 0},
|
||||||
|
order_by="priority asc, capacity desc")
|
||||||
|
|
||||||
# get enabled putaway rules for this item code in this company that have pending capacity
|
if not rules:
|
||||||
# order the rules by priority first
|
return False, None
|
||||||
# if same priority, order by pending capacity (capacity - get how much stock is in the warehouse)
|
|
||||||
# return this list
|
for rule in rules:
|
||||||
# [{'name': "something", "free space": 20}, {'name': "something", "free space": 10}]
|
balance_qty = get_stock_balance(rule.item_code, rule.warehouse, nowdate())
|
||||||
|
free_space = flt(rule.capacity) - flt(balance_qty)
|
||||||
|
|
||||||
|
if free_space > 0:
|
||||||
|
rule["free_space"] = free_space
|
||||||
|
else:
|
||||||
|
del rule
|
||||||
|
|
||||||
|
if not rules:
|
||||||
|
# After iterating through rules, if no rules are left
|
||||||
|
# then there is not enough space left in any rule
|
||||||
|
True, None
|
||||||
|
|
||||||
|
rules = sorted(rules, key = lambda i: (i['priority'], -i['free_space']))
|
||||||
|
return False, rules
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def apply_putaway_rule(items, company):
|
def apply_putaway_rule(items, company):
|
||||||
""" Applies Putaway Rule on line items.
|
""" Applies Putaway Rule on line items.
|
||||||
|
|
||||||
items: List of line items in a Purchase Receipt
|
items: List of Purchase Receipt Item objects
|
||||||
company: Company in Purchase Receipt
|
company: Company in the Purchase Receipt
|
||||||
"""
|
"""
|
||||||
items_not_accomodated = []
|
items_not_accomodated, updated_table = [], []
|
||||||
|
item_wise_rules = defaultdict(list)
|
||||||
|
|
||||||
for item in items:
|
for item in items:
|
||||||
item_qty = item.qty
|
item_qty, item_code = flt(item.qty), item.item_code
|
||||||
at_capacity, rules = get_ordered_putaway_rules(item.item_code, company, item_qty)
|
if not item_qty: continue
|
||||||
|
|
||||||
|
at_capacity, rules = get_ordered_putaway_rules(item_code, company)
|
||||||
|
|
||||||
if not rules:
|
if not rules:
|
||||||
if at_capacity:
|
if at_capacity:
|
||||||
items_not_accomodated.append([item.item_code, item_qty])
|
items_not_accomodated.append([item_code, item_qty])
|
||||||
continue
|
continue
|
||||||
|
|
||||||
item_row_updated = False
|
# maintain item wise rules, to handle if item is entered twice
|
||||||
for rule in rules:
|
# in the table, due to different price, etc.
|
||||||
while item_qty > 0:
|
if not item_wise_rules[item_code]:
|
||||||
if not item_row_updated:
|
item_wise_rules[item_code] = rules
|
||||||
# update pre-existing row
|
|
||||||
item.qty = rule.qty
|
|
||||||
item.warehouse = rule.warehouse
|
|
||||||
item_row_updated = True
|
|
||||||
else:
|
|
||||||
# add rows for split quantity
|
|
||||||
added_row = copy.deepcopy(item)
|
|
||||||
added_row.qty = rule.qty
|
|
||||||
added_row.warehouse = rule.warehouse
|
|
||||||
items.append(added_row)
|
|
||||||
|
|
||||||
item_qty -= flt(rule.qty)
|
for rule in item_wise_rules[item_code]:
|
||||||
|
# it gets split if rule has lesser qty
|
||||||
|
# if rule_qty >= pending_qty => allocate pending_qty in row
|
||||||
|
# if rule_qty < pending_qty => allocate rule_qty in row and check for next rule
|
||||||
|
if item_qty > 0 and rule.free_space:
|
||||||
|
to_allocate = flt(rule.free_space) if item_qty >= flt(rule.free_space) else item_qty
|
||||||
|
new_updated_table_row = copy.deepcopy(item)
|
||||||
|
new_updated_table_row.name = ''
|
||||||
|
new_updated_table_row.idx = 1 if not updated_table else flt(updated_table[-1].idx) + 1
|
||||||
|
new_updated_table_row.qty = to_allocate
|
||||||
|
new_updated_table_row.warehouse = rule.warehouse
|
||||||
|
updated_table.append(new_updated_table_row)
|
||||||
|
|
||||||
|
item_qty -= to_allocate
|
||||||
|
rule["free_space"] -= to_allocate
|
||||||
|
if item_qty == 0: break
|
||||||
|
|
||||||
# if pending qty after applying rules, add row without warehouse
|
# if pending qty after applying rules, add row without warehouse
|
||||||
if item_qty > 0:
|
if item_qty > 0:
|
||||||
added_row = copy.deepcopy(item)
|
added_row = copy.deepcopy(item)
|
||||||
|
added_row.name = ''
|
||||||
|
new_updated_table_row.idx = 1 if not updated_table else flt(updated_table[-1].idx) + 1
|
||||||
added_row.qty = item_qty
|
added_row.qty = item_qty
|
||||||
added_row.warehouse = ''
|
added_row.warehouse = ''
|
||||||
items.append(added_row)
|
updated_table.append(added_row)
|
||||||
items_not_accomodated.append([item.item_code, item_qty])
|
items_not_accomodated.append([item.item_code, item_qty])
|
||||||
|
|
||||||
# need to check pricing rule, item tax impact
|
if items_not_accomodated:
|
||||||
|
msg = _("The following Items, having Putaway Rules, could not be accomodated:") + "<br><br><ul><li>"
|
||||||
|
formatted_item_qty = [entry[0] + " : " + str(entry[1]) for entry in items_not_accomodated]
|
||||||
|
msg += "</li><li>".join(formatted_item_qty)
|
||||||
|
msg += "</li></ul>"
|
||||||
|
frappe.msgprint(msg, title=_("Insufficient Capacity"), is_minimizable=True, wide=True)
|
||||||
|
|
||||||
|
return updated_table if updated_table else items
|
||||||
|
# TODO: check pricing rule, item tax impact
|
||||||
Reference in New Issue
Block a user