chore: Multi UOM support for Putaway

- Added UOM & conversion factor field in Putaway Rule
- Items are split and assigned as per UOM
- Handled Whole UOMs too
This commit is contained in:
marination 2020-11-23 17:35:13 +05:30
parent 9596276b95
commit 90598ea19c
3 changed files with 96 additions and 36 deletions

View File

@ -11,7 +11,32 @@ frappe.ui.form.on('Putaway Rule', {
} }
}; };
}); });
},
uom: function(frm) {
if(frm.doc.item_code && frm.doc.uom) {
return frm.call({
method: "erpnext.stock.get_item_details.get_conversion_factor",
args: {
item_code: frm.doc.item_code,
uom: frm.doc.uom
},
callback: function(r) {
if(!r.exc) {
let stock_capacity = flt(frm.doc.capacity) * flt(r.message.conversion_factor);
frm.set_value('conversion_factor', r.message.conversion_factor);
frm.set_value('stock_capacity', stock_capacity);
} }
}
});
}
},
capacity: function(frm) {
let stock_capacity = flt(frm.doc.capacity) * flt(frm.doc.conversion_factor);
frm.set_value('stock_capacity', stock_capacity);
}
// refresh: function(frm) { // refresh: function(frm) {
// } // }

View File

@ -10,17 +10,19 @@
"item_code", "item_code",
"item_name", "item_name",
"warehouse", "warehouse",
"priority",
"col_break_capacity", "col_break_capacity",
"company", "company",
"capacity", "capacity",
"priority", "uom",
"stock_uom" "conversion_factor",
"stock_uom",
"stock_capacity"
], ],
"fields": [ "fields": [
{ {
"fieldname": "item_code", "fieldname": "item_code",
"fieldtype": "Link", "fieldtype": "Link",
"in_list_view": 1,
"in_standard_filter": 1, "in_standard_filter": 1,
"label": "Item", "label": "Item",
"options": "Item", "options": "Item",
@ -82,11 +84,30 @@
"fieldname": "disable", "fieldname": "disable",
"fieldtype": "Check", "fieldtype": "Check",
"label": "Disable" "label": "Disable"
},
{
"fieldname": "uom",
"fieldtype": "Link",
"label": "UOM",
"options": "UOM"
},
{
"fieldname": "stock_capacity",
"fieldtype": "Float",
"label": "Capacity in Stock UOM",
"read_only": 1
},
{
"default": "1",
"fieldname": "conversion_factor",
"fieldtype": "Float",
"label": "Conversion Factor",
"read_only": 1
} }
], ],
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"links": [], "links": [],
"modified": "2020-11-12 11:20:52.765163", "modified": "2020-11-23 16:53:48.387054",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Stock", "module": "Stock",
"name": "Putaway Rule", "name": "Putaway Rule",

View File

@ -7,7 +7,7 @@ import frappe
import copy import copy
from collections import defaultdict from collections import defaultdict
from frappe import _ from frappe import _
from frappe.utils import flt, nowdate from frappe.utils import flt, floor, nowdate
from frappe.model.document import Document from frappe.model.document import Document
from erpnext.stock.utils import get_stock_balance from erpnext.stock.utils import get_stock_balance
@ -38,14 +38,14 @@ class PutawayRule(Document):
def validate_capacity(self): def validate_capacity(self):
balance_qty = get_stock_balance(self.item_code, self.warehouse, nowdate()) balance_qty = get_stock_balance(self.item_code, self.warehouse, nowdate())
if flt(self.capacity) < flt(balance_qty): if flt(self.stock_capacity) < flt(balance_qty):
frappe.throw(_("Warehouse Capacity for Item '{0}' must be greater than the existing stock level of {1} 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")) .format(self.item_code, frappe.bold(balance_qty)), title=_("Insufficient Capacity"))
@frappe.whitelist() @frappe.whitelist()
def get_ordered_putaway_rules(item_code, company): 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"], rules = frappe.get_all("Putaway Rule", fields=["name", "stock_capacity", "priority", "warehouse"],
filters={"item_code": item_code, "company": company, "disable": 0}, filters={"item_code": item_code, "company": company, "disable": 0},
order_by="priority asc, capacity desc") order_by="priority asc, capacity desc")
@ -54,8 +54,7 @@ def get_ordered_putaway_rules(item_code, company):
for rule in rules: for rule in rules:
balance_qty = get_stock_balance(rule.item_code, rule.warehouse, nowdate()) balance_qty = get_stock_balance(rule.item_code, rule.warehouse, nowdate())
free_space = flt(rule.capacity) - flt(balance_qty) free_space = flt(rule.stock_capacity) - flt(balance_qty)
if free_space > 0: if free_space > 0:
rule["free_space"] = free_space rule["free_space"] = free_space
else: else:
@ -64,7 +63,7 @@ def get_ordered_putaway_rules(item_code, company):
if not rules: if not rules:
# After iterating through rules, if no rules are left # After iterating through rules, if no rules are left
# then there is not enough space left in any rule # then there is not enough space left in any rule
True, None return True, None
rules = sorted(rules, key = lambda i: (i['priority'], -i['free_space'])) rules = sorted(rules, key = lambda i: (i['priority'], -i['free_space']))
return False, rules return False, rules
@ -79,15 +78,33 @@ def apply_putaway_rule(items, company):
items_not_accomodated, updated_table = [], [] items_not_accomodated, updated_table = [], []
item_wise_rules = defaultdict(list) item_wise_rules = defaultdict(list)
def add_row(item, to_allocate, warehouse):
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 = warehouse
updated_table.append(new_updated_table_row)
for item in items: for item in items:
item_qty, item_code = flt(item.qty), item.item_code conversion = flt(item.conversion_factor)
if not item_qty: continue uom_must_be_whole_number = frappe.db.get_value('UOM', item.uom, 'must_be_whole_number')
pending_qty, pending_stock_qty, item_code = flt(item.qty), flt(item.stock_qty), item.item_code
if not pending_qty:
add_row(item, pending_qty, item.warehouse)
continue
at_capacity, rules = get_ordered_putaway_rules(item_code, company) 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_code, item_qty]) # rules available, but no free space
add_row(item, pending_qty, '')
items_not_accomodated.append([item_code, pending_qty])
else:
# no rules to apply
add_row(item, pending_qty, item.warehouse)
continue continue
# maintain item wise rules, to handle if item is entered twice # maintain item wise rules, to handle if item is entered twice
@ -96,31 +113,28 @@ def apply_putaway_rule(items, company):
item_wise_rules[item_code] = rules item_wise_rules[item_code] = rules
for rule in item_wise_rules[item_code]: for rule in item_wise_rules[item_code]:
# it gets split if rule has lesser qty if pending_stock_qty > 0 and rule.free_space:
# if rule_qty >= pending_qty => allocate pending_qty in row stock_qty_to_allocate = flt(rule.free_space) if pending_stock_qty >= flt(rule.free_space) else pending_stock_qty
# if rule_qty < pending_qty => allocate rule_qty in row and check for next rule qty_to_allocate = stock_qty_to_allocate / (conversion or 1)
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 if uom_must_be_whole_number:
rule["free_space"] -= to_allocate qty_to_allocate = floor(qty_to_allocate)
if item_qty == 0: break stock_qty_to_allocate = qty_to_allocate * conversion
# if pending qty after applying rules, add row without warehouse if not qty_to_allocate: break
if item_qty > 0:
added_row = copy.deepcopy(item) add_row(item, qty_to_allocate, rule.warehouse)
added_row.name = ''
new_updated_table_row.idx = 1 if not updated_table else flt(updated_table[-1].idx) + 1 pending_stock_qty -= stock_qty_to_allocate
added_row.qty = item_qty pending_qty -= qty_to_allocate
added_row.warehouse = '' rule["free_space"] -= stock_qty_to_allocate
updated_table.append(added_row)
items_not_accomodated.append([item.item_code, item_qty]) if not pending_stock_qty: break
# if pending qty after applying all rules, add row without warehouse
if pending_stock_qty > 0:
add_row(item, pending_qty, '')
items_not_accomodated.append([item.item_code, pending_qty])
if items_not_accomodated: if items_not_accomodated:
msg = _("The following Items, having Putaway Rules, could not be accomodated:") + "<br><br><ul><li>" msg = _("The following Items, having Putaway Rules, could not be accomodated:") + "<br><br><ul><li>"