[feature] create material request sales order wise from production planning tool

This commit is contained in:
Akhilesh Darjee 2013-12-16 19:32:22 +05:30
parent 7d4c2d62bf
commit 9ff9dafd00
3 changed files with 140 additions and 97 deletions

View File

@ -7,11 +7,9 @@ import webnotes
from webnotes.utils import cstr, flt from webnotes.utils import cstr, flt
from webnotes.model.utils import getlist from webnotes.model.utils import getlist
from webnotes import msgprint, _ from webnotes import msgprint, _
from buying.utils import get_last_purchase_details from buying.utils import get_last_purchase_details
from controllers.buying_controller import BuyingController from controllers.buying_controller import BuyingController
class DocType(BuyingController): class DocType(BuyingController):
def __init__(self, doc, doclist=None): def __init__(self, doc, doclist=None):
self.doc = doc self.doc = doc
@ -107,7 +105,10 @@ class DocType(BuyingController):
msgprint("Item %s is not a purchase item or sub-contracted item. Please check" % (d.item_code), raise_exception=True) msgprint("Item %s is not a purchase item or sub-contracted item. Please check" % (d.item_code), raise_exception=True)
# list criteria that should not repeat if item is stock item # list criteria that should not repeat if item is stock item
e = [d.schedule_date, d.item_code, d.description, d.warehouse, d.uom, d.fields.has_key('prevdoc_docname') and d.prevdoc_docname or '', d.fields.has_key('prevdoc_detail_docname') and d.prevdoc_detail_docname or '', d.fields.has_key('batch_no') and d.batch_no or ''] e = [d.schedule_date, d.item_code, d.description, d.warehouse, d.uom,
d.fields.has_key('prevdoc_docname') and d.prevdoc_docname or d.fields.has_key('sales_order_no') and d.sales_order_no or '',
d.fields.has_key('prevdoc_detail_docname') and d.prevdoc_detail_docname or '',
d.fields.has_key('batch_no') and d.batch_no or '']
# if is not stock item # if is not stock item
f = [d.schedule_date, d.item_code, d.description] f = [d.schedule_date, d.item_code, d.description]

View File

@ -9,7 +9,6 @@ from webnotes.model.bean import getlist
from webnotes.model.code import get_obj from webnotes.model.code import get_obj
from webnotes import msgprint, _ from webnotes import msgprint, _
class DocType: class DocType:
def __init__(self, doc, doclist=[]): def __init__(self, doc, doclist=[]):
self.doc = doc self.doc = doc
@ -47,7 +46,7 @@ class DocType:
def validate_company(self): def validate_company(self):
if not self.doc.company: if not self.doc.company:
msgprint("Please enter Company", raise_exception=1) webnotes.throw(_("Please enter Company"))
def get_open_sales_orders(self): def get_open_sales_orders(self):
""" Pull sales orders which are pending to deliver based on criteria selected""" """ Pull sales orders which are pending to deliver based on criteria selected"""
@ -106,7 +105,7 @@ class DocType:
def get_items(self): def get_items(self):
so_list = filter(None, [d.sales_order for d in getlist(self.doclist, 'pp_so_details')]) so_list = filter(None, [d.sales_order for d in getlist(self.doclist, 'pp_so_details')])
if not so_list: if not so_list:
msgprint("Please enter sales order in the above table") msgprint(_("Please enter sales order in the above table"))
return [] return []
items = webnotes.conn.sql("""select distinct parent, item_code, reserved_warehouse, items = webnotes.conn.sql("""select distinct parent, item_code, reserved_warehouse,
@ -155,21 +154,21 @@ class DocType:
for d in getlist(self.doclist, 'pp_details'): for d in getlist(self.doclist, 'pp_details'):
self.validate_bom_no(d) self.validate_bom_no(d)
if not flt(d.planned_qty): if not flt(d.planned_qty):
msgprint("Please Enter Planned Qty for item: %s at row no: %s" % webnotes.throw(_("Please Enter Planned Qty for item: %s at row no: %s") %
(d.item_code, d.idx), raise_exception=1) (d.item_code, d.idx))
def validate_bom_no(self, d): def validate_bom_no(self, d):
if not d.bom_no: if not d.bom_no:
msgprint("Please enter bom no for item: %s at row no: %s" % webnotes.throw(_("Please enter bom no for item: %s at row no: %s") %
(d.item_code, d.idx), raise_exception=1) (d.item_code, d.idx))
else: else:
bom = webnotes.conn.sql("""select name from `tabBOM` where name = %s and item = %s bom = webnotes.conn.sql("""select name from `tabBOM` where name = %s and item = %s
and docstatus = 1 and is_active = 1""", and docstatus = 1 and is_active = 1""",
(d.bom_no, d.item_code), as_dict = 1) (d.bom_no, d.item_code), as_dict = 1)
if not bom: if not bom:
msgprint("""Incorrect BOM No: %s entered for item: %s at row no: %s webnotes.throw(_("""Incorrect BOM No: %s entered for item: %s at row no: %s
May be BOM is inactive or for other item or does not exists in the system""" % May be BOM is inactive or for other item or does not exists in the system""") %
(d.bom_no, d.item_doce, d.idx), raise_exception=1) (d.bom_no, d.item_doce, d.idx))
def raise_production_order(self): def raise_production_order(self):
"""It will raise production order (Draft) for all distinct FG items""" """It will raise production order (Draft) for all distinct FG items"""
@ -183,16 +182,20 @@ class DocType:
if pro: if pro:
pro = ["""<a href="#Form/Production Order/%s" target="_blank">%s</a>""" % \ pro = ["""<a href="#Form/Production Order/%s" target="_blank">%s</a>""" % \
(p, p) for p in pro] (p, p) for p in pro]
msgprint("Production Order(s) created:\n\n" + '\n'.join(pro)) msgprint(_("Production Order(s) created:\n\n") + '\n'.join(pro))
else : else :
msgprint("No Production Order created.") msgprint(_("No Production Order created."))
def get_distinct_items_and_boms(self): def get_distinct_items_and_boms(self):
""" Club similar BOM and item for processing""" """ Club similar BOM and item for processing
bom_dict {
bom_no: ['sales_order', 'qty']
}
"""
item_dict, bom_dict = {}, {} item_dict, bom_dict = {}, {}
for d in self.doclist.get({"parentfield": "pp_details"}): for d in self.doclist.get({"parentfield": "pp_details"}):
bom_dict[d.bom_no] = bom_dict.get(d.bom_no, 0) + flt(d.planned_qty) bom_dict.setdefault(d.bom_no, []).append([d.sales_order, flt(d.planned_qty)])
item_dict[(d.item_code, d.sales_order, d.warehouse)] = { item_dict[(d.item_code, d.sales_order, d.warehouse)] = {
"production_item" : d.item_code, "production_item" : d.item_code,
"sales_order" : d.sales_order, "sales_order" : d.sales_order,
@ -241,42 +244,53 @@ class DocType:
"item_code": [qty_required, description, stock_uom, min_order_qty] "item_code": [qty_required, description, stock_uom, min_order_qty]
} }
""" """
for bom in bom_dict: bom_wise_item_details = {}
item_list = []
for bom, so_wise_qty in bom_dict.items():
if self.doc.use_multi_level_bom: if self.doc.use_multi_level_bom:
# get all raw materials with sub assembly childs # get all raw materials with sub assembly childs
fl_bom_items = webnotes.conn.sql("""select fb.item_code, for d in webnotes.conn.sql("""select fb.item_code,
ifnull(sum(fb.qty_consumed_per_unit), 0)*%s as qty, ifnull(sum(fb.qty_consumed_per_unit), 0) as qty,
fb.description, fb.stock_uom, it.min_order_qty fb.description, fb.stock_uom, it.min_order_qty
from `tabBOM Explosion Item` fb,`tabItem` it from `tabBOM Explosion Item` fb,`tabItem` it
where it.name = fb.item_code and ifnull(it.is_pro_applicable, 'No') = 'No' where it.name = fb.item_code and ifnull(it.is_pro_applicable, 'No') = 'No'
and ifnull(it.is_sub_contracted_item, 'No') = 'No' and ifnull(it.is_sub_contracted_item, 'No') = 'No'
and fb.docstatus<2 and fb.parent=%s and fb.docstatus<2 and fb.parent=%s
group by item_code, stock_uom""", (flt(bom_dict[bom]), bom)) group by item_code, stock_uom""", bom, as_dict=1):
bom_wise_item_details.setdefault(d.item_code, d)
else: else:
# Get all raw materials considering SA items as raw materials, # Get all raw materials considering SA items as raw materials,
# so no childs of SA items # so no childs of SA items
fl_bom_items = webnotes.conn.sql("""select bom_item.item_code, for d in webnotes.conn.sql("""select bom_item.item_code,
ifnull(sum(bom_item.qty_consumed_per_unit), 0) * %s, ifnull(sum(bom_item.qty_consumed_per_unit), 0) as qty,
bom_item.description, bom_item.stock_uom, item.min_order_qty bom_item.description, bom_item.stock_uom, item.min_order_qty
from `tabBOM Item` bom_item, tabItem item from `tabBOM Item` bom_item, tabItem item
where bom_item.parent = %s and bom_item.docstatus < 2 where bom_item.parent = %s and bom_item.docstatus < 2
and bom_item.item_code = item.name and bom_item.item_code = item.name
group by item_code""", (flt(bom_dict[bom]), bom)) group by item_code""", bom, as_dict=1):
self.make_items_dict(fl_bom_items) bom_wise_item_details.setdefault(d.item_code, d)
for item, item_details in bom_wise_item_details.items():
for so_qty in so_wise_qty:
item_list.append([item, flt(item_details.qty) * so_qty[1], item_details.description,
item_details.stock_uom, item_details.min_order_qty, so_qty[0]])
self.make_items_dict(item_list)
def make_items_dict(self, item_list): def make_items_dict(self, item_list):
for i in item_list: for i in item_list:
self.item_dict[i[0]] = [(flt(self.item_dict.get(i[0], [0])[0]) + flt(i[1])), self.item_dict.setdefault(i[0], []).append([flt(i[1]), i[2], i[3], i[4], i[5]])
i[2], i[3], i[4]]
def get_csv(self): def get_csv(self):
item_list = [['Item Code', 'Description', 'Stock UOM', 'Required Qty', 'Warehouse', item_list = [['Item Code', 'Description', 'Stock UOM', 'Required Qty', 'Warehouse',
'Quantity Requested for Purchase', 'Ordered Qty', 'Actual Qty']] 'Quantity Requested for Purchase', 'Ordered Qty', 'Actual Qty']]
for d in self.item_dict: for item in self.item_dict:
item_list.append([d, self.item_dict[d][1], self.item_dict[d][2], self.item_dict[d][0]]) total_qty = sum([flt(d[0]) for d in self.item_dict[item]])
item_qty= webnotes.conn.sql("""select warehouse, indented_qty, ordered_qty, actual_qty for item_details in self.item_dict[item]:
from `tabBin` where item_code = %s""", d) item_list.append([item, item_details[1], item_details[2], item_details[0]])
item_qty = webnotes.conn.sql("""select warehouse, indented_qty, ordered_qty, actual_qty
from `tabBin` where item_code = %s""", item)
i_qty, o_qty, a_qty = 0, 0, 0 i_qty, o_qty, a_qty = 0, 0, 0
for w in item_qty: for w in item_qty:
i_qty, o_qty, a_qty = i_qty + flt(w[1]), o_qty + flt(w[2]), a_qty + flt(w[3]) i_qty, o_qty, a_qty = i_qty + flt(w[1]), o_qty + flt(w[2]), a_qty + flt(w[3])
@ -293,31 +307,49 @@ class DocType:
""" """
self.validate_data() self.validate_data()
if not self.doc.purchase_request_for_warehouse: if not self.doc.purchase_request_for_warehouse:
webnotes.msgprint("Please enter Warehouse for which Material Request will be raised", webnotes.throw(_("Please enter Warehouse for which Material Request will be raised"))
raise_exception=1)
bom_dict = self.get_distinct_items_and_boms()[0] bom_dict = self.get_distinct_items_and_boms()[0]
self.get_raw_materials(bom_dict) self.get_raw_materials(bom_dict)
if not self.item_dict: if self.item_dict:
return self.insert_purchase_request()
def get_requested_items(self):
item_projected_qty = self.get_projected_qty() item_projected_qty = self.get_projected_qty()
from accounts.utils import get_fiscal_year
fiscal_year = get_fiscal_year(nowdate())[0]
items_to_be_requested = webnotes._dict() items_to_be_requested = webnotes._dict()
for item in self.item_dict:
if flt(self.item_dict[item][0]) > item_projected_qty.get(item, 0):
# shortage
requested_qty = flt(self.item_dict[item][0]) - item_projected_qty.get(item, 0)
# comsider minimum order qty
requested_qty = requested_qty > flt(self.item_dict[item][3]) and \
requested_qty or flt(self.item_dict[item][3])
items_to_be_requested[item] = requested_qty
self.insert_purchase_request(items_to_be_requested, fiscal_year) for item, so_item_qty in self.item_dict.items():
requested_qty = 0
total_qty = sum([flt(d[0]) for d in so_item_qty])
if total_qty > item_projected_qty.get(item, 0):
# shortage
requested_qty = total_qty - item_projected_qty.get(item, 0)
# consider minimum order qty
requested_qty = requested_qty > flt(so_item_qty[0][3]) and \
requested_qty or flt(so_item_qty[0][3])
# distribute requested qty SO wise
for item_details in so_item_qty:
if requested_qty:
sales_order = item_details[4] or "No Sales Order"
if requested_qty <= item_details[0]:
adjusted_qty = requested_qty
else:
adjusted_qty = item_details[0]
items_to_be_requested.setdefault(item, {}).setdefault(sales_order, 0)
items_to_be_requested[item][sales_order] += adjusted_qty
requested_qty -= adjusted_qty
else:
break
# requested qty >= total so qty, due to minimum order qty
if requested_qty:
items_to_be_requested.setdefault(item, {}).setdefault("No Sales Order", 0)
items_to_be_requested[item]["No Sales Order"] += requested_qty
return items_to_be_requested
def get_projected_qty(self): def get_projected_qty(self):
items = self.item_dict.keys() items = self.item_dict.keys()
@ -327,13 +359,17 @@ class DocType:
return dict(item_projected_qty) return dict(item_projected_qty)
def insert_purchase_request(self, items_to_be_requested, fiscal_year): def insert_purchase_request(self):
items_to_be_requested = self.get_requested_items()
from accounts.utils import get_fiscal_year
fiscal_year = get_fiscal_year(nowdate())[0]
purchase_request_list = [] purchase_request_list = []
if items_to_be_requested: if items_to_be_requested:
for item in items_to_be_requested: for item in items_to_be_requested:
item_wrapper = webnotes.bean("Item", item) item_wrapper = webnotes.bean("Item", item)
pr_doclist = [ pr_doclist = [{
{
"doctype": "Material Request", "doctype": "Material Request",
"__islocal": 1, "__islocal": 1,
"naming_series": "IDT", "naming_series": "IDT",
@ -343,8 +379,9 @@ class DocType:
"fiscal_year": fiscal_year, "fiscal_year": fiscal_year,
"requested_by": webnotes.session.user, "requested_by": webnotes.session.user,
"material_request_type": "Purchase" "material_request_type": "Purchase"
}, }]
{ for sales_order, requested_qty in items_to_be_requested[item].items():
pr_doclist.append({
"doctype": "Material Request Item", "doctype": "Material Request Item",
"__islocal": 1, "__islocal": 1,
"parentfield": "indent_details", "parentfield": "indent_details",
@ -354,11 +391,12 @@ class DocType:
"uom": item_wrapper.doc.stock_uom, "uom": item_wrapper.doc.stock_uom,
"item_group": item_wrapper.doc.item_group, "item_group": item_wrapper.doc.item_group,
"brand": item_wrapper.doc.brand, "brand": item_wrapper.doc.brand,
"qty": items_to_be_requested[item], "qty": requested_qty,
"schedule_date": add_days(nowdate(), cint(item_wrapper.doc.lead_time_days)), "schedule_date": add_days(nowdate(), cint(item_wrapper.doc.lead_time_days)),
"warehouse": self.doc.purchase_request_for_warehouse "warehouse": self.doc.purchase_request_for_warehouse,
} "sales_order_no": sales_order if sales_order!="No Sales Order" else None
] })
pr_wrapper = webnotes.bean(pr_doclist) pr_wrapper = webnotes.bean(pr_doclist)
pr_wrapper.ignore_permissions = 1 pr_wrapper.ignore_permissions = 1
pr_wrapper.submit() pr_wrapper.submit()
@ -367,7 +405,7 @@ class DocType:
if purchase_request_list: if purchase_request_list:
pur_req = ["""<a href="#Form/Material Request/%s" target="_blank">%s</a>""" % \ pur_req = ["""<a href="#Form/Material Request/%s" target="_blank">%s</a>""" % \
(p, p) for p in purchase_request_list] (p, p) for p in purchase_request_list]
webnotes.msgprint("Material Request(s) created: \n%s" % msgprint(_("Material Request(s) created: \n%s") %
"\n".join(pur_req)) "\n".join(pur_req))
else: else:
webnotes.msgprint("Nothing to request") msgprint(_("Nothing to request"))

View File

@ -37,20 +37,25 @@ class DocType(BuyingController):
for so_no in so_items.keys(): for so_no in so_items.keys():
for item in so_items[so_no].keys(): for item in so_items[so_no].keys():
already_indented = webnotes.conn.sql("select sum(qty) from `tabMaterial Request Item` where item_code = '%s' and sales_order_no = '%s' and docstatus = 1 and parent != '%s'" % (item, so_no, self.doc.name)) already_indented = webnotes.conn.sql("""select sum(qty) from `tabMaterial Request Item`
where item_code = '%s' and sales_order_no = '%s' and
docstatus = 1 and parent != '%s'""" % (item, so_no, self.doc.name))
already_indented = already_indented and flt(already_indented[0][0]) or 0 already_indented = already_indented and flt(already_indented[0][0]) or 0
actual_so_qty = webnotes.conn.sql("select sum(qty) from `tabSales Order Item` where parent = '%s' and item_code = '%s' and docstatus = 1 group by parent" % (so_no, item)) actual_so_qty = webnotes.conn.sql("""select sum(qty) from `tabSales Order Item`
where parent = '%s' and item_code = '%s' and docstatus = 1
group by parent""" % (so_no, item))
actual_so_qty = actual_so_qty and flt(actual_so_qty[0][0]) or 0 actual_so_qty = actual_so_qty and flt(actual_so_qty[0][0]) or 0
if flt(so_items[so_no][item]) + already_indented > actual_so_qty: if actual_so_qty and (flt(so_items[so_no][item]) + already_indented > actual_so_qty):
msgprint("You can raise indent of maximum qty: %s for item: %s against sales order: %s\n Anyway, you can add more qty in new row for the same item." % (actual_so_qty - already_indented, item, so_no), raise_exception=1) webnotes.throw(_("You can raise indent of maximum qty: %s for item: %s against sales order: %s\
\n Anyway, you can add more qty in new row for the same item.")
% (actual_so_qty - already_indented, item, so_no))
def validate_schedule_date(self): def validate_schedule_date(self):
for d in getlist(self.doclist, 'indent_details'): for d in getlist(self.doclist, 'indent_details'):
if d.schedule_date < self.doc.transaction_date: if d.schedule_date < self.doc.transaction_date:
msgprint("Expected Date cannot be before Material Request Date") webnotes.throw(_("Expected Date cannot be before Material Request Date"))
raise Exception
# Validate # Validate
# --------------------- # ---------------------
@ -80,8 +85,8 @@ class DocType(BuyingController):
for d in getlist(self.doclist, 'indent_details'): for d in getlist(self.doclist, 'indent_details'):
if webnotes.conn.get_value("Item", d.item_code, "is_stock_item") == "Yes": if webnotes.conn.get_value("Item", d.item_code, "is_stock_item") == "Yes":
if not d.warehouse: if not d.warehouse:
msgprint("Please Enter Warehouse for Item %s as it is stock item" webnotes.throw("Please Enter Warehouse for Item %s as it is stock item"
% cstr(d.item_code), raise_exception=1) % cstr(d.item_code))
qty =flt(d.qty) qty =flt(d.qty)
if is_stopped: if is_stopped:
@ -96,16 +101,15 @@ class DocType(BuyingController):
update_bin(args) update_bin(args)
def on_submit(self): def on_submit(self):
webnotes.conn.set(self.doc,'status','Submitted') webnotes.conn.set(self.doc, 'status', 'Submitted')
self.update_bin(is_submit = 1, is_stopped = 0) self.update_bin(is_submit = 1, is_stopped = 0)
def check_modified_date(self): def check_modified_date(self):
mod_db = webnotes.conn.sql("select modified from `tabMaterial Request` where name = '%s'" % self.doc.name) mod_db = webnotes.conn.sql("select modified from `tabMaterial Request` where name = '%s'" % self.doc.name)
date_diff = webnotes.conn.sql("select TIMEDIFF('%s', '%s')" % ( mod_db[0][0],cstr(self.doc.modified))) date_diff = webnotes.conn.sql("select TIMEDIFF('%s', '%s')" % (mod_db[0][0], cstr(self.doc.modified)))
if date_diff and date_diff[0][0]: if date_diff and date_diff[0][0]:
msgprint(cstr(self.doc.doctype) +" => "+ cstr(self.doc.name) +" has been modified. Please Refresh. ") webnotes.throw(cstr(self.doc.doctype) + " => " + cstr(self.doc.name) + " has been modified. Please Refresh.")
raise Exception
def update_status(self, status): def update_status(self, status):
self.check_modified_date() self.check_modified_date()
@ -113,10 +117,10 @@ class DocType(BuyingController):
self.update_bin(is_submit = (status == 'Submitted') and 1 or 0, is_stopped = 1) self.update_bin(is_submit = (status == 'Submitted') and 1 or 0, is_stopped = 1)
# Step 2:=> Set status # Step 2:=> Set status
webnotes.conn.set(self.doc,'status',cstr(status)) webnotes.conn.set(self.doc, 'status', cstr(status))
# Step 3:=> Acknowledge User # Step 3:=> Acknowledge User
msgprint(self.doc.doctype + ": " + self.doc.name + " has been %s." % ((status == 'Submitted') and 'Unstopped' or cstr(status)) ) msgprint(self.doc.doctype + ": " + self.doc.name + _(" has been %s.") % ((status == 'Submitted') and 'Unstopped' or cstr(status)) )
def on_cancel(self): def on_cancel(self):
@ -177,9 +181,9 @@ def update_completed_qty(controller, caller_method):
mr_doctype = webnotes.get_doctype("Material Request") mr_doctype = webnotes.get_doctype("Material Request")
if mr_obj.doc.status in ["Stopped", "Cancelled"]: if mr_obj.doc.status in ["Stopped", "Cancelled"]:
msgprint(_("Material Request") + ": %s, " % mr_obj.doc.name webnotes.throw(_("Material Request") + ": %s, " % mr_obj.doc.name
+ _(mr_doctype.get_label("status")) + " = %s. " % _(mr_obj.doc.status) + _(mr_doctype.get_label("status")) + " = %s. " % _(mr_obj.doc.status)
+ _("Cannot continue."), raise_exception=webnotes.InvalidStatusError) + _("Cannot continue."), exc=webnotes.InvalidStatusError)
_update_requested_qty(controller, mr_obj, mr_items) _update_requested_qty(controller, mr_obj, mr_items)