[Feature] Create Raw Material Request from Sales Order (#15452)
* Add new button to Sales Order form - Request for Raw Materials * Modify get_work_order_items function * Commonify functions in Production Plan to make it compatible with new feature * Create and submit Material Request from Sales Order * Link Sales Order with Material Request * Minor * Rename label * Fix Codacy * Modify as per review suggestions - Move dialog to a new function - Move checkboxes below other fields * Minor changes * Check for permissions * Add common checkboxes for all items * Fix codacy * fix: Travis * fix: Use variable to store query result * fix: Add comment before fetching exploded items * refactor: Break into multiple functions * test: Add test case
This commit is contained in:
parent
6922415d1b
commit
89974b221d
@ -104,13 +104,26 @@ frappe.ui.form.on('Production Plan', {
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
get_items_for_mr: function(frm) {
|
||||
frappe.call({
|
||||
method: "get_items_for_material_requests",
|
||||
method: "erpnext.manufacturing.doctype.production_plan.production_plan.get_items_for_material_requests",
|
||||
freeze: true,
|
||||
doc: frm.doc,
|
||||
callback: function() {
|
||||
args: {doc: frm.doc},
|
||||
callback: function(r) {
|
||||
if(r.message) {
|
||||
frm.set_value('mr_items', []);
|
||||
$.each(r.message, function(i, d) {
|
||||
var item = frm.add_child('mr_items');
|
||||
item.actual_qty = d.actual_qty;
|
||||
item.item_code = d.item_code;
|
||||
item.item_name = d.item_name;
|
||||
item.min_order_qty = d.min_order_qty;
|
||||
item.quantity = d.quantity;
|
||||
item.sales_order = d.sales_order;
|
||||
item.warehouse = d.warehouse;
|
||||
});
|
||||
}
|
||||
refresh_field('mr_items');
|
||||
}
|
||||
});
|
||||
|
@ -10,6 +10,7 @@ from erpnext.manufacturing.doctype.bom.bom import validate_bom_no
|
||||
from frappe.utils import cstr, flt, cint, nowdate, add_days, comma_and, now_datetime
|
||||
from erpnext.manufacturing.doctype.work_order.work_order import get_item_details
|
||||
from six import string_types
|
||||
from erpnext.setup.doctype.item_group.item_group import get_item_group_defaults
|
||||
|
||||
class ProductionPlan(Document):
|
||||
def validate(self):
|
||||
@ -281,102 +282,6 @@ class ProductionPlan(Document):
|
||||
|
||||
return item_dict
|
||||
|
||||
def get_items_for_material_requests(self):
|
||||
self.mr_items = []
|
||||
|
||||
for data in self.po_items:
|
||||
bom_wise_item_details = {}
|
||||
if not data.planned_qty:
|
||||
frappe.throw(_("For row {0}: Enter planned qty").format(data.idx))
|
||||
|
||||
if data.include_exploded_items and data.bom_no and self.include_subcontracted_items:
|
||||
for d in frappe.db.sql("""select bei.item_code, item.default_bom as bom,
|
||||
ifnull(sum(bei.stock_qty/ifnull(bom.quantity, 1)), 0) as qty, item.item_name,
|
||||
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
|
||||
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
|
||||
and bom.name=%s and item.is_stock_item in (1, {0})
|
||||
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:
|
||||
bom_wise_item_details = self.get_subitems(data, bom_wise_item_details, data.bom_no, 1)
|
||||
|
||||
for item, item_details in bom_wise_item_details.items():
|
||||
if item_details.qty > 0:
|
||||
self.add_item_in_material_request_items(item, item_details, data)
|
||||
|
||||
def get_subitems(self, data, bom_wise_item_details, bom_no, parent_qty):
|
||||
items = frappe.db.sql("""
|
||||
SELECT
|
||||
bom_item.item_code, default_material_request_type, item.item_name,
|
||||
ifnull(%(parent_qty)s * sum(bom_item.stock_qty/ifnull(bom.quantity, 1)), 0) as qty,
|
||||
item.is_sub_contracted_item as is_sub_contracted, bom_item.source_warehouse,
|
||||
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,
|
||||
item_default.default_warehouse
|
||||
FROM
|
||||
`tabBOM Item` bom_item
|
||||
JOIN `tabBOM` bom ON bom.name = bom_item.parent
|
||||
JOIN tabItem item ON bom_item.item_code = item.name
|
||||
LEFT JOIN `tabItem Default` item_default
|
||||
ON item.name = item_default.parent and item_default.company = %(company)s
|
||||
where
|
||||
bom.name = %(bom)s
|
||||
and bom_item.docstatus < 2
|
||||
and item.is_stock_item in (1, {0})
|
||||
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
|
||||
}, as_dict=1)
|
||||
|
||||
for d in items:
|
||||
if not data.include_exploded_items or not d.default_bom:
|
||||
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 data.include_exploded_items and d.default_bom:
|
||||
if ((d.default_material_request_type in ["Manufacture", "Purchase"] and
|
||||
not d.is_sub_contracted) or (d.is_sub_contracted and self.include_subcontracted_items)):
|
||||
if d.qty > 0:
|
||||
self.get_subitems(data, bom_wise_item_details, d.default_bom, d.qty)
|
||||
|
||||
return bom_wise_item_details
|
||||
|
||||
def add_item_in_material_request_items(self, item, row, data):
|
||||
total_qty = row.qty * data.planned_qty
|
||||
projected_qty, actual_qty = get_bin_details(row)
|
||||
|
||||
requested_qty = 0
|
||||
if self.ignore_existing_ordered_qty:
|
||||
requested_qty = total_qty
|
||||
else:
|
||||
requested_qty = total_qty - projected_qty
|
||||
|
||||
if requested_qty > 0 and requested_qty < row.min_order_qty:
|
||||
requested_qty = row.min_order_qty
|
||||
|
||||
if requested_qty > 0:
|
||||
self.append('mr_items', {
|
||||
'item_code': item,
|
||||
'item_name': row.item_name,
|
||||
'quantity': requested_qty,
|
||||
'warehouse': row.source_warehouse or row.default_warehouse,
|
||||
'actual_qty': actual_qty,
|
||||
'min_order_qty': row.min_order_qty,
|
||||
'sales_order': data.sales_order
|
||||
})
|
||||
|
||||
def make_work_order(self):
|
||||
wo_list = []
|
||||
self.validate_data()
|
||||
@ -466,6 +371,87 @@ class ProductionPlan(Document):
|
||||
else :
|
||||
msgprint(_("No material request created"))
|
||||
|
||||
def get_exploded_items(bom_wise_item_details, company, bom_no, include_non_stock_items):
|
||||
for d in frappe.db.sql("""select bei.item_code, item.default_bom as bom,
|
||||
ifnull(sum(bei.stock_qty/ifnull(bom.quantity, 1)), 0) as qty, item.item_name,
|
||||
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
|
||||
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
|
||||
and bom.name=%s and item.is_stock_item in (1, {0})
|
||||
group by bei.item_code, bei.stock_uom""".format(0 if include_non_stock_items else 1),
|
||||
(company, bom_no), as_dict=1):
|
||||
bom_wise_item_details.setdefault(d.get('item_code'), d)
|
||||
return bom_wise_item_details
|
||||
|
||||
def get_subitems(doc, data, bom_wise_item_details, bom_no, company, include_non_stock_items, include_subcontracted_items, parent_qty):
|
||||
items = frappe.db.sql("""
|
||||
SELECT
|
||||
bom_item.item_code, default_material_request_type, item.item_name,
|
||||
ifnull(%(parent_qty)s * sum(bom_item.stock_qty/ifnull(bom.quantity, 1)), 0) as qty,
|
||||
item.is_sub_contracted_item as is_sub_contracted, bom_item.source_warehouse,
|
||||
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,
|
||||
item_default.default_warehouse
|
||||
FROM
|
||||
`tabBOM Item` bom_item
|
||||
JOIN `tabBOM` bom ON bom.name = bom_item.parent
|
||||
JOIN tabItem item ON bom_item.item_code = item.name
|
||||
LEFT JOIN `tabItem Default` item_default
|
||||
ON item.name = item_default.parent and item_default.company = %(company)s
|
||||
where
|
||||
bom.name = %(bom)s
|
||||
and bom_item.docstatus < 2
|
||||
and item.is_stock_item in (1, {0})
|
||||
group by bom_item.item_code""".format(0 if include_non_stock_items else 1),{
|
||||
'bom': bom_no,
|
||||
'parent_qty': parent_qty,
|
||||
'company': company
|
||||
}, as_dict=1)
|
||||
|
||||
for d in items:
|
||||
if not data.get('include_exploded_items') or not d.default_bom:
|
||||
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 data.get('include_exploded_items') and d.default_bom:
|
||||
if ((d.default_material_request_type in ["Manufacture", "Purchase"] and
|
||||
not d.is_sub_contracted) or (d.is_sub_contracted and include_subcontracted_items)):
|
||||
if d.qty > 0:
|
||||
get_subitems(doc, data, bom_wise_item_details, d.default_bom, company, include_non_stock_items, include_subcontracted_items, d.qty)
|
||||
return bom_wise_item_details
|
||||
|
||||
def add_item_in_material_request_items(doc, planned_qty, ignore_existing_ordered_qty, item, row, data, warehouse, company):
|
||||
total_qty = row.qty * planned_qty
|
||||
projected_qty, actual_qty = get_bin_details(row)
|
||||
|
||||
requested_qty = 0
|
||||
if ignore_existing_ordered_qty:
|
||||
requested_qty = total_qty
|
||||
else:
|
||||
requested_qty = total_qty - projected_qty
|
||||
if requested_qty > 0 and requested_qty < row.min_order_qty:
|
||||
requested_qty = row.min_order_qty
|
||||
item_group_defaults = get_item_group_defaults(item, company)
|
||||
if requested_qty > 0:
|
||||
doc.setdefault('mr_items', []).append({
|
||||
'item_code': item,
|
||||
'item_name': row.item_name,
|
||||
'quantity': requested_qty,
|
||||
'warehouse': warehouse or row.source_warehouse or row.default_warehouse or item_group_defaults.get("default_warehouse"),
|
||||
'actual_qty': actual_qty,
|
||||
'min_order_qty': row.min_order_qty,
|
||||
'sales_order': data.get('sales_order')
|
||||
})
|
||||
|
||||
def get_sales_orders(self):
|
||||
so_filter = item_filter = ""
|
||||
if self.from_date:
|
||||
@ -520,3 +506,46 @@ def get_bin_details(row):
|
||||
""".format(conditions=conditions), { "item_code": row.item_code }, as_list=1)
|
||||
|
||||
return item_projected_qty and item_projected_qty[0] or (0,0)
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_items_for_material_requests(doc, company=None):
|
||||
if isinstance(doc, string_types):
|
||||
doc = frappe._dict(json.loads(doc))
|
||||
|
||||
doc['mr_items'] = []
|
||||
po_items = doc['po_items'] if doc.get('po_items') else doc['items']
|
||||
|
||||
for data in po_items:
|
||||
warehouse = None
|
||||
bom_wise_item_details = {}
|
||||
|
||||
if data.get('required_qty'):
|
||||
planned_qty = data.get('required_qty')
|
||||
bom_no = data.get('bom')
|
||||
ignore_existing_ordered_qty = data.get('ignore_existing_ordered_qty')
|
||||
include_non_stock_items = 1
|
||||
warehouse = data.get('for_warehouse')
|
||||
if data.get('include_exploded_items'):
|
||||
include_subcontracted_items = 1
|
||||
else:
|
||||
include_subcontracted_items = 0
|
||||
else:
|
||||
planned_qty = data.get('planned_qty')
|
||||
bom_no = data.get('bom_no')
|
||||
include_subcontracted_items = doc['include_subcontracted_items']
|
||||
company = doc['company']
|
||||
include_non_stock_items = doc['include_non_stock_items']
|
||||
ignore_existing_ordered_qty = doc['ignore_existing_ordered_qty']
|
||||
if not planned_qty:
|
||||
frappe.throw(_("For row {0}: Enter Planned Qty").format(data.get('idx')))
|
||||
|
||||
if data.get('include_exploded_items') and bom_no and include_subcontracted_items:
|
||||
# fetch exploded items from BOM
|
||||
bom_wise_item_details = get_exploded_items(bom_wise_item_details, company, bom_no, include_non_stock_items)
|
||||
else:
|
||||
bom_wise_item_details = get_subitems(doc, data, bom_wise_item_details, bom_no, company, include_non_stock_items, include_subcontracted_items, 1)
|
||||
for item, item_details in bom_wise_item_details.items():
|
||||
if item_details.qty > 0:
|
||||
add_item_in_material_request_items(doc, planned_qty, ignore_existing_ordered_qty, item, item_details, data, warehouse, company)
|
||||
|
||||
return doc['mr_items']
|
||||
|
@ -10,6 +10,7 @@ from erpnext.stock.doctype.item.test_item import create_item
|
||||
from erpnext.manufacturing.doctype.production_plan.production_plan import get_sales_orders
|
||||
from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation import create_stock_reconciliation
|
||||
from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order
|
||||
from erpnext.manufacturing.doctype.production_plan.production_plan import get_items_for_material_requests
|
||||
|
||||
class TestProductionPlan(unittest.TestCase):
|
||||
def setUp(self):
|
||||
@ -160,7 +161,9 @@ def create_production_plan(**args):
|
||||
'planned_start_date': args.planned_start_date or now_datetime()
|
||||
}]
|
||||
})
|
||||
pln.get_items_for_material_requests()
|
||||
mr_items = get_items_for_material_requests(pln.as_dict())
|
||||
for d in mr_items:
|
||||
pln.append('mr_items', d)
|
||||
|
||||
if not args.do_not_save:
|
||||
pln.insert()
|
||||
|
@ -150,6 +150,8 @@ erpnext.selling.SalesOrderController = erpnext.selling.SellingController.extend(
|
||||
&& flt(doc.per_delivered, 6) < 100) {
|
||||
this.frm.add_custom_button(__('Material Request'),
|
||||
function() { me.make_material_request() }, __("Make"));
|
||||
this.frm.add_custom_button(__('Request for Raw Materials'),
|
||||
function() { me.make_raw_material_request() }, __("Make"));
|
||||
}
|
||||
|
||||
// make purchase order
|
||||
@ -313,6 +315,86 @@ erpnext.selling.SalesOrderController = erpnext.selling.SellingController.extend(
|
||||
})
|
||||
},
|
||||
|
||||
make_raw_material_request: function() {
|
||||
var me = this;
|
||||
this.frm.call({
|
||||
doc: this.frm.doc,
|
||||
method: 'get_work_order_items',
|
||||
args: {
|
||||
for_raw_material_request: 1
|
||||
},
|
||||
callback: function(r) {
|
||||
if(!r.message) {
|
||||
frappe.msgprint({
|
||||
message: __('No Items with Bill of Materials.'),
|
||||
indicator: 'orange'
|
||||
});
|
||||
return;
|
||||
}
|
||||
else {
|
||||
me.make_raw_material_request_dialog(r);
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
make_raw_material_request_dialog: function(r) {
|
||||
var fields = [
|
||||
{fieldtype:'Check', fieldname:'include_exploded_items',
|
||||
label: __('Include Exploded Items')},
|
||||
{fieldtype:'Check', fieldname:'ignore_existing_ordered_qty',
|
||||
label: __('Ignore Existing Ordered Qty')},
|
||||
{
|
||||
fieldtype:'Table', fieldname: 'items',
|
||||
description: __('Select BOM, Qty and For Warehouse'),
|
||||
fields: [
|
||||
{fieldtype:'Read Only', fieldname:'item_code',
|
||||
label: __('Item Code'), in_list_view:1},
|
||||
{fieldtype:'Link', fieldname:'bom', options: 'BOM', reqd: 1,
|
||||
label: __('BOM'), in_list_view:1, get_query: function(doc) {
|
||||
return {filters: {item: doc.item_code}};
|
||||
}
|
||||
},
|
||||
{fieldtype:'Float', fieldname:'required_qty', reqd: 1,
|
||||
label: __('Qty'), in_list_view:1},
|
||||
{fieldtype:'Link', fieldname:'for_warehouse', options: 'Warehouse',
|
||||
label: __('For Warehouse')}
|
||||
],
|
||||
data: r.message,
|
||||
get_data: function() {
|
||||
return r.message
|
||||
}
|
||||
}
|
||||
]
|
||||
var d = new frappe.ui.Dialog({
|
||||
title: __("Select from Items having BOM"),
|
||||
fields: fields,
|
||||
primary_action: function() {
|
||||
var data = d.get_values();
|
||||
me.frm.call({
|
||||
method: 'erpnext.selling.doctype.sales_order.sales_order.make_raw_material_request',
|
||||
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(__('Material Request {0} submitted.',
|
||||
['<a href="#Form/Material Request/'+r.message.name+'">' + r.message.name+ '</a>']));
|
||||
}
|
||||
d.hide();
|
||||
me.frm.reload_doc();
|
||||
}
|
||||
});
|
||||
},
|
||||
primary_action_label: __('Make')
|
||||
});
|
||||
d.show();
|
||||
},
|
||||
|
||||
make_delivery_note_based_on_delivery_date: function() {
|
||||
var me = this;
|
||||
|
||||
@ -423,7 +505,7 @@ erpnext.selling.SalesOrderController = erpnext.selling.SellingController.extend(
|
||||
filters: {'parent': me.frm.doc.name}
|
||||
}
|
||||
}},
|
||||
|
||||
|
||||
{"fieldtype": "Button", "label": __("Make Purchase Order"), "fieldname": "make_purchase_order", "cssClass": "btn-primary"},
|
||||
]
|
||||
});
|
||||
|
@ -5,8 +5,9 @@ from __future__ import unicode_literals
|
||||
import frappe
|
||||
import json
|
||||
import frappe.utils
|
||||
from frappe.utils import cstr, flt, getdate, comma_and, cint, nowdate
|
||||
from frappe.utils import cstr, flt, getdate, comma_and, cint, nowdate, add_days
|
||||
from frappe import _
|
||||
from six import string_types
|
||||
from frappe.model.utils import get_fetch_values
|
||||
from frappe.model.mapper import get_mapped_doc
|
||||
from erpnext.stock.stock_balance import update_bin_qty, get_reserved_qty
|
||||
@ -17,6 +18,7 @@ from frappe.desk.doctype.auto_repeat.auto_repeat import get_next_schedule_date
|
||||
from erpnext.selling.doctype.customer.customer import check_credit_limit
|
||||
from erpnext.stock.doctype.item.item import get_item_defaults
|
||||
from erpnext.setup.doctype.item_group.item_group import get_item_group_defaults
|
||||
from erpnext.manufacturing.doctype.production_plan.production_plan import get_items_for_material_requests
|
||||
|
||||
form_grid_templates = {
|
||||
"items": "templates/form_grid/item_grid.html"
|
||||
@ -366,7 +368,7 @@ class SalesOrder(SellingController):
|
||||
self.indicator_color = "green"
|
||||
self.indicator_title = _("Paid")
|
||||
|
||||
def get_work_order_items(self):
|
||||
def get_work_order_items(self, for_raw_material_request=0):
|
||||
'''Returns items with BOM that already do not have a linked work order'''
|
||||
items = []
|
||||
|
||||
@ -375,8 +377,13 @@ class SalesOrder(SellingController):
|
||||
bom = get_default_bom_item(i.item_code)
|
||||
if bom:
|
||||
stock_qty = i.qty if i.doctype == 'Packed Item' else i.stock_qty
|
||||
pending_qty= stock_qty - flt(frappe.db.sql('''select sum(qty) from `tabWork Order`
|
||||
if not for_raw_material_request:
|
||||
total_work_order_qty = flt(frappe.db.sql('''select sum(qty) from `tabWork Order`
|
||||
where production_item=%s and sales_order=%s and sales_order_item = %s and docstatus<2''', (i.item_code, self.name, i.name))[0][0])
|
||||
pending_qty = stock_qty - total_work_order_qty
|
||||
else:
|
||||
pending_qty = stock_qty
|
||||
|
||||
if pending_qty:
|
||||
items.append(dict(
|
||||
name= i.name,
|
||||
@ -384,6 +391,7 @@ class SalesOrder(SellingController):
|
||||
bom = bom,
|
||||
warehouse = i.warehouse,
|
||||
pending_qty = pending_qty,
|
||||
required_qty = pending_qty if for_raw_material_request else 0,
|
||||
sales_order_item = i.name
|
||||
))
|
||||
return items
|
||||
@ -846,7 +854,7 @@ def get_supplier(doctype, txt, searchfield, start, page_len, filters):
|
||||
or supplier_name like %(txt)s)
|
||||
and name in (select supplier from `tabSales Order Item` where parent = %(parent)s)
|
||||
and name not in (select supplier from `tabPurchase Order` po inner join `tabPurchase Order Item` poi
|
||||
on po.name=poi.parent where po.docstatus<2 and poi.sales_order=%(parent)s)
|
||||
on po.name=poi.parent where po.docstatus<2 and poi.sales_order=%(parent)s)
|
||||
order by
|
||||
if(locate(%(_txt)s, name), locate(%(_txt)s, name), 99999),
|
||||
if(locate(%(_txt)s, supplier_name), locate(%(_txt)s, supplier_name), 99999),
|
||||
@ -902,3 +910,44 @@ def get_default_bom_item(item_code):
|
||||
bom = bom[0].name if bom else None
|
||||
|
||||
return bom
|
||||
|
||||
@frappe.whitelist()
|
||||
def make_raw_material_request(items, company, sales_order, project=None):
|
||||
if not frappe.has_permission("Sales Order", "write"):
|
||||
frappe.throw(_("Not permitted"), frappe.PermissionError)
|
||||
|
||||
if isinstance(items, string_types):
|
||||
items = frappe._dict(json.loads(items))
|
||||
|
||||
for item in items.get('items'):
|
||||
item["include_exploded_items"] = items.get('include_exploded_items')
|
||||
item["ignore_existing_ordered_qty"] = items.get('ignore_existing_ordered_qty')
|
||||
|
||||
raw_materials = get_items_for_material_requests(items, company)
|
||||
if not raw_materials:
|
||||
frappe.msgprint(_("Material Request not created, as quantity for Raw Materials already available."))
|
||||
|
||||
material_request = frappe.new_doc('Material Request')
|
||||
material_request.update(dict(
|
||||
doctype = 'Material Request',
|
||||
transaction_date = nowdate(),
|
||||
company = company,
|
||||
requested_by = frappe.session.user,
|
||||
material_request_type = 'Purchase'
|
||||
))
|
||||
for item in raw_materials:
|
||||
item_doc = frappe.get_cached_doc('Item', item.get('item_code'))
|
||||
schedule_date = add_days(nowdate(), cint(item_doc.lead_time_days))
|
||||
material_request.append('items', {
|
||||
'item_code': item.get('item_code'),
|
||||
'qty': item.get('quantity'),
|
||||
'schedule_date': schedule_date,
|
||||
'warehouse': item.get('warehouse'),
|
||||
'sales_order': sales_order,
|
||||
'project': project
|
||||
})
|
||||
material_request.insert()
|
||||
material_request.flags.ignore_permissions = 1
|
||||
material_request.run_method("set_missing_values")
|
||||
material_request.submit()
|
||||
return material_request
|
@ -11,8 +11,7 @@ from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
|
||||
from erpnext.selling.doctype.sales_order.sales_order import make_work_orders
|
||||
from erpnext.controllers.accounts_controller import update_child_qty_rate
|
||||
import json
|
||||
|
||||
|
||||
from erpnext.selling.doctype.sales_order.sales_order import make_raw_material_request
|
||||
class TestSalesOrder(unittest.TestCase):
|
||||
def tearDown(self):
|
||||
frappe.set_user("Administrator")
|
||||
@ -327,9 +326,8 @@ class TestSalesOrder(unittest.TestCase):
|
||||
self.assertRaises(frappe.CancelledLinkError, dn.submit)
|
||||
|
||||
def test_service_type_product_bundle(self):
|
||||
from erpnext.stock.doctype.item.test_item import make_item
|
||||
from erpnext.selling.doctype.product_bundle.test_product_bundle import make_product_bundle
|
||||
|
||||
from erpnext.stock.doctype.item.test_item import make_item
|
||||
make_item("_Test Service Product Bundle", {"is_stock_item": 0})
|
||||
make_item("_Test Service Product Bundle Item 1", {"is_stock_item": 0})
|
||||
make_item("_Test Service Product Bundle Item 2", {"is_stock_item": 0})
|
||||
@ -343,9 +341,8 @@ class TestSalesOrder(unittest.TestCase):
|
||||
self.assertTrue("_Test Service Product Bundle Item 2" in [d.item_code for d in so.packed_items])
|
||||
|
||||
def test_mix_type_product_bundle(self):
|
||||
from erpnext.stock.doctype.item.test_item import make_item
|
||||
from erpnext.selling.doctype.product_bundle.test_product_bundle import make_product_bundle
|
||||
|
||||
from erpnext.stock.doctype.item.test_item import make_item
|
||||
make_item("_Test Mix Product Bundle", {"is_stock_item": 0})
|
||||
make_item("_Test Mix Product Bundle Item 1", {"is_stock_item": 1})
|
||||
make_item("_Test Mix Product Bundle Item 2", {"is_stock_item": 0})
|
||||
@ -388,11 +385,10 @@ class TestSalesOrder(unittest.TestCase):
|
||||
|
||||
def test_drop_shipping(self):
|
||||
from erpnext.selling.doctype.sales_order.sales_order import make_purchase_order_for_drop_shipment
|
||||
from erpnext.stock.doctype.item.test_item import make_item
|
||||
from erpnext.buying.doctype.purchase_order.purchase_order import update_status
|
||||
|
||||
make_stock_entry(target="_Test Warehouse - _TC", qty=10, rate=100)
|
||||
|
||||
from erpnext.stock.doctype.item.test_item import make_item
|
||||
po_item = make_item("_Test Item for Drop Shipping", {"is_stock_item": 1, "delivered_by_supplier": 1})
|
||||
|
||||
dn_item = make_item("_Test Regular Item", {"is_stock_item": 1})
|
||||
@ -585,8 +581,8 @@ class TestSalesOrder(unittest.TestCase):
|
||||
self.assertEquals(wo_qty[0][0], so_item_name.get(item))
|
||||
|
||||
def test_serial_no_based_delivery(self):
|
||||
from erpnext.stock.doctype.item.test_item import make_item
|
||||
frappe.set_value("Stock Settings", None, "automatically_set_serial_nos_based_on_fifo", 1)
|
||||
from erpnext.stock.doctype.item.test_item import make_item
|
||||
item = make_item("_Reserved_Serialized_Item", {"is_stock_item": 1,
|
||||
"maintain_stock": 1,
|
||||
"has_serial_no": 1,
|
||||
@ -685,6 +681,55 @@ class TestSalesOrder(unittest.TestCase):
|
||||
se.cancel()
|
||||
self.assertFalse(frappe.db.exists("Serial No", {"sales_order": so.name}))
|
||||
|
||||
def test_request_for_raw_materials(self):
|
||||
from erpnext.stock.doctype.item.test_item import make_item
|
||||
item = make_item("_Test Finished Item", {"is_stock_item": 1,
|
||||
"maintain_stock": 1,
|
||||
"valuation_rate": 500,
|
||||
"item_defaults": [
|
||||
{
|
||||
"default_warehouse": "_Test Warehouse - _TC",
|
||||
"company": "_Test Company"
|
||||
}]
|
||||
})
|
||||
make_item("_Test Raw Item A", {"maintain_stock": 1,
|
||||
"valuation_rate": 100,
|
||||
"item_defaults": [
|
||||
{
|
||||
"default_warehouse": "_Test Warehouse - _TC",
|
||||
"company": "_Test Company"
|
||||
}]
|
||||
})
|
||||
make_item("_Test Raw Item B", {"maintain_stock": 1,
|
||||
"valuation_rate": 200,
|
||||
"item_defaults": [
|
||||
{
|
||||
"default_warehouse": "_Test Warehouse - _TC",
|
||||
"company": "_Test Company"
|
||||
}]
|
||||
})
|
||||
from erpnext.manufacturing.doctype.production_plan.test_production_plan import make_bom
|
||||
make_bom(item=item.item_code, rate=1000,
|
||||
raw_materials = ['_Test Raw Item A', '_Test Raw Item B'])
|
||||
|
||||
so = make_sales_order(**{
|
||||
"item_list": [{
|
||||
"item_code": item.item_code,
|
||||
"qty": 1,
|
||||
"rate":1000
|
||||
}]
|
||||
})
|
||||
so.submit()
|
||||
mr_dict = frappe._dict()
|
||||
items = so.get_work_order_items(1)
|
||||
mr_dict['items'] = items
|
||||
mr_dict['include_exploded_items'] = 0
|
||||
mr_dict['ignore_existing_ordered_qty'] = 1
|
||||
make_raw_material_request(mr_dict, so.company, so.name)
|
||||
mr = frappe.db.sql("""select name from `tabMaterial Request` ORDER BY creation DESC LIMIT 1""", as_dict=1)[0]
|
||||
mr_doc = frappe.get_doc('Material Request',mr.get('name'))
|
||||
self.assertEqual(mr_doc.items[0].sales_order, so.name)
|
||||
|
||||
def make_sales_order(**args):
|
||||
so = frappe.new_doc("Sales Order")
|
||||
args = frappe._dict(args)
|
||||
@ -714,7 +759,7 @@ def make_sales_order(**args):
|
||||
})
|
||||
|
||||
so.delivery_date = add_days(so.transaction_date, 10)
|
||||
|
||||
|
||||
if not args.do_not_save:
|
||||
so.insert()
|
||||
if not args.do_not_submit:
|
||||
|
Loading…
x
Reference in New Issue
Block a user