[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
This commit is contained in:
Manas Solanki 2018-05-28 20:07:08 +05:30 committed by Nabin Hait
parent 2fb63e1724
commit e5e87f7137
12 changed files with 243 additions and 71 deletions

View File

@ -219,6 +219,9 @@ class PurchaseOrder(BuyingController):
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 @@ class PurchaseOrder(BuyingController):
self.update_requested_qty()
self.update_ordered_qty()
self.update_blanket_order(cancel=True)
def on_update(self):
pass

View File

@ -1594,6 +1594,37 @@
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"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,
@ -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",

View File

@ -342,6 +342,18 @@ class StockController(AccountsController):
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):

View File

@ -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");
}
}
});

View File

@ -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",

View File

@ -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"
}
}
})

View File

@ -108,8 +108,11 @@ erpnext.buying.BuyingController = erpnext.TransactionController.extend({
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();
},

View File

@ -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);
}

View File

@ -1323,6 +1323,36 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
}
})
}
},
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)
]);
}
}
})
}
}
});

View File

@ -176,6 +176,8 @@ class SalesOrder(SellingController):
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 @@ class SalesOrder(SellingController):
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 @@ class SalesOrder(SellingController):
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)

View File

@ -1681,38 +1681,6 @@
"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,
"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,
@ -1875,6 +1843,69 @@
"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,
"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,
@ -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",

View File

@ -64,6 +64,8 @@ def get_item_details(args):
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 @@ def get_basic_details(args, item):
company: "",
order_type: "",
is_pos: "",
ignore_pricing_rule: "",
project: "",
qty: "",
stock_qty: "",
@ -735,3 +736,32 @@ def get_serial_no(args, serial_nos=None):
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