[ Enhance ] Production to Work Order (#12902)

* remove occurrences of Production Order

* rename from report and jsons

* Change Production Order to Work Order

* change occurences of production order from other files

* resolve minor conflict issues and reports

* patch added

* codacy fix

* updated patches, leftover changes

* rename reports, rectify patches
This commit is contained in:
Zarrar
2018-03-20 12:38:43 +05:30
committed by Nabin Hait
parent c893268dea
commit 13ddc7e188
93 changed files with 902 additions and 710 deletions

View File

@@ -219,7 +219,7 @@ def get_data():
}, },
{ {
"type": "help", "type": "help",
"label": _("Production Order"), "label": _("Work Order"),
"youtube_id": "ZotgLyp2YFY" "youtube_id": "ZotgLyp2YFY"
}, },

View File

@@ -9,13 +9,13 @@ def get_data():
"items": [ "items": [
{ {
"type": "doctype", "type": "doctype",
"name": "Production Order", "name": "Work Order",
"description": _("Orders released for production."), "description": _("Orders released for production."),
}, },
{ {
"type": "doctype", "type": "doctype",
"name": "Production Plan", "name": "Production Plan",
"description": _("Generate Material Requests (MRP) and Production Orders."), "description": _("Generate Material Requests (MRP) and Work Orders."),
}, },
{ {
"type": "doctype", "type": "doctype",
@@ -92,26 +92,26 @@ def get_data():
{ {
"type": "report", "type": "report",
"is_query_report": True, "is_query_report": True,
"name": "Open Production Orders", "name": "Open Work Orders",
"doctype": "Production Order" "doctype": "Work Order"
}, },
{ {
"type": "report", "type": "report",
"is_query_report": True, "is_query_report": True,
"name": "Production Orders in Progress", "name": "Work Orders in Progress",
"doctype": "Production Order" "doctype": "Work Order"
}, },
{ {
"type": "report", "type": "report",
"is_query_report": True, "is_query_report": True,
"name": "Issued Items Against Production Order", "name": "Issued Items Against Work Order",
"doctype": "Production Order" "doctype": "Work Order"
}, },
{ {
"type": "report", "type": "report",
"is_query_report": True, "is_query_report": True,
"name": "Completed Production Orders", "name": "Completed Work Orders",
"doctype": "Production Order" "doctype": "Work Order"
},{ },{
"type": "page", "type": "page",
"name": "production-analytics", "name": "production-analytics",
@@ -143,7 +143,7 @@ def get_data():
}, },
{ {
"type": "help", "type": "help",
"label": _("Production Order"), "label": _("Work Order"),
"youtube_id": "ZotgLyp2YFY" "youtube_id": "ZotgLyp2YFY"
}, },
] ]

View File

@@ -7,7 +7,7 @@ import frappe, random, erpnext
from frappe.utils.make_random import how_many from frappe.utils.make_random import how_many
from frappe.desk import query_report from frappe.desk import query_report
from erpnext.manufacturing.doctype.workstation.workstation import WorkstationHolidayError from erpnext.manufacturing.doctype.workstation.workstation import WorkstationHolidayError
from erpnext.manufacturing.doctype.production_order.test_production_order import make_prod_order_test_record from erpnext.manufacturing.doctype.work_order.test_work_order import make_prod_order_test_record
def work(): def work():
frappe.set_user(frappe.db.get_global('demo_manufacturing_user')) frappe.set_user(frappe.db.get_global('demo_manufacturing_user'))
@@ -21,13 +21,13 @@ def work():
ppt.purchase_request_for_warehouse = "Stores - WPL" ppt.purchase_request_for_warehouse = "Stores - WPL"
ppt.run_method("get_open_sales_orders") ppt.run_method("get_open_sales_orders")
ppt.run_method("get_items") ppt.run_method("get_items")
ppt.run_method("raise_production_orders") ppt.run_method("raise_work_orders")
ppt.run_method("raise_material_requests") ppt.run_method("raise_material_requests")
frappe.db.commit() frappe.db.commit()
# submit production orders # submit work orders
for pro in frappe.db.get_values("Production Order", {"docstatus": 0}, "name"): for pro in frappe.db.get_values("Work Order", {"docstatus": 0}, "name"):
b = frappe.get_doc("Production Order", pro[0]) b = frappe.get_doc("Work Order", pro[0])
b.wip_warehouse = "Work in Progress - WPL" b.wip_warehouse = "Work in Progress - WPL"
b.submit() b.submit()
frappe.db.commit() frappe.db.commit()
@@ -40,12 +40,12 @@ def work():
# stores -> wip # stores -> wip
if random.random() < 0.3: if random.random() < 0.3:
for pro in query_report.run("Open Production Orders")["result"][:how_many("Stock Entry for WIP")]: for pro in query_report.run("Open Work Orders")["result"][:how_many("Stock Entry for WIP")]:
make_stock_entry_from_pro(pro[0], "Material Transfer for Manufacture") make_stock_entry_from_pro(pro[0], "Material Transfer for Manufacture")
# wip -> fg # wip -> fg
if random.random() < 0.3: if random.random() < 0.3:
for pro in query_report.run("Production Orders in Progress")["result"][:how_many("Stock Entry for FG")]: for pro in query_report.run("Work Orders in Progress")["result"][:how_many("Stock Entry for FG")]:
make_stock_entry_from_pro(pro[0], "Manufacture") make_stock_entry_from_pro(pro[0], "Manufacture")
for bom in frappe.get_all('BOM', fields=['item'], filters = {'with_operations': 1}): for bom in frappe.get_all('BOM', fields=['item'], filters = {'with_operations': 1}):
@@ -57,7 +57,7 @@ def work():
# submit time logs # submit time logs
for timesheet in frappe.get_all("Timesheet", ["name"], {"docstatus": 0, for timesheet in frappe.get_all("Timesheet", ["name"], {"docstatus": 0,
"production_order": ("!=", ""), "to_time": ("<", frappe.flags.current_date)}): "work_order": ("!=", ""), "to_time": ("<", frappe.flags.current_date)}):
timesheet = frappe.get_doc("Timesheet", timesheet.name) timesheet = frappe.get_doc("Timesheet", timesheet.name)
try: try:
timesheet.submit() timesheet.submit()
@@ -68,10 +68,10 @@ def work():
pass pass
def make_stock_entry_from_pro(pro_id, purpose): def make_stock_entry_from_pro(pro_id, purpose):
from erpnext.manufacturing.doctype.production_order.production_order import make_stock_entry from erpnext.manufacturing.doctype.work_order.work_order import make_stock_entry
from erpnext.stock.stock_ledger import NegativeStockError from erpnext.stock.stock_ledger import NegativeStockError
from erpnext.stock.doctype.stock_entry.stock_entry import IncorrectValuationRateError, \ from erpnext.stock.doctype.stock_entry.stock_entry import IncorrectValuationRateError, \
DuplicateEntryForProductionOrderError, OperationsNotCompleteError DuplicateEntryForWorkOrderError, OperationsNotCompleteError
try: try:
st = frappe.get_doc(make_stock_entry(pro_id, purpose)) st = frappe.get_doc(make_stock_entry(pro_id, purpose))
@@ -83,6 +83,6 @@ def make_stock_entry_from_pro(pro_id, purpose):
frappe.db.commit() frappe.db.commit()
st.submit() st.submit()
frappe.db.commit() frappe.db.commit()
except (NegativeStockError, IncorrectValuationRateError, DuplicateEntryForProductionOrderError, except (NegativeStockError, IncorrectValuationRateError, DuplicateEntryForWorkOrderError,
OperationsNotCompleteError): OperationsNotCompleteError):
frappe.db.rollback() frappe.db.rollback()

View File

@@ -6,7 +6,7 @@ data = {
'Supplier', 'Supplier',
'Sales Order', 'Sales Order',
'Purchase Order', 'Purchase Order',
'Production Order', 'Work Order',
'Task', 'Task',
'Accounts', 'Accounts',
'HR', 'HR',

View File

@@ -49,7 +49,7 @@ my_account_context = "erpnext.shopping_cart.utils.update_my_account_context"
email_append_to = ["Job Applicant", "Lead", "Opportunity", "Issue"] email_append_to = ["Job Applicant", "Lead", "Opportunity", "Issue"]
calendars = ["Task", "Production Order", "Leave Application", "Sales Order", "Holiday List", "Course Schedule"] calendars = ["Task", "Work Order", "Leave Application", "Sales Order", "Holiday List", "Course Schedule"]

View File

@@ -72,7 +72,7 @@ class Employee(NestedSet):
user.flags.ignore_permissions = True user.flags.ignore_permissions = True
if "Employee" not in user.get("roles"): if "Employee" not in user.get("roles"):
user.add_roles("Employee") user.append_roles("Employee")
# copy details like Fullname, DOB and Image to User # copy details like Fullname, DOB and Image to User
if self.employee_name and not (user.first_name and user.last_name): if self.employee_name and not (user.first_name and user.last_name):

View File

@@ -48,7 +48,7 @@
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"description": "Disables creation of time logs against Production Orders. Operations shall not be tracked against Production Order", "description": "Disables creation of time logs against Work Orders. Operations shall not be tracked against Work Order",
"fieldname": "disable_capacity_planning", "fieldname": "disable_capacity_planning",
"fieldtype": "Check", "fieldtype": "Check",
"hidden": 0, "hidden": 0,
@@ -454,7 +454,7 @@
"istable": 0, "istable": 0,
"max_attachments": 0, "max_attachments": 0,
"menu_index": 0, "menu_index": 0,
"modified": "2017-07-31 19:25:04.242693", "modified": "2018-02-16 13:18:17.964103",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Manufacturing", "module": "Manufacturing",
"name": "Manufacturing Settings", "name": "Manufacturing Settings",

View File

@@ -37,8 +37,8 @@ frappe.ui.form.on('Production Plan', {
if (frm.doc.docstatus === 1 && frm.doc.po_items if (frm.doc.docstatus === 1 && frm.doc.po_items
&& frm.doc.status != 'Completed') { && frm.doc.status != 'Completed') {
frm.add_custom_button(__("Production Order"), ()=> { frm.add_custom_button(__("Work Order"), ()=> {
frm.trigger("make_production_order"); frm.trigger("make_work_order");
}, __("Make")); }, __("Make"));
} }
@@ -52,9 +52,9 @@ frappe.ui.form.on('Production Plan', {
frm.trigger("material_requirement"); frm.trigger("material_requirement");
}, },
make_production_order: function(frm) { make_work_order: function(frm) {
frappe.call({ frappe.call({
method: "make_production_order", method: "make_work_order",
freeze: true, freeze: true,
doc: frm.doc, doc: frm.doc,
callback: function() { callback: function() {

View File

@@ -44,6 +44,7 @@
"reqd": 1, "reqd": 1,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0,
"unique": 0 "unique": 0
}, },
{ {
@@ -76,6 +77,7 @@
"reqd": 1, "reqd": 1,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0,
"unique": 0 "unique": 0
}, },
{ {
@@ -108,6 +110,7 @@
"reqd": 0, "reqd": 0,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0,
"unique": 0 "unique": 0
}, },
{ {
@@ -137,6 +140,7 @@
"reqd": 0, "reqd": 0,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0,
"unique": 0 "unique": 0
}, },
{ {
@@ -168,6 +172,7 @@
"reqd": 1, "reqd": 1,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0,
"unique": 0 "unique": 0
}, },
{ {
@@ -201,6 +206,7 @@
"reqd": 0, "reqd": 0,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0,
"unique": 0 "unique": 0
}, },
{ {
@@ -232,6 +238,7 @@
"reqd": 0, "reqd": 0,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0,
"unique": 0 "unique": 0
}, },
{ {
@@ -264,6 +271,7 @@
"reqd": 0, "reqd": 0,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0,
"unique": 0 "unique": 0
}, },
{ {
@@ -296,6 +304,7 @@
"reqd": 0, "reqd": 0,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0,
"unique": 0 "unique": 0
}, },
{ {
@@ -328,6 +337,7 @@
"reqd": 0, "reqd": 0,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0,
"unique": 0 "unique": 0
}, },
{ {
@@ -357,6 +367,7 @@
"reqd": 0, "reqd": 0,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0,
"unique": 0, "unique": 0,
"width": "50%" "width": "50%"
}, },
@@ -388,6 +399,7 @@
"reqd": 0, "reqd": 0,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0,
"unique": 0 "unique": 0
}, },
{ {
@@ -418,6 +430,7 @@
"reqd": 0, "reqd": 0,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0,
"unique": 0 "unique": 0
}, },
{ {
@@ -451,6 +464,7 @@
"reqd": 0, "reqd": 0,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0,
"unique": 0 "unique": 0
}, },
{ {
@@ -483,6 +497,7 @@
"reqd": 0, "reqd": 0,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0,
"unique": 0 "unique": 0
}, },
{ {
@@ -514,6 +529,7 @@
"reqd": 0, "reqd": 0,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0,
"unique": 0 "unique": 0
}, },
{ {
@@ -546,6 +562,7 @@
"reqd": 0, "reqd": 0,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0,
"unique": 0 "unique": 0
}, },
{ {
@@ -578,6 +595,7 @@
"reqd": 0, "reqd": 0,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0,
"unique": 0 "unique": 0
}, },
{ {
@@ -609,6 +627,7 @@
"reqd": 0, "reqd": 0,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0,
"unique": 0 "unique": 0
}, },
{ {
@@ -640,6 +659,7 @@
"reqd": 0, "reqd": 0,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0,
"unique": 0 "unique": 0
}, },
{ {
@@ -658,7 +678,7 @@
"in_global_search": 0, "in_global_search": 0,
"in_list_view": 0, "in_list_view": 0,
"in_standard_filter": 0, "in_standard_filter": 0,
"label": "Get Items For Production Order", "label": "Get Items For Work Order",
"length": 0, "length": 0,
"no_copy": 0, "no_copy": 0,
"options": "", "options": "",
@@ -672,6 +692,7 @@
"reqd": 0, "reqd": 0,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0,
"unique": 0 "unique": 0
}, },
{ {
@@ -703,6 +724,7 @@
"reqd": 1, "reqd": 1,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0,
"unique": 0 "unique": 0
}, },
{ {
@@ -734,6 +756,7 @@
"reqd": 0, "reqd": 0,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0,
"unique": 0 "unique": 0
}, },
{ {
@@ -765,6 +788,7 @@
"reqd": 0, "reqd": 0,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0,
"unique": 0 "unique": 0
}, },
{ {
@@ -795,6 +819,7 @@
"reqd": 0, "reqd": 0,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0,
"unique": 0 "unique": 0
}, },
{ {
@@ -824,6 +849,7 @@
"reqd": 0, "reqd": 0,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0,
"unique": 0 "unique": 0
}, },
{ {
@@ -855,6 +881,7 @@
"reqd": 0, "reqd": 0,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0,
"unique": 0 "unique": 0
}, },
{ {
@@ -885,6 +912,7 @@
"reqd": 0, "reqd": 0,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0,
"unique": 0 "unique": 0
}, },
{ {
@@ -915,6 +943,7 @@
"reqd": 0, "reqd": 0,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0,
"unique": 0 "unique": 0
}, },
{ {
@@ -946,6 +975,7 @@
"reqd": 0, "reqd": 0,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0,
"unique": 0 "unique": 0
}, },
{ {
@@ -976,6 +1006,7 @@
"reqd": 0, "reqd": 0,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0,
"unique": 0 "unique": 0
}, },
{ {
@@ -1007,6 +1038,7 @@
"reqd": 0, "reqd": 0,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0,
"unique": 0 "unique": 0
}, },
{ {
@@ -1038,6 +1070,7 @@
"reqd": 0, "reqd": 0,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0,
"unique": 0 "unique": 0
}, },
{ {
@@ -1067,6 +1100,7 @@
"reqd": 0, "reqd": 0,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0,
"unique": 0 "unique": 0
}, },
{ {
@@ -1099,6 +1133,7 @@
"reqd": 0, "reqd": 0,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0,
"unique": 0 "unique": 0
}, },
{ {
@@ -1129,6 +1164,7 @@
"reqd": 0, "reqd": 0,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0,
"unique": 0 "unique": 0
} }
], ],
@@ -1143,7 +1179,7 @@
"issingle": 0, "issingle": 0,
"istable": 0, "istable": 0,
"max_attachments": 0, "max_attachments": 0,
"modified": "2018-02-15 13:18:59.092921", "modified": "2018-03-05 01:36:45.048493",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Manufacturing", "module": "Manufacturing",
"name": "Production Plan", "name": "Production Plan",

View File

@@ -8,7 +8,7 @@ from frappe import msgprint, _
from frappe.model.document import Document from frappe.model.document import Document
from erpnext.manufacturing.doctype.bom.bom import validate_bom_no 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 frappe.utils import cstr, flt, cint, nowdate, add_days, comma_and, now_datetime
from erpnext.manufacturing.doctype.production_order.production_order import get_item_details from erpnext.manufacturing.doctype.work_order.work_order import get_item_details
from six import string_types from six import string_types
class ProductionPlan(Document): class ProductionPlan(Document):
@@ -229,12 +229,12 @@ class ProductionPlan(Document):
def on_cancel(self): def on_cancel(self):
self.db_set('status', 'Cancelled') self.db_set('status', 'Cancelled')
self.delete_draft_production_order() self.delete_draft_work_order()
def delete_draft_production_order(self): def delete_draft_work_order(self):
for d in frappe.get_all('Production Order', fields = ["name"], for d in frappe.get_all('Work Order', fields = ["name"],
filters = {'docstatus': 0, 'production_plan': ("=", self.name)}): filters = {'docstatus': 0, 'production_plan': ("=", self.name)}):
frappe.delete_doc('Production Order', d.name) frappe.delete_doc('Work Order', d.name)
def set_status(self): def set_status(self):
self.status = { self.status = {
@@ -392,37 +392,37 @@ class ProductionPlan(Document):
'sales_order': data.sales_order 'sales_order': data.sales_order
}) })
def make_production_order(self): def make_work_order(self):
pro_list = [] wo_list = []
self.validate_data() self.validate_data()
items_data = self.get_production_items() items_data = self.get_production_items()
for key, item in items_data.items(): for key, item in items_data.items():
production_order = self.create_production_order(item) work_order = self.create_work_order(item)
if production_order: if work_order:
pro_list.append(production_order) wo_list.append(work_order)
frappe.flags.mute_messages = False frappe.flags.mute_messages = False
if pro_list: if wo_list:
pro_list = ["""<a href="#Form/Production Order/%s" target="_blank">%s</a>""" % \ wo_list = ["""<a href="#Form/Work Order/%s" target="_blank">%s</a>""" % \
(p, p) for p in pro_list] (p, p) for p in wo_list]
msgprint(_("{0} created").format(comma_and(pro_list))) msgprint(_("{0} created").format(comma_and(wo_list)))
else : else :
msgprint(_("No Production Orders created")) msgprint(_("No Work Orders created"))
def create_production_order(self, item): def create_work_order(self, item):
from erpnext.manufacturing.doctype.production_order.production_order import OverProductionError, get_default_warehouse from erpnext.manufacturing.doctype.work_order.work_order import OverProductionError, get_default_warehouse
warehouse = get_default_warehouse() warehouse = get_default_warehouse()
pro = frappe.new_doc("Production Order") wo = frappe.new_doc("Work Order")
pro.update(item) wo.update(item)
pro.set_production_order_operations() wo.set_work_order_operations()
if not pro.fg_warehouse: if not wo.fg_warehouse:
pro.fg_warehouse = warehouse.get('fg_warehouse') wo.fg_warehouse = warehouse.get('fg_warehouse')
try: try:
pro.insert() wo.insert()
return pro.name return wo.name
except OverProductionError: except OverProductionError:
pass pass

View File

@@ -6,7 +6,7 @@ def get_data():
'transactions': [ 'transactions': [
{ {
'label': _('Related'), 'label': _('Related'),
'items': ['Production Order', 'Material Request'] 'items': ['Work Order', 'Material Request']
}, },
] ]
} }

View File

@@ -41,18 +41,18 @@ class TestProductionPlan(unittest.TestCase):
self.assertTrue(len(material_requests), 2) self.assertTrue(len(material_requests), 2)
pln.make_production_order() pln.make_work_order()
production_orders = frappe.get_all('Production Order', fields = ['name'], work_orders = frappe.get_all('Work Order', fields = ['name'],
filters = {'production_plan': pln.name}, as_list=1) filters = {'production_plan': pln.name}, as_list=1)
self.assertTrue(len(production_orders), len(pln.po_items)) self.assertTrue(len(work_orders), len(pln.po_items))
for name in material_requests: for name in material_requests:
mr = frappe.get_doc('Material Request', name[0]) mr = frappe.get_doc('Material Request', name[0])
mr.cancel() mr.cancel()
for name in production_orders: for name in work_orders:
mr = frappe.delete_doc('Production Order', name[0]) mr = frappe.delete_doc('Work Order', name[0])
pln = frappe.get_doc('Production Plan', pln.name) pln = frappe.get_doc('Production Plan', pln.name)
pln.cancel() pln.cancel()

View File

@@ -9,7 +9,7 @@ from frappe import msgprint, _
from frappe.model.document import Document from frappe.model.document import Document
from erpnext.manufacturing.doctype.bom.bom import validate_bom_no from erpnext.manufacturing.doctype.bom.bom import validate_bom_no
from erpnext.manufacturing.doctype.production_order.production_order import get_item_details from erpnext.manufacturing.doctype.work_order.work_order import get_item_details
class ProductionPlanningTool(Document): class ProductionPlanningTool(Document):
def clear_table(self, table_name): def clear_table(self, table_name):
@@ -204,8 +204,8 @@ class ProductionPlanningTool(Document):
if not flt(d.planned_qty): if not flt(d.planned_qty):
frappe.throw(_("Please enter Planned Qty for Item {0} at row {1}").format(d.item_code, d.idx)) frappe.throw(_("Please enter Planned Qty for Item {0} at row {1}").format(d.item_code, d.idx))
def raise_production_orders(self): def raise_work_orders(self):
"""It will raise production order (Draft) for all distinct FG items""" """It will raise work order (Draft) for all distinct FG items"""
self.validate_data() self.validate_data()
from erpnext.utilities.transaction_base import validate_uom_is_integer from erpnext.utilities.transaction_base import validate_uom_is_integer
@@ -213,22 +213,22 @@ class ProductionPlanningTool(Document):
items = self.get_production_items() items = self.get_production_items()
pro_list = [] wo_list = []
frappe.flags.mute_messages = True frappe.flags.mute_messages = True
for key in items: for key in items:
production_order = self.create_production_order(items[key]) work_order = self.create_work_order(items[key])
if production_order: if work_order:
pro_list.append(production_order) wo_list.append(work_order)
frappe.flags.mute_messages = False frappe.flags.mute_messages = False
if pro_list: if wo_list:
pro_list = ["""<a href="#Form/Production Order/%s" target="_blank">%s</a>""" % \ wo_list = ["""<a href="#Form/Work Order/%s" target="_blank">%s</a>""" % \
(p, p) for p in pro_list] (p, p) for p in wo_list]
msgprint(_("{0} created").format(comma_and(pro_list))) msgprint(_("{0} created").format(comma_and(wo_list)))
else : else :
msgprint(_("No Production Orders created")) msgprint(_("No Work Orders created"))
def get_production_items(self): def get_production_items(self):
item_dict = {} item_dict = {}
@@ -264,21 +264,21 @@ class ProductionPlanningTool(Document):
return item_dict return item_dict
def create_production_order(self, item_dict): def create_work_order(self, item_dict):
"""Create production order. Called from Production Planning Tool""" """Create work order. Called from Production Planning Tool"""
from erpnext.manufacturing.doctype.production_order.production_order import OverProductionError, get_default_warehouse from erpnext.manufacturing.doctype.work_order.work_order import OverProductionError, get_default_warehouse
warehouse = get_default_warehouse() warehouse = get_default_warehouse()
pro = frappe.new_doc("Production Order") wo = frappe.new_doc("Work Order")
pro.update(item_dict) wo.update(item_dict)
pro.set_production_order_operations() wo.set_work_order_operations()
if warehouse: if warehouse:
pro.wip_warehouse = warehouse.get('wip_warehouse') wo.wip_warehouse = warehouse.get('wip_warehouse')
if not pro.fg_warehouse: if not wo.fg_warehouse:
pro.fg_warehouse = warehouse.get('fg_warehouse') wo.fg_warehouse = warehouse.get('fg_warehouse')
try: try:
pro.insert() wo.insert()
return pro.name return wo.name
except OverProductionError: except OverProductionError:
pass pass

View File

@@ -2,7 +2,7 @@
{ {
"bom_no": "BOM-_Test FG Item-001", "bom_no": "BOM-_Test FG Item-001",
"company": "_Test Company", "company": "_Test Company",
"doctype": "Production Order", "doctype": "Work Order",
"fg_warehouse": "_Test Warehouse 1 - _TC", "fg_warehouse": "_Test Warehouse 1 - _TC",
"production_item": "_Test FG Item", "production_item": "_Test FG Item",
"qty": 10.0, "qty": 10.0,

View File

@@ -1,4 +1,4 @@
QUnit.test("test: production order", function (assert) { QUnit.test("test: work order", function (assert) {
assert.expect(25); assert.expect(25);
let done = assert.async(); let done = assert.async();
let laptop_quantity = 5; let laptop_quantity = 5;
@@ -14,13 +14,13 @@ QUnit.test("test: production order", function (assert) {
}; };
frappe.run_serially([ frappe.run_serially([
// test production order // test work order
() => frappe.set_route("List", "Production Order", "List"), () => frappe.set_route("List", "Work Order", "List"),
() => frappe.timeout(3), () => frappe.timeout(3),
// Create a laptop production order // Create a laptop work order
() => { () => {
return frappe.tests.make('Production Order', [ return frappe.tests.make('Work Order', [
{production_item: 'Laptop'}, {production_item: 'Laptop'},
{company: 'For Testing'}, {company: 'For Testing'},
{qty: laptop_quantity}, {qty: laptop_quantity},
@@ -50,13 +50,13 @@ QUnit.test("test: production order", function (assert) {
}); });
}, },
// Submit the production order // Submit the work order
() => cur_frm.savesubmit(), () => cur_frm.savesubmit(),
() => frappe.timeout(1), () => frappe.timeout(1),
() => frappe.click_button('Yes'), () => frappe.click_button('Yes'),
() => frappe.timeout(2.5), () => frappe.timeout(2.5),
// Confirm the production order timesheet, save and submit it // Confirm the work order timesheet, save and submit it
() => frappe.click_link("TS-00"), () => frappe.click_link("TS-00"),
() => frappe.timeout(1), () => frappe.timeout(1),
() => frappe.click_button("Submit"), () => frappe.click_button("Submit"),
@@ -64,8 +64,8 @@ QUnit.test("test: production order", function (assert) {
() => frappe.click_button("Yes"), () => frappe.click_button("Yes"),
() => frappe.timeout(2.5), () => frappe.timeout(2.5),
// Start the production order process // Start the work order process
() => frappe.set_route("List", "Production Order", "List"), () => frappe.set_route("List", "Work Order", "List"),
() => frappe.timeout(2), () => frappe.timeout(2),
() => frappe.click_link("Laptop"), () => frappe.click_link("Laptop"),
() => frappe.timeout(1), () => frappe.timeout(1),
@@ -82,20 +82,20 @@ QUnit.test("test: production order", function (assert) {
assert.equal(cur_frm.doc.total_outgoing_value, "99000", assert.equal(cur_frm.doc.total_outgoing_value, "99000",
"Outgoing cost is correct"); // Price of each item x5 "Outgoing cost is correct"); // Price of each item x5
}, },
// Submit for production // Submit for work
() => frappe.click_button("Submit"), () => frappe.click_button("Submit"),
() => frappe.timeout(0.5), () => frappe.timeout(0.5),
() => frappe.click_button("Yes"), () => frappe.click_button("Yes"),
() => frappe.timeout(0.5), () => frappe.timeout(0.5),
// Finish the production order by sending for manufacturing // Finish the work order by sending for manufacturing
() => frappe.set_route("List", "Production Order"), () => frappe.set_route("List", "Work Order"),
() => frappe.timeout(1), () => frappe.timeout(1),
() => frappe.click_link("Laptop"), () => frappe.click_link("Laptop"),
() => frappe.timeout(1), () => frappe.timeout(1),
() => { () => {
assert.ok(frappe.tests.is_visible("5 items in progress", 'p'), "Production order initiated"); assert.ok(frappe.tests.is_visible("5 items in progress", 'p'), "Work order initiated");
assert.ok(frappe.tests.is_visible("Finish"), "Finish button visible"); assert.ok(frappe.tests.is_visible("Finish"), "Finish button visible");
}, },
@@ -118,12 +118,12 @@ QUnit.test("test: production order", function (assert) {
() => frappe.timeout(1), () => frappe.timeout(1),
// Manufacturing finished // Manufacturing finished
() => frappe.set_route("List", "Production Order", "List"), () => frappe.set_route("List", "Work Order", "List"),
() => frappe.timeout(1), () => frappe.timeout(1),
() => frappe.click_link("Laptop"), () => frappe.click_link("Laptop"),
() => frappe.timeout(1), () => frappe.timeout(1),
() => assert.ok(frappe.tests.is_visible("5 items produced", 'p'), "Production order completed"), () => assert.ok(frappe.tests.is_visible("5 items produced", 'p'), "Work order completed"),
() => done() () => done()
]); ]);

View File

@@ -7,13 +7,13 @@ import unittest
import frappe import frappe
from frappe.utils import flt, time_diff_in_hours, now, add_days, cint from frappe.utils import flt, time_diff_in_hours, now, add_days, cint
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import set_perpetual_inventory from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import set_perpetual_inventory
from erpnext.manufacturing.doctype.production_order.production_order \ from erpnext.manufacturing.doctype.work_order.work_order \
import make_stock_entry, ItemHasVariantError, stop_unstop import make_stock_entry, ItemHasVariantError, stop_unstop
from erpnext.stock.doctype.stock_entry import test_stock_entry from erpnext.stock.doctype.stock_entry import test_stock_entry
from erpnext.stock.utils import get_bin from erpnext.stock.utils import get_bin
from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order
class TestProductionOrder(unittest.TestCase): class TestWorkOrder(unittest.TestCase):
def setUp(self): def setUp(self):
self.warehouse = '_Test Warehouse 2 - _TC' self.warehouse = '_Test Warehouse 2 - _TC'
self.item = '_Test Item' self.item = '_Test Item'
@@ -24,7 +24,7 @@ class TestProductionOrder(unittest.TestCase):
planned0 = frappe.db.get_value("Bin", {"item_code": "_Test FG Item", planned0 = frappe.db.get_value("Bin", {"item_code": "_Test FG Item",
"warehouse": "_Test Warehouse 1 - _TC"}, "planned_qty") or 0 "warehouse": "_Test Warehouse 1 - _TC"}, "planned_qty") or 0
pro_order = make_prod_order_test_record() wo_order = make_wo_order_test_record()
planned1 = frappe.db.get_value("Bin", {"item_code": "_Test FG Item", planned1 = frappe.db.get_value("Bin", {"item_code": "_Test FG Item",
"warehouse": "_Test Warehouse 1 - _TC"}, "planned_qty") "warehouse": "_Test Warehouse 1 - _TC"}, "planned_qty")
@@ -38,60 +38,59 @@ class TestProductionOrder(unittest.TestCase):
target="Stores - _TC", qty=100, basic_rate=100) target="Stores - _TC", qty=100, basic_rate=100)
# from stores to wip # from stores to wip
s = frappe.get_doc(make_stock_entry(pro_order.name, "Material Transfer for Manufacture", 4)) s = frappe.get_doc(make_stock_entry(wo_order.name, "Material Transfer for Manufacture", 4))
for d in s.get("items"): for d in s.get("items"):
d.s_warehouse = "Stores - _TC" d.s_warehouse = "Stores - _TC"
s.insert() s.insert()
s.submit() s.submit()
# from wip to fg # from wip to fg
s = frappe.get_doc(make_stock_entry(pro_order.name, "Manufacture", 4)) s = frappe.get_doc(make_stock_entry(wo_order.name, "Manufacture", 4))
s.insert() s.insert()
s.submit() s.submit()
self.assertEqual(frappe.db.get_value("Production Order", pro_order.name, "produced_qty"), 4) self.assertEqual(frappe.db.get_value("Work Order", wo_order.name, "produced_qty"), 4)
planned2 = frappe.db.get_value("Bin", {"item_code": "_Test FG Item", planned2 = frappe.db.get_value("Bin", {"item_code": "_Test FG Item",
"warehouse": "_Test Warehouse 1 - _TC"}, "planned_qty") "warehouse": "_Test Warehouse 1 - _TC"}, "planned_qty")
self.assertEqual(planned2, planned0 + 6) self.assertEqual(planned2, planned0 + 6)
return pro_order return wo_order
def test_over_production(self): def test_over_production(self):
from erpnext.manufacturing.doctype.production_order.production_order import StockOverProductionError from erpnext.manufacturing.doctype.work_order.work_order import StockOverProductionError
pro_doc = self.check_planned_qty() wo_doc = self.check_planned_qty()
test_stock_entry.make_stock_entry(item_code="_Test Item", test_stock_entry.make_stock_entry(item_code="_Test Item",
target="_Test Warehouse - _TC", qty=100, basic_rate=100) target="_Test Warehouse - _TC", qty=100, basic_rate=100)
test_stock_entry.make_stock_entry(item_code="_Test Item Home Desktop 100", test_stock_entry.make_stock_entry(item_code="_Test Item Home Desktop 100",
target="_Test Warehouse - _TC", qty=100, basic_rate=100) target="_Test Warehouse - _TC", qty=100, basic_rate=100)
s = frappe.get_doc(make_stock_entry(pro_doc.name, "Manufacture", 7)) s = frappe.get_doc(make_stock_entry(wo_doc.name, "Manufacture", 7))
s.insert() s.insert()
self.assertRaises(StockOverProductionError, s.submit) self.assertRaises(StockOverProductionError, s.submit)
def test_make_time_sheet(self): def test_make_time_sheet(self):
from erpnext.manufacturing.doctype.production_order.production_order import make_timesheet from erpnext.manufacturing.doctype.work_order.work_order import make_timesheet
prod_order = make_prod_order_test_record(item="_Test FG Item 2", wo_order = make_wo_order_test_record(item="_Test FG Item 2",
planned_start_date=now(), qty=1, do_not_save=True) planned_start_date=now(), qty=1, do_not_save=True)
prod_order.set_production_order_operations() wo_order.set_work_order_operations()
prod_order.insert() wo_order.insert()
prod_order.submit() wo_order.submit()
d = prod_order.operations[0] d = wo_order.operations[0]
d.completed_qty = flt(d.completed_qty) d.completed_qty = flt(d.completed_qty)
name = frappe.db.get_value('Timesheet', {'production_order': prod_order.name}, 'name') name = frappe.db.get_value('Timesheet', {'work_order': wo_order.name}, 'name')
time_sheet_doc = frappe.get_doc('Timesheet', name) time_sheet_doc = frappe.get_doc('Timesheet', name)
self.assertEqual(prod_order.company, time_sheet_doc.company) self.assertEqual(wo_order.company, time_sheet_doc.company)
time_sheet_doc.submit() time_sheet_doc.submit()
self.assertEqual(wo_order.name, time_sheet_doc.work_order)
self.assertEqual(prod_order.name, time_sheet_doc.production_order) self.assertEqual((wo_order.qty - d.completed_qty),
self.assertEqual((prod_order.qty - d.completed_qty),
sum([d.completed_qty for d in time_sheet_doc.time_logs])) sum([d.completed_qty for d in time_sheet_doc.time_logs]))
manufacturing_settings = frappe.get_doc({ manufacturing_settings = frappe.get_doc({
@@ -101,49 +100,49 @@ class TestProductionOrder(unittest.TestCase):
manufacturing_settings.save() manufacturing_settings.save()
prod_order.load_from_db() wo_order.load_from_db()
self.assertEqual(prod_order.operations[0].status, "Completed") self.assertEqual(wo_order.operations[0].status, "Completed")
self.assertEqual(prod_order.operations[0].completed_qty, prod_order.qty) self.assertEqual(wo_order.operations[0].completed_qty, wo_order.qty)
self.assertEqual(prod_order.operations[0].actual_operation_time, 60) self.assertEqual(wo_order.operations[0].actual_operation_time, 60)
self.assertEqual(prod_order.operations[0].actual_operating_cost, 6000) self.assertEqual(wo_order.operations[0].actual_operating_cost, 6000)
time_sheet_doc1 = make_timesheet(prod_order.name, prod_order.company) time_sheet_doc1 = make_timesheet(wo_order.name, wo_order.company)
self.assertEqual(len(time_sheet_doc1.get('time_logs')), 0) self.assertEqual(len(time_sheet_doc1.get('time_logs')), 0)
time_sheet_doc.cancel() time_sheet_doc.cancel()
prod_order.load_from_db() wo_order.load_from_db()
self.assertEqual(prod_order.operations[0].status, "Pending") self.assertEqual(wo_order.operations[0].status, "Pending")
self.assertEqual(flt(prod_order.operations[0].completed_qty), 0) self.assertEqual(flt(wo_order.operations[0].completed_qty), 0)
self.assertEqual(flt(prod_order.operations[0].actual_operation_time), 0) self.assertEqual(flt(wo_order.operations[0].actual_operation_time), 0)
self.assertEqual(flt(prod_order.operations[0].actual_operating_cost), 0) self.assertEqual(flt(wo_order.operations[0].actual_operating_cost), 0)
def test_planned_operating_cost(self): def test_planned_operating_cost(self):
prod_order = make_prod_order_test_record(item="_Test FG Item 2", wo_order = make_wo_order_test_record(item="_Test FG Item 2",
planned_start_date=now(), qty=1, do_not_save=True) planned_start_date=now(), qty=1, do_not_save=True)
prod_order.set_production_order_operations() wo_order.set_work_order_operations()
cost = prod_order.planned_operating_cost cost = wo_order.planned_operating_cost
prod_order.qty = 2 wo_order.qty = 2
prod_order.set_production_order_operations() wo_order.set_work_order_operations()
self.assertEqual(prod_order.planned_operating_cost, cost*2) self.assertEqual(wo_order.planned_operating_cost, cost*2)
def test_production_item(self): def test_production_item(self):
prod_order = make_prod_order_test_record(item="_Test FG Item", qty=1, do_not_save=True) wo_order = make_wo_order_test_record(item="_Test FG Item", qty=1, do_not_save=True)
frappe.db.set_value("Item", "_Test FG Item", "end_of_life", "2000-1-1") frappe.db.set_value("Item", "_Test FG Item", "end_of_life", "2000-1-1")
self.assertRaises(frappe.ValidationError, prod_order.save) self.assertRaises(frappe.ValidationError, wo_order.save)
frappe.db.set_value("Item", "_Test FG Item", "end_of_life", None) frappe.db.set_value("Item", "_Test FG Item", "end_of_life", None)
frappe.db.set_value("Item", "_Test FG Item", "disabled", 1) frappe.db.set_value("Item", "_Test FG Item", "disabled", 1)
self.assertRaises(frappe.ValidationError, prod_order.save) self.assertRaises(frappe.ValidationError, wo_order.save)
frappe.db.set_value("Item", "_Test FG Item", "disabled", 0) frappe.db.set_value("Item", "_Test FG Item", "disabled", 0)
prod_order = make_prod_order_test_record(item="_Test Variant Item", qty=1, do_not_save=True) wo_order = make_wo_order_test_record(item="_Test Variant Item", qty=1, do_not_save=True)
self.assertRaises(ItemHasVariantError, prod_order.save) self.assertRaises(ItemHasVariantError, wo_order.save)
def test_reserved_qty_for_production_submit(self): def test_reserved_qty_for_production_submit(self):
self.bin1_at_start = get_bin(self.item, self.warehouse) self.bin1_at_start = get_bin(self.item, self.warehouse)
@@ -151,7 +150,7 @@ class TestProductionOrder(unittest.TestCase):
# reset to correct value # reset to correct value
self.bin1_at_start.update_reserved_qty_for_production() self.bin1_at_start.update_reserved_qty_for_production()
self.pro_order = make_prod_order_test_record(item="_Test FG Item", qty=2, self.wo_order = make_wo_order_test_record(item="_Test FG Item", qty=2,
source_warehouse=self.warehouse) source_warehouse=self.warehouse)
self.bin1_on_submit = get_bin(self.item, self.warehouse) self.bin1_on_submit = get_bin(self.item, self.warehouse)
@@ -165,7 +164,7 @@ class TestProductionOrder(unittest.TestCase):
def test_reserved_qty_for_production_cancel(self): def test_reserved_qty_for_production_cancel(self):
self.test_reserved_qty_for_production_submit() self.test_reserved_qty_for_production_submit()
self.pro_order.cancel() self.wo_order.cancel()
bin1_on_cancel = get_bin(self.item, self.warehouse) bin1_on_cancel = get_bin(self.item, self.warehouse)
@@ -183,7 +182,7 @@ class TestProductionOrder(unittest.TestCase):
self.test_reserved_qty_for_production_submit() self.test_reserved_qty_for_production_submit()
s = frappe.get_doc(make_stock_entry(self.pro_order.name, s = frappe.get_doc(make_stock_entry(self.wo_order.name,
"Material Transfer for Manufacture", 2)) "Material Transfer for Manufacture", 2))
s.submit() s.submit()
@@ -198,7 +197,7 @@ class TestProductionOrder(unittest.TestCase):
self.assertEqual(cint(self.bin1_at_start.projected_qty), self.assertEqual(cint(self.bin1_at_start.projected_qty),
cint(bin1_on_start_production.projected_qty) + 2) cint(bin1_on_start_production.projected_qty) + 2)
s = frappe.get_doc(make_stock_entry(self.pro_order.name, "Manufacture", 2)) s = frappe.get_doc(make_stock_entry(self.wo_order.name, "Manufacture", 2))
bin1_on_end_production = get_bin(self.item, self.warehouse) bin1_on_end_production = get_bin(self.item, self.warehouse)
@@ -220,7 +219,7 @@ class TestProductionOrder(unittest.TestCase):
#2 0 -2 #2 0 -2
s = frappe.get_doc(make_stock_entry(self.pro_order.name, s = frappe.get_doc(make_stock_entry(self.wo_order.name,
"Material Transfer for Manufacture", 1)) "Material Transfer for Manufacture", 1))
s.submit() s.submit()
@@ -238,7 +237,7 @@ class TestProductionOrder(unittest.TestCase):
cint(bin1_on_start_production.projected_qty) + 2) cint(bin1_on_start_production.projected_qty) + 2)
# STOP # STOP
stop_unstop(self.pro_order.name, "Stopped") stop_unstop(self.wo_order.name, "Stopped")
bin1_on_stop_production = get_bin(self.item, self.warehouse) bin1_on_stop_production = get_bin(self.item, self.warehouse)
@@ -249,7 +248,7 @@ class TestProductionOrder(unittest.TestCase):
cint(self.bin1_at_start.projected_qty)) cint(self.bin1_at_start.projected_qty))
def test_scrap_material_qty(self): def test_scrap_material_qty(self):
prod_order = make_prod_order_test_record(planned_start_date=now(), qty=2) wo_order = make_wo_order_test_record(planned_start_date=now(), qty=2)
# add raw materials to stores # add raw materials to stores
test_stock_entry.make_stock_entry(item_code="_Test Item", test_stock_entry.make_stock_entry(item_code="_Test Item",
@@ -257,27 +256,27 @@ class TestProductionOrder(unittest.TestCase):
test_stock_entry.make_stock_entry(item_code="_Test Item Home Desktop 100", test_stock_entry.make_stock_entry(item_code="_Test Item Home Desktop 100",
target="Stores - _TC", qty=10, basic_rate=1000.0) target="Stores - _TC", qty=10, basic_rate=1000.0)
s = frappe.get_doc(make_stock_entry(prod_order.name, "Material Transfer for Manufacture", 2)) s = frappe.get_doc(make_stock_entry(wo_order.name, "Material Transfer for Manufacture", 2))
for d in s.get("items"): for d in s.get("items"):
d.s_warehouse = "Stores - _TC" d.s_warehouse = "Stores - _TC"
s.insert() s.insert()
s.submit() s.submit()
s = frappe.get_doc(make_stock_entry(prod_order.name, "Manufacture", 2)) s = frappe.get_doc(make_stock_entry(wo_order.name, "Manufacture", 2))
s.insert() s.insert()
s.submit() s.submit()
prod_order_details = frappe.db.get_value("Production Order", prod_order.name, wo_order_details = frappe.db.get_value("Work Order", wo_order.name,
["scrap_warehouse", "qty", "produced_qty", "bom_no"], as_dict=1) ["scrap_warehouse", "qty", "produced_qty", "bom_no"], as_dict=1)
scrap_item_details = get_scrap_item_details(prod_order_details.bom_no) scrap_item_details = get_scrap_item_details(wo_order_details.bom_no)
self.assertEqual(prod_order_details.produced_qty, 2) self.assertEqual(wo_order_details.produced_qty, 2)
for item in s.items: for item in s.items:
if item.bom_no and item.item_code in scrap_item_details: if item.bom_no and item.item_code in scrap_item_details:
self.assertEqual(prod_order_details.scrap_warehouse, item.t_warehouse) self.assertEqual(wo_order_details.scrap_warehouse, item.t_warehouse)
self.assertEqual(flt(prod_order_details.qty)*flt(scrap_item_details[item.item_code]), item.qty) self.assertEqual(flt(wo_order_details.qty)*flt(scrap_item_details[item.item_code]), item.qty)
def get_scrap_item_details(bom_no): def get_scrap_item_details(bom_no):
scrap_items = {} scrap_items = {}
@@ -287,34 +286,34 @@ def get_scrap_item_details(bom_no):
return scrap_items return scrap_items
def make_prod_order_test_record(**args): def make_wo_order_test_record(**args):
args = frappe._dict(args) args = frappe._dict(args)
pro_order = frappe.new_doc("Production Order") wo_order = frappe.new_doc("Work Order")
pro_order.production_item = args.production_item or args.item or args.item_code or "_Test FG Item" wo_order.production_item = args.production_item or args.item or args.item_code or "_Test FG Item"
pro_order.bom_no = frappe.db.get_value("BOM", {"item": pro_order.production_item, wo_order.bom_no = frappe.db.get_value("BOM", {"item": wo_order.production_item,
"is_active": 1, "is_default": 1}) "is_active": 1, "is_default": 1})
pro_order.qty = args.qty or 10 wo_order.qty = args.qty or 10
pro_order.wip_warehouse = args.wip_warehouse or "_Test Warehouse - _TC" wo_order.wip_warehouse = args.wip_warehouse or "_Test Warehouse - _TC"
pro_order.fg_warehouse = args.fg_warehouse or "_Test Warehouse 1 - _TC" wo_order.fg_warehouse = args.fg_warehouse or "_Test Warehouse 1 - _TC"
pro_order.scrap_warehouse = args.fg_warehouse or "_Test Scrap Warehouse - _TC" wo_order.scrap_warehouse = args.fg_warehouse or "_Test Scrap Warehouse - _TC"
pro_order.company = args.company or "_Test Company" wo_order.company = args.company or "_Test Company"
pro_order.stock_uom = args.stock_uom or "_Test UOM" wo_order.stock_uom = args.stock_uom or "_Test UOM"
pro_order.use_multi_level_bom=0 wo_order.use_multi_level_bom=0
pro_order.get_items_and_operations_from_bom() wo_order.get_items_and_operations_from_bom()
if args.source_warehouse: if args.source_warehouse:
for item in pro_order.get("required_items"): for item in wo_order.get("required_items"):
item.source_warehouse = args.source_warehouse item.source_warehouse = args.source_warehouse
if args.planned_start_date: if args.planned_start_date:
pro_order.planned_start_date = args.planned_start_date wo_order.planned_start_date = args.planned_start_date
if not args.do_not_save: if not args.do_not_save:
pro_order.insert() wo_order.insert()
if not args.do_not_submit: if not args.do_not_submit:
pro_order.submit() wo_order.submit()
return pro_order return wo_order
test_records = frappe.get_test_records('Production Order') test_records = frappe.get_test_records('Work Order')

View File

@@ -1,7 +1,7 @@
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors // Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
// License: GNU General Public License v3. See license.txt // For license information, please see license.txt
frappe.ui.form.on("Production Order", { frappe.ui.form.on("Work Order", {
setup: function(frm) { setup: function(frm) {
frm.custom_make_buttons = { frm.custom_make_buttons = {
'Timesheet': 'Make Timesheet', 'Timesheet': 'Make Timesheet',
@@ -80,7 +80,7 @@ frappe.ui.form.on("Production Order", {
} }
}); });
// formatter for production order operation // formatter for work order operation
frm.set_indicator_formatter('operation', frm.set_indicator_formatter('operation',
function(doc) { return (frm.doc.qty==doc.completed_qty) ? "green" : "orange" }); function(doc) { return (frm.doc.qty==doc.completed_qty) ? "green" : "orange" });
}, },
@@ -96,17 +96,17 @@ frappe.ui.form.on("Production Order", {
"actual_start_date": "", "actual_start_date": "",
"actual_end_date": "" "actual_end_date": ""
}); });
erpnext.production_order.set_default_warehouse(frm); erpnext.work_order.set_default_warehouse(frm);
} }
}, },
refresh: function(frm) { refresh: function(frm) {
erpnext.toggle_naming_series(); erpnext.toggle_naming_series();
erpnext.production_order.set_custom_buttons(frm); erpnext.work_order.set_custom_buttons(frm);
frm.set_intro(""); frm.set_intro("");
if (frm.doc.docstatus === 0 && !frm.doc.__islocal) { if (frm.doc.docstatus === 0 && !frm.doc.__islocal) {
frm.set_intro(__("Submit this Production Order for further processing.")); frm.set_intro(__("Submit this Work Order for further processing."));
} }
if (frm.doc.docstatus===1) { if (frm.doc.docstatus===1) {
@@ -116,7 +116,7 @@ frappe.ui.form.on("Production Order", {
if(frm.doc.docstatus == 1 && frm.doc.status != 'Stopped'){ if(frm.doc.docstatus == 1 && frm.doc.status != 'Stopped'){
frm.add_custom_button(__('Make Timesheet'), function(){ frm.add_custom_button(__('Make Timesheet'), function(){
frappe.model.open_mapped_doc({ frappe.model.open_mapped_doc({
method: "erpnext.manufacturing.doctype.production_order.production_order.make_new_timesheet", method: "erpnext.manufacturing.doctype.work_order.work_order.make_new_timesheet",
frm: cur_frm frm: cur_frm
}) })
}) })
@@ -160,7 +160,7 @@ frappe.ui.form.on("Production Order", {
production_item: function(frm) { production_item: function(frm) {
if (frm.doc.production_item) { if (frm.doc.production_item) {
frappe.call({ frappe.call({
method: "erpnext.manufacturing.doctype.production_order.production_order.get_item_details", method: "erpnext.manufacturing.doctype.work_order.work_order.get_item_details",
args: { args: {
item: frm.doc.production_item, item: frm.doc.production_item,
project: frm.doc.project project: frm.doc.project
@@ -222,7 +222,7 @@ frappe.ui.form.on("Production Order", {
set_sales_order: function(frm) { set_sales_order: function(frm) {
if(frm.doc.production_item) { if(frm.doc.production_item) {
frappe.call({ frappe.call({
method: "erpnext.manufacturing.doctype.production_order.production_order.query_sales_order", method: "erpnext.manufacturing.doctype.work_order.work_order.query_sales_order",
args: { production_item: frm.doc.production_item }, args: { production_item: frm.doc.production_item },
callback: function(r) { callback: function(r) {
frm.set_query("sales_order", function() { frm.set_query("sales_order", function() {
@@ -239,7 +239,7 @@ frappe.ui.form.on("Production Order", {
} }
}); });
frappe.ui.form.on("Production Order Item", { frappe.ui.form.on("Work Order Item", {
source_warehouse: function(frm, cdt, cdn) { source_warehouse: function(frm, cdt, cdn) {
var row = locals[cdt][cdn]; var row = locals[cdt][cdn];
if(!row.item_code) { if(!row.item_code) {
@@ -260,7 +260,7 @@ frappe.ui.form.on("Production Order Item", {
} }
}) })
frappe.ui.form.on("Production Order Operation", { frappe.ui.form.on("Work Order Operation", {
workstation: function(frm, cdt, cdn) { workstation: function(frm, cdt, cdn) {
var d = locals[cdt][cdn]; var d = locals[cdt][cdn];
if (d.workstation) { if (d.workstation) {
@@ -272,29 +272,29 @@ frappe.ui.form.on("Production Order Operation", {
}, },
callback: function (data) { callback: function (data) {
frappe.model.set_value(d.doctype, d.name, "hour_rate", data.message.hour_rate); frappe.model.set_value(d.doctype, d.name, "hour_rate", data.message.hour_rate);
erpnext.production_order.calculate_cost(frm.doc); erpnext.work_order.calculate_cost(frm.doc);
erpnext.production_order.calculate_total_cost(frm); erpnext.work_order.calculate_total_cost(frm);
} }
}) })
} }
}, },
time_in_mins: function(frm, cdt, cdn) { time_in_mins: function(frm, cdt, cdn) {
erpnext.production_order.calculate_cost(frm.doc); erpnext.work_order.calculate_cost(frm.doc);
erpnext.production_order.calculate_total_cost(frm); erpnext.work_order.calculate_total_cost(frm);
}, },
}); });
erpnext.production_order = { erpnext.work_order = {
set_custom_buttons: function(frm) { set_custom_buttons: function(frm) {
var doc = frm.doc; var doc = frm.doc;
if (doc.docstatus === 1) { if (doc.docstatus === 1) {
if (doc.status != 'Stopped' && doc.status != 'Completed') { if (doc.status != 'Stopped' && doc.status != 'Completed') {
frm.add_custom_button(__('Stop'), function() { frm.add_custom_button(__('Stop'), function() {
erpnext.production_order.stop_production_order(frm, "Stopped"); erpnext.wokr_order.stop_work_order(frm, "Stopped");
}, __("Status")); }, __("Status"));
} else if (doc.status == 'Stopped') { } else if (doc.status == 'Stopped') {
frm.add_custom_button(__('Re-open'), function() { frm.add_custom_button(__('Re-open'), function() {
erpnext.production_order.stop_production_order(frm, "Resumed"); erpnext.work_order.stop_work_order(frm, "Resumed");
}, __("Status")); }, __("Status"));
} }
@@ -303,7 +303,7 @@ erpnext.production_order = {
&& frm.doc.status != 'Stopped') { && frm.doc.status != 'Stopped') {
frm.has_start_btn = true; frm.has_start_btn = true;
var start_btn = frm.add_custom_button(__('Start'), function() { var start_btn = frm.add_custom_button(__('Start'), function() {
erpnext.production_order.make_se(frm, 'Material Transfer for Manufacture'); erpnext.work_order.make_se(frm, 'Material Transfer for Manufacture');
}); });
start_btn.addClass('btn-primary'); start_btn.addClass('btn-primary');
} }
@@ -314,7 +314,7 @@ erpnext.production_order = {
&& frm.doc.status != 'Stopped') { && frm.doc.status != 'Stopped') {
frm.has_finish_btn = true; frm.has_finish_btn = true;
var finish_btn = frm.add_custom_button(__('Finish'), function() { var finish_btn = frm.add_custom_button(__('Finish'), function() {
erpnext.production_order.make_se(frm, 'Manufacture'); erpnext.work_order.make_se(frm, 'Manufacture');
}); });
if(doc.material_transferred_for_manufacturing==doc.qty) { if(doc.material_transferred_for_manufacturing==doc.qty) {
@@ -326,7 +326,7 @@ erpnext.production_order = {
if ((flt(doc.produced_qty) < flt(doc.qty)) && frm.doc.status != 'Stopped') { if ((flt(doc.produced_qty) < flt(doc.qty)) && frm.doc.status != 'Stopped') {
frm.has_finish_btn = true; frm.has_finish_btn = true;
var finish_btn = frm.add_custom_button(__('Finish'), function() { var finish_btn = frm.add_custom_button(__('Finish'), function() {
erpnext.production_order.make_se(frm, 'Manufacture'); erpnext.work_order.make_se(frm, 'Manufacture');
}); });
finish_btn.addClass('btn-primary'); finish_btn.addClass('btn-primary');
} }
@@ -340,7 +340,7 @@ erpnext.production_order = {
doc.planned_operating_cost = 0.0; doc.planned_operating_cost = 0.0;
for(var i=0;i<op.length;i++) { for(var i=0;i<op.length;i++) {
var planned_operating_cost = flt(flt(op[i].hour_rate) * flt(op[i].time_in_mins) / 60, 2); var planned_operating_cost = flt(flt(op[i].hour_rate) * flt(op[i].time_in_mins) / 60, 2);
frappe.model.set_value('Production Order Operation', op[i].name, frappe.model.set_value('Work Order Operation', op[i].name,
"planned_operating_cost", planned_operating_cost); "planned_operating_cost", planned_operating_cost);
doc.planned_operating_cost += planned_operating_cost; doc.planned_operating_cost += planned_operating_cost;
} }
@@ -357,7 +357,7 @@ erpnext.production_order = {
set_default_warehouse: function(frm) { set_default_warehouse: function(frm) {
if (!(frm.doc.wip_warehouse || frm.doc.fg_warehouse)) { if (!(frm.doc.wip_warehouse || frm.doc.fg_warehouse)) {
frappe.call({ frappe.call({
method: "erpnext.manufacturing.doctype.production_order.production_order.get_default_warehouse", method: "erpnext.manufacturing.doctype.work_order.work_order.get_default_warehouse",
callback: function(r) { callback: function(r) {
if(!r.exe) { if(!r.exe) {
frm.set_value("wip_warehouse", r.message.wip_warehouse); frm.set_value("wip_warehouse", r.message.wip_warehouse);
@@ -379,16 +379,16 @@ erpnext.production_order = {
max = flt(max, precision("qty")); max = flt(max, precision("qty"));
frappe.prompt({fieldtype:"Float", label: __("Qty for {0}", [purpose]), fieldname:"qty", frappe.prompt({fieldtype:"Float", label: __("Qty for {0}", [purpose]), fieldname:"qty",
description: __("Max: {0}", [max]), 'default': max }, description: __("Max: {0}", [max]), 'default': max }, function(data)
function(data) { {
if(data.qty > max) { if(data.qty > max) {
frappe.msgprint(__("Quantity must not be more than {0}", [max])); frappe.msgprint(__("Quantity must not be more than {0}", [max]));
return; return;
} }
frappe.call({ frappe.call({
method:"erpnext.manufacturing.doctype.production_order.production_order.make_stock_entry", method:"erpnext.manufacturing.doctype.work_order.work_order.make_stock_entry",
args: { args: {
"production_order_id": frm.doc.name, "work_order_id": frm.doc.name,
"purpose": purpose, "purpose": purpose,
"qty": data.qty "qty": data.qty
}, },
@@ -400,11 +400,11 @@ erpnext.production_order = {
}, __("Select Quantity"), __("Make")); }, __("Select Quantity"), __("Make"));
}, },
stop_production_order: function(frm, status) { stop_work_order: function(frm, status) {
frappe.call({ frappe.call({
method: "erpnext.manufacturing.doctype.production_order.production_order.stop_unstop", method: "erpnext.manufacturing.doctype.work_order.work_order.stop_unstop",
args: { args: {
production_order: frm.doc.name, work_order: frm.doc.name,
status: status status: status
}, },
callback: function(r) { callback: function(r) {

View File

@@ -48,7 +48,7 @@
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"default": "PRO-", "default": "WO-",
"fieldname": "naming_series", "fieldname": "naming_series",
"fieldtype": "Select", "fieldtype": "Select",
"hidden": 0, "hidden": 0,
@@ -61,7 +61,7 @@
"label": "Series", "label": "Series",
"length": 0, "length": 0,
"no_copy": 0, "no_copy": 0,
"options": "PRO-", "options": "WO-",
"permlevel": 0, "permlevel": 0,
"print_hide": 1, "print_hide": 1,
"print_hide_if_no_value": 0, "print_hide_if_no_value": 0,
@@ -626,7 +626,7 @@
"label": "Required Items", "label": "Required Items",
"length": 0, "length": 0,
"no_copy": 1, "no_copy": 1,
"options": "Production Order Item", "options": "Work Order Item",
"permlevel": 0, "permlevel": 0,
"precision": "", "precision": "",
"print_hide": 1, "print_hide": 1,
@@ -901,7 +901,7 @@
"label": "Operations", "label": "Operations",
"length": 0, "length": 0,
"no_copy": 0, "no_copy": 0,
"options": "Production Order Operation", "options": "Work Order Operation",
"permlevel": 0, "permlevel": 0,
"precision": "", "precision": "",
"print_hide": 0, "print_hide": 0,
@@ -1425,7 +1425,7 @@
"no_copy": 1, "no_copy": 1,
"oldfieldname": "amended_from", "oldfieldname": "amended_from",
"oldfieldtype": "Data", "oldfieldtype": "Data",
"options": "Production Order", "options": "Work Order",
"permlevel": 0, "permlevel": 0,
"print_hide": 0, "print_hide": 0,
"print_hide_if_no_value": 0, "print_hide_if_no_value": 0,
@@ -1449,10 +1449,10 @@
"issingle": 0, "issingle": 0,
"istable": 0, "istable": 0,
"max_attachments": 0, "max_attachments": 0,
"modified": "2017-12-20 05:31:56.636724", "modified": "2018-02-13 02:58:11.328693",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Manufacturing", "module": "Manufacturing",
"name": "Production Order", "name": "Work Order",
"owner": "Administrator", "owner": "Administrator",
"permissions": [ "permissions": [
{ {

View File

@@ -25,10 +25,10 @@ class OperationTooLongError(frappe.ValidationError): pass
class ItemHasVariantError(frappe.ValidationError): pass class ItemHasVariantError(frappe.ValidationError): pass
form_grid_templates = { form_grid_templates = {
"operations": "templates/form_grid/production_order_grid.html" "operations": "templates/form_grid/work_order_grid.html"
} }
class ProductionOrder(Document): class WorkOrder(Document):
def validate(self): def validate(self):
self.validate_production_item() self.validate_production_item()
if self.bom_no: if self.bom_no:
@@ -79,7 +79,7 @@ class ProductionOrder(Document):
self.project = so[0].project self.project = so[0].project
if not self.material_request: if not self.material_request:
self.validate_production_order_against_so() self.validate_work_order_against_so()
else: else:
frappe.throw(_("Sales Order {0} is not valid").format(self.sales_order)) frappe.throw(_("Sales Order {0} is not valid").format(self.sales_order))
@@ -111,9 +111,9 @@ class ProductionOrder(Document):
else self.planned_operating_cost else self.planned_operating_cost
self.total_operating_cost = flt(self.additional_operating_cost) + flt(variable_cost) self.total_operating_cost = flt(self.additional_operating_cost) + flt(variable_cost)
def validate_production_order_against_so(self): def validate_work_order_against_so(self):
# already ordered qty # already ordered qty
ordered_qty_against_so = frappe.db.sql("""select sum(qty) from `tabProduction Order` ordered_qty_against_so = frappe.db.sql("""select sum(qty) from `tabWork Order`
where production_item = %s and sales_order = %s and docstatus < 2 and name != %s""", where production_item = %s and sales_order = %s and docstatus < 2 and name != %s""",
(self.production_item, self.sales_order, self.name))[0][0] (self.production_item, self.sales_order, self.name))[0][0]
@@ -138,7 +138,7 @@ class ProductionOrder(Document):
.format(self.production_item, so_qty), OverProductionError) .format(self.production_item, so_qty), OverProductionError)
def update_status(self, status=None): def update_status(self, status=None):
'''Update status of production order if unknown''' '''Update status of work order if unknown'''
if status != "Stopped": if status != "Stopped":
status = self.get_status(status) status = self.get_status(status)
@@ -150,7 +150,7 @@ class ProductionOrder(Document):
return status return status
def get_status(self, status=None): def get_status(self, status=None):
'''Return the status based on stock entries against this production order''' '''Return the status based on stock entries against this work order'''
if not status: if not status:
status = self.status status = self.status
@@ -159,7 +159,7 @@ class ProductionOrder(Document):
elif self.docstatus==1: elif self.docstatus==1:
if status != 'Stopped': if status != 'Stopped':
stock_entries = frappe._dict(frappe.db.sql("""select purpose, sum(fg_completed_qty) stock_entries = frappe._dict(frappe.db.sql("""select purpose, sum(fg_completed_qty)
from `tabStock Entry` where production_order=%s and docstatus=1 from `tabStock Entry` where work_order=%s and docstatus=1
group by purpose""", self.name)) group by purpose""", self.name))
status = "Not Started" status = "Not Started"
@@ -173,18 +173,18 @@ class ProductionOrder(Document):
return status return status
def update_production_order_qty(self): def update_work_order_qty(self):
"""Update **Manufactured Qty** and **Material Transferred for Qty** in Production Order """Update **Manufactured Qty** and **Material Transferred for Qty** in Work Order
based on Stock Entry""" based on Stock Entry"""
for purpose, fieldname in (("Manufacture", "produced_qty"), for purpose, fieldname in (("Manufacture", "produced_qty"),
("Material Transfer for Manufacture", "material_transferred_for_manufacturing")): ("Material Transfer for Manufacture", "material_transferred_for_manufacturing")):
qty = flt(frappe.db.sql("""select sum(fg_completed_qty) qty = flt(frappe.db.sql("""select sum(fg_completed_qty)
from `tabStock Entry` where production_order=%s and docstatus=1 from `tabStock Entry` where work_order=%s and docstatus=1
and purpose=%s""", (self.name, purpose))[0][0]) and purpose=%s""", (self.name, purpose))[0][0])
if qty > self.qty: if qty > self.qty:
frappe.throw(_("{0} ({1}) cannot be greater than planned quanitity ({2}) in Production Order {3}").format(\ frappe.throw(_("{0} ({1}) cannot be greater than planned quanitity ({2}) in Work Order {3}").format(\
self.meta.get_label(fieldname), qty, self.qty, self.name), StockOverProductionError) self.meta.get_label(fieldname), qty, self.qty, self.name), StockOverProductionError)
self.db_set(fieldname, qty) self.db_set(fieldname, qty)
@@ -222,11 +222,11 @@ class ProductionOrder(Document):
def validate_cancel(self): def validate_cancel(self):
if self.status == "Stopped": if self.status == "Stopped":
frappe.throw(_("Stopped Production Order cannot be cancelled, Unstop it first to cancel")) frappe.throw(_("Stopped Work Order cannot be cancelled, Unstop it first to cancel"))
# Check whether any stock entry exists against this Production Order # Check whether any stock entry exists against this Work Order
stock_entry = frappe.db.sql("""select name from `tabStock Entry` stock_entry = frappe.db.sql("""select name from `tabStock Entry`
where production_order = %s and docstatus = 1""", self.name) where work_order = %s and docstatus = 1""", self.name)
if stock_entry: if stock_entry:
frappe.throw(_("Cannot cancel because submitted Stock Entry {0} exists").format(stock_entry[0][0])) frappe.throw(_("Cannot cancel because submitted Stock Entry {0} exists").format(stock_entry[0][0]))
@@ -253,8 +253,8 @@ class ProductionOrder(Document):
if self.material_request: if self.material_request:
frappe.get_doc("Material Request", self.material_request).update_completed_qty([self.material_request_item]) frappe.get_doc("Material Request", self.material_request).update_completed_qty([self.material_request_item])
def set_production_order_operations(self): def set_work_order_operations(self):
"""Fetch operations from BOM and set in 'Production Order'""" """Fetch operations from BOM and set in 'Work Order'"""
self.set('operations', []) self.set('operations', [])
if not self.bom_no \ if not self.bom_no \
@@ -330,10 +330,10 @@ class ProductionOrder(Document):
timesheet.validate_time_logs() timesheet.validate_time_logs()
except OverlapError: except OverlapError:
if frappe.message_log: frappe.message_log.pop() if frappe.message_log: frappe.message_log.pop()
timesheet.schedule_for_production_order(d.idx) timesheet.schedule_for_work_order(d.idx)
except WorkstationHolidayError: except WorkstationHolidayError:
if frappe.message_log: frappe.message_log.pop() if frappe.message_log: frappe.message_log.pop()
timesheet.schedule_for_production_order(d.idx) timesheet.schedule_for_work_order(d.idx)
from_time, to_time = self.get_start_end_time(timesheet, d.name) from_time, to_time = self.get_start_end_time(timesheet, d.name)
@@ -418,12 +418,12 @@ class ProductionOrder(Document):
self.actual_end_date = max(actual_end_dates) self.actual_end_date = max(actual_end_dates)
def delete_timesheet(self): def delete_timesheet(self):
for timesheet in frappe.get_all("Timesheet", ["name"], {"production_order": self.name}): for timesheet in frappe.get_all("Timesheet", ["name"], {"work_order": self.name}):
frappe.delete_doc("Timesheet", timesheet.name) frappe.delete_doc("Timesheet", timesheet.name)
def validate_production_item(self): def validate_production_item(self):
if frappe.db.get_value("Item", self.production_item, "has_variants"): if frappe.db.get_value("Item", self.production_item, "has_variants"):
frappe.throw(_("Production Order cannot be raised against a Item Template"), ItemHasVariantError) frappe.throw(_("Work Order cannot be raised against a Item Template"), ItemHasVariantError)
if self.production_item: if self.production_item:
validate_end_of_life(self.production_item) validate_end_of_life(self.production_item)
@@ -458,7 +458,7 @@ class ProductionOrder(Document):
def get_items_and_operations_from_bom(self): def get_items_and_operations_from_bom(self):
self.set_required_items() self.set_required_items()
self.set_production_order_operations() self.set_work_order_operations()
return check_if_scrap_warehouse_mandatory(self.bom_no) return check_if_scrap_warehouse_mandatory(self.bom_no)
@@ -497,13 +497,13 @@ class ProductionOrder(Document):
def update_transaferred_qty_for_required_items(self): def update_transaferred_qty_for_required_items(self):
'''update transferred qty from submitted stock entries for that item against '''update transferred qty from submitted stock entries for that item against
the production order''' the work order'''
for d in self.required_items: for d in self.required_items:
transferred_qty = frappe.db.sql('''select sum(qty) transferred_qty = frappe.db.sql('''select sum(qty)
from `tabStock Entry` entry, `tabStock Entry Detail` detail from `tabStock Entry` entry, `tabStock Entry Detail` detail
where where
entry.production_order = %s entry.work_order = %s
and entry.purpose = "Material Transfer for Manufacture" and entry.purpose = "Material Transfer for Manufacture"
and entry.docstatus = 1 and entry.docstatus = 1
and detail.parent = entry.name and detail.parent = entry.name
@@ -564,50 +564,50 @@ def check_if_scrap_warehouse_mandatory(bom_no):
return res return res
@frappe.whitelist() @frappe.whitelist()
def set_production_order_ops(name): def set_work_order_ops(name):
po = frappe.get_doc('Production Order', name) po = frappe.get_doc('Work Order', name)
po.set_production_order_operations() po.set_work_order_operations()
po.save() po.save()
@frappe.whitelist() @frappe.whitelist()
def make_stock_entry(production_order_id, purpose, qty=None): def make_stock_entry(work_order_id, purpose, qty=None):
production_order = frappe.get_doc("Production Order", production_order_id) work_order = frappe.get_doc("Work Order", work_order_id)
if not frappe.db.get_value("Warehouse", production_order.wip_warehouse, "is_group") \ if not frappe.db.get_value("Warehouse", work_order.wip_warehouse, "is_group") \
and not production_order.skip_transfer: and not work_order.skip_transfer:
wip_warehouse = production_order.wip_warehouse wip_warehouse = work_order.wip_warehouse
else: else:
wip_warehouse = None wip_warehouse = None
stock_entry = frappe.new_doc("Stock Entry") stock_entry = frappe.new_doc("Stock Entry")
stock_entry.purpose = purpose stock_entry.purpose = purpose
stock_entry.production_order = production_order_id stock_entry.work_order = work_order_id
stock_entry.company = production_order.company stock_entry.company = work_order.company
stock_entry.from_bom = 1 stock_entry.from_bom = 1
stock_entry.bom_no = production_order.bom_no stock_entry.bom_no = work_order.bom_no
stock_entry.use_multi_level_bom = production_order.use_multi_level_bom stock_entry.use_multi_level_bom = work_order.use_multi_level_bom
stock_entry.fg_completed_qty = qty or (flt(production_order.qty) - flt(production_order.produced_qty)) stock_entry.fg_completed_qty = qty or (flt(work_order.qty) - flt(work_order.produced_qty))
if production_order.bom_no: if work_order.bom_no:
stock_entry.inspection_required = frappe.db.get_value('BOM', stock_entry.inspection_required = frappe.db.get_value('BOM',
production_order.bom_no, 'inspection_required') work_order.bom_no, 'inspection_required')
if purpose=="Material Transfer for Manufacture": if purpose=="Material Transfer for Manufacture":
stock_entry.to_warehouse = wip_warehouse stock_entry.to_warehouse = wip_warehouse
stock_entry.project = production_order.project stock_entry.project = work_order.project
else: else:
stock_entry.from_warehouse = wip_warehouse stock_entry.from_warehouse = wip_warehouse
stock_entry.to_warehouse = production_order.fg_warehouse stock_entry.to_warehouse = work_order.fg_warehouse
additional_costs = get_additional_costs(production_order, fg_qty=stock_entry.fg_completed_qty) additional_costs = get_additional_costs(work_order, fg_qty=stock_entry.fg_completed_qty)
stock_entry.project = production_order.project stock_entry.project = work_order.project
stock_entry.set("additional_costs", additional_costs) stock_entry.set("additional_costs", additional_costs)
stock_entry.get_items() stock_entry.get_items()
return stock_entry.as_dict() return stock_entry.as_dict()
@frappe.whitelist() @frappe.whitelist()
def make_timesheet(production_order, company): def make_timesheet(work_order, company):
timesheet = frappe.new_doc("Timesheet") timesheet = frappe.new_doc("Timesheet")
timesheet.employee = "" timesheet.employee = ""
timesheet.production_order = production_order timesheet.work_order = work_order
timesheet.company = company timesheet.company = company
return timesheet return timesheet
@@ -632,7 +632,7 @@ def get_default_warehouse():
@frappe.whitelist() @frappe.whitelist()
def make_new_timesheet(source_name, target_doc=None): def make_new_timesheet(source_name, target_doc=None):
po = frappe.get_doc('Production Order', source_name) po = frappe.get_doc('Work Order', source_name)
ts = po.make_time_logs(open_new=True) ts = po.make_time_logs(open_new=True)
if not ts or not ts.get('time_logs'): if not ts or not ts.get('time_logs'):
@@ -641,16 +641,16 @@ def make_new_timesheet(source_name, target_doc=None):
return ts return ts
@frappe.whitelist() @frappe.whitelist()
def stop_unstop(production_order, status): def stop_unstop(work_order, status):
""" Called from client side on Stop/Unstop event""" """ Called from client side on Stop/Unstop event"""
if not frappe.has_permission("Production Order", "write"): if not frappe.has_permission("Work Order", "write"):
frappe.throw(_("Not permitted"), frappe.PermissionError) frappe.throw(_("Not permitted"), frappe.PermissionError)
pro_order = frappe.get_doc("Production Order", production_order) pro_order = frappe.get_doc("Work Order", work_order)
pro_order.update_status(status) pro_order.update_status(status)
pro_order.update_planned_qty() pro_order.update_planned_qty()
frappe.msgprint(_("Production Order has been {0}").format(status)) frappe.msgprint(_("Work Order has been {0}").format(status))
pro_order.notify_update() pro_order.notify_update()
return pro_order.status return pro_order.status

View File

@@ -1,7 +1,7 @@
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors // Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
// License: GNU General Public License v3. See license.txt // For license information, please see license.txt
frappe.views.calendar["Production Order"] = { frappe.views.calendar["Work Order"] = {
field_map: { field_map: {
"start": "planned_start_date", "start": "planned_start_date",
"end": "planned_end_date", "end": "planned_end_date",

View File

@@ -2,7 +2,7 @@ from frappe import _
def get_data(): def get_data():
return { return {
'fieldname': 'production_order', 'fieldname': 'work_order',
'transactions': [ 'transactions': [
{ {
'items': ['Stock Entry', 'Timesheet'] 'items': ['Stock Entry', 'Timesheet']

View File

@@ -1,4 +1,4 @@
frappe.listview_settings['Production Order'] = { frappe.listview_settings['Work Order'] = {
add_fields: ["bom_no", "status", "sales_order", "qty", add_fields: ["bom_no", "status", "sales_order", "qty",
"produced_qty", "expected_delivery_date", "planned_start_date", "planned_end_date"], "produced_qty", "expected_delivery_date", "planned_start_date", "planned_end_date"],
filters: [["status", "!=", "Stopped"]], filters: [["status", "!=", "Stopped"]],

View File

@@ -354,10 +354,10 @@
"issingle": 0, "issingle": 0,
"istable": 1, "istable": 1,
"max_attachments": 0, "max_attachments": 0,
"modified": "2017-08-18 18:11:10.311736", "modified": "2018-02-13 02:58:11.328693",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Manufacturing", "module": "Manufacturing",
"name": "Production Order Item", "name": "Work Order Item",
"name_case": "", "name_case": "",
"owner": "Administrator", "owner": "Administrator",
"permissions": [], "permissions": [],

View File

@@ -6,8 +6,8 @@ from __future__ import unicode_literals
import frappe import frappe
from frappe.model.document import Document from frappe.model.document import Document
class ProductionOrderItem(Document): class WorkOrderItem(Document):
pass pass
def on_doctype_update(): def on_doctype_update():
frappe.db.add_index("Production Order Item", ["item_code", "source_warehouse"]) frappe.db.add_index("Work Order Item", ["item_code", "source_warehouse"])

View File

@@ -672,10 +672,10 @@
"issingle": 0, "issingle": 0,
"istable": 1, "istable": 1,
"max_attachments": 0, "max_attachments": 0,
"modified": "2017-05-29 18:02:04.252419", "modified": "2018-02-13 02:58:11.328693",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Manufacturing", "module": "Manufacturing",
"name": "Production Order Operation", "name": "Work Order Operation",
"name_case": "", "name_case": "",
"owner": "Administrator", "owner": "Administrator",
"permissions": [], "permissions": [],

View File

@@ -5,5 +5,5 @@ from __future__ import unicode_literals
import frappe import frappe
from frappe.model.document import Document from frappe.model.document import Document
class ProductionOrderOperation(Document): class WorkOrderOperation(Document):
pass pass

View File

@@ -19,7 +19,7 @@ erpnext.ProductionAnalytics = frappe.views.GridReportWithPlot.extend({
title: __("Production Analytics"), title: __("Production Analytics"),
parent: $(wrapper).find('.layout-main'), parent: $(wrapper).find('.layout-main'),
page: wrapper.page, page: wrapper.page,
doctypes: ["Item", "Company", "Fiscal Year", "Production Order"] doctypes: ["Item", "Company", "Fiscal Year", "Work Order"]
}); });
}, },
@@ -86,7 +86,7 @@ erpnext.ProductionAnalytics = frappe.views.GridReportWithPlot.extend({
// add Opening, Closing, Totals rows // add Opening, Closing, Totals rows
// if filtered by account and / or voucher // if filtered by account and / or voucher
var me = this; var me = this;
var all_open_orders = {name:"All Production Orders", "id": "all-open-pos", var all_open_orders = {name:"All Work Orders", "id": "all-open-pos",
checked:true}; checked:true};
var not_started = {name:"Not Started", "id":"not-started-pos", var not_started = {name:"Not Started", "id":"not-started-pos",
checked:true}; checked:true};
@@ -97,7 +97,7 @@ erpnext.ProductionAnalytics = frappe.views.GridReportWithPlot.extend({
var completed = {name:"Completed", "id":"completed-pos", var completed = {name:"Completed", "id":"completed-pos",
checked:true}; checked:true};
$.each(frappe.report_dump.data["Production Order"], function(i, d) { $.each(frappe.report_dump.data["Work Order"], function(i, d) {
var dateobj = frappe.datetime.str_to_obj(d.creation); var dateobj = frappe.datetime.str_to_obj(d.creation);
var date = frappe.datetime.str_to_user(d.creation.split(" ")[0]); var date = frappe.datetime.str_to_user(d.creation.split(" ")[0]);

View File

@@ -1,27 +0,0 @@
{
"add_total_row": 0,
"apply_user_permissions": 1,
"creation": "2013-08-12 12:44:27",
"disabled": 0,
"docstatus": 0,
"doctype": "Report",
"idx": 3,
"is_standard": "Yes",
"modified": "2017-02-24 20:10:32.460097",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "Completed Production Orders",
"owner": "Administrator",
"query": "SELECT\n `tabProduction Order`.name as \"Production Order:Link/Production Order:200\",\n `tabProduction Order`.creation as \"Date:Date:120\",\n `tabProduction Order`.production_item as \"Item:Link/Item:150\",\n `tabProduction Order`.qty as \"To Produce:Int:100\",\n `tabProduction Order`.produced_qty as \"Produced:Int:100\",\n `tabProduction Order`.company as \"Company:Link/Company:\"\nFROM\n `tabProduction Order`\nWHERE\n `tabProduction Order`.docstatus=1\n AND ifnull(`tabProduction Order`.produced_qty,0) = `tabProduction Order`.qty",
"ref_doctype": "Production Order",
"report_name": "Completed Production Orders",
"report_type": "Query Report",
"roles": [
{
"role": "Manufacturing User"
},
{
"role": "Stock User"
}
]
}

View File

@@ -0,0 +1,27 @@
{
"add_total_row": 0,
"apply_user_permissions": 1,
"creation": "2013-08-12 12:44:27",
"disabled": 0,
"docstatus": 0,
"doctype": "Report",
"idx": 3,
"is_standard": "Yes",
"modified": "2018-02-13 04:58:51.549413",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "Completed Work Orders",
"owner": "Administrator",
"query": "SELECT\n `tabWork Order`.name as \"Work Order:Link/Work Order:200\",\n `tabWork Order`.creation as \"Date:Date:120\",\n `tabWork Order`.production_item as \"Item:Link/Item:150\",\n `tabWork Order`.qty as \"To Produce:Int:100\",\n `tabWork Order`.produced_qty as \"Produced:Int:100\",\n `tabWork Order`.company as \"Company:Link/Company:\"\nFROM\n `tabWork Order`\nWHERE\n `tabWork Order`.docstatus=1\n AND ifnull(`tabWork Order`.produced_qty,0) = `tabWork Order`.qty",
"ref_doctype": "Work Order",
"report_name": "Completed Work Orders",
"report_type": "Query Report",
"roles": [
{
"role": "Manufacturing User"
},
{
"role": "Stock User"
}
]
}

View File

@@ -1,27 +0,0 @@
{
"add_total_row": 0,
"apply_user_permissions": 1,
"creation": "2013-05-03 17:48:46",
"disabled": 0,
"docstatus": 0,
"doctype": "Report",
"idx": 3,
"is_standard": "Yes",
"modified": "2017-02-24 20:00:38.101588",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "Issued Items Against Production Order",
"owner": "Administrator",
"query": "select\n ste.production_order as \"Production Order:Link/Production Order:120\",\n ste.posting_date as \"Issue Date:Date:140\",\n ste_item.item_code as \"Item Code:Link/Item:120\",\n\tste_item.description as \"Description::150\",\n\tste_item.transfer_qty as \"Qty:Float:100\",\n\tste_item.stock_uom as \"UOM:Link/UOM:80\",\n\tste_item.amount as \"Amount:Currency:120\",\n\tste_item.serial_no as \"Serial No:Link/Serial No:80\",\n\tste_item.s_warehouse as \"Source Warehouse:Link/Warehouse:120\",\n\tste_item.t_warehouse as \"Target Warehouse:Link/Warehouse:120\",\n\tpro.production_item as \"Finished Goods:Link/Item:120\", \n\tste.name as \"Stock Entry:Link/Stock Entry:120\",\n\tste.company as \"Company:Link/Company:\",\n\t(select bin.projected_qty from `tabBin` bin \n\t\t\twhere bin.item_code= ste_item.item_code and bin.warehouse= ste_item.s_warehouse) as \"Projected Quantity as Source:Float:200\"\nfrom\t`tabStock Entry` ste, `tabStock Entry Detail` ste_item, `tabProduction Order` pro\nwhere\n\tifnull(ste.production_order, '') != '' and ste.name = ste_item.parent \n\tand ste.production_order = pro.name and ste.docstatus = 1\n\tand ste.purpose = 'Material Transfer for Manufacture'\norder by ste.posting_date, ste.production_order, ste_item.item_code",
"ref_doctype": "Production Order",
"report_name": "Issued Items Against Production Order",
"report_type": "Query Report",
"roles": [
{
"role": "Manufacturing User"
},
{
"role": "Stock User"
}
]
}

View File

@@ -0,0 +1,27 @@
{
"add_total_row": 0,
"apply_user_permissions": 1,
"creation": "2013-05-03 17:48:46",
"disabled": 0,
"docstatus": 0,
"doctype": "Report",
"idx": 3,
"is_standard": "Yes",
"modified": "2018-02-13 04:56:57.040163",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "Issued Items Against Work Order",
"owner": "Administrator",
"query": "select\n ste.work_order as \"Work Order:Link/Work Order:120\",\n ste.posting_date as \"Issue Date:Date:140\",\n ste_item.item_code as \"Item Code:Link/Item:120\",\n\tste_item.description as \"Description::150\",\n\tste_item.transfer_qty as \"Qty:Float:100\",\n\tste_item.stock_uom as \"UOM:Link/UOM:80\",\n\tste_item.amount as \"Amount:Currency:120\",\n\tste_item.serial_no as \"Serial No:Link/Serial No:80\",\n\tste_item.s_warehouse as \"Source Warehouse:Link/Warehouse:120\",\n\tste_item.t_warehouse as \"Target Warehouse:Link/Warehouse:120\",\n\two.production_item as \"Finished Goods:Link/Item:120\", \n\tste.name as \"Stock Entry:Link/Stock Entry:120\",\n\tste.company as \"Company:Link/Company:\",\n\t(select bin.projected_qty from `tabBin` bin \n\t\t\twhere bin.item_code= ste_item.item_code and bin.warehouse= ste_item.s_warehouse) as \"Projected Quantity as Source:Float:200\"\nfrom\t`tabStock Entry` ste, `tabStock Entry Detail` ste_item, `tabWork Order` wo\nwhere\n\tifnull(ste.work_order, '') != '' and ste.name = ste_item.parent \n\tand ste.work_order = wo.name and ste.docstatus = 1\n\tand ste.purpose = 'Material Transfer for Manufacture'\norder by ste.posting_date, ste.work_order, ste_item.item_code",
"ref_doctype": "Work Order",
"report_name": "Issued Items Against Work Order",
"report_type": "Query Report",
"roles": [
{
"role": "Manufacturing User"
},
{
"role": "Stock User"
}
]
}

View File

@@ -1,27 +0,0 @@
{
"add_total_row": 0,
"apply_user_permissions": 1,
"creation": "2013-08-12 12:32:30",
"disabled": 0,
"docstatus": 0,
"doctype": "Report",
"idx": 3,
"is_standard": "Yes",
"modified": "2017-02-24 20:10:23.179130",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "Open Production Orders",
"owner": "Administrator",
"query": "SELECT\n `tabProduction Order`.name as \"Production Order:Link/Production Order:200\",\n `tabProduction Order`.creation as \"Date:Date:120\",\n `tabProduction Order`.production_item as \"Item:Link/Item:150\",\n `tabProduction Order`.qty as \"To Produce:Int:100\",\n `tabProduction Order`.produced_qty as \"Produced:Int:100\",\n `tabProduction Order`.company as \"Company:Link/Company:\"\nFROM\n `tabProduction Order`\nWHERE\n `tabProduction Order`.docstatus=1\n AND ifnull(`tabProduction Order`.produced_qty,0) < `tabProduction Order`.qty\n AND NOT EXISTS (SELECT name from `tabStock Entry` where production_order =`tabProduction Order`.name) ",
"ref_doctype": "Production Order",
"report_name": "Open Production Orders",
"report_type": "Query Report",
"roles": [
{
"role": "Manufacturing User"
},
{
"role": "Stock User"
}
]
}

View File

@@ -0,0 +1,27 @@
{
"add_total_row": 0,
"apply_user_permissions": 1,
"creation": "2013-08-12 12:32:30",
"disabled": 0,
"docstatus": 0,
"doctype": "Report",
"idx": 3,
"is_standard": "Yes",
"modified": "2018-02-13 04:58:29.780472",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "Open Work Orders",
"owner": "Administrator",
"query": "SELECT\n `tabWork Order`.name as \"Work Order:Link/Work Order:200\",\n `tabWork Order`.creation as \"Date:Date:120\",\n `tabWork Order`.production_item as \"Item:Link/Item:150\",\n `tabWork Order`.qty as \"To Produce:Int:100\",\n `tabWork Order`.produced_qty as \"Produced:Int:100\",\n `tabWork Order`.company as \"Company:Link/Company:\"\nFROM\n `tabWork Order`\nWHERE\n `tabWork Order`.docstatus=1\n AND ifnull(`tabWork Order`.produced_qty,0) < `tabWork Order`.qty\n AND NOT EXISTS (SELECT name from `tabStock Entry` where work_order =`tabWork Order`.name) ",
"ref_doctype": "Work Order",
"report_name": "Open Work Orders",
"report_type": "Query Report",
"roles": [
{
"role": "Manufacturing User"
},
{
"role": "Stock User"
}
]
}

View File

@@ -1,27 +0,0 @@
{
"add_total_row": 0,
"apply_user_permissions": 1,
"creation": "2013-08-12 12:43:47",
"disabled": 0,
"docstatus": 0,
"doctype": "Report",
"idx": 3,
"is_standard": "Yes",
"modified": "2017-02-24 20:10:40.304828",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "Production Orders in Progress",
"owner": "Administrator",
"query": "SELECT\n `tabProduction Order`.name as \"Production Order:Link/Production Order:200\",\n `tabProduction Order`.creation as \"Date:Date:120\",\n `tabProduction Order`.production_item as \"Item:Link/Item:150\",\n `tabProduction Order`.qty as \"To Produce:Int:100\",\n `tabProduction Order`.produced_qty as \"Produced:Int:100\",\n `tabProduction Order`.company as \"Company:Link/Company:\"\nFROM\n `tabProduction Order`\nWHERE\n `tabProduction Order`.docstatus=1\n AND ifnull(`tabProduction Order`.produced_qty,0) < `tabProduction Order`.qty\n AND EXISTS (SELECT name from `tabStock Entry` where production_order =`tabProduction Order`.name) ",
"ref_doctype": "Production Order",
"report_name": "Production Orders in Progress",
"report_type": "Query Report",
"roles": [
{
"role": "Manufacturing User"
},
{
"role": "Stock User"
}
]
}

View File

@@ -1,7 +1,8 @@
// Copyright (c) 2016, Velometro Mobility Inc and contributors // Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt // For license information, please see license.txt
/* eslint-disable */
frappe.query_reports["Production Order Stock Report"] = { frappe.query_reports["Work Order Stock Report"] = {
"filters": [ "filters": [
{ {
"fieldname": "warehouse", "fieldname": "warehouse",

View File

@@ -8,13 +8,13 @@
"idx": 0, "idx": 0,
"is_standard": "Yes", "is_standard": "Yes",
"letter_head": "", "letter_head": "",
"modified": "2017-02-24 19:59:07.792058", "modified": "2018-02-13 04:47:04.158213",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Manufacturing", "module": "Manufacturing",
"name": "Production Order Stock Report", "name": "Work Order Stock Report",
"owner": "Administrator", "owner": "Administrator",
"ref_doctype": "Production Order", "ref_doctype": "Work Order",
"report_name": "Production Order Stock Report", "report_name": "Work Order Stock Report",
"report_type": "Script Report", "report_type": "Script Report",
"roles": [ "roles": [
{ {

View File

@@ -6,20 +6,20 @@ from frappe.utils import cint
import frappe import frappe
def execute(filters=None): def execute(filters=None):
prod_list = get_production_orders() wo_list = get_work_orders()
data = get_item_list(prod_list, filters) data = get_item_list(wo_list, filters)
columns = get_columns() columns = get_columns()
return columns, data return columns, data
def get_item_list(prod_list, filters): def get_item_list(wo_list, filters):
out = [] out = []
#Add a row for each item/qty #Add a row for each item/qty
for prod_details in prod_list: for wo_details in wo_list:
desc = frappe.db.get_value("BOM", prod_details.bom_no, "description") desc = frappe.db.get_value("BOM", wo_details.bom_no, "description")
for prod_item_details in frappe.db.get_values("Production Order Item", for wo_item_details in frappe.db.get_values("Work Order Item",
{"parent": prod_details.name}, ["item_code", "source_warehouse"], as_dict=1): {"parent": wo_details.name}, ["item_code", "source_warehouse"], as_dict=1):
item_list = frappe.db.sql("""SELECT item_list = frappe.db.sql("""SELECT
bom_item.item_code as item_code, bom_item.item_code as item_code,
@@ -35,15 +35,15 @@ def get_item_list(prod_list, filters):
and bom.name = %(bom)s and bom.name = %(bom)s
GROUP BY GROUP BY
bom_item.item_code""", bom_item.item_code""",
{"bom": prod_details.bom_no, "warehouse": prod_item_details.source_warehouse, {"bom": wo_details.bom_no, "warehouse": wo_item_details.source_warehouse,
"filterhouse": filters.warehouse, "item_code": prod_item_details.item_code}, as_dict=1) "filterhouse": filters.warehouse, "item_code": wo_item_details.item_code}, as_dict=1)
stock_qty = 0 stock_qty = 0
count = 0 count = 0
buildable_qty = prod_details.qty buildable_qty = wo_details.qty
for item in item_list: for item in item_list:
count = count + 1 count = count + 1
if item.build_qty >= (prod_details.qty - prod_details.produced_qty): if item.build_qty >= (wo_details.qty - wo_details.produced_qty):
stock_qty = stock_qty + 1 stock_qty = stock_qty + 1
elif buildable_qty >= item.build_qty: elif buildable_qty >= item.build_qty:
buildable_qty = item.build_qty buildable_qty = item.build_qty
@@ -54,15 +54,15 @@ def get_item_list(prod_list, filters):
build = "N" build = "N"
row = frappe._dict({ row = frappe._dict({
"production_order": prod_details.name, "work_order": wo_details.name,
"status": prod_details.status, "status": wo_details.status,
"req_items": cint(count), "req_items": cint(count),
"instock": stock_qty, "instock": stock_qty,
"description": desc, "description": desc,
"source_warehouse": prod_item_details.source_warehouse, "source_warehouse": wo_item_details.source_warehouse,
"item_code": prod_item_details.item_code, "item_code": wo_item_details.item_code,
"bom_no": prod_details.bom_no, "bom_no": wo_details.bom_no,
"qty": prod_details.qty, "qty": wo_details.qty,
"buildable_qty": buildable_qty, "buildable_qty": buildable_qty,
"ready_to_build": build "ready_to_build": build
}) })
@@ -71,18 +71,18 @@ def get_item_list(prod_list, filters):
return out return out
def get_production_orders(): def get_work_orders():
out = frappe.get_all("Production Order", filters={"docstatus": 1, "status": ( "!=","Completed")}, out = frappe.get_all("Work Order", filters={"docstatus": 1, "status": ( "!=","Completed")},
fields=["name","status", "bom_no", "qty", "produced_qty"], order_by='name') fields=["name","status", "bom_no", "qty", "produced_qty"], order_by='name')
return out return out
def get_columns(): def get_columns():
columns = [{ columns = [{
"fieldname": "production_order", "fieldname": "work_order",
"label": "Production Order", "label": "Work Order",
"fieldtype": "Link", "fieldtype": "Link",
"options": "Production Order", "options": "Work Order",
"width": 110 "width": 110
}, { }, {
"fieldname": "bom_no", "fieldname": "bom_no",

View File

@@ -0,0 +1,27 @@
{
"add_total_row": 0,
"apply_user_permissions": 1,
"creation": "2013-08-12 12:43:47",
"disabled": 0,
"docstatus": 0,
"doctype": "Report",
"idx": 3,
"is_standard": "Yes",
"modified": "2018-02-13 04:56:02.206353",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "Work Orders in Progress",
"owner": "Administrator",
"query": "SELECT\n `tabWork Order`.name as \"Work Order:Link/Work Order:200\",\n `tabWork Order`.creation as \"Date:Date:120\",\n `tabWork Order`.production_item as \"Item:Link/Item:150\",\n `tabWork Order`.qty as \"To Produce:Int:100\",\n `tabWork Order`.produced_qty as \"Produced:Int:100\",\n `tabWork Order`.company as \"Company:Link/Company:\"\nFROM\n `tabWork Order`\nWHERE\n `tabWork Order`.docstatus=1\n AND ifnull(`tabWork Order`.produced_qty,0) < `tabWork Order`.qty\n AND EXISTS (SELECT name from `tabStock Entry` where work_order =`tabWork Order`.name) ",
"ref_doctype": "Work Order",
"report_name": "Work Orders in Progress",
"report_type": "Query Report",
"roles": [
{
"role": "Manufacturing User"
},
{
"role": "Stock User"
}
]
}

View File

@@ -1,6 +1,7 @@
execute:import unidecode # new requirement execute:import unidecode # new requirement
erpnext.patches.v8_0.move_perpetual_inventory_setting erpnext.patches.v8_0.move_perpetual_inventory_setting
erpnext.patches.v10_0.rename_schools_to_education erpnext.patches.v10_0.rename_schools_to_education
erpnext.patches.v11_0.rename_production_order_to_work_order
erpnext.patches.v4_0.validate_v3_patch erpnext.patches.v4_0.validate_v3_patch
erpnext.patches.v4_0.fix_employee_user_id erpnext.patches.v4_0.fix_employee_user_id
erpnext.patches.v4_0.remove_employee_role_if_no_employee erpnext.patches.v4_0.remove_employee_role_if_no_employee
@@ -198,7 +199,7 @@ execute:frappe.db.sql("update `tabStock Ledger Entry` set stock_queue = '[]' whe
erpnext.patches.v5_4.fix_missing_item_images erpnext.patches.v5_4.fix_missing_item_images
erpnext.patches.v5_4.stock_entry_additional_costs erpnext.patches.v5_4.stock_entry_additional_costs
erpnext.patches.v5_4.cleanup_journal_entry #2015-08-14 erpnext.patches.v5_4.cleanup_journal_entry #2015-08-14
execute:frappe.db.sql("update `tabProduction Order` pro set description = (select description from tabItem where name=pro.production_item) where ifnull(description, '') = ''") erpnext.patches.v5_7.update_item_description_based_on_item_master
erpnext.patches.v5_7.item_template_attributes erpnext.patches.v5_7.item_template_attributes
execute:frappe.delete_doc_if_exists("DocType", "Manage Variants") execute:frappe.delete_doc_if_exists("DocType", "Manage Variants")
execute:frappe.delete_doc_if_exists("DocType", "Manage Variants Item") execute:frappe.delete_doc_if_exists("DocType", "Manage Variants Item")

View File

@@ -0,0 +1,35 @@
# Copyright (c) 2018, Frappe and Contributors
# License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals
from frappe.model.rename_doc import rename_doc
from frappe.model.utils.rename_field import rename_field
import frappe
def execute():
rename_doc('DocType', 'Production Order', 'Work Order', force=True)
frappe.reload_doc('manufacturing', 'doctype', 'work_order')
rename_doc('DocType', 'Production Order Item', 'Work Order Item', force=True)
frappe.reload_doc('manufacturing', 'doctype', 'work_order_item')
rename_doc('DocType', 'Production Order Operation', 'Work Order Operation', force=True)
frappe.reload_doc('manufacturing', 'doctype', 'work_order_operation')
frappe.reload_doc('projects', 'doctype', 'timesheet')
frappe.reload_doc('stock', 'doctype', 'stock_entry')
rename_field("Timesheet", "production_order", "work_order")
rename_field("Stock Entry", "production_order", "work_order")
frappe.rename_doc("Report", "Production Orders in Progress", "Work Orders in Progress", force=True)
frappe.rename_doc("Report", "Completed Production Orders", "Completed Work Orders", force=True)
frappe.rename_doc("Report", "Open Production Orders", "Open Work Orders", force=True)
frappe.rename_doc("Report", "Issued Items Against Production Order", "Issued Items Against Work Order", force=True)
frappe.rename_doc("Report", "Production Order Stock Report", "Work Order Stock Report", force=True)
frappe.db.sql("""update `tabDesktop Icon` \
set label='Work Order', module_name='Work Order' \
where label='Production Order'""")
frappe.db.sql("""update `tabDesktop Icon` \
set link='List/Work Order' \
where link='List/Production Order'""")

View File

@@ -18,7 +18,7 @@ doctype_series_map = {
'Lead': 'LEAD-', 'Lead': 'LEAD-',
'Opportunity': 'OPTY-', 'Opportunity': 'OPTY-',
'Packing Slip': 'PS-', 'Packing Slip': 'PS-',
'Production Order': 'PRO-', 'Work Order': 'WO-',
'Purchase Invoice': 'PINV-', 'Purchase Invoice': 'PINV-',
'Purchase Order': 'PO-', 'Purchase Order': 'PO-',
'Purchase Receipt': 'PREC-', 'Purchase Receipt': 'PREC-',

View File

@@ -5,5 +5,5 @@ from __future__ import unicode_literals
import frappe import frappe
def execute(): def execute():
frappe.db.sql("""update `tabStock Entry` set purpose='Manufacture' where purpose='Manufacture/Repack' and ifnull(production_order,"")!="" """) frappe.db.sql("""update `tabStock Entry` set purpose='Manufacture' where purpose='Manufacture/Repack' and ifnull(work_order,"")!="" """)
frappe.db.sql("""update `tabStock Entry` set purpose='Repack' where purpose='Manufacture/Repack' and ifnull(production_order,"")="" """) frappe.db.sql("""update `tabStock Entry` set purpose='Repack' where purpose='Manufacture/Repack' and ifnull(work_order,"")="" """)

View File

@@ -5,9 +5,9 @@ from __future__ import unicode_literals
import frappe import frappe
def execute(): def execute():
for po in frappe.db.sql("""select name from `tabProduction Order` where docstatus < 2""", as_dict=1): for wo in frappe.db.sql("""select name from `tabWork Order` where docstatus < 2""", as_dict=1):
prod_order = frappe.get_doc("Production Order", po.name) work_order = frappe.get_doc("Work Order", wo.name)
if prod_order.operations: if work_order.operations:
prod_order.flags.ignore_validate_update_after_submit = True work_order.flags.ignore_validate_update_after_submit = True
prod_order.calculate_time() work_order.calculate_time()
prod_order.save() work_order.save()

View File

@@ -51,7 +51,7 @@ rename_map = {
["other_charges", "taxes"], ["other_charges", "taxes"],
["advance_allocation_details", "advances"] ["advance_allocation_details", "advances"]
], ],
"Production Order": [ "Work Order": [
["production_order_operations", "operations"] ["production_order_operations", "operations"]
], ],
"BOM": [ "BOM": [
@@ -216,7 +216,7 @@ def execute():
frappe.rename_doc("DocType", old_dt, new_dt, force=True) frappe.rename_doc("DocType", old_dt, new_dt, force=True)
# reload new child doctypes # reload new child doctypes
frappe.reload_doc("manufacturing", "doctype", "production_order_operation") frappe.reload_doc("manufacturing", "doctype", "work_order_operation")
frappe.reload_doc("manufacturing", "doctype", "workstation_working_hour") frappe.reload_doc("manufacturing", "doctype", "workstation_working_hour")
frappe.reload_doc("stock", "doctype", "item_variant") frappe.reload_doc("stock", "doctype", "item_variant")
frappe.reload_doc("hr", "doctype", "salary_detail") frappe.reload_doc("hr", "doctype", "salary_detail")

View File

@@ -2,4 +2,4 @@ import frappe
def execute(): def execute():
frappe.db.sql("""update `tabStock Entry` set purpose='Material Transfer for Manufacture' frappe.db.sql("""update `tabStock Entry` set purpose='Material Transfer for Manufacture'
where ifnull(production_order, '')!='' and purpose='Material Transfer'""") where ifnull(work_order, '')!='' and purpose='Material Transfer'""")

View File

@@ -1,9 +1,9 @@
import frappe import frappe
def execute(): def execute():
frappe.reload_doctype("Production Order") frappe.reload_doctype("Work Order")
frappe.db.sql("""update `tabProduction Order` set material_transferred_for_manufacturing= frappe.db.sql("""update `tabWork Order` set material_transferred_for_manufacturing=
(select sum(fg_completed_qty) from `tabStock Entry` (select sum(fg_completed_qty) from `tabStock Entry`
where docstatus=1 where docstatus=1
and production_order=`tabProduction Order`.name and work_order=`tabWork Order`.name
and purpose = "Material Transfer for Manufacture")""") and purpose = "Material Transfer for Manufacture")""")

View File

@@ -1,18 +1,18 @@
import frappe import frappe
def execute(): def execute():
pro_order_qty_transferred = frappe._dict() wo_order_qty_transferred = frappe._dict()
for se in frappe.db.sql("""select production_order, sum(fg_completed_qty) as transferred_qty for se in frappe.db.sql("""select work_order, sum(fg_completed_qty) as transferred_qty
from `tabStock Entry` from `tabStock Entry`
where docstatus=1 and ifnull(production_order, '') != '' where docstatus=1 and ifnull(work_order, '') != ''
and purpose = 'Material Transfer for Manufacture' and purpose = 'Material Transfer for Manufacture'
group by production_order""", as_dict=1): group by work_order""", as_dict=1):
pro_order_qty_transferred.setdefault(se.production_order, se.transferred_qty) wo_order_qty_transferred.setdefault(se.work_order, se.transferred_qty)
for d in frappe.get_all("Production Order", filters={"docstatus": 1}, fields=["name", "qty"]): for d in frappe.get_all("Work Order", filters={"docstatus": 1}, fields=["name", "qty"]):
if d.name in pro_order_qty_transferred: if d.name in wo_order_qty_transferred:
material_transferred_for_manufacturing = pro_order_qty_transferred.get(d.name) \ material_transferred_for_manufacturing = wo_order_qty_transferred.get(d.name) \
if pro_order_qty_transferred.get(d.name) <= d.qty else d.qty if wo_order_qty_transferred.get(d.name) <= d.qty else d.qty
frappe.db.sql("""update `tabProduction Order` set material_transferred_for_manufacturing=%s frappe.db.sql("""update `tabWork Order` set material_transferred_for_manufacturing=%s
where name=%s""", (material_transferred_for_manufacturing, d.name)) where name=%s""", (material_transferred_for_manufacturing, d.name))

View File

@@ -0,0 +1,12 @@
import frappe
def execute():
name = frappe.db.sql(""" select name from `tabPatch Log` \
where \
patch like 'execute:frappe.db.sql("update `tabProduction Order` pro set description%' """)
if not name:
frappe.db.sql("update `tabProduction Order` pro \
set \
description = (select description from tabItem where name=pro.production_item) \
where \
ifnull(description, '') = ''")

View File

@@ -7,7 +7,7 @@ from erpnext.stock.stock_balance import get_planned_qty, update_bin_qty
def execute(): def execute():
for item_code, warehouse in frappe.db.sql("""select distinct production_item, fg_warehouse for item_code, warehouse in frappe.db.sql("""select distinct production_item, fg_warehouse
from `tabProduction Order`"""): from `tabWork Order`"""):
if frappe.db.exists("Item", item_code) and frappe.db.exists("Warehouse", warehouse): if frappe.db.exists("Item", item_code) and frappe.db.exists("Warehouse", warehouse):
update_bin_qty(item_code, warehouse, { update_bin_qty(item_code, warehouse, {
"planned_qty": get_planned_qty(item_code, warehouse) "planned_qty": get_planned_qty(item_code, warehouse)

View File

@@ -7,7 +7,7 @@ from frappe.model.utils.rename_field import rename_field
def execute(): def execute():
doc_list = ["Production Order", "BOM", "Purchase Invoice Item", "Sales Invoice", doc_list = ["Work Order", "BOM", "Purchase Invoice Item", "Sales Invoice",
"Purchase Order Item", "Stock Entry", "Delivery Note", "Sales Order", "Purchase Order Item", "Stock Entry", "Delivery Note", "Sales Order",
"Purchase Receipt Item", "Supplier Quotation Item"] "Purchase Receipt Item", "Supplier Quotation Item"]

View File

@@ -1,5 +1,5 @@
import frappe import frappe
from erpnext.manufacturing.doctype.production_order.production_order \ from erpnext.manufacturing.doctype.work_order.work_order \
import make_timesheet, add_timesheet_detail import make_timesheet, add_timesheet_detail
def execute(): def execute():
@@ -11,12 +11,12 @@ def execute():
for data in frappe.db.sql("select * from `tabTime Log`", as_dict=1): for data in frappe.db.sql("select * from `tabTime Log`", as_dict=1):
if data.task: if data.task:
company = frappe.db.get_value("Task", data.task, "company") company = frappe.db.get_value("Task", data.task, "company")
elif data.production_order: elif data.work_order:
company = frappe.db.get_value("Prodction Order", data.production_order, "company") company = frappe.db.get_value("Work Order", data.work_order, "company")
else: else:
company = frappe.db.get_single_value('Global Defaults', 'default_company') company = frappe.db.get_single_value('Global Defaults', 'default_company')
time_sheet = make_timesheet(data.production_order, company) time_sheet = make_timesheet(data.work_order, company)
args = get_timelog_data(data) args = get_timelog_data(data)
add_timesheet_detail(time_sheet, args) add_timesheet_detail(time_sheet, args)
if data.docstatus == 2: if data.docstatus == 2:
@@ -40,7 +40,7 @@ def execute():
time_sheet.db_set("docstatus", 1) time_sheet.db_set("docstatus", 1)
for d in time_sheet.get("time_logs"): for d in time_sheet.get("time_logs"):
d.db_set("docstatus", 1) d.db_set("docstatus", 1)
time_sheet.update_production_order(time_sheet.name) time_sheet.update_work_order(time_sheet.name)
time_sheet.update_task_and_project() time_sheet.update_task_and_project()
if data.docstatus == 2: if data.docstatus == 2:
time_sheet.db_set("docstatus", 2) time_sheet.db_set("docstatus", 2)

View File

@@ -1,6 +1,6 @@
import frappe import frappe
from frappe.utils import cint from frappe.utils import cint
from erpnext.manufacturing.doctype.production_order.production_order import add_timesheet_detail from erpnext.manufacturing.doctype.work_order.work_order import add_timesheet_detail
from erpnext.patches.v7_0.convert_timelog_to_timesheet import get_timelog_data from erpnext.patches.v7_0.convert_timelog_to_timesheet import get_timelog_data
def execute(): def execute():

View File

@@ -3,8 +3,8 @@ import frappe
from erpnext.stock.stock_balance import repost_stock from erpnext.stock.stock_balance import repost_stock
def execute(): def execute():
frappe.reload_doc('manufacturing', 'doctype', 'production_order_item') frappe.reload_doc('manufacturing', 'doctype', 'work_order_item')
frappe.reload_doc('manufacturing', 'doctype', 'production_order') frappe.reload_doc('manufacturing', 'doctype', 'work_order')
modified_items = frappe.db.sql_list(""" modified_items = frappe.db.sql_list("""
select name from `tabItem` select name from `tabItem`
@@ -26,12 +26,12 @@ def execute():
item_warehouses_with_transactions += list(frappe.db.sql(""" item_warehouses_with_transactions += list(frappe.db.sql("""
select distinct production_item, fg_warehouse select distinct production_item, fg_warehouse
from `tabProduction Order` where docstatus=1 and production_item in ({0})""" from `tabWork Order` where docstatus=1 and production_item in ({0})"""
.format(', '.join(['%s']*len(modified_items))), tuple(modified_items))) .format(', '.join(['%s']*len(modified_items))), tuple(modified_items)))
item_warehouses_with_transactions += list(frappe.db.sql(""" item_warehouses_with_transactions += list(frappe.db.sql("""
select distinct pr_item.item_code, pr_item.source_warehouse select distinct pr_item.item_code, pr_item.source_warehouse
from `tabProduction Order` pr, `tabProduction Order Item` pr_item from `tabWork Order` pr, `tabWork Order Item` pr_item
where pr_item.parent and pr.name and pr.docstatus=1 and pr_item.item_code in ({0})""" where pr_item.parent and pr.name and pr.docstatus=1 and pr_item.item_code in ({0})"""
.format(', '.join(['%s']*len(modified_items))), tuple(modified_items))) .format(', '.join(['%s']*len(modified_items))), tuple(modified_items)))

View File

@@ -6,41 +6,41 @@ import frappe
def execute(): def execute():
# reload schema # reload schema
for doctype in ("Production Order", "Production Order Item", "Production Order Operation", for doctype in ("Work Order", "Work Order Item", "Work Order Operation",
"BOM Item", "BOM Explosion Item", "BOM"): "BOM Item", "BOM Explosion Item", "BOM"):
frappe.reload_doctype(doctype) frappe.reload_doctype(doctype)
# fetch all draft and submitted production orders # fetch all draft and submitted work orders
fields = ["name"] fields = ["name"]
if "source_warehouse" in frappe.db.get_table_columns("Production Order"): if "source_warehouse" in frappe.db.get_table_columns("Work Order"):
fields.append("source_warehouse") fields.append("source_warehouse")
pro_orders = frappe.get_all("Production Order", filters={"docstatus": ["!=", 2]}, fields=fields) wo_orders = frappe.get_all("Work Order", filters={"docstatus": ["!=", 2]}, fields=fields)
count = 0 count = 0
for p in pro_orders: for p in wo_orders:
pro_order = frappe.get_doc("Production Order", p.name) wo_order = frappe.get_doc("Work Order", p.name)
count += 1 count += 1
# set required items table # set required items table
pro_order.set_required_items() wo_order.set_required_items()
for item in pro_order.get("required_items"): for item in wo_order.get("required_items"):
# set source warehouse based on parent # set source warehouse based on parent
if not item.source_warehouse and "source_warehouse" in fields: if not item.source_warehouse and "source_warehouse" in fields:
item.source_warehouse = pro_order.get("source_warehouse") item.source_warehouse = wo_order.get("source_warehouse")
item.db_update() item.db_update()
if pro_order.docstatus == 1: if wo_order.docstatus == 1:
# update transferred qty based on Stock Entry, it also updates db # update transferred qty based on Stock Entry, it also updates db
pro_order.update_transaferred_qty_for_required_items() wo_order.update_transaferred_qty_for_required_items()
# Set status where it was 'Unstopped', as it is deprecated # Set status where it was 'Unstopped', as it is deprecated
if pro_order.status == "Unstopped": if wo_order.status == "Unstopped":
status = pro_order.get_status() status = wo_order.get_status()
pro_order.db_set("status", status) wo_order.db_set("status", status)
elif pro_order.status == "Stopped": elif wo_order.status == "Stopped":
pro_order.update_reserved_qty_for_production() wo_order.update_reserved_qty_for_production()
if count % 200 == 0: if count % 200 == 0:
frappe.db.commit() frappe.db.commit()

View File

@@ -5,5 +5,5 @@ import frappe
def execute(): def execute():
for dt in ("Sales Order Item", "Purchase Order Item", for dt in ("Sales Order Item", "Purchase Order Item",
"Material Request Item", "Production Order Item", "Packed Item"): "Material Request Item", "Work Order Item", "Packed Item"):
frappe.get_doc("DocType", dt).run_module_method("on_doctype_update") frappe.get_doc("DocType", dt).run_module_method("on_doctype_update")

View File

@@ -11,5 +11,5 @@ def execute():
#Check more than one company exists #Check more than one company exists
if len(company) > 1: if len(company) > 1:
frappe.db.sql(""" update `tabTimesheet` set `tabTimesheet`.company = frappe.db.sql(""" update `tabTimesheet` set `tabTimesheet`.company =
(select company from `tabProduction Order` where name = `tabTimesheet`.production_order) (select company from `tabWork Order` where name = `tabTimesheet`.work_order)
where production_order is not null and production_order !=''""") where workn_order is not null and work_order !=''""")

View File

@@ -3,7 +3,7 @@ import frappe
def execute(): def execute():
frappe.db.sql(""" frappe.db.sql("""
update `tabBOM Item` bom, `tabProduction Order Item` po_item update `tabBOM Item` bom, `tabWork Order Item` po_item
set po_item.item_name = bom.item_name, set po_item.item_name = bom.item_name,
po_item.description = bom.description po_item.description = bom.description
where po_item.item_code = bom.item_code where po_item.item_code = bom.item_code

View File

@@ -0,0 +1,23 @@
/* eslint-disable */
// rename this file from _test_[name] to test_[name] to activate
// and remove above this line
QUnit.test("test: Timesheet", function (assert) {
let done = assert.async();
// number of asserts
assert.expect(1);
frappe.run_serially([
// insert a new Timesheet
() => frappe.tests.make('Timesheet', [
// values to be set
{key: 'value'}
]),
() => {
assert.equal(cur_frm.doc.key, 'value');
},
() => done()
]);
});

View File

@@ -204,7 +204,7 @@
"collapsible": 0, "collapsible": 0,
"collapsible_depends_on": "", "collapsible_depends_on": "",
"columns": 0, "columns": 0,
"depends_on": "eval:!doc.production_order || doc.docstatus == 1", "depends_on": "eval:!doc.work_order || doc.docstatus == 1",
"fieldname": "employee_detail", "fieldname": "employee_detail",
"fieldtype": "Section Break", "fieldtype": "Section Break",
"hidden": 0, "hidden": 0,
@@ -421,8 +421,8 @@
"collapsible": 0, "collapsible": 0,
"collapsible_depends_on": "", "collapsible_depends_on": "",
"columns": 0, "columns": 0,
"depends_on": "production_order", "depends_on": "work_order",
"fieldname": "production_detail", "fieldname": "work_detail",
"fieldtype": "Section Break", "fieldtype": "Section Break",
"hidden": 0, "hidden": 0,
"ignore_user_permissions": 0, "ignore_user_permissions": 0,
@@ -431,7 +431,7 @@
"in_global_search": 0, "in_global_search": 0,
"in_list_view": 0, "in_list_view": 0,
"in_standard_filter": 0, "in_standard_filter": 0,
"label": "Production Detail", "label": "Work Detail",
"length": 0, "length": 0,
"no_copy": 0, "no_copy": 0,
"permlevel": 0, "permlevel": 0,
@@ -452,7 +452,7 @@
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"fieldname": "production_order", "fieldname": "work_order",
"fieldtype": "Link", "fieldtype": "Link",
"hidden": 0, "hidden": 0,
"ignore_user_permissions": 0, "ignore_user_permissions": 0,
@@ -461,10 +461,10 @@
"in_global_search": 0, "in_global_search": 0,
"in_list_view": 0, "in_list_view": 0,
"in_standard_filter": 0, "in_standard_filter": 0,
"label": "Production Order", "label": "Work Order",
"length": 0, "length": 0,
"no_copy": 1, "no_copy": 1,
"options": "Production Order", "options": "Work Order",
"permlevel": 0, "permlevel": 0,
"precision": "", "precision": "",
"print_hide": 0, "print_hide": 0,
@@ -938,7 +938,7 @@
"issingle": 0, "issingle": 0,
"istable": 0, "istable": 0,
"max_attachments": 0, "max_attachments": 0,
"modified": "2017-06-13 14:28:51.838769", "modified": "2018-01-07 11:44:09.573900",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Projects", "module": "Projects",
"name": "Timesheet", "name": "Timesheet",

View File

@@ -16,7 +16,7 @@ from erpnext.manufacturing.doctype.workstation.workstation import (check_if_with
from erpnext.manufacturing.doctype.manufacturing_settings.manufacturing_settings import get_mins_between_operations from erpnext.manufacturing.doctype.manufacturing_settings.manufacturing_settings import get_mins_between_operations
class OverlapError(frappe.ValidationError): pass class OverlapError(frappe.ValidationError): pass
class OverProductionLoggedError(frappe.ValidationError): pass class OverWorkLoggedError(frappe.ValidationError): pass
class Timesheet(Document): class Timesheet(Document):
def onload(self): def onload(self):
@@ -97,18 +97,18 @@ class Timesheet(Document):
self.set_status() self.set_status()
def on_cancel(self): def on_cancel(self):
self.update_production_order(None) self.update_work_order(None)
self.update_task_and_project() self.update_task_and_project()
def on_submit(self): def on_submit(self):
self.validate_mandatory_fields() self.validate_mandatory_fields()
self.update_production_order(self.name) self.update_work_order(self.name)
self.update_task_and_project() self.update_task_and_project()
def validate_mandatory_fields(self): def validate_mandatory_fields(self):
if self.production_order: if self.work_order:
production_order = frappe.get_doc("Production Order", self.production_order) work_order = frappe.get_doc("Work Order", self.work_order)
pending_qty = flt(production_order.qty) - flt(production_order.produced_qty) pending_qty = flt(work_order.qty) - flt(work_order.produced_qty)
for data in self.time_logs: for data in self.time_logs:
if not data.from_time and not data.to_time: if not data.from_time and not data.to_time:
@@ -120,16 +120,16 @@ class Timesheet(Document):
if flt(data.hours) == 0.0: if flt(data.hours) == 0.0:
frappe.throw(_("Row {0}: Hours value must be greater than zero.").format(data.idx)) frappe.throw(_("Row {0}: Hours value must be greater than zero.").format(data.idx))
if self.production_order and flt(data.completed_qty) == 0: if self.work_order and flt(data.completed_qty) == 0:
frappe.throw(_("Row {0}: Completed Qty must be greater than zero.").format(data.idx)) frappe.throw(_("Row {0}: Completed Qty must be greater than zero.").format(data.idx))
if self.production_order and flt(pending_qty) < flt(data.completed_qty) and flt(pending_qty) > 0: if self.work_order and flt(pending_qty) < flt(data.completed_qty) and flt(pending_qty) > 0:
frappe.throw(_("Row {0}: Completed Qty cannot be more than {1} for operation {2}").format(data.idx, pending_qty, data.operation), frappe.throw(_("Row {0}: Completed Qty cannot be more than {1} for operation {2}").format(data.idx, pending_qty, data.operation),
OverProductionLoggedError) OverWorkLoggedError)
def update_production_order(self, time_sheet): def update_work_order(self, time_sheet):
if self.production_order: if self.work_order:
pro = frappe.get_doc('Production Order', self.production_order) pro = frappe.get_doc('Work Order', self.work_order)
for timesheet in self.time_logs: for timesheet in self.time_logs:
for data in pro.operations: for data in pro.operations:
@@ -152,8 +152,8 @@ class Timesheet(Document):
return frappe.db.sql("""select return frappe.db.sql("""select
sum(tsd.hours*60) as mins, sum(tsd.completed_qty) as completed_qty, min(tsd.from_time) as from_time, sum(tsd.hours*60) as mins, sum(tsd.completed_qty) as completed_qty, min(tsd.from_time) as from_time,
max(tsd.to_time) as to_time from `tabTimesheet Detail` as tsd, `tabTimesheet` as ts where max(tsd.to_time) as to_time from `tabTimesheet Detail` as tsd, `tabTimesheet` as ts where
ts.production_order = %s and tsd.operation_id = %s and ts.docstatus=1 and ts.name = tsd.parent""", ts.work_order = %s and tsd.operation_id = %s and ts.docstatus=1 and ts.name = tsd.parent""",
(self.production_order, operation_id), as_dict=1)[0] (self.work_order, operation_id), as_dict=1)[0]
def update_task_and_project(self): def update_task_and_project(self):
tasks, projects = [], [] tasks, projects = [], []
@@ -181,7 +181,7 @@ class Timesheet(Document):
def validate_overlap(self, data): def validate_overlap(self, data):
settings = frappe.get_single('Projects Settings') settings = frappe.get_single('Projects Settings')
if self.production_order: if self.work_order:
self.validate_overlap_for("workstation", data, data.workstation, settings.ignore_workstation_time_overlap) self.validate_overlap_for("workstation", data, data.workstation, settings.ignore_workstation_time_overlap)
else: else:
self.validate_overlap_for("user", data, self.user, settings.ignore_user_time_overlap) self.validate_overlap_for("user", data, self.user, settings.ignore_user_time_overlap)
@@ -232,7 +232,7 @@ class Timesheet(Document):
if args.workstation and args.from_time and args.to_time: if args.workstation and args.from_time and args.to_time:
check_if_within_operating_hours(args.workstation, args.operation, args.from_time, args.to_time) check_if_within_operating_hours(args.workstation, args.operation, args.from_time, args.to_time)
def schedule_for_production_order(self, index): def schedule_for_work_order(self, index):
for data in self.time_logs: for data in self.time_logs:
if data.idx == index: if data.idx == index:
self.move_to_next_day(data) #check for workstation holiday self.move_to_next_day(data) #check for workstation holiday

View File

@@ -1,5 +1,6 @@
{ {
"allow_copy": 0, "allow_copy": 0,
"allow_guest_to_view": 0,
"allow_import": 0, "allow_import": 0,
"allow_rename": 0, "allow_rename": 0,
"beta": 0, "beta": 0,
@@ -11,6 +12,7 @@
"editable_grid": 1, "editable_grid": 1,
"fields": [ "fields": [
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@@ -21,7 +23,9 @@
"ignore_user_permissions": 0, "ignore_user_permissions": 0,
"ignore_xss_filter": 0, "ignore_xss_filter": 0,
"in_filter": 0, "in_filter": 0,
"in_global_search": 0,
"in_list_view": 1, "in_list_view": 1,
"in_standard_filter": 0,
"label": "Activity Type", "label": "Activity Type",
"length": 0, "length": 0,
"no_copy": 0, "no_copy": 0,
@@ -38,6 +42,7 @@
"unique": 0 "unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@@ -48,7 +53,9 @@
"ignore_user_permissions": 0, "ignore_user_permissions": 0,
"ignore_xss_filter": 0, "ignore_xss_filter": 0,
"in_filter": 0, "in_filter": 0,
"in_global_search": 0,
"in_list_view": 1, "in_list_view": 1,
"in_standard_filter": 0,
"label": "From Time", "label": "From Time",
"length": 0, "length": 0,
"no_copy": 0, "no_copy": 0,
@@ -65,6 +72,7 @@
"unique": 0 "unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@@ -75,7 +83,9 @@
"ignore_user_permissions": 0, "ignore_user_permissions": 0,
"ignore_xss_filter": 0, "ignore_xss_filter": 0,
"in_filter": 0, "in_filter": 0,
"in_global_search": 0,
"in_list_view": 0, "in_list_view": 0,
"in_standard_filter": 0,
"length": 0, "length": 0,
"no_copy": 0, "no_copy": 0,
"permlevel": 0, "permlevel": 0,
@@ -91,6 +101,7 @@
"unique": 0 "unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@@ -101,7 +112,9 @@
"ignore_user_permissions": 0, "ignore_user_permissions": 0,
"ignore_xss_filter": 0, "ignore_xss_filter": 0,
"in_filter": 0, "in_filter": 0,
"in_global_search": 0,
"in_list_view": 1, "in_list_view": 1,
"in_standard_filter": 0,
"label": "Hrs", "label": "Hrs",
"length": 0, "length": 0,
"no_copy": 0, "no_copy": 0,
@@ -117,6 +130,7 @@
"unique": 0 "unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@@ -127,7 +141,9 @@
"ignore_user_permissions": 0, "ignore_user_permissions": 0,
"ignore_xss_filter": 0, "ignore_xss_filter": 0,
"in_filter": 0, "in_filter": 0,
"in_global_search": 0,
"in_list_view": 0, "in_list_view": 0,
"in_standard_filter": 0,
"label": "To Time", "label": "To Time",
"length": 0, "length": 0,
"no_copy": 0, "no_copy": 0,
@@ -144,6 +160,7 @@
"unique": 0 "unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@@ -154,7 +171,9 @@
"ignore_user_permissions": 0, "ignore_user_permissions": 0,
"ignore_xss_filter": 0, "ignore_xss_filter": 0,
"in_filter": 0, "in_filter": 0,
"in_global_search": 0,
"in_list_view": 0, "in_list_view": 0,
"in_standard_filter": 0,
"length": 0, "length": 0,
"no_copy": 0, "no_copy": 0,
"permlevel": 0, "permlevel": 0,
@@ -170,18 +189,21 @@
"unique": 0 "unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"depends_on": "eval:parent.production_order", "depends_on": "eval:parent.work_order",
"fieldname": "completed_qty", "fieldname": "completed_qty",
"fieldtype": "Float", "fieldtype": "Float",
"hidden": 0, "hidden": 0,
"ignore_user_permissions": 0, "ignore_user_permissions": 0,
"ignore_xss_filter": 0, "ignore_xss_filter": 0,
"in_filter": 0, "in_filter": 0,
"in_global_search": 0,
"in_list_view": 0, "in_list_view": 0,
"in_standard_filter": 0,
"label": "Completed Qty", "label": "Completed Qty",
"length": 0, "length": 0,
"no_copy": 0, "no_copy": 0,
@@ -198,18 +220,21 @@
"unique": 0 "unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"depends_on": "eval:parent.production_order", "depends_on": "eval:parent.work_order",
"fieldname": "workstation", "fieldname": "workstation",
"fieldtype": "Link", "fieldtype": "Link",
"hidden": 0, "hidden": 0,
"ignore_user_permissions": 0, "ignore_user_permissions": 0,
"ignore_xss_filter": 0, "ignore_xss_filter": 0,
"in_filter": 0, "in_filter": 0,
"in_global_search": 0,
"in_list_view": 0, "in_list_view": 0,
"in_standard_filter": 0,
"label": "Workstation", "label": "Workstation",
"length": 0, "length": 0,
"no_copy": 0, "no_copy": 0,
@@ -227,6 +252,7 @@
"unique": 0 "unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@@ -237,7 +263,9 @@
"ignore_user_permissions": 0, "ignore_user_permissions": 0,
"ignore_xss_filter": 0, "ignore_xss_filter": 0,
"in_filter": 0, "in_filter": 0,
"in_global_search": 0,
"in_list_view": 0, "in_list_view": 0,
"in_standard_filter": 0,
"length": 0, "length": 0,
"no_copy": 0, "no_copy": 0,
"permlevel": 0, "permlevel": 0,
@@ -253,18 +281,21 @@
"unique": 0 "unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"depends_on": "eval:parent.production_order", "depends_on": "eval:parent.work_order",
"fieldname": "operation", "fieldname": "operation",
"fieldtype": "Link", "fieldtype": "Link",
"hidden": 0, "hidden": 0,
"ignore_user_permissions": 0, "ignore_user_permissions": 0,
"ignore_xss_filter": 0, "ignore_xss_filter": 0,
"in_filter": 0, "in_filter": 0,
"in_global_search": 0,
"in_list_view": 0, "in_list_view": 0,
"in_standard_filter": 0,
"label": "Operation", "label": "Operation",
"length": 0, "length": 0,
"no_copy": 0, "no_copy": 0,
@@ -282,18 +313,21 @@
"unique": 0 "unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"depends_on": "eval:parent.production_order", "depends_on": "eval:parent.work_order",
"fieldname": "operation_id", "fieldname": "operation_id",
"fieldtype": "Data", "fieldtype": "Data",
"hidden": 1, "hidden": 1,
"ignore_user_permissions": 0, "ignore_user_permissions": 0,
"ignore_xss_filter": 0, "ignore_xss_filter": 0,
"in_filter": 0, "in_filter": 0,
"in_global_search": 0,
"in_list_view": 0, "in_list_view": 0,
"in_standard_filter": 0,
"label": "Operation Id", "label": "Operation Id",
"length": 0, "length": 0,
"no_copy": 0, "no_copy": 0,
@@ -310,6 +344,7 @@
"unique": 0 "unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@@ -320,7 +355,9 @@
"ignore_user_permissions": 0, "ignore_user_permissions": 0,
"ignore_xss_filter": 0, "ignore_xss_filter": 0,
"in_filter": 0, "in_filter": 0,
"in_global_search": 0,
"in_list_view": 0, "in_list_view": 0,
"in_standard_filter": 0,
"length": 0, "length": 0,
"no_copy": 0, "no_copy": 0,
"permlevel": 0, "permlevel": 0,
@@ -336,6 +373,7 @@
"unique": 0 "unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@@ -346,7 +384,9 @@
"ignore_user_permissions": 0, "ignore_user_permissions": 0,
"ignore_xss_filter": 0, "ignore_xss_filter": 0,
"in_filter": 0, "in_filter": 0,
"in_global_search": 0,
"in_list_view": 1, "in_list_view": 1,
"in_standard_filter": 0,
"label": "Project", "label": "Project",
"length": 0, "length": 0,
"no_copy": 0, "no_copy": 0,
@@ -364,6 +404,7 @@
"unique": 0 "unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@@ -374,7 +415,9 @@
"ignore_user_permissions": 0, "ignore_user_permissions": 0,
"ignore_xss_filter": 0, "ignore_xss_filter": 0,
"in_filter": 0, "in_filter": 0,
"in_global_search": 0,
"in_list_view": 0, "in_list_view": 0,
"in_standard_filter": 0,
"length": 0, "length": 0,
"no_copy": 0, "no_copy": 0,
"permlevel": 0, "permlevel": 0,
@@ -390,6 +433,7 @@
"unique": 0 "unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@@ -401,7 +445,9 @@
"ignore_user_permissions": 0, "ignore_user_permissions": 0,
"ignore_xss_filter": 0, "ignore_xss_filter": 0,
"in_filter": 0, "in_filter": 0,
"in_global_search": 0,
"in_list_view": 0, "in_list_view": 0,
"in_standard_filter": 0,
"label": "Task", "label": "Task",
"length": 0, "length": 0,
"no_copy": 0, "no_copy": 0,
@@ -419,6 +465,7 @@
"unique": 0 "unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@@ -429,7 +476,9 @@
"ignore_user_permissions": 0, "ignore_user_permissions": 0,
"ignore_xss_filter": 0, "ignore_xss_filter": 0,
"in_filter": 0, "in_filter": 0,
"in_global_search": 0,
"in_list_view": 0, "in_list_view": 0,
"in_standard_filter": 0,
"length": 0, "length": 0,
"no_copy": 0, "no_copy": 0,
"permlevel": 0, "permlevel": 0,
@@ -445,6 +494,7 @@
"unique": 0 "unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 1, "allow_on_submit": 1,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@@ -456,7 +506,9 @@
"ignore_user_permissions": 0, "ignore_user_permissions": 0,
"ignore_xss_filter": 0, "ignore_xss_filter": 0,
"in_filter": 0, "in_filter": 0,
"in_global_search": 0,
"in_list_view": 1, "in_list_view": 1,
"in_standard_filter": 0,
"label": "Bill", "label": "Bill",
"length": 0, "length": 0,
"no_copy": 0, "no_copy": 0,
@@ -473,6 +525,7 @@
"unique": 0 "unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@@ -483,7 +536,9 @@
"ignore_user_permissions": 0, "ignore_user_permissions": 0,
"ignore_xss_filter": 0, "ignore_xss_filter": 0,
"in_filter": 0, "in_filter": 0,
"in_global_search": 0,
"in_list_view": 0, "in_list_view": 0,
"in_standard_filter": 0,
"length": 0, "length": 0,
"no_copy": 0, "no_copy": 0,
"permlevel": 0, "permlevel": 0,
@@ -499,6 +554,7 @@
"unique": 0 "unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 1, "allow_on_submit": 1,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@@ -510,7 +566,9 @@
"ignore_user_permissions": 0, "ignore_user_permissions": 0,
"ignore_xss_filter": 0, "ignore_xss_filter": 0,
"in_filter": 0, "in_filter": 0,
"in_global_search": 0,
"in_list_view": 0, "in_list_view": 0,
"in_standard_filter": 0,
"label": "Billing Hours", "label": "Billing Hours",
"length": 0, "length": 0,
"no_copy": 0, "no_copy": 0,
@@ -527,6 +585,7 @@
"unique": 0 "unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@@ -538,7 +597,9 @@
"ignore_user_permissions": 0, "ignore_user_permissions": 0,
"ignore_xss_filter": 0, "ignore_xss_filter": 0,
"in_filter": 0, "in_filter": 0,
"in_global_search": 0,
"in_list_view": 0, "in_list_view": 0,
"in_standard_filter": 0,
"length": 0, "length": 0,
"no_copy": 0, "no_copy": 0,
"permlevel": 0, "permlevel": 0,
@@ -554,6 +615,7 @@
"unique": 0 "unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@@ -565,7 +627,9 @@
"ignore_user_permissions": 0, "ignore_user_permissions": 0,
"ignore_xss_filter": 0, "ignore_xss_filter": 0,
"in_filter": 0, "in_filter": 0,
"in_global_search": 0,
"in_list_view": 0, "in_list_view": 0,
"in_standard_filter": 0,
"label": "Billing Rate", "label": "Billing Rate",
"length": 0, "length": 0,
"no_copy": 0, "no_copy": 0,
@@ -582,6 +646,7 @@
"unique": 0 "unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 1, "allow_on_submit": 1,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@@ -595,7 +660,9 @@
"ignore_user_permissions": 0, "ignore_user_permissions": 0,
"ignore_xss_filter": 0, "ignore_xss_filter": 0,
"in_filter": 0, "in_filter": 0,
"in_global_search": 0,
"in_list_view": 0, "in_list_view": 0,
"in_standard_filter": 0,
"label": "Billing Amount", "label": "Billing Amount",
"length": 0, "length": 0,
"no_copy": 0, "no_copy": 0,
@@ -612,6 +679,7 @@
"unique": 0 "unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@@ -622,7 +690,9 @@
"ignore_user_permissions": 0, "ignore_user_permissions": 0,
"ignore_xss_filter": 0, "ignore_xss_filter": 0,
"in_filter": 0, "in_filter": 0,
"in_global_search": 0,
"in_list_view": 0, "in_list_view": 0,
"in_standard_filter": 0,
"length": 0, "length": 0,
"no_copy": 0, "no_copy": 0,
"permlevel": 0, "permlevel": 0,
@@ -638,6 +708,7 @@
"unique": 0 "unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@@ -648,7 +719,9 @@
"ignore_user_permissions": 0, "ignore_user_permissions": 0,
"ignore_xss_filter": 0, "ignore_xss_filter": 0,
"in_filter": 0, "in_filter": 0,
"in_global_search": 0,
"in_list_view": 0, "in_list_view": 0,
"in_standard_filter": 0,
"label": "Costing Rate", "label": "Costing Rate",
"length": 0, "length": 0,
"no_copy": 0, "no_copy": 0,
@@ -665,6 +738,7 @@
"unique": 0 "unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 1, "allow_on_submit": 1,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@@ -677,7 +751,9 @@
"ignore_user_permissions": 0, "ignore_user_permissions": 0,
"ignore_xss_filter": 0, "ignore_xss_filter": 0,
"in_filter": 0, "in_filter": 0,
"in_global_search": 0,
"in_list_view": 0, "in_list_view": 0,
"in_standard_filter": 0,
"label": "Costing Amount", "label": "Costing Amount",
"length": 0, "length": 0,
"no_copy": 0, "no_copy": 0,
@@ -694,6 +770,7 @@
"unique": 0 "unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@@ -704,7 +781,9 @@
"ignore_user_permissions": 0, "ignore_user_permissions": 0,
"ignore_xss_filter": 0, "ignore_xss_filter": 0,
"in_filter": 0, "in_filter": 0,
"in_global_search": 0,
"in_list_view": 0, "in_list_view": 0,
"in_standard_filter": 0,
"label": "Reference", "label": "Reference",
"length": 0, "length": 0,
"no_copy": 0, "no_copy": 0,
@@ -721,6 +800,7 @@
"unique": 0 "unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 1, "allow_on_submit": 1,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@@ -731,7 +811,9 @@
"ignore_user_permissions": 0, "ignore_user_permissions": 0,
"ignore_xss_filter": 0, "ignore_xss_filter": 0,
"in_filter": 0, "in_filter": 0,
"in_global_search": 0,
"in_list_view": 0, "in_list_view": 0,
"in_standard_filter": 0,
"label": "Sales Invoice", "label": "Sales Invoice",
"length": 0, "length": 0,
"no_copy": 1, "no_copy": 1,
@@ -749,17 +831,17 @@
"unique": 0 "unique": 0
} }
], ],
"has_web_view": 0,
"hide_heading": 0, "hide_heading": 0,
"hide_toolbar": 0, "hide_toolbar": 0,
"idx": 1, "idx": 1,
"image_view": 0, "image_view": 0,
"in_create": 0, "in_create": 0,
"in_dialog": 0,
"is_submittable": 0, "is_submittable": 0,
"issingle": 0, "issingle": 0,
"istable": 1, "istable": 1,
"max_attachments": 0, "max_attachments": 0,
"modified": "2016-11-03 16:01:10.519549", "modified": "2018-01-07 11:46:04.045313",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Projects", "module": "Projects",
"name": "Timesheet Detail", "name": "Timesheet Detail",
@@ -768,6 +850,8 @@
"quick_entry": 1, "quick_entry": 1,
"read_only": 0, "read_only": 0,
"read_only_onload": 0, "read_only_onload": 0,
"show_name_in_global_search": 0,
"sort_order": "ASC", "sort_order": "ASC",
"track_changes": 0,
"track_seen": 0 "track_seen": 0
} }

View File

@@ -492,8 +492,8 @@ frappe.help.help_links['Form/BOM'] = [
{ label: 'Nested BOM Structure', url: 'https://frappe.github.io/erpnext/user/manual/en/manufacturing/articles/nested-bom-structure' }, { label: 'Nested BOM Structure', url: 'https://frappe.github.io/erpnext/user/manual/en/manufacturing/articles/nested-bom-structure' },
] ]
frappe.help.help_links['Form/Production Order'] = [ frappe.help.help_links['Form/Work Order'] = [
{ label: 'Production Order', url: 'https://frappe.github.io/erpnext/user/manual/en/manufacturing/production-order' }, { label: 'Work Order', url: 'https://frappe.github.io/erpnext/user/manual/en/manufacturing/production-order' },
] ]
frappe.help.help_links['Form/Workstation'] = [ frappe.help.help_links['Form/Workstation'] = [

View File

@@ -106,8 +106,8 @@ erpnext.selling.SalesOrderController = erpnext.selling.SellingController.extend(
if(flt(doc.per_delivered, 2) < 100 && ["Sales", "Shopping Cart"].indexOf(doc.order_type)!==-1 && allow_delivery) { if(flt(doc.per_delivered, 2) < 100 && ["Sales", "Shopping Cart"].indexOf(doc.order_type)!==-1 && allow_delivery) {
this.frm.add_custom_button(__('Delivery'), this.frm.add_custom_button(__('Delivery'),
function() { me.make_delivery_note_based_on_delivery_date(); }, __("Make")); function() { me.make_delivery_note_based_on_delivery_date(); }, __("Make"));
this.frm.add_custom_button(__('Production Order'), this.frm.add_custom_button(__('Work Order'),
function() { me.make_production_order() }, __("Make")); function() { me.make_work_order() }, __("Make"));
this.frm.page.set_inner_btn_group_as_primary(__("Make")); this.frm.page.set_inner_btn_group_as_primary(__("Make"));
} }
@@ -192,15 +192,15 @@ erpnext.selling.SalesOrderController = erpnext.selling.SellingController.extend(
this.order_type(doc); this.order_type(doc);
}, },
make_production_order() { make_work_order() {
var me = this; var me = this;
this.frm.call({ this.frm.call({
doc: this.frm.doc, doc: this.frm.doc,
method: 'get_production_order_items', method: 'get_work_order_items',
callback: function(r) { callback: function(r) {
if(!r.message) { if(!r.message) {
frappe.msgprint({ frappe.msgprint({
title: __('Production Order not created'), title: __('Work Order not created'),
message: __('No Items with Bill of Materials to Manufacture'), message: __('No Items with Bill of Materials to Manufacture'),
indicator: 'orange' indicator: 'orange'
}); });
@@ -208,8 +208,8 @@ erpnext.selling.SalesOrderController = erpnext.selling.SellingController.extend(
} }
else if(!r.message) { else if(!r.message) {
frappe.msgprint({ frappe.msgprint({
title: __('Production Order not created'), title: __('Work Order not created'),
message: __('Production Order already created for all items with BOM'), message: __('Work Order already created for all items with BOM'),
indicator: 'orange' indicator: 'orange'
}); });
return; return;
@@ -240,7 +240,7 @@ erpnext.selling.SalesOrderController = erpnext.selling.SellingController.extend(
primary_action: function() { primary_action: function() {
var data = d.get_values(); var data = d.get_values();
me.frm.call({ me.frm.call({
method: 'make_production_orders', method: 'make_work_orders',
args: { args: {
items: data, items: data,
company: me.frm.doc.company, company: me.frm.doc.company,
@@ -251,9 +251,9 @@ erpnext.selling.SalesOrderController = erpnext.selling.SellingController.extend(
callback: function(r) { callback: function(r) {
if(r.message) { if(r.message) {
frappe.msgprint({ frappe.msgprint({
message: __('Production Orders Created: {0}', message: __('Work Orders Created: {0}',
[r.message.map(function(d) { [r.message.map(function(d) {
return repl('<a href="#Form/Production Order/%(name)s">%(name)s</a>', {name:d}) return repl('<a href="#Form/Work Order/%(name)s">%(name)s</a>', {name:d})
}).join(', ')]), }).join(', ')]),
indicator: 'green' indicator: 'green'
}) })

View File

@@ -242,14 +242,14 @@ class SalesOrder(SellingController):
frappe.throw(_("Maintenance Visit {0} must be cancelled before cancelling this Sales Order") frappe.throw(_("Maintenance Visit {0} must be cancelled before cancelling this Sales Order")
.format(comma_and(submit_mv))) .format(comma_and(submit_mv)))
# check production order # check work order
pro_order = frappe.db.sql_list(""" pro_order = frappe.db.sql_list("""
select name select name
from `tabProduction Order` from `tabWork Order`
where sales_order = %s and docstatus = 1""", self.name) where sales_order = %s and docstatus = 1""", self.name)
if pro_order: if pro_order:
frappe.throw(_("Production Order {0} must be cancelled before cancelling this Sales Order") frappe.throw(_("Work Order {0} must be cancelled before cancelling this Sales Order")
.format(comma_and(pro_order))) .format(comma_and(pro_order)))
def check_modified_date(self): def check_modified_date(self):
@@ -347,8 +347,8 @@ class SalesOrder(SellingController):
self.indicator_color = "green" self.indicator_color = "green"
self.indicator_title = _("Paid") self.indicator_title = _("Paid")
def get_production_order_items(self): def get_work_order_items(self):
'''Returns items with BOM that already do not have a linked production order''' '''Returns items with BOM that already do not have a linked work order'''
items = [] items = []
for table in [self.items, self.packed_items]: for table in [self.items, self.packed_items]:
@@ -356,7 +356,7 @@ class SalesOrder(SellingController):
bom = get_default_bom_item(i.item_code) bom = get_default_bom_item(i.item_code)
if bom: if bom:
stock_qty = i.qty if i.doctype == 'Packed Item' else i.stock_qty 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 `tabProduction Order` pending_qty= stock_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]) 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])
if pending_qty: if pending_qty:
items.append(dict( items.append(dict(
@@ -776,8 +776,8 @@ def get_supplier(doctype, txt, searchfield, start, page_len, filters):
}) })
@frappe.whitelist() @frappe.whitelist()
def make_production_orders(items, sales_order, company, project=None): def make_work_orders(items, sales_order, company, project=None):
'''Make Production Orders against the given Sales Order for the given `items`''' '''Make Work Orders against the given Sales Order for the given `items`'''
items = json.loads(items).get('items') items = json.loads(items).get('items')
out = [] out = []
@@ -787,8 +787,8 @@ def make_production_orders(items, sales_order, company, project=None):
if not i.get("pending_qty"): if not i.get("pending_qty"):
frappe.throw(_("Please select Qty against item {0}").format(i.get("item_code"))) frappe.throw(_("Please select Qty against item {0}").format(i.get("item_code")))
production_order = frappe.get_doc(dict( work_order = frappe.get_doc(dict(
doctype='Production Order', doctype='Work Order',
production_item=i['item_code'], production_item=i['item_code'],
bom_no=i.get('bom'), bom_no=i.get('bom'),
qty=i['pending_qty'], qty=i['pending_qty'],
@@ -798,9 +798,9 @@ def make_production_orders(items, sales_order, company, project=None):
project=project, project=project,
fg_warehouse=i['warehouse'] fg_warehouse=i['warehouse']
)).insert() )).insert()
production_order.set_production_order_operations() work_order.set_work_order_operations()
production_order.save() work_order.save()
out.append(production_order) out.append(work_order)
return [p.name for p in out] return [p.name for p in out]

View File

@@ -28,7 +28,7 @@ def get_data():
}, },
{ {
'label': _('Manufacturing'), 'label': _('Manufacturing'),
'items': ['Production Order'] 'items': ['Work Order']
}, },
{ {
'label': _('Reference'), 'label': _('Reference'),

View File

@@ -9,7 +9,7 @@ from erpnext.selling.doctype.sales_order.sales_order \
import make_material_request, make_delivery_note, make_sales_invoice, WarehouseRequired import make_material_request, make_delivery_note, make_sales_invoice, WarehouseRequired
from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
from frappe.tests.test_permissions import set_user_permission_doctypes from frappe.tests.test_permissions import set_user_permission_doctypes
from erpnext.selling.doctype.sales_order.sales_order import make_production_orders from erpnext.selling.doctype.sales_order.sales_order import make_work_orders
import json import json
@@ -530,7 +530,7 @@ class TestSalesOrder(unittest.TestCase):
si.insert() si.insert()
self.assertTrue(si.get('payment_schedule')) self.assertTrue(si.get('payment_schedule'))
def test_make_production_order(self): def test_make_work_order(self):
# Make a new Sales Order # Make a new Sales Order
so = make_sales_order(**{ so = make_sales_order(**{
"item_list": [{ "item_list": [{
@@ -545,10 +545,10 @@ class TestSalesOrder(unittest.TestCase):
}] }]
}) })
# Raise Production Orders # Raise Work Orders
po_items= [] po_items= []
so_item_name= {} so_item_name= {}
for item in so.get_production_order_items(): for item in so.get_work_order_items():
po_items.append({ po_items.append({
"warehouse": item.get("warehouse"), "warehouse": item.get("warehouse"),
"item_code": item.get("item_code"), "item_code": item.get("item_code"),
@@ -557,12 +557,12 @@ class TestSalesOrder(unittest.TestCase):
"bom": item.get("bom") "bom": item.get("bom")
}) })
so_item_name[item.get("sales_order_item")]= item.get("pending_qty") so_item_name[item.get("sales_order_item")]= item.get("pending_qty")
make_production_orders(json.dumps({"items":po_items}), so.name, so.company) make_work_orders(json.dumps({"items":po_items}), so.name, so.company)
# Check if Production Orders were raised # Check if Work Orders were raised
for item in so_item_name: for item in so_item_name:
po_qty = frappe.db.sql("select sum(qty) from `tabProduction Order` where sales_order=%s and sales_order_item=%s", (so.name, item)) wo_qty = frappe.db.sql("select sum(qty) from `tabWork Order` where sales_order=%s and sales_order_item=%s", (so.name, item))
self.assertEqual(po_qty[0][0], so_item_name.get(item)) self.assertEquals(wo_qty[0][0], so_item_name.get(item))
def make_sales_order(**args): def make_sales_order(**args):
so = frappe.new_doc("Sales Order") so = frappe.new_doc("Sales Order")

View File

@@ -4,7 +4,7 @@ QUnit.test("Test: Company", function (assert) {
let done = assert.async(); let done = assert.async();
frappe.run_serially([ frappe.run_serially([
// Added company for Production Order testing // Added company for Work Order testing
() => frappe.set_route("List", "Company"), () => frappe.set_route("List", "Company"),
() => frappe.new_doc("Company"), () => frappe.new_doc("Company"),
() => frappe.timeout(1), () => frappe.timeout(1),

View File

@@ -52,7 +52,7 @@ def get_notification_config():
"status": ("not in", ("Completed", "Closed")), "status": ("not in", ("Completed", "Closed")),
"docstatus": ("<", 2) "docstatus": ("<", 2)
}, },
"Production Order": { "status": ("in", ("Draft", "Not Started", "In Process")) }, "Work Order": { "status": ("in", ("Draft", "Not Started", "In Process")) },
"BOM": {"docstatus": 0}, "BOM": {"docstatus": 0},
"Timesheet": {"status": "Draft"}, "Timesheet": {"status": "Draft"},

View File

@@ -97,7 +97,7 @@ data_map = {
"conditions": ["docstatus=1"], "conditions": ["docstatus=1"],
"order_by": "posting_date, posting_time, name", "order_by": "posting_date, posting_time, name",
}, },
"Production Order": { "Work Order": {
"columns": ["name", "production_item as item_code", "columns": ["name", "production_item as item_code",
"(qty - produced_qty) as qty", "(qty - produced_qty) as qty",
"fg_warehouse as warehouse"], "fg_warehouse as warehouse"],
@@ -293,7 +293,7 @@ data_map = {
}, },
# Manufacturing # Manufacturing
"Production Order": { "Work Order": {
"columns": ["name","status","creation","planned_start_date","planned_end_date","status","actual_start_date","actual_end_date", "modified"], "columns": ["name","status","creation","planned_start_date","planned_end_date","status","actual_start_date","actual_end_date", "modified"],
"conditions": ["docstatus = 1"], "conditions": ["docstatus = 1"],
"order_by": "creation" "order_by": "creation"

View File

@@ -49,7 +49,7 @@ class TestBatch(unittest.TestCase):
return receipt return receipt
def test_stock_entry_incoming(self): def test_stock_entry_incoming(self):
'''Test batch creation via Stock Entry (Production Order)''' '''Test batch creation via Stock Entry (Work Order)'''
self.make_batch_item('ITEM-BATCH-1') self.make_batch_item('ITEM-BATCH-1')

View File

@@ -75,9 +75,9 @@ class Bin(Document):
def update_reserved_qty_for_production(self): def update_reserved_qty_for_production(self):
'''Update qty reserved for production from Production Item tables '''Update qty reserved for production from Production Item tables
in open production orders''' in open work orders'''
self.reserved_qty_for_production = frappe.db.sql('''select sum(required_qty - transferred_qty) self.reserved_qty_for_production = frappe.db.sql('''select sum(required_qty - transferred_qty)
from `tabProduction Order` pro, `tabProduction Order Item` item from `tabWork Order` pro, `tabWork Order Item` item
where where
item.item_code = %s item.item_code = %s
and item.parent = pro.name and item.parent = pro.name

View File

@@ -7,7 +7,7 @@ def get_data():
.format('<a href="#query-report/Stock Ledger">' + _('Stock Ledger') + '</a>'), .format('<a href="#query-report/Stock Ledger">' + _('Stock Ledger') + '</a>'),
'fieldname': 'item_code', 'fieldname': 'item_code',
'non_standard_fieldnames': { 'non_standard_fieldnames': {
'Production Order': 'production_item', 'Work Order': 'production_item',
'Product Bundle': 'new_item_code', 'Product Bundle': 'new_item_code',
'BOM': 'item', 'BOM': 'item',
'Batch': 'item' 'Batch': 'item'
@@ -40,7 +40,7 @@ def get_data():
}, },
{ {
'label': _('Manufacture'), 'label': _('Manufacture'),
'items': ['Production Order'] 'items': ['Work Order']
} }
] ]
} }

View File

@@ -10,7 +10,7 @@ frappe.ui.form.on('Material Request', {
'Purchase Order': 'Purchase Order', 'Purchase Order': 'Purchase Order',
'Request for Quotation': 'Request for Quotation', 'Request for Quotation': 'Request for Quotation',
'Supplier Quotation': 'Supplier Quotation', 'Supplier Quotation': 'Supplier Quotation',
'Production Order': 'Production Order' 'Work Order': 'Work Order'
} }
// formatter for material request item // formatter for material request item
@@ -98,8 +98,8 @@ erpnext.buying.MaterialRequestController = erpnext.buying.BuyingController.exten
this.make_supplier_quotation, __("Make")); this.make_supplier_quotation, __("Make"));
if(doc.material_request_type === "Manufacture") if(doc.material_request_type === "Manufacture")
cur_frm.add_custom_button(__("Production Order"), cur_frm.add_custom_button(__("Work Order"),
function() { me.raise_production_orders() }, __("Make")); function() { me.raise_work_orders() }, __("Make"));
cur_frm.page.set_inner_btn_group_as_primary(__("Make")); cur_frm.page.set_inner_btn_group_as_primary(__("Make"));
@@ -224,10 +224,10 @@ erpnext.buying.MaterialRequestController = erpnext.buying.BuyingController.exten
}); });
}, },
raise_production_orders: function() { raise_work_orders: function() {
var me = this; var me = this;
frappe.call({ frappe.call({
method:"erpnext.stock.doctype.material_request.material_request.raise_production_orders", method:"erpnext.stock.doctype.material_request.material_request.raise_work_orders",
args: { args: {
"material_request": me.frm.doc.name "material_request": me.frm.doc.name
}, },

View File

@@ -12,7 +12,7 @@ from frappe import msgprint, _
from frappe.model.mapper import get_mapped_doc from frappe.model.mapper import get_mapped_doc
from erpnext.stock.stock_balance import update_bin_qty, get_indented_qty from erpnext.stock.stock_balance import update_bin_qty, get_indented_qty
from erpnext.controllers.buying_controller import BuyingController from erpnext.controllers.buying_controller import BuyingController
from erpnext.manufacturing.doctype.production_order.production_order import get_item_details from erpnext.manufacturing.doctype.work_order.work_order import get_item_details
from erpnext.buying.utils import check_for_closed_status, validate_for_items from erpnext.buying.utils import check_for_closed_status, validate_for_items
from six import string_types from six import string_types
@@ -168,7 +168,7 @@ class MaterialRequest(BuyingController):
elif self.material_request_type == "Manufacture": elif self.material_request_type == "Manufacture":
d.ordered_qty = flt(frappe.db.sql("""select sum(qty) d.ordered_qty = flt(frappe.db.sql("""select sum(qty)
from `tabProduction Order` where material_request = %s from `tabWork Order` where material_request = %s
and material_request_item = %s and docstatus = 1""", and material_request_item = %s and docstatus = 1""",
(self.name, d.name))[0][0]) (self.name, d.name))[0][0])
@@ -415,36 +415,36 @@ def make_stock_entry(source_name, target_doc=None):
return doclist return doclist
@frappe.whitelist() @frappe.whitelist()
def raise_production_orders(material_request): def raise_work_orders(material_request):
mr= frappe.get_doc("Material Request", material_request) mr= frappe.get_doc("Material Request", material_request)
errors =[] errors =[]
production_orders = [] work_orders = []
default_wip_warehouse = frappe.db.get_single_value("Manufacturing Settings", "default_wip_warehouse") default_wip_warehouse = frappe.db.get_single_value("Manufacturing Settings", "default_wip_warehouse")
for d in mr.items: for d in mr.items:
if (d.qty - d.ordered_qty) >0: if (d.qty - d.ordered_qty) >0:
if frappe.db.get_value("BOM", {"item": d.item_code, "is_default": 1}): if frappe.db.get_value("BOM", {"item": d.item_code, "is_default": 1}):
prod_order = frappe.new_doc("Production Order") wo_order = frappe.new_doc("Work Order")
prod_order.production_item = d.item_code wo_order.production_item = d.item_code
prod_order.qty = d.qty - d.ordered_qty wo_order.qty = d.qty - d.ordered_qty
prod_order.fg_warehouse = d.warehouse wo_order.fg_warehouse = d.warehouse
prod_order.wip_warehouse = default_wip_warehouse wo_order.wip_warehouse = default_wip_warehouse
prod_order.description = d.description wo_order.description = d.description
prod_order.stock_uom = d.stock_uom wo_order.stock_uom = d.stock_uom
prod_order.expected_delivery_date = d.schedule_date wo_order.expected_delivery_date = d.schedule_date
prod_order.sales_order = d.sales_order wo_order.sales_order = d.sales_order
prod_order.bom_no = get_item_details(d.item_code).bom_no wo_order.bom_no = get_item_details(d.item_code).bom_no
prod_order.material_request = mr.name wo_order.material_request = mr.name
prod_order.material_request_item = d.name wo_order.material_request_item = d.name
prod_order.planned_start_date = mr.transaction_date wo_order.planned_start_date = mr.transaction_date
prod_order.company = mr.company wo_order.company = mr.company
prod_order.save() wo_order.save()
production_orders.append(prod_order.name) work_orders.append(wo_order.name)
else: else:
errors.append(_("Row {0}: Bill of Materials not found for the Item {1}").format(d.idx, d.item_code)) errors.append(_("Row {0}: Bill of Materials not found for the Item {1}").format(d.idx, d.item_code))
if production_orders: if work_orders:
message = ["""<a href="#Form/Production Order/%s" target="_blank">%s</a>""" % \ message = ["""<a href="#Form/Work Order/%s" target="_blank">%s</a>""" % \
(p, p) for p in production_orders] (p, p) for p in work_orders]
msgprint(_("The following Production Orders were created:") + '\n' + new_line_sep(message)) msgprint(_("The following Work Orders were created:") + '\n' + new_line_sep(message))
if errors: if errors:
frappe.throw(_("Productions Orders cannot be raised for:") + '\n' + new_line_sep(errors)) frappe.throw(_("Productions Orders cannot be raised for:") + '\n' + new_line_sep(errors))
return production_orders return work_orders

View File

@@ -11,7 +11,7 @@ def get_data():
}, },
{ {
'label': _('Manufacturing'), 'label': _('Manufacturing'),
'items': ['Production Order'] 'items': ['Work Order']
} }
] ]
} }

View File

@@ -7,7 +7,7 @@
from __future__ import unicode_literals 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_production_orders from erpnext.stock.doctype.material_request.material_request import raise_work_orders
class TestMaterialRequest(unittest.TestCase): class TestMaterialRequest(unittest.TestCase):
def setUp(self): def setUp(self):
@@ -536,8 +536,8 @@ class TestMaterialRequest(unittest.TestCase):
requested_qty = frappe.db.sql("""select indented_qty from `tabBin` where \ requested_qty = frappe.db.sql("""select indented_qty from `tabBin` where \
item_code= %s and warehouse= %s """, (mr.items[0].item_code, mr.items[0].warehouse))[0][0] item_code= %s and warehouse= %s """, (mr.items[0].item_code, mr.items[0].warehouse))[0][0]
prod_order = raise_production_orders(mr.name) prod_order = raise_work_orders(mr.name)
po = frappe.get_doc("Production Order", prod_order[0]) po = frappe.get_doc("Work Order", prod_order[0])
po.wip_warehouse = "_Test Warehouse 1 - _TC" po.wip_warehouse = "_Test Warehouse 1 - _TC"
po.submit() po.submit()

View File

@@ -4,12 +4,12 @@ frappe.provide("erpnext.stock");
frappe.ui.form.on('Stock Entry', { frappe.ui.form.on('Stock Entry', {
setup: function(frm) { setup: function(frm) {
frm.set_query('production_order', function() { frm.set_query('work_order', function() {
return { return {
filters: [ filters: [
['Production Order', 'docstatus', '=', 1], ['Work Order', 'docstatus', '=', 1],
['Production Order', 'qty', '>','`tabProduction Order`.produced_qty'], ['Work Order', 'qty', '>','`tabWork Order`.produced_qty'],
['Production Order', 'company', '=', frm.doc.company] ['Work Order', 'company', '=', frm.doc.company]
] ]
} }
}); });
@@ -565,11 +565,11 @@ erpnext.stock.StockEntry = erpnext.stock.StockController.extend({
}, },
clean_up: function() { clean_up: function() {
// Clear Production Order record from locals, because it is updated via Stock Entry // Clear Work Order record from locals, because it is updated via Stock Entry
if(this.frm.doc.production_order && if(this.frm.doc.work_order &&
in_list(["Manufacture", "Material Transfer for Manufacture"], this.frm.doc.purpose)) { in_list(["Manufacture", "Material Transfer for Manufacture"], this.frm.doc.purpose)) {
frappe.model.remove_from_locals("Production Order", frappe.model.remove_from_locals("Work Order",
this.frm.doc.production_order); this.frm.doc.work_order);
} }
}, },
@@ -578,8 +578,8 @@ erpnext.stock.StockEntry = erpnext.stock.StockController.extend({
if(!this.frm.doc.fg_completed_qty || !this.frm.doc.bom_no) if(!this.frm.doc.fg_completed_qty || !this.frm.doc.bom_no)
frappe.throw(__("BOM and Manufacturing Quantity are required")); frappe.throw(__("BOM and Manufacturing Quantity are required"));
if(this.frm.doc.production_order || this.frm.doc.bom_no) { if(this.frm.doc.work_order || this.frm.doc.bom_no) {
// if production order / bom is mentioned, get items // if work order / bom is mentioned, get items
return this.frm.call({ return this.frm.call({
doc: me.frm.doc, doc: me.frm.doc,
method: "get_items", method: "get_items",
@@ -590,17 +590,17 @@ erpnext.stock.StockEntry = erpnext.stock.StockController.extend({
} }
}, },
production_order: function() { work_order: function() {
var me = this; var me = this;
this.toggle_enable_bom(); this.toggle_enable_bom();
if(!me.frm.doc.production_order) { if(!me.frm.doc.work_order) {
return; return;
} }
return frappe.call({ return frappe.call({
method: "erpnext.stock.doctype.stock_entry.stock_entry.get_production_order_details", method: "erpnext.stock.doctype.stock_entry.stock_entry.get_work_order_details",
args: { args: {
production_order: me.frm.doc.production_order work_order: me.frm.doc.work_order
}, },
callback: function(r) { callback: function(r) {
if (!r.exc) { if (!r.exc) {
@@ -630,7 +630,7 @@ erpnext.stock.StockEntry = erpnext.stock.StockController.extend({
}, },
toggle_enable_bom: function() { toggle_enable_bom: function() {
this.frm.toggle_enable("bom_no", !!!this.frm.doc.production_order); this.frm.toggle_enable("bom_no", !!!this.frm.doc.work_order);
}, },
add_excise_button: function() { add_excise_button: function() {

View File

@@ -178,7 +178,7 @@
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"depends_on": "eval:in_list([\"Material Transfer for Manufacture\", \"Manufacture\"], doc.purpose)", "depends_on": "eval:in_list([\"Material Transfer for Manufacture\", \"Manufacture\"], doc.purpose)",
"fieldname": "production_order", "fieldname": "work_order",
"fieldtype": "Link", "fieldtype": "Link",
"hidden": 0, "hidden": 0,
"ignore_user_permissions": 0, "ignore_user_permissions": 0,
@@ -187,12 +187,12 @@
"in_global_search": 0, "in_global_search": 0,
"in_list_view": 0, "in_list_view": 0,
"in_standard_filter": 0, "in_standard_filter": 0,
"label": "Production Order", "label": "Work Order",
"length": 0, "length": 0,
"no_copy": 0, "no_copy": 0,
"oldfieldname": "production_order", "oldfieldname": "production_order",
"oldfieldtype": "Link", "oldfieldtype": "Link",
"options": "Production Order", "options": "Work Order",
"permlevel": 0, "permlevel": 0,
"print_hide": 1, "print_hide": 1,
"print_hide_if_no_value": 0, "print_hide_if_no_value": 0,
@@ -1894,7 +1894,7 @@
"issingle": 0, "issingle": 0,
"istable": 0, "istable": 0,
"max_attachments": 0, "max_attachments": 0,
"modified": "2018-03-01 12:27:12.884611", "modified": "2018-03-13 12:27:12.884611",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Stock", "module": "Stock",
"name": "Stock Entry", "name": "Stock Entry",

View File

@@ -17,7 +17,7 @@ import json
from six import string_types from six import string_types
class IncorrectValuationRateError(frappe.ValidationError): pass class IncorrectValuationRateError(frappe.ValidationError): pass
class DuplicateEntryForProductionOrderError(frappe.ValidationError): pass class DuplicateEntryForWorkOrderError(frappe.ValidationError): pass
class OperationsNotCompleteError(frappe.ValidationError): pass class OperationsNotCompleteError(frappe.ValidationError): pass
class MaxSampleAlreadyRetainedError(frappe.ValidationError): pass class MaxSampleAlreadyRetainedError(frappe.ValidationError): pass
@@ -37,8 +37,8 @@ class StockEntry(StockController):
def validate(self): def validate(self):
self.pro_doc = frappe._dict() self.pro_doc = frappe._dict()
if self.production_order: if self.work_order:
self.pro_doc = frappe.get_doc('Production Order', self.production_order) self.pro_doc = frappe.get_doc('Work Order', self.work_order)
self.validate_posting_time() self.validate_posting_time()
self.validate_purpose() self.validate_purpose()
@@ -47,7 +47,7 @@ class StockEntry(StockController):
self.validate_uom_is_integer("uom", "qty") self.validate_uom_is_integer("uom", "qty")
self.validate_uom_is_integer("stock_uom", "transfer_qty") self.validate_uom_is_integer("stock_uom", "transfer_qty")
self.validate_warehouse() self.validate_warehouse()
self.validate_production_order() self.validate_work_order()
self.validate_bom() self.validate_bom()
self.validate_finished_goods() self.validate_finished_goods()
self.validate_with_material_request() self.validate_with_material_request()
@@ -72,7 +72,7 @@ class StockEntry(StockController):
from erpnext.stock.doctype.serial_no.serial_no import update_serial_nos_after_submit from erpnext.stock.doctype.serial_no.serial_no import update_serial_nos_after_submit
update_serial_nos_after_submit(self, "items") update_serial_nos_after_submit(self, "items")
self.update_production_order() self.update_work_order()
self.validate_purchase_order() self.validate_purchase_order()
if self.purchase_order and self.purpose == "Subcontract": if self.purchase_order and self.purpose == "Subcontract":
self.update_purchase_order_supplied_items() self.update_purchase_order_supplied_items()
@@ -80,7 +80,7 @@ class StockEntry(StockController):
def on_cancel(self): def on_cancel(self):
self.update_stock_ledger() self.update_stock_ledger()
self.update_production_order() self.update_work_order()
if self.purchase_order and self.purpose == "Subcontract": if self.purchase_order and self.purpose == "Subcontract":
self.update_purchase_order_supplied_items() self.update_purchase_order_supplied_items()
self.make_gl_entries_on_cancel() self.make_gl_entries_on_cancel()
@@ -172,7 +172,7 @@ class StockEntry(StockController):
frappe.throw(_("Target warehouse is mandatory for row {0}").format(d.idx)) frappe.throw(_("Target warehouse is mandatory for row {0}").format(d.idx))
elif self.pro_doc and (cstr(d.t_warehouse) != self.pro_doc.fg_warehouse and cstr(d.t_warehouse) != self.pro_doc.scrap_warehouse): elif self.pro_doc and (cstr(d.t_warehouse) != self.pro_doc.fg_warehouse and cstr(d.t_warehouse) != self.pro_doc.scrap_warehouse):
frappe.throw(_("Target warehouse in row {0} must be same as Production Order").format(d.idx)) frappe.throw(_("Target warehouse in row {0} must be same as Work Order").format(d.idx))
else: else:
d.t_warehouse = None d.t_warehouse = None
@@ -182,39 +182,39 @@ class StockEntry(StockController):
if cstr(d.s_warehouse) == cstr(d.t_warehouse) and not self.purpose == "Material Transfer for Manufacture": if cstr(d.s_warehouse) == cstr(d.t_warehouse) and not self.purpose == "Material Transfer for Manufacture":
frappe.throw(_("Source and target warehouse cannot be same for row {0}").format(d.idx)) frappe.throw(_("Source and target warehouse cannot be same for row {0}").format(d.idx))
def validate_production_order(self): def validate_work_order(self):
if self.purpose in ("Manufacture", "Material Transfer for Manufacture"): if self.purpose in ("Manufacture", "Material Transfer for Manufacture"):
# check if production order is entered # check if work order is entered
if self.purpose=="Manufacture" and self.production_order: if self.purpose=="Manufacture" and self.work_order:
if not self.fg_completed_qty: if not self.fg_completed_qty:
frappe.throw(_("For Quantity (Manufactured Qty) is mandatory")) frappe.throw(_("For Quantity (Manufactured Qty) is mandatory"))
self.check_if_operations_completed() self.check_if_operations_completed()
self.check_duplicate_entry_for_production_order() self.check_duplicate_entry_for_work_order()
elif self.purpose != "Material Transfer": elif self.purpose != "Material Transfer":
self.production_order = None self.work_order = None
def check_if_operations_completed(self): def check_if_operations_completed(self):
"""Check if Time Sheets are completed against before manufacturing to capture operating costs.""" """Check if Time Sheets are completed against before manufacturing to capture operating costs."""
prod_order = frappe.get_doc("Production Order", self.production_order) prod_order = frappe.get_doc("Work Order", self.work_order)
for d in prod_order.get("operations"): for d in prod_order.get("operations"):
total_completed_qty = flt(self.fg_completed_qty) + flt(prod_order.produced_qty) total_completed_qty = flt(self.fg_completed_qty) + flt(prod_order.produced_qty)
if total_completed_qty > flt(d.completed_qty): if total_completed_qty > flt(d.completed_qty):
frappe.throw(_("Row #{0}: Operation {1} is not completed for {2} qty of finished goods in Production Order # {3}. Please update operation status via Time Logs") frappe.throw(_("Row #{0}: Operation {1} is not completed for {2} qty of finished goods in Work Order # {3}. Please update operation status via Time Logs")
.format(d.idx, d.operation, total_completed_qty, self.production_order), OperationsNotCompleteError) .format(d.idx, d.operation, total_completed_qty, self.work_order), OperationsNotCompleteError)
def check_duplicate_entry_for_production_order(self): def check_duplicate_entry_for_work_order(self):
other_ste = [t[0] for t in frappe.db.get_values("Stock Entry", { other_ste = [t[0] for t in frappe.db.get_values("Stock Entry", {
"production_order": self.production_order, "work_order": self.work_order,
"purpose": self.purpose, "purpose": self.purpose,
"docstatus": ["!=", 2], "docstatus": ["!=", 2],
"name": ["!=", self.name] "name": ["!=", self.name]
}, "name")] }, "name")]
if other_ste: if other_ste:
production_item, qty = frappe.db.get_value("Production Order", production_item, qty = frappe.db.get_value("Work Order",
self.production_order, ["production_item", "qty"]) self.work_order, ["production_item", "qty"])
args = other_ste + [production_item] args = other_ste + [production_item]
fg_qty_already_entered = frappe.db.sql("""select sum(transfer_qty) fg_qty_already_entered = frappe.db.sql("""select sum(transfer_qty)
from `tabStock Entry Detail` from `tabStock Entry Detail`
@@ -223,8 +223,8 @@ class StockEntry(StockController):
and ifnull(s_warehouse,'')='' """ % (", ".join(["%s" * len(other_ste)]), "%s"), args)[0][0] and ifnull(s_warehouse,'')='' """ % (", ".join(["%s" * len(other_ste)]), "%s"), args)[0][0]
if fg_qty_already_entered >= qty: if fg_qty_already_entered >= qty:
frappe.throw(_("Stock Entries already created for Production Order ") frappe.throw(_("Stock Entries already created for Work Order ")
+ self.production_order + ":" + ", ".join(other_ste), DuplicateEntryForProductionOrderError) + self.work_order + ":" + ", ".join(other_ste), DuplicateEntryForWorkOrderError)
def set_incoming_rate(self): def set_incoming_rate(self):
for d in self.items: for d in self.items:
@@ -261,8 +261,8 @@ class StockEntry(StockController):
frappe.bold(d.transfer_qty)), frappe.bold(d.transfer_qty)),
NegativeStockError, title=_('Insufficient Stock')) NegativeStockError, title=_('Insufficient Stock'))
def set_serial_nos(self, production_order): def set_serial_nos(self, work_order):
previous_se = frappe.db.get_value("Stock Entry", {"production_order": production_order, previous_se = frappe.db.get_value("Stock Entry", {"work_order": work_order,
"purpose": "Material Transfer for Manufacture"}, "name") "purpose": "Material Transfer for Manufacture"}, "name")
for d in self.get('items'): for d in self.get('items'):
@@ -273,7 +273,7 @@ class StockEntry(StockController):
d.serial_no = transferred_serial_no d.serial_no = transferred_serial_no
def get_stock_and_rate(self): def get_stock_and_rate(self):
self.set_production_order_details() self.set_work_order_details()
self.set_transfer_qty() self.set_transfer_qty()
self.set_actual_qty() self.set_actual_qty()
self.calculate_rate_and_amount() self.calculate_rate_and_amount()
@@ -418,12 +418,12 @@ class StockEntry(StockController):
frappe.throw(_("Quantity in row {0} ({1}) must be same as manufactured quantity {2}"). \ frappe.throw(_("Quantity in row {0} ({1}) must be same as manufactured quantity {2}"). \
format(d.idx, d.transfer_qty, self.fg_completed_qty)) format(d.idx, d.transfer_qty, self.fg_completed_qty))
if self.production_order and self.purpose == "Manufacture" and d.t_warehouse: if self.work_order and self.purpose == "Manufacture" and d.t_warehouse:
items_with_target_warehouse.append(d.item_code) items_with_target_warehouse.append(d.item_code)
if self.production_order and self.purpose == "Manufacture": if self.work_order and self.purpose == "Manufacture":
production_item = frappe.db.get_value("Production Order", production_item = frappe.db.get_value("Work Order",
self.production_order, "production_item") self.work_order, "production_item")
if production_item not in items_with_target_warehouse: if production_item not in items_with_target_warehouse:
frappe.throw(_("Finished Item {0} must be entered for Manufacture type entry") frappe.throw(_("Finished Item {0} must be entered for Manufacture type entry")
.format(production_item)) .format(production_item))
@@ -489,20 +489,20 @@ class StockEntry(StockController):
return gl_entries return gl_entries
def update_production_order(self): def update_work_order(self):
def _validate_production_order(pro_doc): def _validate_work_order(pro_doc):
if flt(pro_doc.docstatus) != 1: if flt(pro_doc.docstatus) != 1:
frappe.throw(_("Production Order {0} must be submitted").format(self.production_order)) frappe.throw(_("Work Order {0} must be submitted").format(self.work_order))
if pro_doc.status == 'Stopped': if pro_doc.status == 'Stopped':
frappe.throw(_("Transaction not allowed against stopped Production Order {0}").format(self.production_order)) frappe.throw(_("Transaction not allowed against stopped Work Order {0}").format(self.work_order))
if self.production_order: if self.work_order:
pro_doc = frappe.get_doc("Production Order", self.production_order) pro_doc = frappe.get_doc("Work Order", self.work_order)
_validate_production_order(pro_doc) _validate_work_order(pro_doc)
pro_doc.run_method("update_status") pro_doc.run_method("update_status")
if self.fg_completed_qty: if self.fg_completed_qty:
pro_doc.run_method("update_production_order_qty") pro_doc.run_method("update_work_order_qty")
if self.purpose == "Manufacture": if self.purpose == "Manufacture":
pro_doc.run_method("update_planned_qty") pro_doc.run_method("update_planned_qty")
@@ -567,24 +567,24 @@ class StockEntry(StockController):
def get_items(self): def get_items(self):
self.set('items', []) self.set('items', [])
self.validate_production_order() self.validate_work_order()
if not self.posting_date or not self.posting_time: if not self.posting_date or not self.posting_time:
frappe.throw(_("Posting date and posting time is mandatory")) frappe.throw(_("Posting date and posting time is mandatory"))
self.set_production_order_details() self.set_work_order_details()
if self.bom_no: if self.bom_no:
if self.purpose in ["Material Issue", "Material Transfer", "Manufacture", "Repack", if self.purpose in ["Material Issue", "Material Transfer", "Manufacture", "Repack",
"Subcontract", "Material Transfer for Manufacture"]: "Subcontract", "Material Transfer for Manufacture"]:
if self.production_order and self.purpose == "Material Transfer for Manufacture": if self.work_order and self.purpose == "Material Transfer for Manufacture":
item_dict = self.get_pending_raw_materials() item_dict = self.get_pending_raw_materials()
if self.to_warehouse and self.pro_doc: if self.to_warehouse and self.pro_doc:
for item in item_dict.values(): for item in item_dict.values():
item["to_warehouse"] = self.pro_doc.wip_warehouse item["to_warehouse"] = self.pro_doc.wip_warehouse
self.add_to_stock_entry_detail(item_dict) self.add_to_stock_entry_detail(item_dict)
elif self.production_order and self.purpose == "Manufacture" and \ elif self.work_order and self.purpose == "Manufacture" and \
frappe.db.get_single_value("Manufacturing Settings", "backflush_raw_materials_based_on")== "Material Transferred for Manufacture": frappe.db.get_single_value("Manufacturing Settings", "backflush_raw_materials_based_on")== "Material Transferred for Manufacture":
self.get_transfered_raw_materials() self.get_transfered_raw_materials()
@@ -595,6 +595,7 @@ class StockEntry(StockController):
item_dict = self.get_bom_raw_materials(self.fg_completed_qty) item_dict = self.get_bom_raw_materials(self.fg_completed_qty)
#Get PO Supplied Items Details #Get PO Supplied Items Details
print('Purchase Order', self.purchase_order, self.purpose)
if self.purchase_order and self.purpose == "Subcontract": if self.purchase_order and self.purpose == "Subcontract":
#Get PO Supplied Items Details #Get PO Supplied Items Details
item_wh = frappe._dict(frappe.db.sql(""" item_wh = frappe._dict(frappe.db.sql("""
@@ -621,8 +622,8 @@ class StockEntry(StockController):
self.add_to_stock_entry_detail(scrap_item_dict, bom_no=self.bom_no) self.add_to_stock_entry_detail(scrap_item_dict, bom_no=self.bom_no)
# fetch the serial_no of the first stock entry for the second stock entry # fetch the serial_no of the first stock entry for the second stock entry
if self.production_order and self.purpose == "Manufacture": if self.work_order and self.purpose == "Manufacture":
self.set_serial_nos(self.production_order) self.set_serial_nos(self.work_order)
# add finished goods item # add finished goods item
if self.purpose in ("Manufacture", "Repack"): if self.purpose in ("Manufacture", "Repack"):
@@ -631,23 +632,23 @@ class StockEntry(StockController):
self.set_actual_qty() self.set_actual_qty()
self.calculate_rate_and_amount(raise_error_if_no_rate=False) self.calculate_rate_and_amount(raise_error_if_no_rate=False)
def set_production_order_details(self): def set_work_order_details(self):
if not getattr(self, "pro_doc", None): if not getattr(self, "pro_doc", None):
self.pro_doc = frappe._dict() self.pro_doc = frappe._dict()
if self.production_order: if self.work_order:
# common validations # common validations
if not self.pro_doc: if not self.pro_doc:
self.pro_doc = frappe.get_doc('Production Order', self.production_order) self.pro_doc = frappe.get_doc('Work Order', self.work_order)
if self.pro_doc: if self.pro_doc:
self.bom_no = self.pro_doc.bom_no self.bom_no = self.pro_doc.bom_no
else: else:
# invalid production order # invalid work order
self.production_order = None self.work_order = None
def load_items_from_bom(self): def load_items_from_bom(self):
if self.production_order: if self.work_order:
item_code = self.pro_doc.production_item item_code = self.pro_doc.production_item
to_warehouse = self.pro_doc.fg_warehouse to_warehouse = self.pro_doc.fg_warehouse
else: else:
@@ -657,7 +658,7 @@ class StockEntry(StockController):
item = frappe.db.get_value("Item", item_code, ["item_name", item = frappe.db.get_value("Item", item_code, ["item_name",
"description", "stock_uom", "expense_account", "buying_cost_center", "name", "default_warehouse"], as_dict=1) "description", "stock_uom", "expense_account", "buying_cost_center", "name", "default_warehouse"], as_dict=1)
if not self.production_order and not to_warehouse: if not self.work_order and not to_warehouse:
# in case of BOM # in case of BOM
to_warehouse = item.default_warehouse to_warehouse = item.default_warehouse
@@ -705,9 +706,9 @@ class StockEntry(StockController):
from `tabStock Entry` se,`tabStock Entry Detail` sed from `tabStock Entry` se,`tabStock Entry Detail` sed
where where
se.name = sed.parent and se.docstatus=1 and se.purpose='Material Transfer for Manufacture' se.name = sed.parent and se.docstatus=1 and se.purpose='Material Transfer for Manufacture'
and se.production_order= %s and ifnull(sed.t_warehouse, '') != '' and se.work_order= %s and ifnull(sed.t_warehouse, '') != ''
group by sed.item_code, sed.t_warehouse group by sed.item_code, sed.t_warehouse
""", self.production_order, as_dict=1) """, self.work_order, as_dict=1)
materials_already_backflushed = frappe.db.sql(""" materials_already_backflushed = frappe.db.sql("""
select select
@@ -716,16 +717,16 @@ class StockEntry(StockController):
`tabStock Entry` se, `tabStock Entry Detail` sed `tabStock Entry` se, `tabStock Entry Detail` sed
where where
se.name = sed.parent and se.docstatus=1 and se.purpose='Manufacture' se.name = sed.parent and se.docstatus=1 and se.purpose='Manufacture'
and se.production_order= %s and ifnull(sed.s_warehouse, '') != '' and se.work_order= %s and ifnull(sed.s_warehouse, '') != ''
group by sed.item_code, sed.s_warehouse group by sed.item_code, sed.s_warehouse
""", self.production_order, as_dict=1) """, self.work_order, as_dict=1)
backflushed_materials= {} backflushed_materials= {}
for d in materials_already_backflushed: for d in materials_already_backflushed:
backflushed_materials.setdefault(d.item_code,[]).append({d.warehouse: d.qty}) backflushed_materials.setdefault(d.item_code,[]).append({d.warehouse: d.qty})
po_qty = frappe.db.sql("""select qty, produced_qty, material_transferred_for_manufacturing from po_qty = frappe.db.sql("""select qty, produced_qty, material_transferred_for_manufacturing from
`tabProduction Order` where name=%s""", self.production_order, as_dict=1)[0] `tabWork Order` where name=%s""", self.work_order, as_dict=1)[0]
manufacturing_qty = flt(po_qty.qty) manufacturing_qty = flt(po_qty.qty)
produced_qty = flt(po_qty.produced_qty) produced_qty = flt(po_qty.produced_qty)
trans_qty = flt(po_qty.material_transferred_for_manufacturing) trans_qty = flt(po_qty.material_transferred_for_manufacturing)
@@ -780,13 +781,13 @@ class StockEntry(StockController):
# show some message # show some message
if not len(item_dict): if not len(item_dict):
frappe.msgprint(_("""All items have already been transferred for this Production Order.""")) frappe.msgprint(_("""All items have already been transferred for this Work Order."""))
return item_dict return item_dict
def get_pro_order_required_items(self): def get_pro_order_required_items(self):
item_dict = frappe._dict() item_dict = frappe._dict()
pro_order = frappe.get_doc("Production Order", self.production_order) pro_order = frappe.get_doc("Work Order", self.work_order)
if not frappe.db.get_value("Warehouse", pro_order.wip_warehouse, "is_group"): if not frappe.db.get_value("Warehouse", pro_order.wip_warehouse, "is_group"):
wip_warehouse = pro_order.wip_warehouse wip_warehouse = pro_order.wip_warehouse
else: else:
@@ -908,32 +909,32 @@ def move_sample_to_retention_warehouse(company, items):
return stock_entry.as_dict() return stock_entry.as_dict()
@frappe.whitelist() @frappe.whitelist()
def get_production_order_details(production_order): def get_work_order_details(work_order):
production_order = frappe.get_doc("Production Order", production_order) work_order = frappe.get_doc("Work Order", work_order)
pending_qty_to_produce = flt(production_order.qty) - flt(production_order.produced_qty) pending_qty_to_produce = flt(work_order.qty) - flt(work_order.produced_qty)
return { return {
"from_bom": 1, "from_bom": 1,
"bom_no": production_order.bom_no, "bom_no": work_order.bom_no,
"use_multi_level_bom": production_order.use_multi_level_bom, "use_multi_level_bom": work_order.use_multi_level_bom,
"wip_warehouse": production_order.wip_warehouse, "wip_warehouse": work_order.wip_warehouse,
"fg_warehouse": production_order.fg_warehouse, "fg_warehouse": work_order.fg_warehouse,
"fg_completed_qty": pending_qty_to_produce, "fg_completed_qty": pending_qty_to_produce,
"additional_costs": get_additional_costs(production_order, fg_qty=pending_qty_to_produce) "additional_costs": get_additional_costs(work_order, fg_qty=pending_qty_to_produce)
} }
def get_additional_costs(production_order=None, bom_no=None, fg_qty=None): def get_additional_costs(work_order=None, bom_no=None, fg_qty=None):
additional_costs = [] additional_costs = []
operating_cost_per_unit = get_operating_cost_per_unit(production_order, bom_no) operating_cost_per_unit = get_operating_cost_per_unit(work_order, bom_no)
if operating_cost_per_unit: if operating_cost_per_unit:
additional_costs.append({ additional_costs.append({
"description": "Operating Cost as per Production Order / BOM", "description": "Operating Cost as per Work Order / BOM",
"amount": operating_cost_per_unit * flt(fg_qty) "amount": operating_cost_per_unit * flt(fg_qty)
}) })
if production_order and production_order.additional_operating_cost and production_order.qty: if work_order and work_order.additional_operating_cost and work_order.qty:
additional_operating_cost_per_unit = \ additional_operating_cost_per_unit = \
flt(production_order.additional_operating_cost) / flt(production_order.qty) flt(work_order.additional_operating_cost) / flt(work_order.qty)
additional_costs.append({ additional_costs.append({
"description": "Additional Operating Cost", "description": "Additional Operating Cost",
@@ -942,19 +943,19 @@ def get_additional_costs(production_order=None, bom_no=None, fg_qty=None):
return additional_costs return additional_costs
def get_operating_cost_per_unit(production_order=None, bom_no=None): def get_operating_cost_per_unit(work_order=None, bom_no=None):
operating_cost_per_unit = 0 operating_cost_per_unit = 0
if production_order: if work_order:
if not bom_no: if not bom_no:
bom_no = production_order.bom_no bom_no = work_order.bom_no
for d in production_order.get("operations"): for d in work_order.get("operations"):
if flt(d.completed_qty): if flt(d.completed_qty):
operating_cost_per_unit += flt(d.actual_operating_cost) / flt(d.completed_qty) operating_cost_per_unit += flt(d.actual_operating_cost) / flt(d.completed_qty)
elif production_order.qty: elif work_order.qty:
operating_cost_per_unit += flt(d.planned_operating_cost) / flt(production_order.qty) operating_cost_per_unit += flt(d.planned_operating_cost) / flt(work_order.qty)
# Get operating cost from BOM if not found in production_order. # Get operating cost from BOM if not found in work_order.
if not operating_cost_per_unit and bom_no: if not operating_cost_per_unit and bom_no:
bom = frappe.db.get_value("BOM", bom_no, ["operating_cost", "quantity"], as_dict=1) bom = frappe.db.get_value("BOM", bom_no, ["operating_cost", "quantity"], as_dict=1)
if bom.quantity: if bom.quantity:

View File

@@ -1,6 +1,6 @@
frappe.listview_settings['Stock Entry'] = { frappe.listview_settings['Stock Entry'] = {
add_fields: ["`tabStock Entry`.`from_warehouse`", "`tabStock Entry`.`to_warehouse`", add_fields: ["`tabStock Entry`.`from_warehouse`", "`tabStock Entry`.`to_warehouse`",
"`tabStock Entry`.`purpose`", "`tabStock Entry`.`production_order`", "`tabStock Entry`.`bom_no`"], "`tabStock Entry`.`purpose`", "`tabStock Entry`.`work_order`", "`tabStock Entry`.`bom_no`"],
column_render: { column_render: {
"from_warehouse": function(doc) { "from_warehouse": function(doc) {
var html = ""; var html = "";

View File

@@ -538,14 +538,14 @@ class TestStockEntry(unittest.TestCase):
self.assertRaises(StockFreezeError, se.submit) self.assertRaises(StockFreezeError, se.submit)
frappe.db.set_value("Stock Settings", None, "stock_frozen_upto_days", 0) frappe.db.set_value("Stock Settings", None, "stock_frozen_upto_days", 0)
def test_production_order(self): def test_work_order(self):
from erpnext.manufacturing.doctype.production_order.production_order \ from erpnext.manufacturing.doctype.work_order.work_order \
import make_stock_entry as _make_stock_entry import make_stock_entry as _make_stock_entry
bom_no, bom_operation_cost = frappe.db.get_value("BOM", {"item": "_Test FG Item 2", bom_no, bom_operation_cost = frappe.db.get_value("BOM", {"item": "_Test FG Item 2",
"is_default": 1, "docstatus": 1}, ["name", "operating_cost"]) "is_default": 1, "docstatus": 1}, ["name", "operating_cost"])
production_order = frappe.new_doc("Production Order") work_order = frappe.new_doc("Work Order")
production_order.update({ work_order.update({
"company": "_Test Company", "company": "_Test Company",
"fg_warehouse": "_Test Warehouse 1 - _TC", "fg_warehouse": "_Test Warehouse 1 - _TC",
"production_item": "_Test FG Item 2", "production_item": "_Test FG Item 2",
@@ -555,13 +555,13 @@ class TestStockEntry(unittest.TestCase):
"wip_warehouse": "_Test Warehouse - _TC", "wip_warehouse": "_Test Warehouse - _TC",
"additional_operating_cost": 1000 "additional_operating_cost": 1000
}) })
production_order.insert() work_order.insert()
production_order.submit() work_order.submit()
make_stock_entry(item_code="_Test Item", target="_Test Warehouse - _TC", qty=50, basic_rate=100) make_stock_entry(item_code="_Test Item", target="_Test Warehouse - _TC", qty=50, basic_rate=100)
make_stock_entry(item_code="_Test Item 2", target="_Test Warehouse - _TC", qty=50, basic_rate=20) make_stock_entry(item_code="_Test Item 2", target="_Test Warehouse - _TC", qty=50, basic_rate=20)
stock_entry = _make_stock_entry(production_order.name, "Manufacture", 1) stock_entry = _make_stock_entry(work_order.name, "Manufacture", 1)
rm_cost = 0 rm_cost = 0
for d in stock_entry.get("items"): for d in stock_entry.get("items"):
@@ -569,15 +569,15 @@ class TestStockEntry(unittest.TestCase):
rm_cost += flt(d.amount) rm_cost += flt(d.amount)
fg_cost = filter(lambda x: x.item_code=="_Test FG Item 2", stock_entry.get("items"))[0].amount fg_cost = filter(lambda x: x.item_code=="_Test FG Item 2", stock_entry.get("items"))[0].amount
self.assertEqual(fg_cost, self.assertEqual(fg_cost,
flt(rm_cost + bom_operation_cost + production_order.additional_operating_cost, 2)) flt(rm_cost + bom_operation_cost + work_order.additional_operating_cost, 2))
def test_variant_production_order(self): def test_variant_work_order(self):
bom_no = frappe.db.get_value("BOM", {"item": "_Test Variant Item", bom_no = frappe.db.get_value("BOM", {"item": "_Test Variant Item",
"is_default": 1, "docstatus": 1}) "is_default": 1, "docstatus": 1})
production_order = frappe.new_doc("Production Order") work_order = frappe.new_doc("Work Order")
production_order.update({ work_order.update({
"company": "_Test Company", "company": "_Test Company",
"fg_warehouse": "_Test Warehouse 1 - _TC", "fg_warehouse": "_Test Warehouse 1 - _TC",
"production_item": "_Test Variant Item-S", "production_item": "_Test Variant Item-S",
@@ -586,12 +586,12 @@ class TestStockEntry(unittest.TestCase):
"stock_uom": "_Test UOM", "stock_uom": "_Test UOM",
"wip_warehouse": "_Test Warehouse - _TC" "wip_warehouse": "_Test Warehouse - _TC"
}) })
production_order.insert() work_order.insert()
production_order.submit() work_order.submit()
from erpnext.manufacturing.doctype.production_order.production_order import make_stock_entry from erpnext.manufacturing.doctype.work_order.work_order import make_stock_entry
stock_entry = frappe.get_doc(make_stock_entry(production_order.name, "Manufacture", 1)) stock_entry = frappe.get_doc(make_stock_entry(work_order.name, "Manufacture", 1))
stock_entry.insert() stock_entry.insert()
self.assertTrue("_Test Variant Item-S" in [d.item_code for d in stock_entry.items]) self.assertTrue("_Test Variant Item-S" in [d.item_code for d in stock_entry.items])

View File

@@ -131,7 +131,7 @@ def get_ordered_qty(item_code, warehouse):
def get_planned_qty(item_code, warehouse): def get_planned_qty(item_code, warehouse):
planned_qty = frappe.db.sql(""" planned_qty = frappe.db.sql("""
select sum(qty - produced_qty) from `tabProduction Order` select sum(qty - produced_qty) from `tabWork Order`
where production_item = %s and fg_warehouse = %s and status not in ("Stopped", "Completed") where production_item = %s and fg_warehouse = %s and status not in ("Stopped", "Completed")
and docstatus=1 and qty > produced_qty""", (item_code, warehouse)) and docstatus=1 and qty > produced_qty""", (item_code, warehouse))

View File

@@ -40,7 +40,7 @@ erpnext/hr/doctype/leave_control_panel/test_leave_control_panel.js
erpnext/hr/doctype/leave_allocation/test_leave_allocation.js erpnext/hr/doctype/leave_allocation/test_leave_allocation.js
erpnext/hr/doctype/leave_application/test_leave_application.js erpnext/hr/doctype/leave_application/test_leave_application.js
erpnext/stock/doctype/warehouse/test_warehouse.js erpnext/stock/doctype/warehouse/test_warehouse.js
erpnext/manufacturing/doctype/production_order/test_production_order.js #long erpnext/manufacturing/doctype/work_order/test_work_order.js #long
erpnext/accounts/page/pos/test_pos.js erpnext/accounts/page/pos/test_pos.js
erpnext/selling/page/point_of_sale/tests/test_point_of_sale.js erpnext/selling/page/point_of_sale/tests/test_point_of_sale.js
erpnext/selling/doctype/product_bundle/test_product_bundle.js erpnext/selling/doctype/product_bundle/test_product_bundle.js