[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