mirror of
https://github.com/frappe/erpnext.git
synced 2026-05-26 16:34:46 +00:00
Contract Manufacturing : Customer Provided Items (#15828)
* Material Request from Production Plan for Customer provided parts * Test cases * Customer web portal for their material requests
This commit is contained in:
committed by
Nabin Hait
parent
35b2627112
commit
2b14d6a058
@@ -133,6 +133,13 @@ website_route_rules = [
|
|||||||
{"from_route": "/admissions", "to_route": "Student Admission"},
|
{"from_route": "/admissions", "to_route": "Student Admission"},
|
||||||
{"from_route": "/boms", "to_route": "BOM"},
|
{"from_route": "/boms", "to_route": "BOM"},
|
||||||
{"from_route": "/timesheets", "to_route": "Timesheet"},
|
{"from_route": "/timesheets", "to_route": "Timesheet"},
|
||||||
|
{"from_route": "/material-requests", "to_route": "Material Request"},
|
||||||
|
{"from_route": "/material-requests/<path:name>", "to_route": "material_request_info",
|
||||||
|
"defaults": {
|
||||||
|
"doctype": "Material Request",
|
||||||
|
"parents": [{"label": _("Material Request"), "route": "material-requests"}]
|
||||||
|
}
|
||||||
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
standard_portal_menu_items = [
|
standard_portal_menu_items = [
|
||||||
@@ -155,6 +162,7 @@ standard_portal_menu_items = [
|
|||||||
{"title": _("Newsletter"), "route": "/newsletters", "reference_doctype": "Newsletter"},
|
{"title": _("Newsletter"), "route": "/newsletters", "reference_doctype": "Newsletter"},
|
||||||
{"title": _("Admission"), "route": "/admissions", "reference_doctype": "Student Admission"},
|
{"title": _("Admission"), "route": "/admissions", "reference_doctype": "Student Admission"},
|
||||||
{"title": _("Certification"), "route": "/certification", "reference_doctype": "Certification Application"},
|
{"title": _("Certification"), "route": "/certification", "reference_doctype": "Certification Application"},
|
||||||
|
{"title": _("Material Request"), "route": "/material-requests", "reference_doctype": "Material Request", "role": "Customer"},
|
||||||
]
|
]
|
||||||
|
|
||||||
default_roles = [
|
default_roles = [
|
||||||
@@ -168,6 +176,7 @@ has_website_permission = {
|
|||||||
"Quotation": "erpnext.controllers.website_list_for_contact.has_website_permission",
|
"Quotation": "erpnext.controllers.website_list_for_contact.has_website_permission",
|
||||||
"Sales Invoice": "erpnext.controllers.website_list_for_contact.has_website_permission",
|
"Sales Invoice": "erpnext.controllers.website_list_for_contact.has_website_permission",
|
||||||
"Supplier Quotation": "erpnext.controllers.website_list_for_contact.has_website_permission",
|
"Supplier Quotation": "erpnext.controllers.website_list_for_contact.has_website_permission",
|
||||||
|
"Material Request": "erpnext.controllers.website_list_for_contact.has_website_permission",
|
||||||
"Delivery Note": "erpnext.controllers.website_list_for_contact.has_website_permission",
|
"Delivery Note": "erpnext.controllers.website_list_for_contact.has_website_permission",
|
||||||
"Issue": "erpnext.support.doctype.issue.issue.has_website_permission",
|
"Issue": "erpnext.support.doctype.issue.issue.has_website_permission",
|
||||||
"Timesheet": "erpnext.controllers.website_list_for_contact.has_website_permission",
|
"Timesheet": "erpnext.controllers.website_list_for_contact.has_website_permission",
|
||||||
|
|||||||
@@ -159,28 +159,30 @@ class BOM(WebsiteGenerator):
|
|||||||
if arg.get('scrap_items'):
|
if arg.get('scrap_items'):
|
||||||
rate = self.get_valuation_rate(arg)
|
rate = self.get_valuation_rate(arg)
|
||||||
elif arg:
|
elif arg:
|
||||||
if arg.get('bom_no') and self.set_rate_of_sub_assembly_item_based_on_bom:
|
#Customer Provided parts will have zero rate
|
||||||
rate = self.get_bom_unitcost(arg['bom_no'])
|
if not frappe.db.get_value('Item', arg["item_code"], 'is_customer_provided_item'):
|
||||||
else:
|
if arg.get('bom_no') and self.set_rate_of_sub_assembly_item_based_on_bom:
|
||||||
if self.rm_cost_as_per == 'Valuation Rate':
|
rate = self.get_bom_unitcost(arg['bom_no'])
|
||||||
rate = self.get_valuation_rate(arg)
|
else:
|
||||||
elif self.rm_cost_as_per == 'Last Purchase Rate':
|
if self.rm_cost_as_per == 'Valuation Rate':
|
||||||
rate = arg.get('last_purchase_rate') \
|
rate = self.get_valuation_rate(arg)
|
||||||
or frappe.db.get_value("Item", arg['item_code'], "last_purchase_rate")
|
elif self.rm_cost_as_per == 'Last Purchase Rate':
|
||||||
elif self.rm_cost_as_per == "Price List":
|
rate = arg.get('last_purchase_rate') \
|
||||||
if not self.buying_price_list:
|
or frappe.db.get_value("Item", arg['item_code'], "last_purchase_rate")
|
||||||
frappe.throw(_("Please select Price List"))
|
elif self.rm_cost_as_per == "Price List":
|
||||||
rate = frappe.db.get_value("Item Price", {"price_list": self.buying_price_list,
|
if not self.buying_price_list:
|
||||||
"item_code": arg["item_code"]}, "price_list_rate") or 0.0
|
frappe.throw(_("Please select Price List"))
|
||||||
|
rate = frappe.db.get_value("Item Price", {"price_list": self.buying_price_list,
|
||||||
|
"item_code": arg["item_code"]}, "price_list_rate") or 0.0
|
||||||
|
|
||||||
price_list_currency = frappe.db.get_value("Price List",
|
price_list_currency = frappe.db.get_value("Price List",
|
||||||
self.buying_price_list, "currency")
|
self.buying_price_list, "currency")
|
||||||
if price_list_currency != self.company_currency():
|
if price_list_currency != self.company_currency():
|
||||||
rate = flt(rate * self.conversion_rate)
|
rate = flt(rate * self.conversion_rate)
|
||||||
|
|
||||||
if not rate:
|
if not rate:
|
||||||
frappe.msgprint(_("{0} not found for Item {1}")
|
frappe.msgprint(_("{0} not found for Item {1}")
|
||||||
.format(self.rm_cost_as_per, arg["item_code"]), alert=True)
|
.format(self.rm_cost_as_per, arg["item_code"]), alert=True)
|
||||||
|
|
||||||
return flt(rate)
|
return flt(rate)
|
||||||
|
|
||||||
|
|||||||
@@ -131,4 +131,4 @@ class TestBOM(unittest.TestCase):
|
|||||||
self.assertEqual(bom.base_total_cost, 33000)
|
self.assertEqual(bom.base_total_cost, 33000)
|
||||||
|
|
||||||
def get_default_bom(item_code="_Test FG Item 2"):
|
def get_default_bom(item_code="_Test FG Item 2"):
|
||||||
return frappe.db.get_value("BOM", {"item": item_code, "is_active": 1, "is_default": 1})
|
return frappe.db.get_value("BOM", {"item": item_code, "is_active": 1, "is_default": 1})
|
||||||
@@ -419,8 +419,8 @@ class ProductionPlan(Document):
|
|||||||
for item in self.mr_items:
|
for item in self.mr_items:
|
||||||
item_doc = frappe.get_cached_doc('Item', item.item_code)
|
item_doc = frappe.get_cached_doc('Item', item.item_code)
|
||||||
|
|
||||||
# key for Sales Order:Material Request Type
|
# key for Sales Order:Material Request Type:Customer
|
||||||
key = '{}:{}'.format(item.sales_order, item_doc.default_material_request_type)
|
key = '{}:{}:{}'.format(item.sales_order, item_doc.default_material_request_type,item_doc.customer or '')
|
||||||
schedule_date = add_days(nowdate(), cint(item_doc.lead_time_days))
|
schedule_date = add_days(nowdate(), cint(item_doc.lead_time_days))
|
||||||
|
|
||||||
if not key in material_request_map:
|
if not key in material_request_map:
|
||||||
@@ -432,7 +432,8 @@ class ProductionPlan(Document):
|
|||||||
"status": "Draft",
|
"status": "Draft",
|
||||||
"company": self.company,
|
"company": self.company,
|
||||||
"requested_by": frappe.session.user,
|
"requested_by": frappe.session.user,
|
||||||
'material_request_type': item_doc.default_material_request_type
|
'material_request_type': item_doc.default_material_request_type,
|
||||||
|
'customer': item_doc.customer or ''
|
||||||
})
|
})
|
||||||
material_request_list.append(material_request)
|
material_request_list.append(material_request)
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -142,12 +142,27 @@ class TestProductionPlan(unittest.TestCase):
|
|||||||
|
|
||||||
self.assertEqual(sales_orders, [])
|
self.assertEqual(sales_orders, [])
|
||||||
|
|
||||||
|
def test_pp_to_mr_customer_provided(self):
|
||||||
|
#Material Request from Production Plan for Customer Provided
|
||||||
|
create_item('CUST-0987', is_customer_provided_item = 1, customer = '_Test Customer', is_purchase_item = 0)
|
||||||
|
create_item('Production Item CUST')
|
||||||
|
for item, raw_materials in {'Production Item CUST': ['Raw Material Item 1', 'CUST-0987']}.items():
|
||||||
|
if not frappe.db.get_value('BOM', {'item': item}):
|
||||||
|
make_bom(item = item, raw_materials = raw_materials)
|
||||||
|
production_plan = create_production_plan(item_code = 'Production Item CUST')
|
||||||
|
production_plan.make_material_request()
|
||||||
|
material_request = frappe.get_value('Material Request Item', {'production_plan': production_plan.name}, 'parent')
|
||||||
|
mr = frappe.get_doc('Material Request', material_request)
|
||||||
|
self.assertTrue(mr.material_request_type, 'Customer Provided')
|
||||||
|
self.assertTrue(mr.customer, '_Test Customer')
|
||||||
|
|
||||||
def create_production_plan(**args):
|
def create_production_plan(**args):
|
||||||
args = frappe._dict(args)
|
args = frappe._dict(args)
|
||||||
|
|
||||||
pln = frappe.get_doc({
|
pln = frappe.get_doc({
|
||||||
'doctype': 'Production Plan',
|
'doctype': 'Production Plan',
|
||||||
'company': args.company or '_Test Company',
|
'company': args.company or '_Test Company',
|
||||||
|
'customer': args.customer or '_Test Customer',
|
||||||
'posting_date': nowdate(),
|
'posting_date': nowdate(),
|
||||||
'include_non_stock_items': args.include_non_stock_items or 1,
|
'include_non_stock_items': args.include_non_stock_items or 1,
|
||||||
'include_subcontracted_items': args.include_subcontracted_items or 1,
|
'include_subcontracted_items': args.include_subcontracted_items or 1,
|
||||||
|
|||||||
@@ -68,12 +68,19 @@ erpnext.buying.BuyingController = erpnext.TransactionController.extend({
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.frm.set_query("item_code", "items", function() {
|
this.frm.set_query("item_code", "items", function() {
|
||||||
if(me.frm.doc.is_subcontracted == "Yes") {
|
if (me.frm.doc.is_subcontracted == "Yes") {
|
||||||
return{
|
return{
|
||||||
query: "erpnext.controllers.queries.item_query",
|
query: "erpnext.controllers.queries.item_query",
|
||||||
filters:{ 'is_sub_contracted_item': 1 }
|
filters:{ 'is_sub_contracted_item': 1 }
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
|
else if (me.frm.doc.material_request_type == "Customer Provided") {
|
||||||
|
return{
|
||||||
|
query: "erpnext.controllers.queries.item_query",
|
||||||
|
filters:{ 'customer': me.frm.doc.customer }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
return{
|
return{
|
||||||
query: "erpnext.controllers.queries.item_query",
|
query: "erpnext.controllers.queries.item_query",
|
||||||
filters: {'is_purchase_item': 1}
|
filters: {'is_purchase_item': 1}
|
||||||
|
|||||||
@@ -177,6 +177,9 @@ class DeliveryNote(SellingController):
|
|||||||
frappe.msgprint(_("Note: Item {0} entered multiple times").format(d.item_code))
|
frappe.msgprint(_("Note: Item {0} entered multiple times").format(d.item_code))
|
||||||
else:
|
else:
|
||||||
chk_dupl_itm.append(f)
|
chk_dupl_itm.append(f)
|
||||||
|
#Customer Provided parts will have zero valuation rate
|
||||||
|
if frappe.db.get_value('Item', d.item_code, 'is_customer_provided_item'):
|
||||||
|
d.allow_zero_valuation_rate = 1
|
||||||
|
|
||||||
def validate_warehouse(self):
|
def validate_warehouse(self):
|
||||||
super(DeliveryNote, self).validate_warehouse()
|
super(DeliveryNote, self).validate_warehouse()
|
||||||
|
|||||||
@@ -115,6 +115,8 @@ frappe.ui.form.on("Item", {
|
|||||||
['is_stock_item', 'has_serial_no', 'has_batch_no'].forEach((fieldname) => {
|
['is_stock_item', 'has_serial_no', 'has_batch_no'].forEach((fieldname) => {
|
||||||
frm.set_df_property(fieldname, 'read_only', stock_exists);
|
frm.set_df_property(fieldname, 'read_only', stock_exists);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
frm.toggle_reqd('customer', frm.doc.is_customer_provided_item ? 1:0);
|
||||||
},
|
},
|
||||||
|
|
||||||
validate: function(frm){
|
validate: function(frm){
|
||||||
@@ -124,6 +126,10 @@ frappe.ui.form.on("Item", {
|
|||||||
image: function() {
|
image: function() {
|
||||||
refresh_field("image_view");
|
refresh_field("image_view");
|
||||||
},
|
},
|
||||||
|
|
||||||
|
is_customer_provided_item: function(frm) {
|
||||||
|
frm.toggle_reqd('customer', frm.doc.is_customer_provided_item ? 1:0);
|
||||||
|
},
|
||||||
|
|
||||||
is_fixed_asset: function(frm) {
|
is_fixed_asset: function(frm) {
|
||||||
frm.call({
|
frm.call({
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -124,6 +124,7 @@ class Item(WebsiteGenerator):
|
|||||||
self.validate_uom_conversion_factor()
|
self.validate_uom_conversion_factor()
|
||||||
self.validate_item_defaults()
|
self.validate_item_defaults()
|
||||||
self.update_defaults_from_item_group()
|
self.update_defaults_from_item_group()
|
||||||
|
self.validate_customer_provided_part()
|
||||||
|
|
||||||
if not self.get("__islocal"):
|
if not self.get("__islocal"):
|
||||||
self.old_item_group = frappe.db.get_value(self.doctype, self.name, "item_group")
|
self.old_item_group = frappe.db.get_value(self.doctype, self.name, "item_group")
|
||||||
@@ -143,6 +144,14 @@ class Item(WebsiteGenerator):
|
|||||||
if cint(frappe.db.get_single_value('Stock Settings', 'clean_description_html')):
|
if cint(frappe.db.get_single_value('Stock Settings', 'clean_description_html')):
|
||||||
self.description = clean_html(self.description)
|
self.description = clean_html(self.description)
|
||||||
|
|
||||||
|
def validate_customer_provided_part(self):
|
||||||
|
if self.is_customer_provided_item:
|
||||||
|
if self.is_purchase_item:
|
||||||
|
frappe.throw(_('"Customer Provided Item" cannot be Purchase Item also'))
|
||||||
|
if self.valuation_rate:
|
||||||
|
frappe.throw(_('"Customer Provided Item" cannot have Valuation Rate'))
|
||||||
|
self.default_material_request_type = "Customer Provided"
|
||||||
|
|
||||||
def add_price(self, price_list=None):
|
def add_price(self, price_list=None):
|
||||||
'''Add a new price'''
|
'''Add a new price'''
|
||||||
if not price_list:
|
if not price_list:
|
||||||
|
|||||||
@@ -319,7 +319,7 @@ def make_item_variant():
|
|||||||
|
|
||||||
test_records = frappe.get_test_records('Item')
|
test_records = frappe.get_test_records('Item')
|
||||||
|
|
||||||
def create_item(item_code, is_stock_item=None, valuation_rate=0, warehouse=None):
|
def create_item(item_code, is_stock_item=None, valuation_rate=0, warehouse=None, is_customer_provided_item=None, customer=None, is_purchase_item=None):
|
||||||
if not frappe.db.exists("Item", item_code):
|
if not frappe.db.exists("Item", item_code):
|
||||||
item = frappe.new_doc("Item")
|
item = frappe.new_doc("Item")
|
||||||
item.item_code = item_code
|
item.item_code = item_code
|
||||||
@@ -328,6 +328,9 @@ def create_item(item_code, is_stock_item=None, valuation_rate=0, warehouse=None)
|
|||||||
item.item_group = "All Item Groups"
|
item.item_group = "All Item Groups"
|
||||||
item.is_stock_item = is_stock_item or 1
|
item.is_stock_item = is_stock_item or 1
|
||||||
item.valuation_rate = valuation_rate or 0.0
|
item.valuation_rate = valuation_rate or 0.0
|
||||||
|
item.is_purchase_item = is_purchase_item
|
||||||
|
item.is_customer_provided_item = is_customer_provided_item
|
||||||
|
item.customer = customer or ''
|
||||||
item.append("item_defaults", {
|
item.append("item_defaults", {
|
||||||
"default_warehouse": warehouse or '_Test Warehouse - _TC',
|
"default_warehouse": warehouse or '_Test Warehouse - _TC',
|
||||||
"company": "_Test Company"
|
"company": "_Test Company"
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ frappe.ui.form.on('Material Request', {
|
|||||||
|
|
||||||
refresh: function(frm) {
|
refresh: function(frm) {
|
||||||
frm.events.make_custom_buttons(frm);
|
frm.events.make_custom_buttons(frm);
|
||||||
|
frm.toggle_reqd('customer', frm.doc.material_request_type=="Customer Provided");
|
||||||
},
|
},
|
||||||
|
|
||||||
make_custom_buttons: function(frm) {
|
make_custom_buttons: function(frm) {
|
||||||
@@ -61,6 +62,11 @@ frappe.ui.form.on('Material Request', {
|
|||||||
() => frm.events.make_stock_entry(frm), __("Make"));
|
() => frm.events.make_stock_entry(frm), __("Make"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (frm.doc.material_request_type === "Customer Provided") {
|
||||||
|
frm.add_custom_button(__("Material Receipt"),
|
||||||
|
() => frm.events.make_stock_entry(frm), __("Make"));
|
||||||
|
}
|
||||||
|
|
||||||
if (frm.doc.material_request_type === "Purchase") {
|
if (frm.doc.material_request_type === "Purchase") {
|
||||||
frm.add_custom_button(__('Purchase Order'),
|
frm.add_custom_button(__('Purchase Order'),
|
||||||
() => frm.events.make_purchase_order(frm), __("Make"));
|
() => frm.events.make_purchase_order(frm), __("Make"));
|
||||||
@@ -259,6 +265,9 @@ frappe.ui.form.on('Material Request', {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
material_request_type: function(frm) {
|
||||||
|
frm.toggle_reqd('customer', frm.doc.material_request_type=="Customer Provided");
|
||||||
|
},
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -70,7 +70,7 @@ class MaterialRequest(BuyingController):
|
|||||||
from erpnext.controllers.status_updater import validate_status
|
from erpnext.controllers.status_updater import validate_status
|
||||||
validate_status(self.status,
|
validate_status(self.status,
|
||||||
["Draft", "Submitted", "Stopped", "Cancelled", "Pending",
|
["Draft", "Submitted", "Stopped", "Cancelled", "Pending",
|
||||||
"Partially Ordered", "Ordered", "Issued", "Transferred"])
|
"Partially Ordered", "Ordered", "Issued", "Transferred", "Received"])
|
||||||
|
|
||||||
validate_for_items(self)
|
validate_for_items(self)
|
||||||
|
|
||||||
@@ -154,7 +154,7 @@ class MaterialRequest(BuyingController):
|
|||||||
|
|
||||||
for d in self.get("items"):
|
for d in self.get("items"):
|
||||||
if d.name in mr_items:
|
if d.name in mr_items:
|
||||||
if self.material_request_type in ("Material Issue", "Material Transfer"):
|
if self.material_request_type in ("Material Issue", "Material Transfer", "Customer Provided"):
|
||||||
d.ordered_qty = flt(frappe.db.sql("""select sum(transfer_qty)
|
d.ordered_qty = flt(frappe.db.sql("""select sum(transfer_qty)
|
||||||
from `tabStock Entry Detail` where material_request = %s
|
from `tabStock Entry Detail` where material_request = %s
|
||||||
and material_request_item = %s and docstatus = 1""",
|
and material_request_item = %s and docstatus = 1""",
|
||||||
@@ -239,6 +239,18 @@ def update_item(obj, target, source_parent):
|
|||||||
target.qty = flt(flt(obj.stock_qty) - flt(obj.ordered_qty))/ target.conversion_factor
|
target.qty = flt(flt(obj.stock_qty) - flt(obj.ordered_qty))/ target.conversion_factor
|
||||||
target.stock_qty = (target.qty * target.conversion_factor)
|
target.stock_qty = (target.qty * target.conversion_factor)
|
||||||
|
|
||||||
|
def get_list_context(context=None):
|
||||||
|
from erpnext.controllers.website_list_for_contact import get_list_context
|
||||||
|
list_context = get_list_context(context)
|
||||||
|
list_context.update({
|
||||||
|
'show_sidebar': True,
|
||||||
|
'show_search': True,
|
||||||
|
'no_breadcrumbs': True,
|
||||||
|
'title': _('Material Request'),
|
||||||
|
})
|
||||||
|
|
||||||
|
return list_context
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def update_status(name, status):
|
def update_status(name, status):
|
||||||
material_request = frappe.get_doc('Material Request', name)
|
material_request = frappe.get_doc('Material Request', name)
|
||||||
@@ -400,7 +412,7 @@ def make_stock_entry(source_name, target_doc=None):
|
|||||||
target.transfer_qty = qty * obj.conversion_factor
|
target.transfer_qty = qty * obj.conversion_factor
|
||||||
target.conversion_factor = obj.conversion_factor
|
target.conversion_factor = obj.conversion_factor
|
||||||
|
|
||||||
if source_parent.material_request_type == "Material Transfer":
|
if source_parent.material_request_type == "Material Transfer" or source_parent.material_request_type == "Customer Provided":
|
||||||
target.t_warehouse = obj.warehouse
|
target.t_warehouse = obj.warehouse
|
||||||
else:
|
else:
|
||||||
target.s_warehouse = obj.warehouse
|
target.s_warehouse = obj.warehouse
|
||||||
@@ -410,6 +422,9 @@ def make_stock_entry(source_name, target_doc=None):
|
|||||||
if source.job_card:
|
if source.job_card:
|
||||||
target.purpose = 'Material Transfer for Manufacture'
|
target.purpose = 'Material Transfer for Manufacture'
|
||||||
|
|
||||||
|
if source.material_request_type == "Customer Provided":
|
||||||
|
target.purpose = "Material Receipt"
|
||||||
|
|
||||||
target.run_method("calculate_rate_and_amount")
|
target.run_method("calculate_rate_and_amount")
|
||||||
target.set_job_card_data()
|
target.set_job_card_data()
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ frappe.listview_settings['Material Request'] = {
|
|||||||
return [__("Transfered"), "green", "per_ordered,=,100"];
|
return [__("Transfered"), "green", "per_ordered,=,100"];
|
||||||
} else if (doc.material_request_type == "Material Issue") {
|
} else if (doc.material_request_type == "Material Issue") {
|
||||||
return [__("Issued"), "green", "per_ordered,=,100"];
|
return [__("Issued"), "green", "per_ordered,=,100"];
|
||||||
|
} else if (doc.material_request_type == "Customer Provided") {
|
||||||
|
return [__("Received"), "green", "per_ordered,=,100"];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ from __future__ import unicode_literals
|
|||||||
import frappe, unittest, erpnext
|
import frappe, unittest, erpnext
|
||||||
from frappe.utils import flt, today
|
from frappe.utils import flt, today
|
||||||
from erpnext.stock.doctype.material_request.material_request import raise_work_orders
|
from erpnext.stock.doctype.material_request.material_request import raise_work_orders
|
||||||
|
from erpnext.stock.doctype.item.test_item import create_item
|
||||||
|
|
||||||
class TestMaterialRequest(unittest.TestCase):
|
class TestMaterialRequest(unittest.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
@@ -601,11 +602,25 @@ class TestMaterialRequest(unittest.TestCase):
|
|||||||
mr = frappe.get_doc("Material Request", mr.name)
|
mr = frappe.get_doc("Material Request", mr.name)
|
||||||
self.assertEqual(mr.per_ordered, 100)
|
self.assertEqual(mr.per_ordered, 100)
|
||||||
|
|
||||||
|
def test_customer_provided_parts_mr(self):
|
||||||
|
from erpnext.stock.doctype.material_request.material_request import make_stock_entry
|
||||||
|
create_item('CUST-0987', is_customer_provided_item = 1, customer = '_Test Customer', is_purchase_item = 0)
|
||||||
|
mr = make_material_request(item_code='CUST-0987', material_request_type='Customer Provided')
|
||||||
|
se = make_stock_entry(mr.name)
|
||||||
|
se.insert()
|
||||||
|
se.submit()
|
||||||
|
self.assertEqual(se.get("items")[0].amount, 0)
|
||||||
|
self.assertEqual(se.get("items")[0].material_request, mr.name)
|
||||||
|
mr = frappe.get_doc("Material Request", mr.name)
|
||||||
|
mr.submit()
|
||||||
|
self.assertEqual(mr.per_ordered, 100)
|
||||||
|
|
||||||
def make_material_request(**args):
|
def make_material_request(**args):
|
||||||
args = frappe._dict(args)
|
args = frappe._dict(args)
|
||||||
mr = frappe.new_doc("Material Request")
|
mr = frappe.new_doc("Material Request")
|
||||||
mr.material_request_type = args.material_request_type or "Purchase"
|
mr.material_request_type = args.material_request_type or "Purchase"
|
||||||
mr.company = args.company or "_Test Company"
|
mr.company = args.company or "_Test Company"
|
||||||
|
mr.customer = args.customer or '_Test Customer'
|
||||||
mr.append("items", {
|
mr.append("items", {
|
||||||
"item_code": args.item_code or "_Test Item",
|
"item_code": args.item_code or "_Test Item",
|
||||||
"qty": args.qty or 10,
|
"qty": args.qty or 10,
|
||||||
|
|||||||
@@ -179,6 +179,10 @@ class StockEntry(StockController):
|
|||||||
frappe.throw(_("Row #{0}: Please specify Serial No for Item {1}").format(item.idx, item.item_code),
|
frappe.throw(_("Row #{0}: Please specify Serial No for Item {1}").format(item.idx, item.item_code),
|
||||||
frappe.MandatoryError)
|
frappe.MandatoryError)
|
||||||
|
|
||||||
|
#Customer Provided parts will have zero valuation rate
|
||||||
|
if frappe.db.get_value('Item', item.item_code, 'is_customer_provided_item'):
|
||||||
|
item.allow_zero_valuation_rate = 1
|
||||||
|
|
||||||
def validate_qty(self):
|
def validate_qty(self):
|
||||||
manufacture_purpose = ["Manufacture", "Material Consumption for Manufacture"]
|
manufacture_purpose = ["Manufacture", "Material Consumption for Manufacture"]
|
||||||
|
|
||||||
|
|||||||
@@ -720,6 +720,13 @@ class TestStockEntry(unittest.TestCase):
|
|||||||
for d in stock_entry.get('items'):
|
for d in stock_entry.get('items'):
|
||||||
self.assertEqual(item_quantity.get(d.item_code), d.qty)
|
self.assertEqual(item_quantity.get(d.item_code), d.qty)
|
||||||
|
|
||||||
|
def test_customer_provided_parts_se(self):
|
||||||
|
create_item('CUST-0987', is_customer_provided_item = 1, customer = '_Test Customer', is_purchase_item = 0)
|
||||||
|
se = make_stock_entry(item_code='CUST-0987', purporse = 'Material Receipt', qty=4, to_warehouse = "_Test Warehouse - _TC")
|
||||||
|
self.assertEqual(se.get("items")[0].allow_zero_valuation_rate, 1)
|
||||||
|
self.assertEqual(se.get("items")[0].amount, 0)
|
||||||
|
|
||||||
|
|
||||||
def make_serialized_item(item_code=None, serial_no=None, target_warehouse=None):
|
def make_serialized_item(item_code=None, serial_no=None, target_warehouse=None):
|
||||||
se = frappe.copy_doc(test_records[0])
|
se = frappe.copy_doc(test_records[0])
|
||||||
se.get("items")[0].item_code = item_code or "_Test Serialized Item With Series"
|
se.get("items")[0].item_code = item_code or "_Test Serialized Item With Series"
|
||||||
|
|||||||
74
erpnext/templates/pages/material_request_info.html
Normal file
74
erpnext/templates/pages/material_request_info.html
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
{% extends "templates/web.html" %}
|
||||||
|
{% from "erpnext/templates/includes/order/order_macros.html" import item_name_and_description %}
|
||||||
|
|
||||||
|
{% block breadcrumbs %}
|
||||||
|
{% include "templates/includes/breadcrumbs.html" %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block title %}{{ doc.name }}{% endblock %}
|
||||||
|
|
||||||
|
{% block header %}
|
||||||
|
<h1>{{ doc.name }}</h1>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block header_actions %}
|
||||||
|
<a class='btn btn-xs btn-default' href='/printview?doctype={{ doc.doctype}}&name={{ doc.name }}&format={{ print_format }}' target="_blank" rel="noopener noreferrer">{{ _("Print") }}</a>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block page_content %}
|
||||||
|
|
||||||
|
<div class="row transaction-subheading">
|
||||||
|
<div class="col-xs-6">
|
||||||
|
|
||||||
|
<span class="indicator {{ doc.indicator_color or ("blue" if doc.docstatus==1 else "darkgrey") }}">
|
||||||
|
{{ _(doc.get('indicator_title')) or _(doc.status) or _("Submitted") }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="col-xs-6 text-muted text-right small">
|
||||||
|
{{ frappe.utils.formatdate(doc.transaction_date, 'medium') }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% if doc._header %}
|
||||||
|
{{ doc._header }}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<div class="order-container">
|
||||||
|
|
||||||
|
<!-- items -->
|
||||||
|
<div class="order-item-table">
|
||||||
|
<div class="row order-items order-item-header text-muted">
|
||||||
|
<div class="col-sm-6 col-xs-6 h6 text-uppercase">
|
||||||
|
{{ _("Item") }}
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-3 col-xs-3 text-right h6 text-uppercase">
|
||||||
|
{{ _("Work Order") }}
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-3 col-xs-3 text-right h6 text-uppercase">
|
||||||
|
{{ _("Quantity") }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% for d in doc.items %}
|
||||||
|
{% if d.customer_provided %}
|
||||||
|
<div class="row order-items">
|
||||||
|
<div class="col-sm-6 col-xs-6">
|
||||||
|
{{ item_name_and_description(d) }}
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-3 col-xs-3 text-right">
|
||||||
|
{% for wo in d.work_orders %}
|
||||||
|
<p class="text-muted small">{{_(wo.name) }}</p>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-3 col-xs-3 text-right">
|
||||||
|
{{ d.qty }}
|
||||||
|
{% if d.delivered_qty is defined and d.delivered_qty != None %}
|
||||||
|
<p class="text-muted small">{{
|
||||||
|
_("Delivered: {0}").format(d.delivered_qty) }}</p>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
49
erpnext/templates/pages/material_request_info.py
Normal file
49
erpnext/templates/pages/material_request_info.py
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||||
|
# License: GNU General Public License v3. See license.txt
|
||||||
|
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
import frappe
|
||||||
|
from frappe import _
|
||||||
|
|
||||||
|
from frappe.utils import flt
|
||||||
|
|
||||||
|
def get_context(context):
|
||||||
|
context.no_cache = 1
|
||||||
|
context.show_sidebar = True
|
||||||
|
context.doc = frappe.get_doc(frappe.form_dict.doctype, frappe.form_dict.name)
|
||||||
|
if hasattr(context.doc, "set_indicator"):
|
||||||
|
context.doc.set_indicator()
|
||||||
|
|
||||||
|
context.parents = frappe.form_dict.parents
|
||||||
|
context.title = frappe.form_dict.name
|
||||||
|
|
||||||
|
if not frappe.has_website_permission(context.doc):
|
||||||
|
frappe.throw(_("Not Permitted"), frappe.PermissionError)
|
||||||
|
|
||||||
|
default_print_format = frappe.db.get_value('Property Setter', dict(property='default_print_format', doc_type=frappe.form_dict.doctype), "value")
|
||||||
|
if default_print_format:
|
||||||
|
context.print_format = default_print_format
|
||||||
|
else:
|
||||||
|
context.print_format = "Standard"
|
||||||
|
context.doc.items = get_more_items_info(context.doc.items, context.doc.name)
|
||||||
|
|
||||||
|
def get_more_items_info(items, material_request):
|
||||||
|
for item in items:
|
||||||
|
item.customer_provided = frappe.get_value('Item', item.item_code, 'is_customer_provided_item')
|
||||||
|
item.work_orders = frappe.db.sql("""
|
||||||
|
select
|
||||||
|
wo.name, wo.status, wo_item.consumed_qty
|
||||||
|
from
|
||||||
|
`tabWork Order Item` wo_item, `tabWork Order` wo
|
||||||
|
where
|
||||||
|
wo_item.item_code=%s
|
||||||
|
and wo_item.consumed_qty=0
|
||||||
|
and wo_item.parent=wo.name
|
||||||
|
and wo.status not in ('Completed', 'Cancelled', 'Stopped')
|
||||||
|
order by
|
||||||
|
wo.name asc""", item.item_code, as_dict=1)
|
||||||
|
item.delivered_qty = flt(frappe.db.sql("""select sum(transfer_qty)
|
||||||
|
from `tabStock Entry Detail` where material_request = %s
|
||||||
|
and item_code = %s and docstatus = 1""",
|
||||||
|
(material_request, item.item_code))[0][0])
|
||||||
|
return items
|
||||||
Reference in New Issue
Block a user