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:
parent
9596276b95
commit
90598ea19c
@ -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) {
|
||||||
|
|
||||||
// }
|
// }
|
||||||
|
|||||||
@ -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",
|
||||||
|
|||||||
@ -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>"
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user