[enhance] production order from sales order

This commit is contained in:
Rushabh Mehta 2017-02-02 17:41:44 +05:30
parent ed94aa4798
commit 6ede4a3809
5 changed files with 234 additions and 254 deletions

File diff suppressed because it is too large Load Diff

View File

@ -249,7 +249,7 @@ class ProductionPlanningTool(Document):
"wip_warehouse" : "",
"fg_warehouse" : d.warehouse,
"status" : "Draft",
"project" : frappe.db.get_value("Sales Order", d.sales_order, "project")
"project" : frappe.db.get_value("Sales Order", d.sales_order, "project")
}
""" Club similar BOM and item for processing in case of Sales Orders """
@ -345,53 +345,53 @@ class ProductionPlanningTool(Document):
def get_subitems(self,bom_wise_item_details, bom, parent_qty, include_sublevel, only_raw, supply_subs,non_stock_item=0):
items = frappe.db.sql("""
SELECT
bom_item.item_code,
SELECT
bom_item.item_code,
default_material_request_type,
ifnull(%(parent_qty)s * sum(bom_item.qty/ifnull(bom.quantity, 1)), 0) as qty,
item.is_sub_contracted_item as is_sub_contracted,
item.is_sub_contracted_item as is_sub_contracted,
item.default_bom as default_bom,
bom_item.description as description,
bom_item.stock_uom as stock_uom,
item.min_order_qty as min_order_qty
bom_item.description as description,
bom_item.stock_uom as stock_uom,
item.min_order_qty as min_order_qty
FROM
`tabBOM Item` bom_item,
`tabBOM` bom,
`tabBOM Item` bom_item,
`tabBOM` bom,
tabItem item
where
bom.name = bom_item.parent
and bom.name = %(bom)s
bom.name = bom_item.parent
and bom.name = %(bom)s
and bom_item.docstatus < 2
and bom_item.item_code = item.name
""" + ("and item.is_stock_item = 1", "")[non_stock_item] + """
group by bom_item.item_code""", {"bom": bom, "parent_qty": parent_qty}, as_dict=1)
for d in items:
if ((d.default_material_request_type == "Purchase"
and not (d.is_sub_contracted and only_raw and include_sublevel))
if ((d.default_material_request_type == "Purchase"
and not (d.is_sub_contracted and only_raw and include_sublevel))
or (d.default_material_request_type == "Manufacture" and not only_raw)):
if d.item_code in bom_wise_item_details:
bom_wise_item_details[d.item_code].qty = bom_wise_item_details[d.item_code].qty + d.qty
else:
bom_wise_item_details[d.item_code] = d
if include_sublevel:
if ((d.default_material_request_type == "Purchase" and d.is_sub_contracted and supply_subs)
if ((d.default_material_request_type == "Purchase" and d.is_sub_contracted and supply_subs)
or (d.default_material_request_type == "Manufacture")):
my_qty = 0
projected_qty = self.get_item_projected_qty(d.item_code)
if self.create_material_requests_for_all_required_qty:
my_qty = d.qty
elif (bom_wise_item_details[d.item_code].qty - d.qty) < projected_qty:
my_qty = bom_wise_item_details[d.item_code].qty - projected_qty
else:
my_qty = d.qty
if my_qty > 0:
self.get_subitems(bom_wise_item_details,
self.get_subitems(bom_wise_item_details,
d.default_bom, my_qty, include_sublevel, only_raw, supply_subs)
return bom_wise_item_details
@ -408,12 +408,12 @@ class ProductionPlanningTool(Document):
item_list.append([item, self.item_dict[item][0][1], self.item_dict[item][0][2], total_qty])
item_qty = frappe.db.sql("""select warehouse, indented_qty, ordered_qty, actual_qty
from `tabBin` where item_code = %s""", item, as_dict=1)
i_qty, o_qty, a_qty = 0, 0, 0
for w in item_qty:
i_qty, o_qty, a_qty = i_qty + flt(w.indented_qty), o_qty + \
flt(w.ordered_qty), a_qty + flt(w.actual_qty)
item_list.append(['', '', '', '', w.warehouse, flt(w.indented_qty),
flt(w.ordered_qty), flt(w.actual_qty)])
if item_qty:
@ -485,15 +485,15 @@ class ProductionPlanningTool(Document):
def get_item_projected_qty(self,item):
item_projected_qty = frappe.db.sql("""
select ifnull(sum(projected_qty),0) as qty
from `tabBin`
from `tabBin`
where item_code = %(item_code)s and warehouse=%(warehouse)s
""", {
"item_code": item,
"item_code": item,
"warehouse": self.purchase_request_for_warehouse
}, as_dict=1)
return item_projected_qty[0].qty
def get_projected_qty(self):
items = self.item_dict.keys()
item_projected_qty = frappe.db.sql("""select item_code, sum(projected_qty)

View File

@ -4,11 +4,23 @@
{% include 'erpnext/selling/sales_common.js' %}
frappe.ui.form.on("Sales Order", {
setup: function(frm) {
$.extend(frm.cscript, new erpnext.selling.SalesOrderController({frm: frm}));
},
onload: function(frm) {
erpnext.queries.setup_queries(frm, "Warehouse", function() {
return erpnext.queries.warehouse(frm.doc);
});
frm.set_query('project', function(doc, cdt, cdn) {
return {
query: "erpnext.controllers.queries.get_project_name",
filters: {
'customer': doc.customer
}
}
});
// formatter for material request item
frm.set_indicator_formatter('item_code',
function(doc) { return (doc.qty<=doc.delivered_qty) ? "green" : "orange" })
@ -17,6 +29,7 @@ frappe.ui.form.on("Sales Order", {
erpnext.selling.SalesOrderController = erpnext.selling.SellingController.extend({
refresh: function(doc, dt, dn) {
var me = this;
this._super();
var allow_purchase = false;
var allow_delivery = false;
@ -24,8 +37,8 @@ erpnext.selling.SalesOrderController = erpnext.selling.SellingController.extend(
if(doc.docstatus==1) {
if(doc.status != 'Closed') {
for (var i in cur_frm.doc.items) {
var item = cur_frm.doc.items[i];
for (var i in this.frm.doc.items) {
var item = this.frm.doc.items[i];
if(item.delivered_by_supplier === 1 || item.supplier){
if(item.qty > flt(item.ordered_qty)
&& item.qty > flt(item.delivered_qty)) {
@ -47,55 +60,69 @@ erpnext.selling.SalesOrderController = erpnext.selling.SellingController.extend(
if (this.frm.has_perm("submit")) {
// close
if(flt(doc.per_delivered, 2) < 100 || flt(doc.per_billed) < 100) {
cur_frm.add_custom_button(__('Close'), this.close_sales_order, __("Status"))
this.frm.add_custom_button(__('Close'),
function() { me.close_sales_order() }, __("Status"))
}
}
// delivery note
if(flt(doc.per_delivered, 2) < 100 && ["Sales", "Shopping Cart"].indexOf(doc.order_type)!==-1 && allow_delivery) {
cur_frm.add_custom_button(__('Delivery'), this.make_delivery_note, __("Make"));
cur_frm.page.set_inner_btn_group_as_primary(__("Make"));
this.frm.add_custom_button(__('Delivery'),
function() { me.make_delivery_note() }, __("Make"));
this.frm.add_custom_button(__('Production Order'),
function() { me.make_production_order() }, __("Make"));
this.frm.page.set_inner_btn_group_as_primary(__("Make"));
}
// sales invoice
if(flt(doc.per_billed, 2) < 100) {
cur_frm.add_custom_button(__('Invoice'), this.make_sales_invoice, __("Make"));
this.frm.add_custom_button(__('Invoice'),
function() { me.make_sales_invoice() }, __("Make"));
}
// material request
if(!doc.order_type || ["Sales", "Shopping Cart"].indexOf(doc.order_type)!==-1
&& flt(doc.per_delivered, 2) < 100) {
cur_frm.add_custom_button(__('Material Request'), this.make_material_request, __("Make"));
this.frm.add_custom_button(__('Material Request'),
function() { me.make_material_request() }, __("Make"));
}
// make purchase order
if(flt(doc.per_delivered, 2) < 100 && allow_purchase) {
cur_frm.add_custom_button(__('Purchase Order'), cur_frm.cscript.make_purchase_order, __("Make"));
this.frm.add_custom_button(__('Purchase Order'),
function() { me.make_purchase_order() }, __("Make"));
}
// payment request
if(flt(doc.per_billed)==0) {
cur_frm.add_custom_button(__('Payment Request'), this.make_payment_request, __("Make"));
cur_frm.add_custom_button(__('Payment'), cur_frm.cscript.make_payment_entry, __("Make"));
this.frm.add_custom_button(__('Payment Request'),
function() { me.make_payment_request() }, __("Make"));
this.frm.add_custom_button(__('Payment'),
function() { me.make_payment_entry() }, __("Make"));
}
// maintenance
if(flt(doc.per_delivered, 2) < 100 &&
["Sales", "Shopping Cart"].indexOf(doc.order_type)===-1) {
cur_frm.add_custom_button(__('Maintenance Visit'), this.make_maintenance_visit, __("Make"));
cur_frm.add_custom_button(__('Maintenance Schedule'), this.make_maintenance_schedule, __("Make"));
this.frm.add_custom_button(__('Maintenance Visit'),
function() { me.make_maintenance_visit() }, __("Make"));
this.frm.add_custom_button(__('Maintenance Schedule'),
function() { me.make_maintenance_schedule() }, __("Make"));
}
} else {
if (this.frm.has_perm("submit")) {
// un-close
cur_frm.add_custom_button(__('Re-open'), cur_frm.cscript['Unclose Sales Order'], __("Status"));
this.frm.add_custom_button(__('Re-open'), function() {
me.frm.cscript.update_status('Re-open', 'Draft')
}, __("Status"));
}
}
}
if (this.frm.doc.docstatus===0) {
cur_frm.add_custom_button(__('Quotation'),
this.frm.add_custom_button(__('Quotation'),
function() {
erpnext.utils.map_current_doc({
method: "erpnext.selling.doctype.quotation.quotation.make_sales_order",
@ -103,9 +130,9 @@ erpnext.selling.SalesOrderController = erpnext.selling.SellingController.extend(
get_query_filters: {
docstatus: 1,
status: ["!=", "Lost"],
order_type: cur_frm.doc.order_type,
customer: cur_frm.doc.customer || undefined,
company: cur_frm.doc.company
order_type: me.frm.doc.order_type,
customer: me.frm.doc.customer || undefined,
company: me.frm.doc.company
}
})
}, __("Get items from"));
@ -114,6 +141,82 @@ erpnext.selling.SalesOrderController = erpnext.selling.SellingController.extend(
this.order_type(doc);
},
make_production_order() {
var me = this;
this.frm.call({
doc: this.frm.doc,
method: 'get_production_order_items',
callback: function(r) {
if(!r.message.every(function(d) { return !!d.bom })) {
frappe.msgprint({
title: __('Production Order not created'),
message: __('No Items with Bill of Materials to Manufacture'),
indicator: 'orange'
});
return;
}
else if(!r.message.every(function(d) { return !!d.pending_qty })) {
frappe.msgprint({
title: __('Production Order not created'),
message: __('Production Order already created for all items with BOM'),
indicator: 'orange'
});
return;
} else {
var fields = [
{fieldtype:'Table', fieldname: 'items',
description: __('Select BOM and Qty for Production'),
fields: [
{fieldtype:'Read Only', fieldname:'item_code',
label: __('Item Code'), in_list_view:1},
{fieldtype:'Link', fieldname:'bom', options: 'BOM',
label: __('Select BOM'), in_list_view:1, get_query: function(doc) {
return {filters: {item: doc.item_code}};
}},
{fieldtype:'Float', fieldname:'pending_qty',
label: __('Qty'), in_list_view:1},
],
get_data: function() {
return r.message
}
}
]
var d = new frappe.ui.Dialog({
title: __('Select Items to Manufacture'),
fields: fields,
primary_action: function() {
data = d.get_values();
me.frm.call({
method: 'make_production_orders',
args: {
items: data,
company: me.frm.doc.company,
sales_order: me.frm.docname,
project: me.frm.project
},
freeze: true,
callback: function(r) {
if(r.message) {
frappe.msgprint({
message: __('Production Orders Created: {0}',
[r.message.map(function(d) {
return repl('<a href="#Form/Production Order/%(name)s">%(name)s</a>', {name:d})
}).join(', ')]),
indicator: 'green'
})
}
d.hide();
}
});
},
primary_action_label: __('Make')
});
d.show();
}
}
});
},
order_type: function() {
this.frm.toggle_reqd("delivery_date", this.frm.doc.order_type == "Sales");
},
@ -125,39 +228,40 @@ erpnext.selling.SalesOrderController = erpnext.selling.SellingController.extend(
make_material_request: function() {
frappe.model.open_mapped_doc({
method: "erpnext.selling.doctype.sales_order.sales_order.make_material_request",
frm: cur_frm
frm: this.frm
})
},
make_delivery_note: function() {
frappe.model.open_mapped_doc({
method: "erpnext.selling.doctype.sales_order.sales_order.make_delivery_note",
frm: cur_frm
frm: this.frm
})
},
make_sales_invoice: function() {
frappe.model.open_mapped_doc({
method: "erpnext.selling.doctype.sales_order.sales_order.make_sales_invoice",
frm: cur_frm
frm: this.frm
})
},
make_maintenance_schedule: function() {
frappe.model.open_mapped_doc({
method: "erpnext.selling.doctype.sales_order.sales_order.make_maintenance_schedule",
frm: cur_frm
frm: this.frm
})
},
make_maintenance_visit: function() {
frappe.model.open_mapped_doc({
method: "erpnext.selling.doctype.sales_order.sales_order.make_maintenance_visit",
frm: cur_frm
frm: this.frm
})
},
make_purchase_order: function(){
var me = this;
var dialog = new frappe.ui.Dialog({
title: __("For Supplier"),
fields: [
@ -165,7 +269,7 @@ erpnext.selling.SalesOrderController = erpnext.selling.SellingController.extend(
"get_query": function () {
return {
query:"erpnext.selling.doctype.sales_order.sales_order.get_supplier",
filters: {'parent': cur_frm.doc.name}
filters: {'parent': me.frm.doc.name}
}
}, "reqd": 1 },
{"fieldtype": "Button", "label": __("Make Purchase Order"), "fieldname": "make_purchase_order", "cssClass": "btn-primary"},
@ -180,7 +284,7 @@ erpnext.selling.SalesOrderController = erpnext.selling.SellingController.extend(
type: "GET",
method: "erpnext.selling.doctype.sales_order.sales_order.make_purchase_order_for_drop_shipment",
args: {
"source_name": cur_frm.doc.name,
"source_name": me.frm.doc.name,
"for_supplier": args.supplier
},
freeze: true,
@ -195,44 +299,25 @@ erpnext.selling.SalesOrderController = erpnext.selling.SellingController.extend(
dialog.show();
},
close_sales_order: function(){
cur_frm.cscript.update_status("Close", "Closed")
this.frm.cscript.update_status("Close", "Closed")
},
update_status: function(label, status){
var doc = this.frm.doc;
frappe.ui.form.is_saving = true;
frappe.call({
method: "erpnext.selling.doctype.sales_order.sales_order.update_status",
args: {status: status, name: doc.name},
callback: function(r){
this.frm.reload_doc();
},
always: function() {
frappe.ui.form.is_saving = false;
}
});
},
on_submit: function(doc, cdt, cdn) {
if(cint(frappe.boot.notification_settings.sales_order)) {
this.frm.email_doc(frappe.boot.notification_settings.sales_order_message);
}
}
});
// for backward compatibility: combine new and previous states
$.extend(cur_frm.cscript, new erpnext.selling.SalesOrderController({frm: cur_frm}));
cur_frm.fields_dict['project'].get_query = function(doc, cdt, cdn) {
return {
query: "erpnext.controllers.queries.get_project_name",
filters: {
'customer': doc.customer
}
}
}
cur_frm.cscript.update_status = function(label, status){
var doc = cur_frm.doc;
frappe.ui.form.is_saving = true;
frappe.call({
method: "erpnext.selling.doctype.sales_order.sales_order.update_status",
args: {status: status, name: doc.name},
callback: function(r){
cur_frm.reload_doc();
},
always: function() {
frappe.ui.form.is_saving = false;
}
});
}
cur_frm.cscript['Unclose Sales Order'] = function() {
cur_frm.cscript.update_status('Re-open', 'Draft')
}
cur_frm.cscript.on_submit = function(doc, cdt, cdn) {
if(cint(frappe.boot.notification_settings.sales_order)) {
cur_frm.email_doc(frappe.boot.notification_settings.sales_order_message);
}
};

View File

@ -304,6 +304,24 @@ class SalesOrder(SellingController):
self.indicator_color = "green"
self.indicator_title = _("Paid")
def get_production_order_items(self):
'''Returns items with BOM that already do not have a linked production order'''
items = []
for i in self.packed_items or self.items:
bom = frappe.get_all('BOM', dict(item=i.item_code, is_active=True),
order_by='is_default desc')
bom = bom[0].name if bom else None
items.append(dict(
item_code= i.item_code,
bom = bom,
warehouse = i.warehouse,
pending_qty= i.qty - flt(frappe.db.sql('''select sum(qty) from `tabProduction Order`
where production_item=%s and sales_order=%s''', (i.item_code, self.name))[0][0])
))
return items
def on_recurring(self, reference_doc):
mcount = month_map[reference_doc.recurring_type]
self.set("delivery_date", get_next_date(reference_doc.delivery_date, mcount,
@ -442,7 +460,7 @@ def make_sales_invoice(source_name, target_doc=None, ignore_permissions=False):
target.amount = flt(source.amount) - flt(source.billed_amt)
target.base_amount = target.amount * flt(source_parent.conversion_rate)
target.qty = target.amount / flt(source.rate) if (source.rate and source.billed_amt) else source.qty
item = frappe.db.get_value("Item", target.item_code, ["item_group", "selling_cost_center"], as_dict=1)
target.cost_center = frappe.db.get_value("Project", source_parent.project, "cost_center") \
or item.selling_cost_center \
@ -652,6 +670,27 @@ def get_supplier(doctype, txt, searchfield, start, page_len, filters):
'parent': filters.get('parent')
})
@frappe.whitelist()
def make_production_orders(items, sales_order, company, project=None):
'''Make Production Orders against the given Sales Order for the given `items`'''
items = json.loads(items).get('items')
out = []
for i in items:
production_order = frappe.get_doc(dict(
doctype='Production Order',
production_item=i['item_code'],
bom_no=i['bom'],
qty=i['pending_qty'],
company=company,
sales_order=sales_order,
project=project,
fg_warehouse=i['warehouse']
)).insert()
out.append(production_order)
return [p.name for p in out]
@frappe.whitelist()
def update_status(status, name):
so = frappe.get_doc("Sales Order", name)

View File

@ -41,6 +41,10 @@ erpnext.stock.StockEntry = erpnext.stock.StockController.extend({
}
}
}
this.frm.set_indicator_formatter('item_code',
function(doc) { return (doc.qty<=doc.actual_qty) ? "green" : "orange" })
},
onload_post_render: function() {