[enhancement] default warehouse not mandatory in time, re-order level and re-order qty must always be set per warehosue

This commit is contained in:
Rushabh Mehta 2016-02-08 11:52:22 +05:30
parent 060f32d452
commit 3cdf3544fb
12 changed files with 82 additions and 169 deletions

View File

@ -423,6 +423,8 @@ def make_time_log(name, operation, from_time=None, to_time=None, qty=None, proj
@frappe.whitelist() @frappe.whitelist()
def get_default_warehouse(): def get_default_warehouse():
wip_warehouse = frappe.db.get_single_value("Manufacturing Settings", "default_wip_warehouse") wip_warehouse = frappe.db.get_single_value("Manufacturing Settings",
fg_warehouse = frappe.db.get_single_value("Manufacturing Settings", "default_fg_warehouse") "default_wip_warehouse")
fg_warehouse = frappe.db.get_single_value("Manufacturing Settings",
"default_fg_warehouse")
return {"wip_warehouse": wip_warehouse, "fg_warehouse": fg_warehouse} return {"wip_warehouse": wip_warehouse, "fg_warehouse": fg_warehouse}

View File

@ -247,3 +247,4 @@ erpnext.patches.v6_16.create_manufacturer_records
execute:frappe.db.sql("update `tabPricing Rule` set title=name where title='' or title is null") #2016-01-27 execute:frappe.db.sql("update `tabPricing Rule` set title=name where title='' or title is null") #2016-01-27
erpnext.patches.v6_20.set_party_account_currency_in_orders erpnext.patches.v6_20.set_party_account_currency_in_orders
erpnext.patches.v6_19.comment_feed_communication erpnext.patches.v6_19.comment_feed_communication
erpnext.patches.v6_21.fix_reorder_level

View File

View File

@ -0,0 +1,24 @@
from __future__ import unicode_literals
import frappe
from erpnext.stock.doctype.item.item import DuplicateReorderRows
def execute():
if frappe.db.has_column("Item", "re_order_level"):
for item in frappe.db.sql("""select name, default_warehouse, re_order_level, re_order_qty
from tabItem
where ifnull(re_order_level, 0) != 0
and ifnull(re_order_qty, 0) != 0""", as_dict=1):
item_doc = frappe.get_doc("Item", item.name)
item_doc.append("reorder_levels", {
"warehouse": item.default_warehouse,
"warehouse_reorder_level": item.re_order_level,
"warehouse_reorder_qty": item.re_order_qty,
"material_request_type": "Purchase" if item_doc.is_purchase_item else "Transfer"
})
try:
item_doc.save()
except DuplicateReorderRows:
pass

View File

@ -46,8 +46,7 @@ data_map = {
# Stock # Stock
"Item": { "Item": {
"columns": ["name", "if(item_name=name, '', item_name) as item_name", "description", "columns": ["name", "if(item_name=name, '', item_name) as item_name", "description",
"item_group as parent_item_group", "stock_uom", "brand", "valuation_method", "item_group as parent_item_group", "stock_uom", "brand", "valuation_method"],
"re_order_level", "re_order_qty"],
# "conditions": ["docstatus < 2"], # "conditions": ["docstatus < 2"],
"order_by": "name", "order_by": "name",
"links": { "links": {

View File

@ -72,8 +72,6 @@ frappe.ui.form.on("Item", {
(frm.doc.__onload && frm.doc.__onload.sle_exists=="exists") ? false : true); (frm.doc.__onload && frm.doc.__onload.sle_exists=="exists") ? false : true);
} }
erpnext.item.toggle_reqd(frm);
erpnext.item.toggle_attributes(frm); erpnext.item.toggle_attributes(frm);
}, },
@ -102,7 +100,6 @@ frappe.ui.form.on("Item", {
}, },
is_stock_item: function(frm) { is_stock_item: function(frm) {
erpnext.item.toggle_reqd(frm);
if(frm.doc.is_pro_applicable && !frm.doc.is_stock_item) if(frm.doc.is_pro_applicable && !frm.doc.is_stock_item)
frm.set_value("is_pro_applicable", 0); frm.set_value("is_pro_applicable", 0);
}, },
@ -180,10 +177,6 @@ $.extend(erpnext.item, {
}, },
toggle_reqd: function(frm) {
frm.toggle_reqd("default_warehouse", frm.doc.is_stock_item);
},
make_dashboard: function(frm) { make_dashboard: function(frm) {
frm.dashboard.reset(); frm.dashboard.reset();
if(frm.doc.__islocal) if(frm.doc.__islocal)

View File

@ -790,108 +790,7 @@
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"depends_on": "eval:(doc.is_stock_item && !doc.apply_warehouse_wise_reorder_level)", "depends_on": "",
"description": "Automatically create Material Request if quantity falls below this level",
"fieldname": "re_order_level",
"fieldtype": "Float",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Re-order Level",
"length": 0,
"no_copy": 0,
"oldfieldname": "re_order_level",
"oldfieldtype": "Currency",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"depends_on": "eval:(doc.is_stock_item && !doc.apply_warehouse_wise_reorder_level)",
"fieldname": "re_order_qty",
"fieldtype": "Float",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Re-order Qty",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"depends_on": "is_stock_item",
"fieldname": "apply_warehouse_wise_reorder_level",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Apply Warehouse-wise Reorder Level",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"collapsible_depends_on": "reorder_levels",
"depends_on": "eval:(doc.is_stock_item && doc.apply_warehouse_wise_reorder_level)",
"fieldname": "section_break_31",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Reorder level based on Warehouse",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"depends_on": "eval:(doc.is_stock_item && doc.apply_warehouse_wise_reorder_level)",
"description": "Will also apply for variants unless overrridden", "description": "Will also apply for variants unless overrridden",
"fieldname": "reorder_levels", "fieldname": "reorder_levels",
"fieldtype": "Table", "fieldtype": "Table",
@ -1463,6 +1362,35 @@
"set_only_once": 0, "set_only_once": 0,
"unique": 0 "unique": 0
}, },
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"default": "",
"depends_on": "eval:doc.is_sales_item",
"description": "Allow in Sales Order of type \"Service\"",
"fieldname": "is_service_item",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 1,
"in_list_view": 0,
"label": "Is Service Item",
"length": 0,
"no_copy": 0,
"oldfieldname": "is_service_item",
"oldfieldtype": "Select",
"options": "",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{ {
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
@ -2309,7 +2237,7 @@
"issingle": 0, "issingle": 0,
"istable": 0, "istable": 0,
"max_attachments": 1, "max_attachments": 1,
"modified": "2016-01-26 05:31:58.950718", "modified": "2016-02-08 01:15:52.177625",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Stock", "module": "Stock",
"name": "Item", "name": "Item",
@ -2329,7 +2257,7 @@
"print": 1, "print": 1,
"read": 1, "read": 1,
"report": 1, "report": 1,
"role": "Material Master Manager", "role": "Item Manager",
"set_user_permissions": 0, "set_user_permissions": 0,
"share": 1, "share": 1,
"submit": 0, "submit": 0,
@ -2349,7 +2277,7 @@
"print": 1, "print": 1,
"read": 1, "read": 1,
"report": 1, "report": 1,
"role": "Material Manager", "role": "Stock Manager",
"set_user_permissions": 0, "set_user_permissions": 0,
"share": 0, "share": 0,
"submit": 0, "submit": 0,
@ -2357,7 +2285,7 @@
}, },
{ {
"amend": 0, "amend": 0,
"apply_user_permissions": 1, "apply_user_permissions": 0,
"cancel": 0, "cancel": 0,
"create": 0, "create": 0,
"delete": 0, "delete": 0,
@ -2369,7 +2297,7 @@
"print": 1, "print": 1,
"read": 1, "read": 1,
"report": 1, "report": 1,
"role": "Material User", "role": "Stock User",
"set_user_permissions": 0, "set_user_permissions": 0,
"share": 0, "share": 0,
"submit": 0, "submit": 0,
@ -2377,7 +2305,7 @@
}, },
{ {
"amend": 0, "amend": 0,
"apply_user_permissions": 1, "apply_user_permissions": 0,
"cancel": 0, "cancel": 0,
"create": 0, "create": 0,
"delete": 0, "delete": 0,
@ -2397,7 +2325,7 @@
}, },
{ {
"amend": 0, "amend": 0,
"apply_user_permissions": 1, "apply_user_permissions": 0,
"cancel": 0, "cancel": 0,
"create": 0, "create": 0,
"delete": 0, "delete": 0,
@ -2417,7 +2345,7 @@
}, },
{ {
"amend": 0, "amend": 0,
"apply_user_permissions": 1, "apply_user_permissions": 0,
"cancel": 0, "cancel": 0,
"create": 0, "create": 0,
"delete": 0, "delete": 0,
@ -2437,7 +2365,7 @@
}, },
{ {
"amend": 0, "amend": 0,
"apply_user_permissions": 1, "apply_user_permissions": 0,
"cancel": 0, "cancel": 0,
"create": 0, "create": 0,
"delete": 0, "delete": 0,
@ -2457,7 +2385,7 @@
}, },
{ {
"amend": 0, "amend": 0,
"apply_user_permissions": 1, "apply_user_permissions": 0,
"cancel": 0, "cancel": 0,
"create": 0, "create": 0,
"delete": 0, "delete": 0,

View File

@ -14,7 +14,7 @@ from frappe.website.render import clear_cache
from frappe.website.doctype.website_slideshow.website_slideshow import get_slideshow from frappe.website.doctype.website_slideshow.website_slideshow import get_slideshow
from erpnext.controllers.item_variant import get_variant, copy_attributes_to_variant, ItemVariantExistsError from erpnext.controllers.item_variant import get_variant, copy_attributes_to_variant, ItemVariantExistsError
class WarehouseNotSet(frappe.ValidationError): pass class DuplicateReorderRows(frappe.ValidationError): pass
class Item(WebsiteGenerator): class Item(WebsiteGenerator):
website = frappe._dict( website = frappe._dict(
@ -58,7 +58,6 @@ class Item(WebsiteGenerator):
if not self.stock_uom: if not self.stock_uom:
msgprint(_("Please enter default Unit of Measure"), raise_exception=1) msgprint(_("Please enter default Unit of Measure"), raise_exception=1)
self.check_warehouse_is_set_for_stock_item()
self.validate_uom() self.validate_uom()
self.add_default_uom_in_conversion_factor_table() self.add_default_uom_in_conversion_factor_table()
self.validate_conversion_factor() self.validate_conversion_factor()
@ -303,11 +302,6 @@ class Item(WebsiteGenerator):
if not find_variant(combination): if not find_variant(combination):
context.disabled_attributes.setdefault(attr.attribute, []).append(combination[-1]) context.disabled_attributes.setdefault(attr.attribute, []).append(combination[-1])
def check_warehouse_is_set_for_stock_item(self):
if self.is_stock_item==1 and not self.default_warehouse and frappe.get_all("Warehouse"):
frappe.msgprint(_("Default Warehouse is mandatory for stock Item."),
raise_exception=WarehouseNotSet)
def add_default_uom_in_conversion_factor_table(self): def add_default_uom_in_conversion_factor_table(self):
uom_conv_list = [d.uom for d in self.get("uoms")] uom_conv_list = [d.uom for d in self.get("uoms")]
if self.stock_uom not in uom_conv_list: if self.stock_uom not in uom_conv_list:
@ -409,22 +403,14 @@ class Item(WebsiteGenerator):
you can not change the values of 'Has Serial No', 'Has Batch No', 'Is Stock Item' and 'Valuation Method'")) you can not change the values of 'Has Serial No', 'Has Batch No', 'Is Stock Item' and 'Valuation Method'"))
def validate_reorder_level(self): def validate_reorder_level(self):
if cint(self.apply_warehouse_wise_reorder_level): if len(self.get("reorder_levels", {"material_request_type": "Purchase"})):
self.re_order_level, self.re_order_qty = 0, 0
else:
self.set("reorder_levels", [])
if self.re_order_level or len(self.get("reorder_levels", {"material_request_type": "Purchase"})):
if not (self.is_purchase_item or self.is_pro_applicable): if not (self.is_purchase_item or self.is_pro_applicable):
frappe.throw(_("""To set reorder level, item must be a Purchase Item or Manufacturing Item""")) frappe.throw(_("""To set reorder level, item must be a Purchase Item or Manufacturing Item"""))
if self.re_order_level and not self.re_order_qty:
frappe.throw(_("Please set reorder quantity"))
for d in self.get("reorder_levels"): for d in self.get("reorder_levels"):
if d.warehouse_reorder_level and not d.warehouse_reorder_qty: if d.warehouse_reorder_level and not d.warehouse_reorder_qty:
frappe.throw(_("Row #{0}: Please set reorder quantity").format(d.idx)) frappe.throw(_("Row #{0}: Please set reorder quantity").format(d.idx))
def validate_warehouse_for_reorder(self): def validate_warehouse_for_reorder(self):
warehouse = [] warehouse = []
for i in self.get("reorder_levels"): for i in self.get("reorder_levels"):
@ -432,7 +418,7 @@ class Item(WebsiteGenerator):
warehouse += [i.get("warehouse")] warehouse += [i.get("warehouse")]
else: else:
frappe.throw(_("Row {0}: An Reorder entry already exists for this warehouse {1}") frappe.throw(_("Row {0}: An Reorder entry already exists for this warehouse {1}")
.format(i.idx, i.warehouse)) .format(i.idx, i.warehouse), DuplicateReorderRows)
def check_if_sle_exists(self): def check_if_sle_exists(self):
sle = frappe.db.sql("""select name from `tabStock Ledger Entry` sle = frappe.db.sql("""select name from `tabStock Ledger Entry`

View File

@ -6,7 +6,6 @@ import unittest
import frappe import frappe
from frappe.test_runner import make_test_records from frappe.test_runner import make_test_records
from erpnext.stock.doctype.item.item import WarehouseNotSet
from erpnext.controllers.item_variant import create_variant, ItemVariantExistsError, InvalidItemAttributeValueError from erpnext.controllers.item_variant import create_variant, ItemVariantExistsError, InvalidItemAttributeValueError
test_ignore = ["BOM"] test_ignore = ["BOM"]
@ -45,18 +44,6 @@ class TestItem(unittest.TestCase):
item = frappe.get_doc("Item", item_code) item = frappe.get_doc("Item", item_code)
return item return item
# def test_template_cannot_have_stock(self):
# item = self.get_item(10)
# make_stock_entry(item_code=item.name, target="Stores - _TC", qty=1, basic_rate=1)
# item.has_variants = 1
# self.assertRaises(ItemTemplateCannotHaveStock, item.save)
def test_default_warehouse(self):
item = frappe.copy_doc(test_records[0])
item.is_stock_item = 1
item.default_warehouse = None
self.assertRaises(WarehouseNotSet, item.insert)
def test_get_item_details(self): def test_get_item_details(self):
from erpnext.stock.get_item_details import get_item_details from erpnext.stock.get_item_details import get_item_details
to_check = { to_check = {

View File

@ -102,6 +102,8 @@ erpnext.buying.MaterialRequestController = erpnext.buying.BuyingController.exten
fields: [ fields: [
{"fieldname":"bom", "fieldtype":"Link", "label":__("BOM"), {"fieldname":"bom", "fieldtype":"Link", "label":__("BOM"),
options:"BOM", reqd: 1}, options:"BOM", reqd: 1},
{"fieldname":"warehouse", "fieldtype":"Link", "label":__("Warehouse"),
options:"Warehouse", reqd: 1, label:"For Warehouse"},
{"fieldname":"fetch_exploded", "fieldtype":"Check", {"fieldname":"fetch_exploded", "fieldtype":"Check",
"label":__("Fetch exploded BOM (including sub-assemblies)"), "default":1}, "label":__("Fetch exploded BOM (including sub-assemblies)"), "default":1},
{fieldname:"fetch", "label":__("Get Items from BOM"), "fieldtype":"Button"} {fieldname:"fetch", "label":__("Get Items from BOM"), "fieldtype":"Button"}
@ -119,7 +121,7 @@ erpnext.buying.MaterialRequestController = erpnext.buying.BuyingController.exten
var d = frappe.model.add_child(cur_frm.doc, "Material Request Item", "items"); var d = frappe.model.add_child(cur_frm.doc, "Material Request Item", "items");
d.item_code = item.item_code; d.item_code = item.item_code;
d.description = item.description; d.description = item.description;
d.warehouse = item.default_warehouse; d.warehouse = d.warehouse;
d.uom = item.stock_uom; d.uom = item.stock_uom;
d.qty = item.qty; d.qty = item.qty;
}); });

View File

@ -2,7 +2,7 @@
# License: GNU General Public License v3. See license.txt # License: GNU General Public License v3. See license.txt
import frappe import frappe
from frappe.utils import flt, cstr, nowdate, add_days, cint from frappe.utils import flt, nowdate, add_days, cint
from frappe import _ from frappe import _
def reorder_item(): def reorder_item():
@ -26,10 +26,9 @@ def _reorder_item():
and (is_purchase_item=1 or is_sub_contracted_item=1) and (is_purchase_item=1 or is_sub_contracted_item=1)
and disabled=0 and disabled=0
and (end_of_life is null or end_of_life='0000-00-00' or end_of_life > %(today)s) and (end_of_life is null or end_of_life='0000-00-00' or end_of_life > %(today)s)
and ((re_order_level is not null and re_order_level > 0) and (exists (select name from `tabItem Reorder` ir where ir.parent=item.name)
or exists (select name from `tabItem Reorder` ir where ir.parent=item.name)
or (variant_of is not null and variant_of != '' or (variant_of is not null and variant_of != ''
and exists (select name from `tabItem Reorder` ir where ir.parent=item.variant_of)) and exists (select name from `tabItem Reorder` ir where ir.parent=item.variant_of))
)""", )""",
{"today": nowdate()}) {"today": nowdate()})
@ -73,10 +72,6 @@ def _reorder_item():
add_to_material_request(item_code, d.warehouse, d.warehouse_reorder_level, add_to_material_request(item_code, d.warehouse, d.warehouse_reorder_level,
d.warehouse_reorder_qty, d.material_request_type) d.warehouse_reorder_qty, d.material_request_type)
else:
# raise for default warehouse
add_to_material_request(item_code, item.default_warehouse, item.re_order_level, item.re_order_qty, "Purchase")
if material_requests: if material_requests:
return create_material_request(material_requests) return create_material_request(material_requests)

View File

@ -42,19 +42,15 @@ def get_data(filters):
re_order_level = re_order_qty = 0 re_order_level = re_order_qty = 0
if bin.warehouse==item.default_warehouse:
re_order_level = item.re_order_level or 0
re_order_qty = item.re_order_qty or 0
for d in item.get("reorder_levels"): for d in item.get("reorder_levels"):
if d.warehouse == bin.warehouse: if d.warehouse == bin.warehouse:
re_order_level = d.warehouse_reorder_level re_order_level = d.warehouse_reorder_level
re_order_qty = d.warehouse_reorder_qty re_order_qty = d.warehouse_reorder_qty
shortage_qty = re_order_level - flt(bin.projected_qty) if re_order_level else 0 shortage_qty = re_order_level - flt(bin.projected_qty) if re_order_level else 0
data.append([item.name, item.item_name, item.description, item.item_group, item.brand, bin.warehouse, data.append([item.name, item.item_name, item.description, item.item_group, item.brand, bin.warehouse,
item.stock_uom, bin.actual_qty, bin.planned_qty, bin.indented_qty, bin.ordered_qty, item.stock_uom, bin.actual_qty, bin.planned_qty, bin.indented_qty, bin.ordered_qty,
bin.reserved_qty, bin.projected_qty, re_order_level, re_order_qty, shortage_qty]) bin.reserved_qty, bin.projected_qty, re_order_level, re_order_qty, shortage_qty])
return data return data