From 49f9747385b71a66402474b7edeccc896e84a0de Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Thu, 30 Aug 2018 18:50:48 +0530 Subject: [PATCH] major(manufacturing): fixes to ux, material requests to purchase order based on default supplier etc (#15267) * major(manufacturing): fixes to ux, material requests to purchase order based on default supplier etc * fix: remove debug --- .../doctype/sales_invoice/sales_invoice.js | 6 +- .../doctype/purchase_order/purchase_order.js | 17 +- erpnext/manufacturing/doctype/bom/bom.py | 3 +- .../production_plan/production_plan.py | 87 ++-- erpnext/public/js/utils.js | 3 +- .../doctype/sales_order/sales_order.js | 2 +- .../doctype/company/company_dashboard.py | 3 - erpnext/stock/doctype/item/item.json | 4 +- .../material_request/material_request.js | 393 +++++++++--------- .../material_request/material_request.json | 4 +- .../material_request/material_request.py | 37 +- .../stock/doctype/stock_entry/stock_entry.js | 4 +- erpnext/stock/get_item_details.py | 8 +- 13 files changed, 298 insertions(+), 273 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js index 194c3641d7c..5fefd6e50ed 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js @@ -497,15 +497,15 @@ cur_frm.fields_dict["items"].grid.get_field("cost_center").get_query = function( } cur_frm.cscript.income_account = function(doc, cdt, cdn) { - erpnext.utils.copy_value_in_all_row(doc, cdt, cdn, "items", "income_account"); + erpnext.utils.copy_value_in_all_rows(doc, cdt, cdn, "items", "income_account"); } cur_frm.cscript.expense_account = function(doc, cdt, cdn) { - erpnext.utils.copy_value_in_all_row(doc, cdt, cdn, "items", "expense_account"); + erpnext.utils.copy_value_in_all_rows(doc, cdt, cdn, "items", "expense_account"); } cur_frm.cscript.cost_center = function(doc, cdt, cdn) { - erpnext.utils.copy_value_in_all_row(doc, cdt, cdn, "items", "cost_center"); + erpnext.utils.copy_value_in_all_rows(doc, cdt, cdn, "items", "cost_center"); } cur_frm.set_query("debit_to", function(doc) { diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.js b/erpnext/buying/doctype/purchase_order/purchase_order.js index d7ffe040214..74af4ceeb88 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.js +++ b/erpnext/buying/doctype/purchase_order/purchase_order.js @@ -64,7 +64,7 @@ frappe.ui.form.on("Purchase Order Item", { var row = locals[cdt][cdn]; if (row.schedule_date) { if(!frm.doc.schedule_date) { - erpnext.utils.copy_value_in_all_row(frm.doc, cdt, cdn, "items", "schedule_date"); + erpnext.utils.copy_value_in_all_rows(frm.doc, cdt, cdn, "items", "schedule_date"); } else { set_schedule_date(frm); } @@ -309,6 +309,7 @@ erpnext.buying.PurchaseOrderController = erpnext.buying.BuyingController.extend( method: "erpnext.stock.doctype.material_request.material_request.make_purchase_order", source_doctype: "Material Request", target: me.frm, + args: args, setters: { company: me.frm.doc.company }, @@ -319,7 +320,7 @@ erpnext.buying.PurchaseOrderController = erpnext.buying.BuyingController.extend( per_ordered: ["<", 99.99], } }) - }, __("Add items from")); + }, __("Get items from")); this.frm.add_custom_button(__('Supplier Quotation'), function() { @@ -335,7 +336,7 @@ erpnext.buying.PurchaseOrderController = erpnext.buying.BuyingController.extend( status: ["!=", "Stopped"], } }) - }, __("Add items from")); + }, __("Get items from")); this.frm.add_custom_button(__('Update rate as per last purchase'), function() { @@ -364,7 +365,7 @@ erpnext.buying.PurchaseOrderController = erpnext.buying.BuyingController.extend( }, callback: function(r) { if(r.exc) return; - + var i = 0; var item_length = me.frm.doc.items.length; while (i < item_length) { @@ -379,17 +380,17 @@ erpnext.buying.PurchaseOrderController = erpnext.buying.BuyingController.extend( d.qty = d.qty - my_qty; me.frm.doc.items[i].stock_qty = my_qty * me.frm.doc.items[i].conversion_factor; me.frm.doc.items[i].qty = my_qty; - + frappe.msgprint("Assigning " + d.mr_name + " to " + d.item_code + " (row " + me.frm.doc.items[i].idx + ")"); if (qty > 0) { frappe.msgprint("Splitting " + qty + " units of " + d.item_code); var new_row = frappe.model.add_child(me.frm.doc, me.frm.doc.items[i].doctype, "items"); item_length++; - + for (var key in me.frm.doc.items[i]) { new_row[key] = me.frm.doc.items[i][key]; } - + new_row.idx = item_length; new_row["stock_qty"] = new_row.conversion_factor * qty; new_row["qty"] = qty; @@ -477,7 +478,7 @@ cur_frm.fields_dict['items'].grid.get_field('bom').get_query = function(doc, cdt function set_schedule_date(frm) { if(frm.doc.schedule_date){ - erpnext.utils.copy_value_in_all_row(frm.doc, frm.doc.doctype, frm.doc.name, "items", "schedule_date"); + erpnext.utils.copy_value_in_all_rows(frm.doc, frm.doc.doctype, frm.doc.name, "items", "schedule_date"); } } diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py index e2e920954ba..77df650569a 100644 --- a/erpnext/manufacturing/doctype/bom/bom.py +++ b/erpnext/manufacturing/doctype/bom/bom.py @@ -173,7 +173,7 @@ class BOM(WebsiteGenerator): if not rate: frappe.msgprint(_("{0} not found for Item {1}") - .format(self.rm_cost_as_per, arg["item_code"])) + .format(self.rm_cost_as_per, arg["item_code"]), alert=True) return flt(rate) @@ -561,7 +561,6 @@ def get_bom_items_as_dict(bom, company, qty=1, fetch_exploded=1, fetch_scrap_ite where bom_item.docstatus < 2 and bom.name = %(bom)s - and is_stock_item = 1 {where_conditions} group by item_code, stock_uom order by idx""" diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py index 46fdb8501af..12f2f04e38a 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py @@ -280,7 +280,7 @@ class ProductionPlan(Document): item_dict[(d.item_code, d.sales_order, d.warehouse)] = item_details return item_dict - + def get_items_for_material_requests(self): self.mr_items = [] @@ -295,15 +295,15 @@ class ProductionPlan(Document): bei.description, bei.stock_uom, item.min_order_qty, bei.source_warehouse, item.default_material_request_type, item.min_order_qty, item_default.default_warehouse from - `tabBOM Explosion Item` bei + `tabBOM Explosion Item` bei JOIN `tabBOM` bom ON bom.name = bei.parent JOIN `tabItem` item ON item.name = bei.item_code LEFT JOIN `tabItem Default` item_default ON item_default.parent = item.name and item_default.company=%s where - bei.docstatus < 2 + bei.docstatus < 2 and bom.name=%s and item.is_stock_item in (1, {0}) - group by bei.item_code, bei.stock_uom""".format(self.include_non_stock_items), + group by bei.item_code, bei.stock_uom""".format(0 if self.include_non_stock_items else 1), (self.company, data.bom_no), as_dict=1): bom_wise_item_details.setdefault(d.item_code, d) else: @@ -332,7 +332,7 @@ class ProductionPlan(Document): bom.name = %(bom)s and bom_item.docstatus < 2 and item.is_stock_item in (1, {0}) - group by bom_item.item_code""".format(self.include_non_stock_items),{ + group by bom_item.item_code""".format(0 if self.include_non_stock_items else 1),{ 'bom': bom_no, 'parent_qty': parent_qty, 'company': self.company @@ -412,61 +412,60 @@ class ProductionPlan(Document): pass def make_material_request(self): + '''Create Material Requests grouped by Sales Order and Material Request Type''' material_request_list = [] + material_request_map = {} - item_details = self.get_itemwise_qty() - for item_code, rows in item_details.items(): - item_doc = frappe.get_doc("Item", item_code) + for item in self.mr_items: + item_doc = frappe.get_cached_doc('Item', item.item_code) + + # key for Sales Order:Material Request Type + key = '{}:{}'.format(item.sales_order, item_doc.default_material_request_type) schedule_date = add_days(nowdate(), cint(item_doc.lead_time_days)) - material_request = frappe.new_doc("Material Request") - material_request.update({ - "transaction_date": nowdate(), - "status": "Draft", - "company": self.company, - "requested_by": frappe.session.user, + if not key in material_request_map: + # make a new MR for the combination + material_request_map[key] = frappe.new_doc("Material Request") + material_request = material_request_map[key] + material_request.update({ + "transaction_date": nowdate(), + "status": "Draft", + "company": self.company, + "requested_by": frappe.session.user, + 'material_request_type': item_doc.default_material_request_type + }) + material_request_list.append(material_request) + else: + material_request = material_request_map[key] + + # add item + material_request.append("items", { + "item_code": item.item_code, + "qty": item.quantity, "schedule_date": schedule_date, - 'material_request_type': item_doc.default_material_request_type + "warehouse": item.warehouse, + "sales_order": item.sales_order, + 'production_plan': self.name, + 'material_request_plan_item': item.name, + "project": frappe.db.get_value("Sales Order", item.sales_order, "project") \ + if item.sales_order else None }) - for idx in rows: - child = self.mr_items[cint(idx)-1] - material_request.append("items", { - "item_code": item_code, - "qty": child.quantity, - "schedule_date": schedule_date, - "warehouse": child.warehouse, - "sales_order": child.sales_order, - 'production_plan': self.name, - 'material_request_plan_item': child.name, - "project": frappe.db.get_value("Sales Order", child.sales_order, "project") \ - if child.sales_order else None - }) - + for material_request in material_request_list: + # submit material_request.flags.ignore_permissions = 1 material_request.run_method("set_missing_values") material_request.submit() - material_request_list.append(material_request.name) - + frappe.flags.mute_messages = False if material_request_list: - material_request_list = ["""%s""" % \ - (p, p) for p in material_request_list] + material_request_list = ["""{1}""".format(m.name, m.name) \ + for m in material_request_list] msgprint(_("{0} created").format(comma_and(material_request_list))) else : msgprint(_("No material request created")) - def get_itemwise_qty(self): - item_details = {} - for data in self.get('mr_items'): - if data.item_code in item_details: - item_details[data.item_code].append(data.idx) - else: - item_details.setdefault(data.item_code, [data.idx]) - - return item_details - def get_sales_orders(self): so_filter = item_filter = "" if self.from_date: @@ -516,7 +515,7 @@ def get_bin_details(row): conditions = " and warehouse='{0}'".format(frappe.db.escape(warehouse)) item_projected_qty = frappe.db.sql(""" select ifnull(sum(projected_qty),0) as projected_qty, - ifnull(sum(actual_qty),0) as actual_qty from `tabBin` + ifnull(sum(actual_qty),0) as actual_qty from `tabBin` where item_code = %(item_code)s {conditions} """.format(conditions=conditions), { "item_code": row.item_code }, as_list=1) diff --git a/erpnext/public/js/utils.js b/erpnext/public/js/utils.js index e4c1d5314c2..682577bab04 100644 --- a/erpnext/public/js/utils.js +++ b/erpnext/public/js/utils.js @@ -118,7 +118,7 @@ $.extend(erpnext.utils, { return dict[party_type]; }, - copy_value_in_all_row: function(doc, dt, dn, table_fieldname, fieldname) { + copy_value_in_all_rows: function(doc, dt, dn, table_fieldname, fieldname) { var d = locals[dt][dn]; if(d[fieldname]){ var cl = doc[table_fieldname] || []; @@ -487,6 +487,7 @@ erpnext.utils.map_current_doc = function(opts) { "method": opts.method, "source_names": opts.source_name, "target_doc": cur_frm.doc, + 'args': opts.args }, callback: function(r) { if(!r.exc) { diff --git a/erpnext/selling/doctype/sales_order/sales_order.js b/erpnext/selling/doctype/sales_order/sales_order.js index 9de9c6b0e51..e8698f1ebaa 100644 --- a/erpnext/selling/doctype/sales_order/sales_order.js +++ b/erpnext/selling/doctype/sales_order/sales_order.js @@ -82,7 +82,7 @@ frappe.ui.form.on("Sales Order Item", { }, delivery_date: function(frm, cdt, cdn) { if(!frm.doc.delivery_date) { - erpnext.utils.copy_value_in_all_row(frm.doc, cdt, cdn, "items", "delivery_date"); + erpnext.utils.copy_value_in_all_rows(frm.doc, cdt, cdn, "items", "delivery_date"); } } }); diff --git a/erpnext/setup/doctype/company/company_dashboard.py b/erpnext/setup/doctype/company/company_dashboard.py index 7526dd6c547..868284a75bf 100644 --- a/erpnext/setup/doctype/company/company_dashboard.py +++ b/erpnext/setup/doctype/company/company_dashboard.py @@ -2,9 +2,6 @@ from frappe import _ def get_data(): return { - 'heatmap': True, - 'heatmap_message': _('This is based on transactions against this Company. See timeline below for details'), - 'graph': True, 'graph_method': "frappe.utils.goal.get_monthly_goal_graph_data", 'graph_method_args': { diff --git a/erpnext/stock/doctype/item/item.json b/erpnext/stock/doctype/item/item.json index 9161f2d149b..2851537ed99 100644 --- a/erpnext/stock/doctype/item/item.json +++ b/erpnext/stock/doctype/item/item.json @@ -1841,7 +1841,7 @@ "in_global_search": 0, "in_list_view": 0, "in_standard_filter": 0, - "label": "Defaults", + "label": "Sales, Purchase, Accounting Defaults", "length": 0, "no_copy": 0, "permlevel": 0, @@ -3951,7 +3951,7 @@ "issingle": 0, "istable": 0, "max_attachments": 1, - "modified": "2018-08-29 06:27:10.198002", + "modified": "2018-08-30 05:28:12.312880", "modified_by": "Administrator", "module": "Stock", "name": "Item", diff --git a/erpnext/stock/doctype/material_request/material_request.js b/erpnext/stock/doctype/material_request/material_request.js index 9a80a9aa2fe..5fd7a6a2c82 100644 --- a/erpnext/stock/doctype/material_request/material_request.js +++ b/erpnext/stock/doctype/material_request/material_request.js @@ -1,6 +1,7 @@ // Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors // License: GNU General Public License v3. See license.txt +// eslint-disable-next-line {% include 'erpnext/public/js/controllers/buying.js' %}; frappe.ui.form.on('Material Request', { @@ -11,24 +12,122 @@ frappe.ui.form.on('Material Request', { 'Request for Quotation': 'Request for Quotation', 'Supplier Quotation': 'Supplier Quotation', 'Work Order': 'Work Order' - } + }; // formatter for material request item frm.set_indicator_formatter('item_code', - function(doc) { return (doc.qty<=doc.ordered_qty) ? "green" : "orange" }) + function(doc) { return (doc.qty<=doc.ordered_qty) ? "green" : "orange"; }); + + frm.set_query("item_code", "items", function() { + return { + query: "erpnext.controllers.queries.item_query" + }; + }); }, + onload: function(frm) { // add item, if previous view was item erpnext.utils.add_item(frm); - //set schedule_date + // set schedule_date set_schedule_date(frm); - frm.fields_dict["items"].grid.get_field("warehouse").get_query = function(doc, cdt, cdn){ - return{ + frm.fields_dict["items"].grid.get_field("warehouse").get_query = function(doc) { + return { filters: {'company': doc.company} + }; + }; + }, + + refresh: function(frm) { + frm.events.make_custom_buttons(frm); + }, + + make_custom_buttons: function(frm) { + if (frm.doc.docstatus==0) { + frm.add_custom_button(__("Bill of Materials"), + () => frm.events.get_items_from_bom(frm), __("Get items from")); + } + + if (frm.doc.docstatus == 1 && frm.doc.status != 'Stopped') { + if (flt(frm.doc.per_ordered, 2) < 100) { + // make + if (frm.doc.material_request_type === "Material Transfer") { + frm.add_custom_button(__("Transfer Material"), + () => frm.events.make_stock_entry(frm), __("Make")); + } + + if (frm.doc.material_request_type === "Material Issue") { + frm.add_custom_button(__("Issue Material"), + () => frm.events.make_stock_entry(frm), __("Make")); + } + + if (frm.doc.material_request_type === "Purchase") { + frm.add_custom_button(__('Purchase Order'), + () => frm.events.make_purchase_order(frm), __("Make")); + } + + if (frm.doc.material_request_type === "Purchase") { + frm.add_custom_button(__("Request for Quotation"), + () => frm.events.make_request_for_quotation(frm), __("Make")); + } + + if (frm.doc.material_request_type === "Purchase") { + frm.add_custom_button(__("Supplier Quotation"), + () => frm.events.make_supplier_quotation(frm), __("Make")); + } + + if (frm.doc.material_request_type === "Manufacture") { + frm.add_custom_button(__("Work Order"), + () => frm.events.raise_work_orders(frm), __("Make")); + } + + frm.page.set_inner_btn_group_as_primary(__("Make")); + + // stop + frm.add_custom_button(__('Stop'), + () => frm.events.update_status(frm, 'Stop')); + } } + + if (frm.doc.docstatus===0) { + frm.add_custom_button(__('Sales Order'), () => frm.events.get_items_from_sales_order(frm), + __("Get items from")); + } + + if (frm.doc.docstatus == 1 && frm.doc.status == 'Stopped') { + frm.add_custom_button(__('Re-open'), () => frm.events.update_status(frm, 'Submitted')); + } }, + + update_status: function(frm, stop_status) { + frappe.call({ + method: 'erpnext.stock.material_request.material_request.update_status', + args: { name: frm.doc.name, status: stop_status }, + callback(r) { + if (!r.exc) { + frm.reload_doc(); + } + } + }); + }, + + get_items_from_sales_order: function(frm) { + erpnext.utils.map_current_doc({ + method: "erpnext.selling.doctype.sales_order.sales_order.make_material_request", + source_doctype: "Sales Order", + target: frm, + setters: { + company: frm.doc.company + }, + get_query_filters: { + docstatus: 1, + status: ["!=", "Closed"], + per_delivered: ["<", 99.99], + } + }); + }, + get_item_data: function(frm, item) { frm.call({ method: "erpnext.stock.get_item_details.get_item_details", @@ -45,7 +144,6 @@ frappe.ui.form.on('Material Request', { stock_qty: item.stock_qty, company: frm.doc.company, conversion_rate: 1, - name: frm.doc.name, material_request_type: frm.doc.material_request_type, plc_conversion_rate: 1, rate: item.rate, @@ -62,133 +160,19 @@ frappe.ui.form.on('Material Request', { } }); }, -}); -frappe.ui.form.on("Material Request Item", { - qty: function (frm, doctype, name) { - var d = locals[doctype][name]; - if (flt(d.qty) < flt(d.min_order_qty)) { - frappe.msgprint(__("Warning: Material Requested Qty is less than Minimum Order Qty")); - } - - const item = locals[doctype][name]; - frm.events.get_item_data(frm, item); - }, - - rate: function(frm, doctype, name) { - const item = locals[doctype][name]; - frm.events.get_item_data(frm, item); - }, - - item_code: function(frm, doctype, name) { - const item = locals[doctype][name]; - item.rate = 0 - set_schedule_date(frm); - frm.events.get_item_data(frm, item); - }, - - schedule_date: function(frm, cdt, cdn) { - var row = locals[cdt][cdn]; - if (row.schedule_date) { - if(!frm.doc.schedule_date) { - erpnext.utils.copy_value_in_all_row(frm.doc, cdt, cdn, "items", "schedule_date"); - } else { - set_schedule_date(frm); - } - } - } -}); - -erpnext.buying.MaterialRequestController = erpnext.buying.BuyingController.extend({ - onload: function(doc) { - this._super(); - this.frm.set_query("item_code", "items", function() { - return { - query: "erpnext.controllers.queries.item_query" - } - }); - }, - - refresh: function(doc) { - var me = this; - this._super(); - - if(doc.docstatus==0) { - cur_frm.add_custom_button(__("Get Items from BOM"), - cur_frm.cscript.get_items_from_bom, "fa fa-sitemap", "btn-default"); - } - - if(doc.docstatus == 1 && doc.status != 'Stopped') { - if(flt(doc.per_ordered, 2) < 100) { - // make - if(doc.material_request_type === "Material Transfer") - cur_frm.add_custom_button(__("Transfer Material"), - this.make_stock_entry, __("Make")); - - if(doc.material_request_type === "Material Issue") - cur_frm.add_custom_button(__("Issue Material"), - this.make_stock_entry, __("Make")); - - if(doc.material_request_type === "Purchase") - cur_frm.add_custom_button(__('Purchase Order'), - this.make_purchase_order, __("Make")); - - if(doc.material_request_type === "Purchase") - cur_frm.add_custom_button(__("Request for Quotation"), - this.make_request_for_quotation, __("Make")); - - if(doc.material_request_type === "Purchase") - cur_frm.add_custom_button(__("Supplier Quotation"), - this.make_supplier_quotation, __("Make")); - - if(doc.material_request_type === "Manufacture") - cur_frm.add_custom_button(__("Work Order"), - function() { me.raise_work_orders() }, __("Make")); - - cur_frm.page.set_inner_btn_group_as_primary(__("Make")); - - // stop - me.frm.add_custom_button(__('Stop'), - me.frm.cscript['Stop Material Request']); - - } - } - - if (this.frm.doc.docstatus===0) { - this.frm.add_custom_button(__('Sales Order'), - function() { - erpnext.utils.map_current_doc({ - method: "erpnext.selling.doctype.sales_order.sales_order.make_material_request", - source_doctype: "Sales Order", - target: me.frm, - setters: { - company: me.frm.doc.company - }, - get_query_filters: { - docstatus: 1, - status: ["!=", "Closed"], - per_delivered: ["<", 99.99], - } - }) - }, __("Get items from")); - } - - if(doc.docstatus == 1 && doc.status == 'Stopped') - me.frm.add_custom_button(__('Re-open'), - me.frm.cscript['Unstop Material Request']); - - }, - - get_items_from_bom: function() { + get_items_from_bom: function(frm) { var d = new frappe.ui.Dialog({ title: __("Get Items from BOM"), fields: [ {"fieldname":"bom", "fieldtype":"Link", "label":__("BOM"), - options:"BOM", reqd: 1, get_query: function(){ - return {filters: { docstatus:1 }} + options:"BOM", reqd: 1, get_query: function() { + return {filters: { docstatus:1 }}; }}, {"fieldname":"warehouse", "fieldtype":"Link", "label":__("Warehouse"), options:"Warehouse", reqd: 1}, + {"fieldname":"qty", "fieldtype":"Float", "label":__("Quantity"), + reqd: 1, "default": 1}, {"fieldname":"fetch_exploded", "fieldtype":"Check", "label":__("Fetch exploded BOM (including sub-assemblies)"), "default":1}, {fieldname:"fetch", "label":__("Get Items from BOM"), "fieldtype":"Button"} @@ -197,15 +181,15 @@ erpnext.buying.MaterialRequestController = erpnext.buying.BuyingController.exten d.get_input("fetch").on("click", function() { var values = d.get_values(); if(!values) return; - values["company"] = cur_frm.doc.company; + values["company"] = frm.doc.company; frappe.call({ method: "erpnext.manufacturing.doctype.bom.bom.get_bom_items", args: values, callback: function(r) { - if(!r.message) { - frappe.throw(__("BOM does not contain any stock item")) + if (!r.message) { + frappe.throw(__("BOM does not contain any stock item")); } else { - erpnext.utils.remove_empty_first_row(cur_frm, "items"); + erpnext.utils.remove_empty_first_row(frm, "items"); $.each(r.message, function(i, item) { var d = frappe.model.add_child(cur_frm.doc, "Material Request Item", "items"); d.item_code = item.item_code; @@ -226,6 +210,94 @@ erpnext.buying.MaterialRequestController = erpnext.buying.BuyingController.exten d.show(); }, + make_purchase_order: function(frm) { + frappe.prompt( + {fieldname:'default_supplier', label: __('For Default Supplier (optional)'), fieldtype: 'Link', options: 'Supplier'}, + (values) => { + frappe.model.open_mapped_doc({ + method: "erpnext.stock.doctype.material_request.material_request.make_purchase_order", + frm: frm, + args: { default_supplier: values.default_supplier }, + run_link_triggers: true + }); + } + ) + }, + + make_request_for_quotation: function(frm) { + frappe.model.open_mapped_doc({ + method: "erpnext.stock.doctype.material_request.material_request.make_request_for_quotation", + frm: frm, + run_link_triggers: true + }); + }, + + make_supplier_quotation: function(frm) { + frappe.model.open_mapped_doc({ + method: "erpnext.stock.doctype.material_request.material_request.make_supplier_quotation", + frm: frm + }); + }, + + make_stock_entry: function(frm) { + frappe.model.open_mapped_doc({ + method: "erpnext.stock.doctype.material_request.material_request.make_stock_entry", + frm: frm + }); + }, + + raise_work_orders: function(frm) { + frappe.call({ + method:"erpnext.stock.doctype.material_request.material_request.raise_work_orders", + args: { + "material_request": frm.doc.name + }, + callback: function(r) { + if(r.message.length) { + frm.reload_doc(); + } + } + }); + }, + +}); + +frappe.ui.form.on("Material Request Item", { + qty: function (frm, doctype, name) { + var d = locals[doctype][name]; + if (flt(d.qty) < flt(d.min_order_qty)) { + frappe.msgprint(__("Warning: Material Requested Qty is less than Minimum Order Qty")); + } + + const item = locals[doctype][name]; + frm.events.get_item_data(frm, item); + }, + + rate: function(frm, doctype, name) { + const item = locals[doctype][name]; + frm.events.get_item_data(frm, item); + }, + + item_code: function(frm, doctype, name) { + const item = locals[doctype][name]; + item.rate = 0; + set_schedule_date(frm); + frm.events.get_item_data(frm, item); + }, + + schedule_date: function(frm, cdt, cdn) { + var row = locals[cdt][cdn]; + if (row.schedule_date) { + if(!frm.doc.schedule_date) { + erpnext.utils.copy_value_in_all_rows(frm.doc, cdt, cdn, "items", "schedule_date"); + } else { + set_schedule_date(frm); + } + } + } +}); + +erpnext.buying.MaterialRequestController = erpnext.buying.BuyingController.extend({ tc_name: function() { this.get_terms(); }, @@ -234,7 +306,7 @@ erpnext.buying.MaterialRequestController = erpnext.buying.BuyingController.exten // to override item code trigger from transaction.js }, - validate_company_and_party: function(party_field) { + validate_company_and_party: function() { return true; }, @@ -242,51 +314,6 @@ erpnext.buying.MaterialRequestController = erpnext.buying.BuyingController.exten return; }, - make_purchase_order: function() { - frappe.model.open_mapped_doc({ - method: "erpnext.stock.doctype.material_request.material_request.make_purchase_order", - frm: cur_frm, - run_link_triggers: true - }); - }, - - make_request_for_quotation: function(){ - frappe.model.open_mapped_doc({ - method: "erpnext.stock.doctype.material_request.material_request.make_request_for_quotation", - frm: cur_frm, - run_link_triggers: true - }); - }, - - make_supplier_quotation: function() { - frappe.model.open_mapped_doc({ - method: "erpnext.stock.doctype.material_request.material_request.make_supplier_quotation", - frm: cur_frm - }); - }, - - make_stock_entry: function() { - frappe.model.open_mapped_doc({ - method: "erpnext.stock.doctype.material_request.material_request.make_stock_entry", - frm: cur_frm - }); - }, - - raise_work_orders: function() { - var me = this; - frappe.call({ - method:"erpnext.stock.doctype.material_request.material_request.raise_work_orders", - args: { - "material_request": me.frm.doc.name - }, - callback: function(r) { - if(r.message.length) { - me.frm.reload_doc(); - } - } - }); - }, - validate: function() { set_schedule_date(this.frm); }, @@ -313,22 +340,8 @@ erpnext.buying.MaterialRequestController = erpnext.buying.BuyingController.exten // for backward compatibility: combine new and previous states $.extend(cur_frm.cscript, new erpnext.buying.MaterialRequestController({frm: cur_frm})); -cur_frm.cscript['Stop Material Request'] = function() { - var doc = cur_frm.doc; - $c('runserverobj', {'method':'update_status', 'arg': 'Stopped', 'docs': doc}, function(r,rt) { - cur_frm.refresh(); - }); -}; - -cur_frm.cscript['Unstop Material Request'] = function(){ - var doc = cur_frm.doc; - $c('runserverobj', {'method':'update_status', 'arg': 'Submitted','docs': doc}, function(r,rt) { - cur_frm.refresh(); - }); -}; - function set_schedule_date(frm) { if(frm.doc.schedule_date){ - erpnext.utils.copy_value_in_all_row(frm.doc, frm.doc.doctype, frm.doc.name, "items", "schedule_date"); + erpnext.utils.copy_value_in_all_rows(frm.doc, frm.doc.doctype, frm.doc.name, "items", "schedule_date"); } } diff --git a/erpnext/stock/doctype/material_request/material_request.json b/erpnext/stock/doctype/material_request/material_request.json index 14e7955a2e7..9927265e3f7 100644 --- a/erpnext/stock/doctype/material_request/material_request.json +++ b/erpnext/stock/doctype/material_request/material_request.json @@ -59,7 +59,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_global_search": 0, - "in_list_view": 1, + "in_list_view": 0, "in_standard_filter": 0, "label": "Series", "length": 0, @@ -793,7 +793,7 @@ "istable": 0, "max_attachments": 0, "menu_index": 0, - "modified": "2018-08-29 06:28:35.129352", + "modified": "2018-08-30 07:28:01.070112", "modified_by": "Administrator", "module": "Stock", "name": "Material Request", diff --git a/erpnext/stock/doctype/material_request/material_request.py b/erpnext/stock/doctype/material_request/material_request.py index df406549864..730ec3eb437 100644 --- a/erpnext/stock/doctype/material_request/material_request.py +++ b/erpnext/stock/doctype/material_request/material_request.py @@ -14,6 +14,7 @@ from erpnext.stock.stock_balance import update_bin_qty, get_indented_qty from erpnext.controllers.buying_controller import BuyingController from erpnext.manufacturing.doctype.work_order.work_order import get_item_details from erpnext.buying.utils import check_for_closed_status, validate_for_items +from erpnext.stock.doctype.item.item import get_item_defaults from six import string_types @@ -73,21 +74,16 @@ class MaterialRequest(BuyingController): validate_for_items(self) - # self.set_title() + self.set_title() # self.validate_qty_against_so() # NOTE: Since Item BOM and FG quantities are combined, using current data, it cannot be validated # Though the creation of Material Request from a Production Plan can be rethought to fix this def set_title(self): '''Set title as comma separated list of items''' - items = [] - for d in self.items: - if d.item_code not in items: - items.append(d.item_code) - if(len(items)==4): - break + items = ', '.join([d.item_name for d in self.items][:4]) - self.title = ', '.join(items) + self.title = _('{0} for {1}'.format(self.material_request_type, items))[:100] def on_submit(self): # frappe.db.set(self, 'status', 'Submitted') @@ -243,11 +239,30 @@ def update_item(obj, target, source_parent): target.qty = flt(flt(obj.stock_qty) - flt(obj.ordered_qty))/ target.conversion_factor target.stock_qty = (target.qty * target.conversion_factor) +@frappe.whitelist() +def update_status(name, status): + material_request = frappe.get_doc('Material Request', name) + material_request.check_permission('write') + material_request.update_status(status) + @frappe.whitelist() def make_purchase_order(source_name, target_doc=None): + def postprocess(source, target_doc): + if frappe.flags.args and frappe.flags.args.default_supplier: + # items only for given default supplier + supplier_items = [] + for d in target_doc.items: + default_supplier = get_item_defaults(d.item_code, target_doc.company).get('default_supplier') + if frappe.flags.args.default_supplier == default_supplier: + supplier_items.append(d) + target_doc.items = supplier_items + set_missing_values(source, target_doc) + def select_item(d): + return d.ordered_qty < d.stock_qty + doclist = get_mapped_doc("Material Request", source_name, { "Material Request": { "doctype": "Purchase Order", @@ -267,7 +282,7 @@ def make_purchase_order(source_name, target_doc=None): ["sales_order_item", "sales_order_item"] ], "postprocess": update_item, - "condition": lambda doc: doc.ordered_qty < doc.stock_qty + "condition": select_item } }, target_doc, postprocess) @@ -334,8 +349,8 @@ def make_purchase_order_based_on_supplier(source_name, target_doc=None): return target_doc def get_material_requests_based_on_supplier(supplier): - supplier_items = [d[0] for d in frappe.db.get_values("Item", - {"default_supplier": supplier})] + supplier_items = [d.parent for d in frappe.db.get_all("Item Default", + {"default_supplier": supplier}, 'parent')] if supplier_items: material_requests = frappe.db.sql_list("""select distinct mr.name from `tabMaterial Request` mr, `tabMaterial Request Item` mr_item diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.js b/erpnext/stock/doctype/stock_entry/stock_entry.js index e468533b8a7..fa3501a1325 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.js +++ b/erpnext/stock/doctype/stock_entry/stock_entry.js @@ -472,10 +472,10 @@ frappe.ui.form.on('Stock Entry Detail', { } }, expense_account: function(frm, cdt, cdn) { - erpnext.utils.copy_value_in_all_row(frm.doc, cdt, cdn, "items", "expense_account"); + erpnext.utils.copy_value_in_all_rows(frm.doc, cdt, cdn, "items", "expense_account"); }, cost_center: function(frm, cdt, cdn) { - erpnext.utils.copy_value_in_all_row(frm.doc, cdt, cdn, "items", "cost_center"); + erpnext.utils.copy_value_in_all_rows(frm.doc, cdt, cdn, "items", "cost_center"); }, sample_quantity: function(frm, cdt, cdn) { validate_sample_quantity(frm, cdt, cdn); diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index 420d9d8c3e3..cbf8217a85a 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -333,13 +333,13 @@ def get_default_cost_center(args, item, item_group): cost_center = None if args.get('project'): cost_center = frappe.db.get_value("Project", args.get("project"), "cost_center", cache=True) - + if not cost_center: if args.get('customer'): cost_center = item.get('selling_cost_center') or item_group.get('selling_cost_center') else: cost_center = item.get('buying_cost_center') or item_group.get('buying_cost_center') - + return cost_center or args.get("cost_center") def get_default_supplier(args, item, item_group): @@ -401,7 +401,7 @@ def insert_item_price(args): }) item_price.insert() frappe.msgprint(_("Item Price added for {0} in Price List {1}").format(args.item_code, - args.price_list)) + args.price_list), alert=True) def get_item_price(args, item_code): """ @@ -658,7 +658,7 @@ def get_serial_no_details(item_code, warehouse, stock_qty, serial_no): def get_bin_details_and_serial_nos(item_code, warehouse, has_batch_no, stock_qty=None, serial_no=None): bin_details_and_serial_nos = {} bin_details_and_serial_nos.update(get_bin_details(item_code, warehouse)) - if stock_qty > 0: + if flt(stock_qty) > 0: if has_batch_no: args = frappe._dict({"item_code":item_code, "warehouse":warehouse, "stock_qty":stock_qty}) serial_no = get_serial_no(args)