[feature] Blanket Order

- Creaete Sales or Purchase order from the blanket order
- If there is any blanket order for the customer/supplier rates will be fetched from that order
- Manually selecting the Blanket order will change the rates accordingly
- Upon submission of the order, the ordered qty will be updated in the Blanket Order
diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.py b/erpnext/buying/doctype/purchase_order/purchase_order.py
index f258546..c6a5dae 100644
--- a/erpnext/buying/doctype/purchase_order/purchase_order.py
+++ b/erpnext/buying/doctype/purchase_order/purchase_order.py
@@ -219,6 +219,9 @@
 		frappe.get_doc('Authorization Control').validate_approving_authority(self.doctype,
 			self.company, self.base_grand_total)
 
+		self.update_blanket_order()
+
+
 	def on_cancel(self):
 		super(PurchaseOrder, self).on_cancel()
 
@@ -241,6 +244,9 @@
 		self.update_requested_qty()
 		self.update_ordered_qty()
 
+		self.update_blanket_order(cancel=True)
+
+
 	def on_update(self):
 		pass
 
diff --git a/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json b/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json
index fa372ba..2812519 100755
--- a/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json
+++ b/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json
@@ -1600,6 +1600,37 @@
    "bold": 0, 
    "collapsible": 0, 
    "columns": 0, 
+   "fieldname": "delivered_by_supplier", 
+   "fieldtype": "Check", 
+   "hidden": 0, 
+   "ignore_user_permissions": 0, 
+   "ignore_xss_filter": 0, 
+   "in_filter": 0, 
+   "in_global_search": 0, 
+   "in_list_view": 0, 
+   "in_standard_filter": 0, 
+   "label": "To be delivered to customer", 
+   "length": 0, 
+   "no_copy": 0, 
+   "permlevel": 0, 
+   "precision": "", 
+   "print_hide": 1, 
+   "print_hide_if_no_value": 0, 
+   "read_only": 1, 
+   "remember_last_selected_value": 0, 
+   "report_hide": 0, 
+   "reqd": 0, 
+   "search_index": 0, 
+   "set_only_once": 0, 
+   "translatable": 0, 
+   "unique": 0
+  }, 
+  {
+   "allow_bulk_edit": 0, 
+   "allow_on_submit": 0, 
+   "bold": 0, 
+   "collapsible": 0, 
+   "columns": 0, 
    "fieldname": "blanket_order", 
    "fieldtype": "Link", 
    "hidden": 0, 
@@ -1611,7 +1642,7 @@
    "in_standard_filter": 0, 
    "label": "Blanket Order", 
    "length": 0, 
-   "no_copy": 0, 
+   "no_copy": 1, 
    "options": "Blanket Order", 
    "permlevel": 0, 
    "precision": "", 
@@ -1632,8 +1663,8 @@
    "bold": 0, 
    "collapsible": 0, 
    "columns": 0, 
-   "fieldname": "delivered_by_supplier", 
-   "fieldtype": "Check", 
+   "fieldname": "blanket_order_rate", 
+   "fieldtype": "Currency", 
    "hidden": 0, 
    "ignore_user_permissions": 0, 
    "ignore_xss_filter": 0, 
@@ -1641,9 +1672,9 @@
    "in_global_search": 0, 
    "in_list_view": 0, 
    "in_standard_filter": 0, 
-   "label": "To be delivered to customer", 
+   "label": "Blanket Order Rate", 
    "length": 0, 
-   "no_copy": 0, 
+   "no_copy": 1, 
    "permlevel": 0, 
    "precision": "", 
    "print_hide": 1, 
@@ -2083,7 +2114,7 @@
  "issingle": 0, 
  "istable": 1, 
  "max_attachments": 0, 
- "modified": "2018-05-24 09:15:37.473332", 
+ "modified": "2018-05-28 08:43:23.251530", 
  "modified_by": "Administrator", 
  "module": "Buying", 
  "name": "Purchase Order Item", 
diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py
index a76a3b3..6ed9507 100644
--- a/erpnext/controllers/stock_controller.py
+++ b/erpnext/controllers/stock_controller.py
@@ -342,6 +342,18 @@
 				if self.docstatus==1:
 					raise frappe.ValidationError
 
+	def update_blanket_order(self, cancel=False):
+		for item in self.items:
+			if item.blanket_order:
+				ordered_qty, doc_name = frappe.db.get_value("Blanket Order Item", {"parent": item.blanket_order}, ["ordered_qty", "name"])
+				if not cancel:
+					ordered_qty = ordered_qty + item.qty
+				else:
+					ordered_qty = ordered_qty - item.qty
+				ordered_qty = flt(ordered_qty, item.precision("qty"))
+				frappe.db.set_value("Blanket Order Item", doc_name, "ordered_qty", ordered_qty)
+
+
 def update_gl_entries_after(posting_date, posting_time, for_warehouses=None, for_items=None,
 		warehouse_account=None):
 	def _delete_gl_entries(voucher_type, voucher_no):
diff --git a/erpnext/manufacturing/doctype/blanket_order/blanket_order.js b/erpnext/manufacturing/doctype/blanket_order/blanket_order.js
index 5e6a2a6..0c02d1c 100644
--- a/erpnext/manufacturing/doctype/blanket_order/blanket_order.js
+++ b/erpnext/manufacturing/doctype/blanket_order/blanket_order.js
@@ -3,41 +3,28 @@
 
 frappe.ui.form.on('Blanket Order', {
 	refresh: function(frm) {
-		if (frm.doc.customer) {
+		if (frm.doc.customer && frm.doc.docstatus === 1) {
 			frm.add_custom_button(__('View Orders'), function() {
 				frappe.set_route('List', 'Sales Order', {blanket_order: frm.doc.name});
 			});
-			if (frm.doc.docstatus === 1) {
-				frm.add_primary_button(__("Create Sales Order"), function(){
-					
+			frm.add_custom_button(__("Create Sales Order"), function(){
+				frappe.model.open_mapped_doc({
+					method: "erpnext.manufacturing.doctype.blanket_order.blanket_order.make_sales_order",
+					frm: frm
 				});
-			}
+			}).addClass("btn-primary");
 		}
 
-		if (frm.doc.supplier) {
+		if (frm.doc.supplier && frm.doc.docstatus === 1) {
 			frm.add_custom_button(__('View Orders'), function() {
 				frappe.set_route('List', 'Purchase Order', {blanket_order: frm.doc.name});
 			});
-		}
-
-		// if (frm.doc.project) {
-		// 	frm.add_custom_button(__('Project'), function() {
-		// 		frappe.set_route("Form", "Project", frm.doc.project);
-		// 	},__("View"));
-		// 	frm.add_custom_button(__('Task'), function() {
-		// 		frappe.set_route('List', 'Task', {project: frm.doc.project});
-		// 	},__("View"));
-		// }
-
-		if ((!frm.doc.employee) && (frm.doc.docstatus === 1)) {
-			frm.add_custom_button(__('Employee'), function () {
+			frm.add_custom_button(__("Create Purchase Order"), function(){
 				frappe.model.open_mapped_doc({
-					method: "erpnext.hr.doctype.employee_onboarding.employee_onboarding.make_employee",
+					method: "erpnext.manufacturing.doctype.blanket_order.blanket_order.make_purchase_order",
 					frm: frm
 				});
-			}, __("Make"));
-			frm.page.set_inner_btn_group_as_primary(__("Make"));
+			}).addClass("btn-primary");
 		}
-
 	}
 });
diff --git a/erpnext/manufacturing/doctype/blanket_order/blanket_order.json b/erpnext/manufacturing/doctype/blanket_order/blanket_order.json
index a3247ec..8ad31ca 100644
--- a/erpnext/manufacturing/doctype/blanket_order/blanket_order.json
+++ b/erpnext/manufacturing/doctype/blanket_order/blanket_order.json
@@ -26,7 +26,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, 
@@ -437,7 +437,7 @@
  "issingle": 0, 
  "istable": 0, 
  "max_attachments": 0, 
- "modified": "2018-05-24 07:56:37.911486", 
+ "modified": "2018-05-28 05:56:05.922333", 
  "modified_by": "Administrator", 
  "module": "Manufacturing", 
  "name": "Blanket Order", 
@@ -467,6 +467,7 @@
  "quick_entry": 1, 
  "read_only": 0, 
  "read_only_onload": 0, 
+ "search_fields": "order_type, to_date", 
  "show_name_in_global_search": 0, 
  "sort_field": "modified", 
  "sort_order": "DESC", 
diff --git a/erpnext/manufacturing/doctype/blanket_order/blanket_order.py b/erpnext/manufacturing/doctype/blanket_order/blanket_order.py
index c23c3dc..06b6810 100644
--- a/erpnext/manufacturing/doctype/blanket_order/blanket_order.py
+++ b/erpnext/manufacturing/doctype/blanket_order/blanket_order.py
@@ -5,6 +5,39 @@
 from __future__ import unicode_literals
 import frappe
 from frappe.model.document import Document
+from frappe.model.mapper import get_mapped_doc
+
 
 class BlanketOrder(Document):
 	pass
+
+
+@frappe.whitelist()
+def make_sales_order(source_name):
+	return get_mapped_doc("Blanket Order", source_name, {
+		"Blanket Order": {
+			"doctype": "Sales Order"
+		},
+		"Blanket Order Item": {
+			"doctype": "Sales Order Item",
+			"field_map": {
+				"rate": "blanket_order_rate",
+				"parent": "blanket_order"
+			}
+		}
+	})
+
+@frappe.whitelist()
+def make_purchase_order(source_name):
+	return get_mapped_doc("Blanket Order", source_name, {
+		"Blanket Order": {
+			"doctype": "Purchase Order"
+		},
+		"Blanket Order Item": {
+			"doctype": "Purchase Order Item",
+			"field_map": {
+				"rate": "blanket_order_rate",
+				"parent": "blanket_order"
+			}
+		}
+	})
diff --git a/erpnext/public/js/controllers/buying.js b/erpnext/public/js/controllers/buying.js
index bbeb8e9..7eaba09 100644
--- a/erpnext/public/js/controllers/buying.js
+++ b/erpnext/public/js/controllers/buying.js
@@ -108,8 +108,11 @@
 		var item = frappe.get_doc(cdt, cdn);
 		frappe.model.round_floats_in(item, ["price_list_rate", "discount_percentage"]);
 
-		item.rate = flt(item.price_list_rate * (1 - item.discount_percentage / 100.0),
-			precision("rate", item));
+		let item_rate = item.price_list_rate;
+		if (doc.doctype == "Purchase Order" && item.blanket_order_rate) {
+			item_rate = item.blanket_order_rate;
+		}
+		item.rate = flt(item_rate * (1 - item.discount_percentage / 100.0), precision("rate", item));
 
 		this.calculate_taxes_and_totals();
 	},
diff --git a/erpnext/public/js/controllers/taxes_and_totals.js b/erpnext/public/js/controllers/taxes_and_totals.js
index 71c098f..0047186 100644
--- a/erpnext/public/js/controllers/taxes_and_totals.js
+++ b/erpnext/public/js/controllers/taxes_and_totals.js
@@ -4,12 +4,15 @@
 erpnext.taxes_and_totals = erpnext.payments.extend({
 	setup: function() {},
 	apply_pricing_rule_on_item: function(item){
-
+		let effective_item_rate = item.price_list_rate;
+		if (item.parenttype === "Sales Order" && item.blanket_order_rate) {
+			effective_item_rate = item.blanket_order_rate;
+		}
 		if(item.margin_type == "Percentage"){
-			item.rate_with_margin = flt(item.price_list_rate)
-				+ flt(item.price_list_rate) * ( flt(item.margin_rate_or_amount) / 100);
+			item.rate_with_margin = flt(effective_item_rate)
+				+ flt(effective_item_rate) * ( flt(item.margin_rate_or_amount) / 100);
 		} else {
-			item.rate_with_margin = flt(item.price_list_rate) + flt(item.margin_rate_or_amount);
+			item.rate_with_margin = flt(effective_item_rate) + flt(item.margin_rate_or_amount);
 			item.base_rate_with_margin = flt(item.rate_with_margin) * flt(this.frm.doc.conversion_rate);
 		}
 
diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js
index ff0d134..3ed4e73 100644
--- a/erpnext/public/js/controllers/transaction.js
+++ b/erpnext/public/js/controllers/transaction.js
@@ -1323,6 +1323,36 @@
 				}
 			})
 		}
+	},
+
+	blanket_order: function(doc, cdt, cdn) {
+		var me = this;
+		var item = locals[cdt][cdn];
+		if (item.blanket_order && (item.parenttype=="Sales Order" || item.parenttype=="Purchase Order")) {
+			frappe.call({
+				method: "erpnext.stock.get_item_details.get_blanket_order_details",
+				args: {
+					args:{
+						item_code: item.item_code,
+						customer: doc.customer,
+						supplier: doc.supplier,
+						company: doc.company,
+						transaction_date: doc.transaction_date,
+						blanket_order: item.blanket_order
+					}
+				},
+				callback: function(r) {
+					if (!r.message) {
+						frappe.throw(__("Invalid Blanket Order for the selected Customer and Item"))
+					} else {
+						frappe.run_serially([
+							() => frappe.model.set_value(cdt, cdn, "blanket_order_rate", r.message.blanket_order_rate),
+							() => me.frm.script_manager.trigger("price_list_rate", cdt, cdn)
+						]);
+					}
+				}
+			})
+		}
 	}
 });
 
diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py
index 46303b5..95c7c92 100755
--- a/erpnext/selling/doctype/sales_order/sales_order.py
+++ b/erpnext/selling/doctype/sales_order/sales_order.py
@@ -176,6 +176,8 @@
 		self.update_project()
 		self.update_prevdoc_status('submit')
 
+		self.update_blanket_order()
+
 	def on_cancel(self):
 		# Cannot cancel closed SO
 		if self.status == 'Closed':
@@ -188,6 +190,8 @@
 
 		frappe.db.set(self, 'status', 'Cancelled')
 
+		self.update_blanket_order(cancel=True)
+
 	def update_project(self):
 		project_list = []
 		if self.project:
@@ -382,6 +386,7 @@
 			d.set("delivery_date", get_next_schedule_date(reference_delivery_date,
 														  auto_repeat_doc.frequency, cint(auto_repeat_doc.repeat_on_day)))
 
+
 def get_list_context(context=None):
 	from erpnext.controllers.website_list_for_contact import get_list_context
 	list_context = get_list_context(context)
diff --git a/erpnext/selling/doctype/sales_order_item/sales_order_item.json b/erpnext/selling/doctype/sales_order_item/sales_order_item.json
index 9d8e298..904d8fa 100644
--- a/erpnext/selling/doctype/sales_order_item/sales_order_item.json
+++ b/erpnext/selling/doctype/sales_order_item/sales_order_item.json
@@ -1687,38 +1687,6 @@
    "bold": 0, 
    "collapsible": 0, 
    "columns": 0, 
-   "fieldname": "blanket_order", 
-   "fieldtype": "Link", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "label": "Blanket Order", 
-   "length": 0, 
-   "no_copy": 0, 
-   "options": "Blanket Order", 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
    "fieldname": "brand", 
    "fieldtype": "Link", 
    "hidden": 1, 
@@ -1877,6 +1845,69 @@
   }, 
   {
    "allow_bulk_edit": 0, 
+   "allow_on_submit": 0, 
+   "bold": 0, 
+   "collapsible": 0, 
+   "columns": 0, 
+   "fieldname": "blanket_order", 
+   "fieldtype": "Link", 
+   "hidden": 0, 
+   "ignore_user_permissions": 0, 
+   "ignore_xss_filter": 0, 
+   "in_filter": 0, 
+   "in_global_search": 0, 
+   "in_list_view": 0, 
+   "in_standard_filter": 0, 
+   "label": "Blanket Order", 
+   "length": 0, 
+   "no_copy": 1, 
+   "options": "Blanket Order", 
+   "permlevel": 0, 
+   "precision": "", 
+   "print_hide": 0, 
+   "print_hide_if_no_value": 0, 
+   "read_only": 0, 
+   "remember_last_selected_value": 0, 
+   "report_hide": 0, 
+   "reqd": 0, 
+   "search_index": 0, 
+   "set_only_once": 0, 
+   "translatable": 0, 
+   "unique": 0
+  }, 
+  {
+   "allow_bulk_edit": 0, 
+   "allow_on_submit": 0, 
+   "bold": 0, 
+   "collapsible": 0, 
+   "columns": 0, 
+   "fieldname": "blanket_order_rate", 
+   "fieldtype": "Currency", 
+   "hidden": 0, 
+   "ignore_user_permissions": 0, 
+   "ignore_xss_filter": 0, 
+   "in_filter": 0, 
+   "in_global_search": 0, 
+   "in_list_view": 0, 
+   "in_standard_filter": 0, 
+   "label": "Blanket Order Rate", 
+   "length": 0, 
+   "no_copy": 1, 
+   "permlevel": 0, 
+   "precision": "", 
+   "print_hide": 1, 
+   "print_hide_if_no_value": 0, 
+   "read_only": 1, 
+   "remember_last_selected_value": 0, 
+   "report_hide": 0, 
+   "reqd": 0, 
+   "search_index": 0, 
+   "set_only_once": 0, 
+   "translatable": 0, 
+   "unique": 0
+  }, 
+  {
+   "allow_bulk_edit": 0, 
    "allow_on_submit": 1, 
    "bold": 0, 
    "collapsible": 0, 
@@ -2307,7 +2338,7 @@
  "istable": 1, 
  "max_attachments": 0, 
  "menu_index": 0, 
- "modified": "2018-05-24 09:14:45.810084", 
+ "modified": "2018-05-28 05:52:36.908884", 
  "modified_by": "Administrator", 
  "module": "Selling", 
  "name": "Sales Order Item", 
diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py
index 3b1b4f3..2eed4c9 100644
--- a/erpnext/stock/get_item_details.py
+++ b/erpnext/stock/get_item_details.py
@@ -64,6 +64,8 @@
 	else:
 		out.update(get_valuation_rate(args.item_code, args.company, out.get("warehouse")))
 
+	update_party_blanket_order(args, out)
+
 	get_price_list_rate(args, item_doc, out)
 
 	if args.customer and cint(args.is_pos):
@@ -185,7 +187,6 @@
 			company: "",
 			order_type: "",
 			is_pos: "",
-			ignore_pricing_rule: "",
 			project: "",
 			qty: "",
 			stock_qty: "",
@@ -735,3 +736,32 @@
 		serial_no = serial_nos
 
 	return serial_no
+
+
+def update_party_blanket_order(args, out):
+	blanket_order_details = get_blanket_order_details(args)
+	if blanket_order_details:
+		out.update(blanket_order_details)
+
+@frappe.whitelist()
+def get_blanket_order_details(args):
+	if isinstance(args, string_types):
+		args = frappe._dict(json.loads(args))
+
+	blanket_order_details = None
+	condition1, condition2 = ' ', ' '
+	if args.item_code:
+		if args.customer and args.doctype == "Sales Order":
+			condition1 = ' and bo.customer=%(customer)s '
+		elif args.supplier and args.doctype == "Purchase Order":
+			condition1 = ' and bo.supplier=%(supplier)s '
+		if args.blanket_order:
+			condition2 = ' and bo.name =%(blanket_order)s '
+		blanket_order_details = frappe.db.sql('''
+				select boi.rate as blanket_order_rate, bo.name as blanket_order
+				from `tabBlanket Order` bo, `tabBlanket Order Item` boi
+				where bo.to_date>=%(transaction_date)s and bo.company=%(company)s and boi.item_code=%(item_code)s
+					and bo.docstatus=1 and bo.name = boi.parent {0} {1}
+			'''.format(condition1, condition2), args, as_dict=True)
+		blanket_order_details = blanket_order_details[0] if blanket_order_details else ''
+	return blanket_order_details