Merge branch 'neil_capacity_planning' into v5.0
This commit is contained in:
		
						commit
						496dfc4bbf
					
				| @ -411,4 +411,3 @@ cur_frm.set_query("debit_to", function(doc) { | ||||
| 		] | ||||
| 	} | ||||
| }); | ||||
| 
 | ||||
|  | ||||
| @ -25,7 +25,17 @@ def get_data(): | ||||
| 				{ | ||||
| 					"type": "doctype", | ||||
| 					"name": "Workstation", | ||||
| 					"description": _("Where manufacturing operations are carried out."), | ||||
| 					"description": _("Where manufacturing operations are carried."), | ||||
| 				}, | ||||
| 				{ | ||||
| 					"type": "doctype", | ||||
| 					"name": "Operation", | ||||
| 					"description": _("Details of the operations carried out."), | ||||
| 				}, | ||||
| 				{ | ||||
| 					"type": "doctype", | ||||
| 					"name": "Manufacturing Settings", | ||||
| 					"description": _("Global settings for all manufacturing processes."), | ||||
| 				}, | ||||
| 
 | ||||
| 			] | ||||
|  | ||||
| @ -1,5 +1,6 @@ | ||||
| { | ||||
|  "allow_import": 1,  | ||||
|  "autoname": "field:holiday_list_name",  | ||||
|  "creation": "2013-01-10 16:34:14",  | ||||
|  "docstatus": 0,  | ||||
|  "doctype": "DocType",  | ||||
| @ -13,7 +14,8 @@ | ||||
|    "oldfieldname": "holiday_list_name",  | ||||
|    "oldfieldtype": "Data",  | ||||
|    "permlevel": 0,  | ||||
|    "reqd": 1 | ||||
|    "reqd": 1,  | ||||
|    "unique": 1 | ||||
|   },  | ||||
|   { | ||||
|    "fieldname": "is_default",  | ||||
| @ -72,7 +74,7 @@ | ||||
|  ],  | ||||
|  "icon": "icon-calendar",  | ||||
|  "idx": 1,  | ||||
|  "modified": "2014-05-09 02:16:38.887266",  | ||||
|  "modified": "2014-11-25 15:42:22.419054",  | ||||
|  "modified_by": "Administrator",  | ||||
|  "module": "HR",  | ||||
|  "name": "Holiday List",  | ||||
|  | ||||
| @ -11,9 +11,6 @@ from frappe import throw, _ | ||||
| from frappe.model.document import Document | ||||
| 
 | ||||
| class HolidayList(Document): | ||||
| 	def autoname(self): | ||||
| 		self.name = make_autoname(self.fiscal_year + "/" + self.holiday_list_name + "/.###") | ||||
| 
 | ||||
| 	def validate(self): | ||||
| 		self.update_default_holiday_list() | ||||
| 
 | ||||
|  | ||||
| @ -5,11 +5,11 @@ | ||||
|   "holiday_list_details": [ | ||||
|    { | ||||
|     "description": "New Year",  | ||||
|     "doctype": "Holiday",  | ||||
|     "holiday_date": "2013-01-01",  | ||||
|     "parent": "_Test Holiday List",  | ||||
|     "parentfield": "holiday_list_details",  | ||||
|     "parenttype": "Holiday List" | ||||
|     "holiday_date": "2013-01-01" | ||||
|    }, | ||||
|    { | ||||
|     "description": "Test Holiday",  | ||||
|     "holiday_date": "2013-02-01" | ||||
|    } | ||||
|   ],  | ||||
|   "holiday_list_name": "_Test Holiday List",  | ||||
|  | ||||
| @ -12,7 +12,7 @@ cur_frm.cscript.refresh = function(doc,dt,dn){ | ||||
| 	} | ||||
| 
 | ||||
| 	cur_frm.cscript.with_operations(doc); | ||||
| 	erpnext.bom.set_operation_no(doc); | ||||
| 	erpnext.bom.set_operation(doc); | ||||
| } | ||||
| 
 | ||||
| cur_frm.cscript.update_cost = function() { | ||||
| @ -26,62 +26,36 @@ cur_frm.cscript.update_cost = function() { | ||||
| } | ||||
| 
 | ||||
| cur_frm.cscript.with_operations = function(doc) { | ||||
| 	cur_frm.fields_dict["bom_materials"].grid.set_column_disp("operation_no", doc.with_operations); | ||||
| 	cur_frm.fields_dict["bom_materials"].grid.toggle_reqd("operation_no", doc.with_operations); | ||||
| 	cur_frm.fields_dict["bom_materials"].grid.set_column_disp("operation", doc.with_operations); | ||||
| 	cur_frm.fields_dict["bom_materials"].grid.toggle_reqd("operation", doc.with_operations); | ||||
| } | ||||
| 
 | ||||
| cur_frm.cscript.operation_no = function(doc, cdt, cdn) { | ||||
| 	var child = locals[cdt][cdn]; | ||||
| 	if(child.parentfield=="bom_operations") erpnext.bom.set_operation_no(doc); | ||||
| } | ||||
| 
 | ||||
| erpnext.bom.set_operation_no = function(doc) { | ||||
| 	var op_table = doc.bom_operations || []; | ||||
| erpnext.bom.set_operation = function(doc) { | ||||
| 	var op_table = doc["bom_operations"] || []; | ||||
| 	var operations = []; | ||||
| 
 | ||||
| 	for (var i=0, j=op_table.length; i<j; i++) { | ||||
| 		var op = op_table[i].operation_no; | ||||
| 		if (op && !inList(operations, op)) operations.push(op); | ||||
| 		operations[i] = (i+1); | ||||
| 	} | ||||
| 
 | ||||
| 	frappe.meta.get_docfield("BOM Item", "operation_no", | ||||
| 		cur_frm.docname).options = operations.join("\n"); | ||||
| 
 | ||||
| 	$.each(doc.bom_materials || [], function(i, v) { | ||||
| 		if(!inList(operations, cstr(v.operation_no))) v.operation_no = null; | ||||
| 	}); | ||||
| 	frappe.meta.get_docfield("BOM Item", "operation", cur_frm.docname).options = operations.join("\n"); | ||||
| 
 | ||||
| 	refresh_field("bom_materials"); | ||||
| } | ||||
| 
 | ||||
| cur_frm.cscript.bom_operations_remove = function(){ | ||||
| 	erpnext.bom.set_operation_no(doc); | ||||
| 	erpnext.bom.set_operation(doc); | ||||
| } | ||||
| 
 | ||||
| cur_frm.add_fetch("item", "description", "description"); | ||||
| cur_frm.add_fetch("item", "stock_uom", "uom"); | ||||
| 
 | ||||
| cur_frm.cscript.workstation = function(doc,dt,dn) { | ||||
| 	var d = locals[dt][dn]; | ||||
| 	frappe.model.with_doc("Workstation", d.workstation, function(name, r) { | ||||
| 		d.hour_rate = r.docs[0].hour_rate; | ||||
| 		refresh_field("hour_rate", dn, "bom_operations"); | ||||
| 		d.fixed_cycle_cost = r.docs[0].fixed_cycle_cost; | ||||
| 		refresh_field("fixed_cycle_cost", dn, "bom_operations"); | ||||
| 		erpnext.bom.calculate_op_cost(doc); | ||||
| 		erpnext.bom.calculate_fixed_cost(doc); | ||||
| 		erpnext.bom.calculate_total(doc); | ||||
| 	}); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| cur_frm.cscript.hour_rate = function(doc, dt, dn) { | ||||
| 	erpnext.bom.calculate_op_cost(doc); | ||||
| 	erpnext.bom.calculate_fixed_cost(doc); | ||||
| 	erpnext.bom.calculate_total(doc); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| cur_frm.cscript.time_in_mins = cur_frm.cscript.hour_rate; | ||||
| 
 | ||||
| cur_frm.cscript.item_code = function(doc, cdt, cdn) { | ||||
| @ -138,23 +112,14 @@ cur_frm.cscript.rate = function(doc, cdt, cdn) { | ||||
| 
 | ||||
| erpnext.bom.calculate_op_cost = function(doc) { | ||||
| 	var op = doc.bom_operations || []; | ||||
| 	total_op_cost = 0; | ||||
| 	doc.operating_cost = 0.0; | ||||
| 	for(var i=0;i<op.length;i++) { | ||||
| 		op_cost =	flt(flt(op[i].hour_rate) * flt(op[i].time_in_mins) / 60, 2); | ||||
| 		set_multiple('BOM Operation',op[i].name, {'operating_cost': op_cost}, 'bom_operations'); | ||||
| 		total_op_cost += op_cost; | ||||
| 	} | ||||
| 	doc.operating_cost = total_op_cost; | ||||
| 	refresh_field('operating_cost'); | ||||
| } | ||||
| 		operating_cost = flt(flt(op[i].hour_rate) * flt(op[i].time_in_mins) / 60, 2); | ||||
| 		frappe.model.set_value('BOM Operation',op[i].name, "operating_cost", operating_cost); | ||||
| 
 | ||||
| erpnext.bom.calculate_fixed_cost = function(doc) { | ||||
| 	var op = doc.bom_operations || []; | ||||
| 	var total_fixed_cost = 0; | ||||
| 	for(var i=0;i<op.length;i++) { | ||||
| 		total_fixed_cost += flt(op[i].fixed_cycle_cost); | ||||
| 		doc.operating_cost += operating_cost; | ||||
| 	} | ||||
| 	cur_frm.set_value("total_fixed_cost", total_fixed_cost); | ||||
| 	refresh_field('operating_cost'); | ||||
| } | ||||
| 
 | ||||
| erpnext.bom.calculate_rm_cost = function(doc) { | ||||
| @ -173,10 +138,8 @@ erpnext.bom.calculate_rm_cost = function(doc) { | ||||
| 
 | ||||
| // Calculate Total Cost
 | ||||
| erpnext.bom.calculate_total = function(doc) { | ||||
| 	doc.total_variable_cost = flt(doc.raw_material_cost) + flt(doc.operating_cost) ; | ||||
| 	refresh_field('total_variable_cost'); | ||||
| 	doc.total_cost = flt(doc.total_fixed_cost) + flt(doc.total_variable_cost); | ||||
| 	refresh_field('total_cost'); | ||||
| 	total_cost = flt(doc.operating_cost) + flt(doc.raw_material_cost); | ||||
| 	frappe.model.set_value(doc.doctype, doc.name, "total_cost", total_cost); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| @ -196,7 +159,10 @@ cur_frm.fields_dict['project_name'].get_query = function(doc, dt, dn) { | ||||
| 
 | ||||
| cur_frm.fields_dict['bom_materials'].grid.get_field('item_code').get_query = function(doc) { | ||||
| 	return{ | ||||
| 		query: "erpnext.controllers.queries.item_query" | ||||
| 		query: "erpnext.controllers.queries.item_query", | ||||
| 		filters: { | ||||
| 			"name": "!" + cstr(doc.item) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| @ -214,7 +180,39 @@ cur_frm.fields_dict['bom_materials'].grid.get_field('bom_no').get_query = functi | ||||
| cur_frm.cscript.validate = function(doc, dt, dn) { | ||||
| 	erpnext.bom.calculate_op_cost(doc); | ||||
| 	erpnext.bom.calculate_rm_cost(doc); | ||||
| 	erpnext.bom.calculate_fixed_cost(doc); | ||||
| 	erpnext.bom.calculate_total(doc); | ||||
| } | ||||
| 
 | ||||
| frappe.ui.form.on("BOM Operation", "operation", function(frm, cdt, cdn) { | ||||
| 	var d = locals[cdt][cdn]; | ||||
| 
 | ||||
|     frappe.call({ | ||||
|         "method": "frappe.client.get", | ||||
|         args: { | ||||
|             doctype: "Operation", | ||||
|             name: d.operation | ||||
|         }, | ||||
|         callback: function (data) { | ||||
| 			frappe.model.set_value(d.doctype, d.name, "opn_description", data.message.opn_description); | ||||
| 			frappe.model.set_value(d.doctype, d.name, "workstation", data.message.workstation); | ||||
| 			erpnext.bom.set_operation(frm.doc); | ||||
|         } | ||||
|     }) | ||||
| }); | ||||
| 
 | ||||
| frappe.ui.form.on("BOM Operation", "workstation", function(frm, cdt, cdn) { | ||||
| 	var d = locals[cdt][cdn]; | ||||
| 
 | ||||
|     frappe.call({ | ||||
|         "method": "frappe.client.get", | ||||
|         args: { | ||||
|             doctype: "Workstation", | ||||
|             name: d.workstation | ||||
|         }, | ||||
|         callback: function (data) { | ||||
| 			frappe.model.set_value(d.doctype, d.name, "hour_rate", data.message.hour_rate); | ||||
| 			erpnext.bom.calculate_op_cost(frm.doc); | ||||
| 			erpnext.bom.calculate_total(frm.doc); | ||||
|         } | ||||
|     }) | ||||
| }); | ||||
|  | ||||
| @ -128,17 +128,18 @@ | ||||
|    "permlevel": 0 | ||||
|   },  | ||||
|   { | ||||
|    "fieldname": "raw_material_cost",  | ||||
|    "fieldname": "operating_cost",  | ||||
|    "fieldtype": "Currency",  | ||||
|    "label": "Total Raw Material Cost",  | ||||
|    "in_list_view": 1,  | ||||
|    "label": "Operating Cost",  | ||||
|    "options": "Company:company:default_currency",  | ||||
|    "permlevel": 0,  | ||||
|    "read_only": 1 | ||||
|   },  | ||||
|   { | ||||
|    "fieldname": "operating_cost",  | ||||
|    "fieldname": "raw_material_cost",  | ||||
|    "fieldtype": "Currency",  | ||||
|    "label": "Total Operating Cost",  | ||||
|    "label": "Raw Material Cost",  | ||||
|    "options": "Company:company:default_currency",  | ||||
|    "permlevel": 0,  | ||||
|    "read_only": 1 | ||||
| @ -148,23 +149,6 @@ | ||||
|    "fieldtype": "Column Break",  | ||||
|    "permlevel": 0 | ||||
|   },  | ||||
|   { | ||||
|    "fieldname": "total_variable_cost",  | ||||
|    "fieldtype": "Currency",  | ||||
|    "in_list_view": 1,  | ||||
|    "label": "Total Variable Cost",  | ||||
|    "options": "Company:company:default_currency",  | ||||
|    "permlevel": 0,  | ||||
|    "read_only": 1 | ||||
|   },  | ||||
|   { | ||||
|    "fieldname": "total_fixed_cost",  | ||||
|    "fieldtype": "Currency",  | ||||
|    "label": "Total Fixed Cost",  | ||||
|    "options": "Company:company:default_currency",  | ||||
|    "permlevel": 0,  | ||||
|    "read_only": 1 | ||||
|   },  | ||||
|   { | ||||
|    "fieldname": "total_cost",  | ||||
|    "fieldtype": "Currency",  | ||||
| @ -260,7 +244,7 @@ | ||||
|  "is_submittable": 1,  | ||||
|  "issingle": 0,  | ||||
|  "istable": 0,  | ||||
|  "modified": "2014-12-12 11:13:12.146205",  | ||||
|  "modified": "2014-12-23 16:39:14.197038",  | ||||
|  "modified_by": "Administrator",  | ||||
|  "module": "Manufacturing",  | ||||
|  "name": "BOM",  | ||||
|  | ||||
| @ -27,7 +27,6 @@ class BOM(Document): | ||||
| 		from erpnext.utilities.transaction_base import validate_uom_is_integer | ||||
| 		validate_uom_is_integer(self, "stock_uom", "qty") | ||||
| 
 | ||||
| 		self.validate_operations() | ||||
| 		self.validate_materials() | ||||
| 		self.set_bom_material_details() | ||||
| 		self.calculate_cost() | ||||
| @ -119,11 +118,10 @@ class BOM(Document): | ||||
| 			return | ||||
| 
 | ||||
| 		for d in self.get("bom_materials"): | ||||
| 			d.rate = self.get_bom_material_detail({ | ||||
| 				'item_code': d.item_code, | ||||
| 				'bom_no': d.bom_no, | ||||
| 				'qty': d.qty | ||||
| 			})["rate"] | ||||
| 			rate = self.get_bom_material_detail({'item_code': d.item_code, 'bom_no': d.bom_no, | ||||
| 				'qty': d.qty})["rate"] | ||||
| 			if rate: | ||||
| 				d.rate = rate | ||||
| 
 | ||||
| 		if self.docstatus == 1: | ||||
| 			self.ignore_validate_update_after_submit = True | ||||
| @ -131,20 +129,31 @@ class BOM(Document): | ||||
| 		self.save() | ||||
| 
 | ||||
| 	def get_bom_unitcost(self, bom_no): | ||||
| 		bom = frappe.db.sql("""select name, total_variable_cost/quantity as unit_cost from `tabBOM` | ||||
| 		bom = frappe.db.sql("""select name, total_cost/quantity as unit_cost from `tabBOM` | ||||
| 			where is_active = 1 and name = %s""", bom_no, as_dict=1) | ||||
| 		return bom and bom[0]['unit_cost'] or 0 | ||||
| 
 | ||||
| 	def get_valuation_rate(self, args): | ||||
| 		""" Get weighted average of valuation rate from all warehouses """ | ||||
| 
 | ||||
| 		total_qty, total_value = 0.0, 0.0 | ||||
| 		total_qty, total_value, valuation_rate = 0.0, 0.0, 0.0 | ||||
| 		for d in frappe.db.sql("""select actual_qty, stock_value from `tabBin` | ||||
| 			where item_code=%s and actual_qty > 0""", args['item_code'], as_dict=1): | ||||
| 			where item_code=%s""", args['item_code'], as_dict=1): | ||||
| 				total_qty += flt(d.actual_qty) | ||||
| 				total_value += flt(d.stock_value) | ||||
| 
 | ||||
| 		return total_value / total_qty if total_qty else 0.0 | ||||
| 		if total_qty: | ||||
| 			valuation_rate =  total_value / total_qty | ||||
| 
 | ||||
| 		if valuation_rate <= 0: | ||||
| 			last_valuation_rate = frappe.db.sql("""select valuation_rate | ||||
| 				from `tabStock Ledger Entry` | ||||
| 				where item_code = %s and ifnull(valuation_rate, 0) > 0 | ||||
| 				order by posting_date desc, posting_time desc, name desc limit 1""", args['item_code']) | ||||
| 
 | ||||
| 			valuation_rate = flt(last_valuation_rate[0][0]) if last_valuation_rate else 0 | ||||
| 
 | ||||
| 		return valuation_rate | ||||
| 
 | ||||
| 	def manage_default_bom(self): | ||||
| 		""" Uncheck others if current one is selected as default, | ||||
| @ -171,7 +180,7 @@ class BOM(Document): | ||||
| 		if not self.with_operations: | ||||
| 			self.set('bom_operations', []) | ||||
| 			for d in self.get("bom_materials"): | ||||
| 				d.operation_no = None | ||||
| 				d.operation = None | ||||
| 
 | ||||
| 	def validate_main_item(self): | ||||
| 		""" Validate main FG item""" | ||||
| @ -183,23 +192,10 @@ class BOM(Document): | ||||
| 			self.description = ret[0] | ||||
| 			self.uom = ret[1] | ||||
| 
 | ||||
| 	def validate_operations(self): | ||||
| 		""" Check duplicate operation no""" | ||||
| 		self.op = [] | ||||
| 		for d in self.get('bom_operations'): | ||||
| 			if cstr(d.operation_no) in self.op: | ||||
| 				frappe.throw(_("Operation {0} is repeated in Operations Table").format(d.operation_no)) | ||||
| 			else: | ||||
| 				# add operation in op list | ||||
| 				self.op.append(cstr(d.operation_no)) | ||||
| 
 | ||||
| 	def validate_materials(self): | ||||
| 		""" Validate raw material entries """ | ||||
| 		check_list = [] | ||||
| 		for m in self.get('bom_materials'): | ||||
| 			# check if operation no not in op table | ||||
| 			if self.with_operations and cstr(m.operation_no) not in self.op: | ||||
| 				frappe.throw(_("Operation {0} not present in Operations Table").format(m.operation_no)) | ||||
| 
 | ||||
| 			if m.bom_no: | ||||
| 				validate_bom_no(m.item_code, m.bom_no) | ||||
| @ -207,7 +203,7 @@ class BOM(Document): | ||||
| 			if flt(m.qty) <= 0: | ||||
| 				frappe.throw(_("Quantity required for Item {0} in row {1}").format(m.item_code, m.idx)) | ||||
| 
 | ||||
| 			self.check_if_item_repeated(m.item_code, m.operation_no, check_list) | ||||
| 			self.check_if_item_repeated(m.item_code, m.operation, check_list) | ||||
| 
 | ||||
| 
 | ||||
| 	def check_if_item_repeated(self, item, op, check_list): | ||||
| @ -261,26 +257,20 @@ class BOM(Document): | ||||
| 		"""Calculate bom totals""" | ||||
| 		self.calculate_op_cost() | ||||
| 		self.calculate_rm_cost() | ||||
| 		self.total_variable_cost = self.raw_material_cost + self.operating_cost | ||||
| 		self.total_cost = self.total_variable_cost + self.total_fixed_cost | ||||
| 		self.total_cost = self.operating_cost + self.raw_material_cost | ||||
| 
 | ||||
| 	def calculate_op_cost(self): | ||||
| 		"""Update workstation rate and calculates totals""" | ||||
| 		total_op_cost, fixed_cost = 0, 0 | ||||
| 		self.operating_cost = 0 | ||||
| 		for d in self.get('bom_operations'): | ||||
| 			if d.workstation: | ||||
| 				w = frappe.db.get_value("Workstation", d.workstation, ["hour_rate", "fixed_cycle_cost"]) | ||||
| 				if not d.hour_rate: | ||||
| 					d.hour_rate = flt(w[0]) | ||||
| 
 | ||||
| 				fixed_cost += flt(w[1]) | ||||
| 					d.hour_rate = flt(frappe.db.get_value("Workstation", d.workstation, "hour_rate")) | ||||
| 
 | ||||
| 			if d.hour_rate and d.time_in_mins: | ||||
| 				d.operating_cost = flt(d.hour_rate) * flt(d.time_in_mins) / 60.0 | ||||
| 			total_op_cost += flt(d.operating_cost) | ||||
| 
 | ||||
| 		self.operating_cost = total_op_cost | ||||
| 		self.total_fixed_cost = fixed_cost | ||||
| 			self.operating_cost += flt(d.operating_cost) | ||||
| 
 | ||||
| 	def calculate_rm_cost(self): | ||||
| 		"""Fetch RM rate as per today's valuation rate and calculate totals""" | ||||
| @ -426,4 +416,3 @@ def validate_bom_no(item, bom_no): | ||||
| 	if item and not (bom.item == item or \ | ||||
| 		bom.item == frappe.db.get_value("Item", item, "variant_of")): | ||||
| 		frappe.throw(_("BOM {0} does not belong to Item {1}").format(bom_no, item)) | ||||
| 
 | ||||
|  | ||||
| @ -15,6 +15,6 @@ | ||||
| 		</div> | ||||
| 	</div> | ||||
| 	<div class="col-xs-2 text-right"> | ||||
| 		{%= doc.get_formatted("total_variable_cost") %} | ||||
| 		{%= doc.get_formatted("total_cost") %} | ||||
| 	</div> | ||||
| </div> | ||||
|  | ||||
| @ -1,3 +1,3 @@ | ||||
| frappe.listview_settings['BOM'] = { | ||||
| 	add_fields: ["is_active", "is_default", "total_variable_cost"] | ||||
| 	add_fields: ["is_active", "is_default", "total_cost"] | ||||
| }; | ||||
|  | ||||
| @ -58,7 +58,7 @@ | ||||
|  { | ||||
|   "bom_operations": [ | ||||
|    { | ||||
|     "operation_no": "1", | ||||
|     "operation": "_Test Operation 1", | ||||
|     "opn_description": "_Test", | ||||
|     "workstation": "_Test Workstation 1", | ||||
|     "time_in_min": 60, | ||||
| @ -67,7 +67,7 @@ | ||||
|    ], | ||||
|   "bom_materials": [ | ||||
|    { | ||||
|     "operation_no": 1, | ||||
|     "operation": 1, | ||||
|     "amount": 5000.0, | ||||
|     "doctype": "BOM Item", | ||||
|     "item_code": "_Test Item", | ||||
| @ -77,7 +77,7 @@ | ||||
|     "stock_uom": "_Test UOM" | ||||
|    }, | ||||
|    { | ||||
|     "operation_no": 1, | ||||
|     "operation": 1, | ||||
|     "amount": 2000.0, | ||||
|     "bom_no": "BOM/_Test Item Home Desktop Manufactured/001", | ||||
|     "doctype": "BOM Item", | ||||
| @ -99,7 +99,7 @@ | ||||
|  { | ||||
|   "bom_operations": [ | ||||
|    { | ||||
|     "operation_no": "1", | ||||
|     "operation": "_Test Operation 1", | ||||
|     "opn_description": "_Test", | ||||
|     "workstation": "_Test Workstation 1", | ||||
|     "time_in_min": 60, | ||||
| @ -108,7 +108,7 @@ | ||||
|    ], | ||||
|   "bom_materials": [ | ||||
|    { | ||||
|     "operation_no": 1, | ||||
|     "operation": 1, | ||||
|     "amount": 5000.0, | ||||
|     "doctype": "BOM Item", | ||||
|     "item_code": "_Test Item", | ||||
|  | ||||
| @ -4,10 +4,10 @@ | ||||
|  "doctype": "DocType", | ||||
|  "fields": [ | ||||
|   { | ||||
|    "fieldname": "operation_no",  | ||||
|    "fieldname": "operation", | ||||
|    "fieldtype": "Select", | ||||
|    "in_list_view": 1, | ||||
|    "label": "Operation No",  | ||||
|    "label": "Operation", | ||||
|    "oldfieldname": "operation_no", | ||||
|    "oldfieldtype": "Data", | ||||
|    "permlevel": 0, | ||||
|  | ||||
| @ -4,15 +4,27 @@ | ||||
|  "doctype": "DocType",  | ||||
|  "fields": [ | ||||
|   { | ||||
|    "fieldname": "operation_no",  | ||||
|    "fieldtype": "Data",  | ||||
|    "fieldname": "operation",  | ||||
|    "fieldtype": "Link",  | ||||
|    "in_list_view": 1,  | ||||
|    "label": "Operation No",  | ||||
|    "label": "Operation",  | ||||
|    "oldfieldname": "operation_no",  | ||||
|    "oldfieldtype": "Data",  | ||||
|    "options": "Operation",  | ||||
|    "permlevel": 0,  | ||||
|    "reqd": 1 | ||||
|   },  | ||||
|   { | ||||
|    "fieldname": "workstation",  | ||||
|    "fieldtype": "Link",  | ||||
|    "in_list_view": 1,  | ||||
|    "label": "Workstation",  | ||||
|    "oldfieldname": "workstation",  | ||||
|    "oldfieldtype": "Link",  | ||||
|    "options": "Workstation",  | ||||
|    "permlevel": 0,  | ||||
|    "reqd": 0 | ||||
|   },  | ||||
|   { | ||||
|    "fieldname": "opn_description",  | ||||
|    "fieldtype": "Text",  | ||||
| @ -28,35 +40,25 @@ | ||||
|    "fieldtype": "Column Break",  | ||||
|    "permlevel": 0 | ||||
|   },  | ||||
|   { | ||||
|    "fieldname": "workstation",  | ||||
|    "fieldtype": "Link",  | ||||
|    "in_list_view": 1,  | ||||
|    "label": "Workstation",  | ||||
|    "oldfieldname": "workstation",  | ||||
|    "oldfieldtype": "Link",  | ||||
|    "options": "Workstation",  | ||||
|    "permlevel": 0,  | ||||
|    "reqd": 0 | ||||
|   },  | ||||
|   { | ||||
|    "fieldname": "hour_rate",  | ||||
|    "fieldtype": "Currency",  | ||||
|    "fieldtype": "Float",  | ||||
|    "in_list_view": 0,  | ||||
|    "label": "Hour Rate",  | ||||
|    "oldfieldname": "hour_rate",  | ||||
|    "oldfieldtype": "Currency",  | ||||
|    "options": "Company:company:default_currency",  | ||||
|    "permlevel": 0,  | ||||
|    "reqd": 0 | ||||
|   },  | ||||
|   { | ||||
|    "description": "In minutes",  | ||||
|    "fieldname": "time_in_mins",  | ||||
|    "fieldtype": "Float",  | ||||
|    "in_list_view": 0,  | ||||
|    "label": "Operation Time (mins)",  | ||||
|    "label": "Operation Time ",  | ||||
|    "oldfieldname": "time_in_mins",  | ||||
|    "oldfieldtype": "Currency",  | ||||
|    "options": "",  | ||||
|    "permlevel": 0,  | ||||
|    "reqd": 0 | ||||
|   },  | ||||
| @ -68,22 +70,14 @@ | ||||
|    "label": "Operating Cost",  | ||||
|    "oldfieldname": "operating_cost",  | ||||
|    "oldfieldtype": "Currency",  | ||||
|    "options": "Company:company:default_currency",  | ||||
|    "permlevel": 0,  | ||||
|    "read_only": 1,  | ||||
|    "reqd": 0 | ||||
|   },  | ||||
|   { | ||||
|    "fieldname": "fixed_cycle_cost",  | ||||
|    "fieldtype": "Currency",  | ||||
|    "in_list_view": 0,  | ||||
|    "label": "Fixed Cycle Cost",  | ||||
|    "options": "Company:company:default_currency",  | ||||
|    "permlevel": 0 | ||||
|   } | ||||
|  ],  | ||||
|  "idx": 1,  | ||||
|  "istable": 1,  | ||||
|  "modified": "2014-12-12 11:16:49.031521",  | ||||
|  "modified": "2014-12-23 15:01:54.340605",  | ||||
|  "modified_by": "Administrator",  | ||||
|  "module": "Manufacturing",  | ||||
|  "name": "BOM Operation",  | ||||
|  | ||||
| @ -25,7 +25,7 @@ class BOMReplaceTool(Document): | ||||
| 			frappe.throw(_("Current BOM and New BOM can not be same")) | ||||
| 
 | ||||
| 	def update_new_bom(self): | ||||
| 		current_bom_unitcost = frappe.db.sql("""select total_variable_cost/quantity | ||||
| 		current_bom_unitcost = frappe.db.sql("""select total_cost/quantity | ||||
| 			from `tabBOM` where name = %s""", self.current_bom) | ||||
| 		current_bom_unitcost = current_bom_unitcost and flt(current_bom_unitcost[0][0]) or 0 | ||||
| 		frappe.db.sql("""update `tabBOM Item` set bom_no=%s, | ||||
|  | ||||
| @ -0,0 +1,84 @@ | ||||
| { | ||||
|  "allow_copy": 0,  | ||||
|  "allow_import": 0,  | ||||
|  "allow_rename": 0,  | ||||
|  "creation": "2014-11-27 14:12:07.542534",  | ||||
|  "custom": 0,  | ||||
|  "docstatus": 0,  | ||||
|  "doctype": "DocType",  | ||||
|  "document_type": "Master",  | ||||
|  "fields": [ | ||||
|   { | ||||
|    "description": "Will not allow to make time logs outside \"Workstation operation timings\"",  | ||||
|    "fieldname": "dont_allow_overtime",  | ||||
|    "fieldtype": "Check",  | ||||
|    "label": "Don't allow overtime",  | ||||
|    "permlevel": 0,  | ||||
|    "precision": "" | ||||
|   },  | ||||
|   { | ||||
|    "default": "",  | ||||
|    "fieldname": "allow_production_on_holidays",  | ||||
|    "fieldtype": "Check",  | ||||
|    "in_list_view": 1,  | ||||
|    "label": "Allow Production on Holidays",  | ||||
|    "options": "",  | ||||
|    "permlevel": 0,  | ||||
|    "precision": "" | ||||
|   },  | ||||
|   { | ||||
|    "fieldname": "column_break_3",  | ||||
|    "fieldtype": "Column Break",  | ||||
|    "permlevel": 0,  | ||||
|    "precision": "" | ||||
|   },  | ||||
|   { | ||||
|    "default": "30",  | ||||
|    "description": "Delay in start time of production order operations if automatically make time logs is used.\n(in mins)",  | ||||
|    "fieldname": "operations_start_delay",  | ||||
|    "fieldtype": "Float",  | ||||
|    "in_list_view": 1,  | ||||
|    "label": "Operations Start Delay",  | ||||
|    "permlevel": 0,  | ||||
|    "precision": "" | ||||
|   } | ||||
|  ],  | ||||
|  "hide_heading": 0,  | ||||
|  "hide_toolbar": 0,  | ||||
|  "icon": "icon-wrench",  | ||||
|  "in_create": 0,  | ||||
|  "in_dialog": 0,  | ||||
|  "is_submittable": 0,  | ||||
|  "issingle": 1,  | ||||
|  "istable": 0,  | ||||
|  "modified": "2014-12-22 12:43:15.261503",  | ||||
|  "modified_by": "Administrator",  | ||||
|  "module": "Manufacturing",  | ||||
|  "name": "Manufacturing Settings",  | ||||
|  "name_case": "",  | ||||
|  "owner": "Administrator",  | ||||
|  "permissions": [ | ||||
|   { | ||||
|    "amend": 0,  | ||||
|    "apply_user_permissions": 0,  | ||||
|    "cancel": 0,  | ||||
|    "create": 1,  | ||||
|    "delete": 0,  | ||||
|    "email": 0,  | ||||
|    "export": 0,  | ||||
|    "import": 0,  | ||||
|    "permlevel": 0,  | ||||
|    "print": 0,  | ||||
|    "read": 1,  | ||||
|    "report": 0,  | ||||
|    "role": "Manufacturing Manager",  | ||||
|    "set_user_permissions": 0,  | ||||
|    "submit": 0,  | ||||
|    "write": 1 | ||||
|   } | ||||
|  ],  | ||||
|  "read_only": 0,  | ||||
|  "read_only_onload": 0,  | ||||
|  "sort_field": "modified",  | ||||
|  "sort_order": "DESC" | ||||
| } | ||||
| @ -0,0 +1,9 @@ | ||||
| # Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors and contributors | ||||
| # For license information, please see license.txt | ||||
| 
 | ||||
| from __future__ import unicode_literals | ||||
| import frappe | ||||
| from frappe.model.document import Document | ||||
| 
 | ||||
| class ManufacturingSettings(Document): | ||||
| 	pass | ||||
							
								
								
									
										0
									
								
								erpnext/manufacturing/doctype/operation/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								erpnext/manufacturing/doctype/operation/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										109
									
								
								erpnext/manufacturing/doctype/operation/operation.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										109
									
								
								erpnext/manufacturing/doctype/operation/operation.json
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,109 @@ | ||||
| { | ||||
|  "allow_copy": 0,  | ||||
|  "allow_import": 1,  | ||||
|  "allow_rename": 1,  | ||||
|  "autoname": "field:operation",  | ||||
|  "creation": "2014-11-07 16:20:30.683186",  | ||||
|  "custom": 0,  | ||||
|  "docstatus": 0,  | ||||
|  "doctype": "DocType",  | ||||
|  "document_type": "Master",  | ||||
|  "fields": [ | ||||
|   { | ||||
|    "allow_on_submit": 0,  | ||||
|    "fieldname": "operation",  | ||||
|    "fieldtype": "Data",  | ||||
|    "hidden": 0,  | ||||
|    "ignore_user_permissions": 0,  | ||||
|    "in_filter": 0,  | ||||
|    "in_list_view": 1,  | ||||
|    "label": "Operation",  | ||||
|    "no_copy": 0,  | ||||
|    "permlevel": 0,  | ||||
|    "precision": "",  | ||||
|    "print_hide": 0,  | ||||
|    "read_only": 0,  | ||||
|    "report_hide": 0,  | ||||
|    "reqd": 1,  | ||||
|    "search_index": 0,  | ||||
|    "set_only_once": 0,  | ||||
|    "unique": 0 | ||||
|   },  | ||||
|   { | ||||
|    "fieldname": "column_break_2",  | ||||
|    "fieldtype": "Column Break",  | ||||
|    "permlevel": 0,  | ||||
|    "precision": "" | ||||
|   },  | ||||
|   { | ||||
|    "fieldname": "workstation",  | ||||
|    "fieldtype": "Link",  | ||||
|    "label": "Default Workstation",  | ||||
|    "options": "Workstation",  | ||||
|    "permlevel": 0,  | ||||
|    "precision": "" | ||||
|   },  | ||||
|   { | ||||
|    "fieldname": "section_break_4",  | ||||
|    "fieldtype": "Section Break",  | ||||
|    "permlevel": 0,  | ||||
|    "precision": "" | ||||
|   },  | ||||
|   { | ||||
|    "fieldname": "opn_description",  | ||||
|    "fieldtype": "Text",  | ||||
|    "label": "Operation Description",  | ||||
|    "permlevel": 0,  | ||||
|    "precision": "" | ||||
|   } | ||||
|  ],  | ||||
|  "hide_heading": 0,  | ||||
|  "hide_toolbar": 0,  | ||||
|  "icon": "icon-wrench",  | ||||
|  "in_create": 0,  | ||||
|  "in_dialog": 0,  | ||||
|  "is_submittable": 0,  | ||||
|  "issingle": 0,  | ||||
|  "istable": 0,  | ||||
|  "modified": "2014-12-18 16:21:59.462435",  | ||||
|  "modified_by": "Administrator",  | ||||
|  "module": "Manufacturing",  | ||||
|  "name": "Operation",  | ||||
|  "name_case": "",  | ||||
|  "owner": "Administrator",  | ||||
|  "permissions": [ | ||||
|   { | ||||
|    "amend": 0,  | ||||
|    "apply_user_permissions": 0,  | ||||
|    "cancel": 0,  | ||||
|    "create": 1,  | ||||
|    "delete": 1,  | ||||
|    "email": 0,  | ||||
|    "export": 1,  | ||||
|    "import": 1,  | ||||
|    "permlevel": 0,  | ||||
|    "print": 0,  | ||||
|    "read": 1,  | ||||
|    "report": 0,  | ||||
|    "role": "Manufacturing User",  | ||||
|    "set_user_permissions": 0,  | ||||
|    "submit": 0,  | ||||
|    "write": 1 | ||||
|   },  | ||||
|   { | ||||
|    "create": 1,  | ||||
|    "delete": 1,  | ||||
|    "export": 1,  | ||||
|    "import": 1,  | ||||
|    "permlevel": 0,  | ||||
|    "read": 1,  | ||||
|    "report": 1,  | ||||
|    "role": "Manufacturing Manager",  | ||||
|    "write": 1 | ||||
|   } | ||||
|  ],  | ||||
|  "read_only": 0,  | ||||
|  "read_only_onload": 0,  | ||||
|  "sort_field": "modified",  | ||||
|  "sort_order": "DESC" | ||||
| } | ||||
							
								
								
									
										14
									
								
								erpnext/manufacturing/doctype/operation/operation.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								erpnext/manufacturing/doctype/operation/operation.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,14 @@ | ||||
| # Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors and contributors | ||||
| # For license information, please see license.txt | ||||
| 
 | ||||
| from __future__ import unicode_literals | ||||
| import frappe | ||||
| from frappe.model.document import Document | ||||
| 
 | ||||
| class Operation(Document): | ||||
| 	def calculate_op_cost(self): | ||||
| 		if self.hour_rate and self.time_in_mins: | ||||
| 			self.operating_cost = flt(self.hour_rate) * flt(self.time_in_mins) / 60.0 | ||||
| 		else : | ||||
| 			self.operating_cost = 0 | ||||
| 
 | ||||
							
								
								
									
										10
									
								
								erpnext/manufacturing/doctype/operation/test_operation.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								erpnext/manufacturing/doctype/operation/test_operation.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,10 @@ | ||||
| # Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors | ||||
| # See license.txt | ||||
| 
 | ||||
| import frappe | ||||
| import unittest | ||||
| 
 | ||||
| test_records = frappe.get_test_records('Operation') | ||||
| 
 | ||||
| class TestOperation(unittest.TestCase): | ||||
| 	pass | ||||
| @ -0,0 +1,7 @@ | ||||
| [ | ||||
| 	{ | ||||
| 		"doctype": "Operation", | ||||
| 		"operation": "_Test Operation 1", | ||||
| 		"workstation": "_Test Workstation 1" | ||||
| 	} | ||||
| ] | ||||
| @ -3,7 +3,6 @@ | ||||
| 
 | ||||
| $.extend(cur_frm.cscript, { | ||||
| 	onload: function (doc, dt, dn) { | ||||
| 
 | ||||
| 		if (!doc.status) doc.status = 'Draft'; | ||||
| 		cfn_set_fields(doc, dt, dn); | ||||
| 
 | ||||
| @ -33,9 +32,12 @@ $.extend(cur_frm.cscript, { | ||||
| 	}, | ||||
| 
 | ||||
| 	production_item: function(doc) { | ||||
| 		return this.frm.call({ | ||||
| 			method: "get_item_details", | ||||
| 			args: { item: doc.production_item } | ||||
| 		frappe.call({ | ||||
| 			method: "erpnext.manufacturing.doctype.production_order.production_order.get_item_details", | ||||
| 			args: { item: doc.production_item }, | ||||
| 			callback: function(r) { | ||||
| 				cur_frm.set_value(r.message); | ||||
| 			} | ||||
| 		}); | ||||
| 	}, | ||||
| 
 | ||||
| @ -53,6 +55,45 @@ $.extend(cur_frm.cscript, { | ||||
| 				frappe.set_route("Form", doclist[0].doctype, doclist[0].name); | ||||
| 			} | ||||
| 		}); | ||||
| 	}, | ||||
| 
 | ||||
| 	bom_no: function() { | ||||
| 		return this.frm.call({ | ||||
| 			doc: this.frm.doc, | ||||
| 			method: "set_production_order_operations", | ||||
| 			callback: function(r) { | ||||
| 				if(!r.exc) refresh_field("production_order_operations"); | ||||
| 			} | ||||
| 		}); | ||||
| 	}, | ||||
| 
 | ||||
| 	make_time_log: function(doc, cdt, cdn){ | ||||
| 		var child = locals[cdt][cdn] | ||||
| 		frappe.call({ | ||||
| 			method:"erpnext.manufacturing.doctype.production_order.production_order.make_time_log", | ||||
| 			args: { | ||||
| 				"name": doc.name, | ||||
| 				"operation": child.idx + ". " + child.operation, | ||||
| 				"from_time": child.planned_start_time, | ||||
| 				"to_time": child.planned_end_time, | ||||
| 				"project": doc.project, | ||||
| 				"workstation": child.workstation, | ||||
| 				"qty": flt(doc.qty) - flt(child.completed_qty) | ||||
| 			}, | ||||
| 			callback: function(r) { | ||||
| 				var doclist = frappe.model.sync(r.message); | ||||
| 				frappe.set_route("Form", doclist[0].doctype, doclist[0].name); | ||||
| 			} | ||||
| 		}); | ||||
| 	}, | ||||
| 
 | ||||
| 	auto_time_log: function(doc){ | ||||
| 		frappe.call({ | ||||
| 			method:"erpnext.manufacturing.doctype.production_order.production_order.auto_make_time_log", | ||||
| 			args: { | ||||
| 				"production_order_id": doc.name | ||||
| 			} | ||||
| 		}); | ||||
| 	} | ||||
| }); | ||||
| 
 | ||||
| @ -124,4 +165,7 @@ cur_frm.set_query("bom_no", function(doc) { | ||||
| 	} else msgprint(__("Please enter Production Item first")); | ||||
| }); | ||||
| 
 | ||||
| cur_frm.add_fetch('bom_no', 'total_fixed_cost', 'total_fixed_cost'); | ||||
| frappe.ui.form.on("Production Order", "additional_operating_cost", function(frm) { | ||||
| 	var variable_cost = frm.doc.actual_operating_cost ? flt(frm.doc.actual_operating_cost) : flt(frm.doc.planned_operating_cost) | ||||
| 	frm.set_value("total_operating_cost", (flt(frm.doc.additional_operating_cost) + variable_cost)) | ||||
| }) | ||||
|  | ||||
| @ -22,6 +22,7 @@ | ||||
|    "reqd": 1 | ||||
|   },  | ||||
|   { | ||||
|    "default": "Draft",  | ||||
|    "depends_on": "eval:!doc.__islocal",  | ||||
|    "fieldname": "status",  | ||||
|    "fieldtype": "Select",  | ||||
| @ -51,7 +52,7 @@ | ||||
|    "reqd": 1 | ||||
|   },  | ||||
|   { | ||||
|    "depends_on": "production_item",  | ||||
|    "depends_on": "",  | ||||
|    "description": "Bill of Material to be considered for manufacturing",  | ||||
|    "fieldname": "bom_no",  | ||||
|    "fieldtype": "Link",  | ||||
| @ -64,14 +65,6 @@ | ||||
|    "read_only": 0,  | ||||
|    "reqd": 1 | ||||
|   },  | ||||
|   { | ||||
|    "default": "1",  | ||||
|    "description": "If checked, BOM for sub-assembly items will be considered for getting raw materials. Otherwise, all sub-assembly items will be treated as a raw material.",  | ||||
|    "fieldname": "use_multi_level_bom",  | ||||
|    "fieldtype": "Check",  | ||||
|    "label": "Use Multi-Level BOM",  | ||||
|    "permlevel": 0 | ||||
|   },  | ||||
|   { | ||||
|    "fieldname": "column_break1",  | ||||
|    "fieldtype": "Column Break",  | ||||
| @ -81,16 +74,7 @@ | ||||
|    "width": "50%" | ||||
|   },  | ||||
|   { | ||||
|    "description": "Manufacture against Sales Order",  | ||||
|    "fieldname": "sales_order",  | ||||
|    "fieldtype": "Link",  | ||||
|    "label": "Sales Order",  | ||||
|    "options": "Sales Order",  | ||||
|    "permlevel": 0,  | ||||
|    "read_only": 0 | ||||
|   },  | ||||
|   { | ||||
|    "depends_on": "production_item",  | ||||
|    "depends_on": "",  | ||||
|    "fieldname": "qty",  | ||||
|    "fieldtype": "Float",  | ||||
|    "in_list_view": 1,  | ||||
| @ -102,13 +86,7 @@ | ||||
|    "reqd": 1 | ||||
|   },  | ||||
|   { | ||||
|    "depends_on": "production_item",  | ||||
|    "fieldname": "total_fixed_cost",  | ||||
|    "fieldtype": "Float",  | ||||
|    "label": "Total Fixed Cost",  | ||||
|    "permlevel": 0 | ||||
|   },  | ||||
|   { | ||||
|    "default": "0",  | ||||
|    "depends_on": "eval:doc.docstatus==1",  | ||||
|    "description": "Automatically updated via Stock Entry of type Manufacture or Repack",  | ||||
|    "fieldname": "produced_qty",  | ||||
| @ -121,26 +99,64 @@ | ||||
|    "read_only": 1 | ||||
|   },  | ||||
|   { | ||||
|    "depends_on": "sales_order",  | ||||
|    "default": "1",  | ||||
|    "description": "If checked, BOM for sub-assembly items will be considered for getting raw materials. Otherwise, all sub-assembly items will be treated as a raw material.",  | ||||
|    "fieldname": "use_multi_level_bom",  | ||||
|    "fieldtype": "Check",  | ||||
|    "label": "Use Multi-Level BOM",  | ||||
|    "permlevel": 0 | ||||
|   },  | ||||
|   { | ||||
|    "fieldname": "time",  | ||||
|    "fieldtype": "Section Break",  | ||||
|    "label": "Time",  | ||||
|    "options": "icon-time",  | ||||
|    "permlevel": 0,  | ||||
|    "precision": "" | ||||
|   },  | ||||
|   { | ||||
|    "depends_on": "",  | ||||
|    "fieldname": "expected_delivery_date",  | ||||
|    "fieldtype": "Date",  | ||||
|    "label": "Expected Delivery Date",  | ||||
|    "permlevel": 0,  | ||||
|    "read_only": 0 | ||||
|   },  | ||||
|   { | ||||
|    "fieldname": "planned_start_date",  | ||||
|    "fieldtype": "Datetime",  | ||||
|    "label": "Planned Start Date",  | ||||
|    "permlevel": 0,  | ||||
|    "precision": "" | ||||
|   },  | ||||
|   { | ||||
|    "fieldname": "planned_end_date",  | ||||
|    "fieldtype": "Datetime",  | ||||
|    "label": "Planned End Date",  | ||||
|    "permlevel": 0,  | ||||
|    "precision": "" | ||||
|   },  | ||||
|   { | ||||
|    "fieldname": "column_break_13",  | ||||
|    "fieldtype": "Column Break",  | ||||
|    "permlevel": 0,  | ||||
|    "precision": "" | ||||
|   },  | ||||
|   { | ||||
|    "fieldname": "actual_start_date",  | ||||
|    "fieldtype": "Datetime",  | ||||
|    "label": "Actual Start Date",  | ||||
|    "permlevel": 0,  | ||||
|    "precision": "",  | ||||
|    "read_only": 1 | ||||
|   },  | ||||
|   { | ||||
|    "fieldname": "start_date",  | ||||
|    "fieldname": "actual_end_date",  | ||||
|    "fieldtype": "Datetime",  | ||||
|    "label": "Start Date",  | ||||
|    "label": "Actual End Date",  | ||||
|    "permlevel": 0,  | ||||
|    "precision": "" | ||||
|   },  | ||||
|   { | ||||
|    "fieldname": "end_date",  | ||||
|    "fieldtype": "Datetime",  | ||||
|    "label": "End Date",  | ||||
|    "permlevel": 0,  | ||||
|    "precision": "" | ||||
|    "precision": "",  | ||||
|    "read_only": 1 | ||||
|   },  | ||||
|   { | ||||
|    "fieldname": "warehouses",  | ||||
| @ -150,7 +166,7 @@ | ||||
|    "permlevel": 0 | ||||
|   },  | ||||
|   { | ||||
|    "depends_on": "production_item",  | ||||
|    "depends_on": "",  | ||||
|    "description": "Manufactured quantity will be updated in this warehouse",  | ||||
|    "fieldname": "fg_warehouse",  | ||||
|    "fieldtype": "Link",  | ||||
| @ -174,6 +190,86 @@ | ||||
|    "permlevel": 0,  | ||||
|    "reqd": 0 | ||||
|   },  | ||||
|   { | ||||
|    "depends_on": "",  | ||||
|    "fieldname": "operations",  | ||||
|    "fieldtype": "Section Break",  | ||||
|    "label": "Operations",  | ||||
|    "options": "icon-wrench",  | ||||
|    "permlevel": 0,  | ||||
|    "precision": "" | ||||
|   },  | ||||
|   { | ||||
|    "fieldname": "production_order_operations",  | ||||
|    "fieldtype": "Table",  | ||||
|    "label": "Production Order Operations",  | ||||
|    "options": "Production Order Operation",  | ||||
|    "permlevel": 0,  | ||||
|    "precision": "",  | ||||
|    "read_only": 1 | ||||
|   },  | ||||
|   { | ||||
|    "fieldname": "section_break_22",  | ||||
|    "fieldtype": "Section Break",  | ||||
|    "label": "Operation Cost",  | ||||
|    "options": "",  | ||||
|    "permlevel": 0,  | ||||
|    "precision": "" | ||||
|   },  | ||||
|   { | ||||
|    "fieldname": "planned_operating_cost",  | ||||
|    "fieldtype": "Currency",  | ||||
|    "label": "Planned Operating Cost",  | ||||
|    "no_copy": 0,  | ||||
|    "options": "Company:company:default_currency",  | ||||
|    "permlevel": 0,  | ||||
|    "precision": "",  | ||||
|    "read_only": 1 | ||||
|   },  | ||||
|   { | ||||
|    "fieldname": "actual_operating_cost",  | ||||
|    "fieldtype": "Currency",  | ||||
|    "label": "Actual Operating Cost",  | ||||
|    "no_copy": 1,  | ||||
|    "options": "Company:company:default_currency",  | ||||
|    "permlevel": 0,  | ||||
|    "precision": "",  | ||||
|    "read_only": 1 | ||||
|   },  | ||||
|   { | ||||
|    "fieldname": "additional_operating_cost",  | ||||
|    "fieldtype": "Currency",  | ||||
|    "label": "Additional Operating Cost",  | ||||
|    "no_copy": 1,  | ||||
|    "options": "Company:company:default_currency",  | ||||
|    "permlevel": 0,  | ||||
|    "precision": "" | ||||
|   },  | ||||
|   { | ||||
|    "fieldname": "column_break_24",  | ||||
|    "fieldtype": "Column Break",  | ||||
|    "permlevel": 0,  | ||||
|    "precision": "" | ||||
|   },  | ||||
|   { | ||||
|    "fieldname": "total_operating_cost",  | ||||
|    "fieldtype": "Currency",  | ||||
|    "label": "Total Operating Cost",  | ||||
|    "no_copy": 1,  | ||||
|    "options": "Company:company:default_currency",  | ||||
|    "permlevel": 0,  | ||||
|    "precision": "",  | ||||
|    "read_only": 1 | ||||
|   },  | ||||
|   { | ||||
|    "allow_on_submit": 1,  | ||||
|    "depends_on": "eval:doc.docstatus==1",  | ||||
|    "fieldname": "auto_time_log",  | ||||
|    "fieldtype": "Button",  | ||||
|    "label": "Automatically Make Time logs",  | ||||
|    "permlevel": 0,  | ||||
|    "precision": "" | ||||
|   },  | ||||
|   { | ||||
|    "fieldname": "more_info",  | ||||
|    "fieldtype": "Section Break",  | ||||
| @ -189,6 +285,24 @@ | ||||
|    "permlevel": 0,  | ||||
|    "read_only": 1 | ||||
|   },  | ||||
|   { | ||||
|    "depends_on": "",  | ||||
|    "fieldname": "stock_uom",  | ||||
|    "fieldtype": "Link",  | ||||
|    "label": "Stock UOM",  | ||||
|    "oldfieldname": "stock_uom",  | ||||
|    "oldfieldtype": "Data",  | ||||
|    "options": "UOM",  | ||||
|    "permlevel": 0,  | ||||
|    "read_only": 1 | ||||
|   },  | ||||
|   { | ||||
|    "fieldname": "column_break2",  | ||||
|    "fieldtype": "Column Break",  | ||||
|    "permlevel": 0,  | ||||
|    "read_only": 0,  | ||||
|    "width": "50%" | ||||
|   },  | ||||
|   { | ||||
|    "fieldname": "project_name",  | ||||
|    "fieldtype": "Link",  | ||||
| @ -201,22 +315,13 @@ | ||||
|    "read_only": 0 | ||||
|   },  | ||||
|   { | ||||
|    "fieldname": "column_break2",  | ||||
|    "fieldtype": "Column Break",  | ||||
|    "permlevel": 0,  | ||||
|    "read_only": 0,  | ||||
|    "width": "50%" | ||||
|   },  | ||||
|   { | ||||
|    "depends_on": "production_item",  | ||||
|    "fieldname": "stock_uom",  | ||||
|    "description": "Manufacture against Sales Order",  | ||||
|    "fieldname": "sales_order",  | ||||
|    "fieldtype": "Link",  | ||||
|    "label": "Stock UOM",  | ||||
|    "oldfieldname": "stock_uom",  | ||||
|    "oldfieldtype": "Data",  | ||||
|    "options": "UOM",  | ||||
|    "label": "Sales Order",  | ||||
|    "options": "Sales Order",  | ||||
|    "permlevel": 0,  | ||||
|    "read_only": 1 | ||||
|    "read_only": 0 | ||||
|   },  | ||||
|   { | ||||
|    "fieldname": "company",  | ||||
| @ -246,7 +351,7 @@ | ||||
|  "idx": 1,  | ||||
|  "in_create": 0,  | ||||
|  "is_submittable": 1,  | ||||
|  "modified": "2014-10-27 13:42:31.476892",  | ||||
|  "modified": "2014-12-23 15:07:26.516227",  | ||||
|  "modified_by": "Administrator",  | ||||
|  "module": "Manufacturing",  | ||||
|  "name": "Production Order",  | ||||
|  | ||||
| @ -4,15 +4,23 @@ | ||||
| from __future__ import unicode_literals | ||||
| import frappe, json | ||||
| 
 | ||||
| from frappe.utils import flt, nowdate | ||||
| from frappe.utils import flt, nowdate, cstr, get_datetime, getdate | ||||
| from frappe import _ | ||||
| from frappe.model.document import Document | ||||
| from erpnext.manufacturing.doctype.bom.bom import validate_bom_no | ||||
| from dateutil.relativedelta import relativedelta | ||||
| 
 | ||||
| class OverProductionError(frappe.ValidationError): pass | ||||
| class StockOverProductionError(frappe.ValidationError): pass | ||||
| 
 | ||||
| form_grid_templates = { | ||||
| 	"production_order_operations": "templates/form_grid/production_order_grid.html" | ||||
| } | ||||
| 
 | ||||
| class ProductionOrder(Document): | ||||
| 	def __setup__(self): | ||||
| 		self.holidays = frappe._dict() | ||||
| 
 | ||||
| 	def validate(self): | ||||
| 		if self.docstatus == 0: | ||||
| 			self.status = "Draft" | ||||
| @ -26,7 +34,7 @@ class ProductionOrder(Document): | ||||
| 
 | ||||
| 		self.validate_sales_order() | ||||
| 		self.validate_warehouse() | ||||
| 		self.set_fixed_cost() | ||||
| 		self.calculate_operating_cost() | ||||
| 
 | ||||
| 		from erpnext.utilities.transaction_base import validate_uom_is_integer | ||||
| 		validate_uom_is_integer(self, "stock_uom", ["qty", "produced_qty"]) | ||||
| @ -50,9 +58,16 @@ class ProductionOrder(Document): | ||||
| 		for w in [self.fg_warehouse, self.wip_warehouse]: | ||||
| 			validate_warehouse_company(w, self.company) | ||||
| 
 | ||||
| 	def set_fixed_cost(self): | ||||
| 		if self.total_fixed_cost==None: | ||||
| 			self.total_fixed_cost = frappe.db.get_value("BOM", self.bom_no, "total_fixed_cost") | ||||
| 	def calculate_operating_cost(self): | ||||
| 		self.planned_operating_cost, self.actual_operating_cost = 0.0, 0.0 | ||||
| 		for d in self.get("production_order_operations"): | ||||
| 			d.actual_operating_cost = flt(d.hour_rate) * flt(d.actual_operation_time) / 60 | ||||
| 
 | ||||
| 			self.planned_operating_cost += flt(d.planned_operating_cost) | ||||
| 			self.actual_operating_cost += flt(d.actual_operating_cost) | ||||
| 
 | ||||
| 		variable_cost = self.actual_operating_cost if self.actual_operating_cost else self.planned_operating_cost | ||||
| 		self.total_operating_cost = flt(self.additional_operating_cost) + flt(variable_cost) | ||||
| 
 | ||||
| 	def validate_production_order_against_so(self): | ||||
| 		# already ordered qty | ||||
| @ -145,6 +160,56 @@ class ProductionOrder(Document): | ||||
| 		from erpnext.stock.utils import update_bin | ||||
| 		update_bin(args) | ||||
| 
 | ||||
| 	def set_production_order_operations(self): | ||||
| 		"""Fetch operations from BOM and set in 'Production Order'""" | ||||
| 
 | ||||
| 		self.set('production_order_operations', []) | ||||
| 
 | ||||
| 		operations = frappe.db.sql("""select operation, opn_description, workstation, | ||||
| 			hour_rate, time_in_mins, operating_cost as "planned_operating_cost", "Pending" as status | ||||
| 			from `tabBOM Operation` where parent = %s""", self.bom_no, as_dict=1) | ||||
| 
 | ||||
| 		self.set('production_order_operations', operations) | ||||
| 
 | ||||
| 		self.plan_operations() | ||||
| 		self.calculate_operating_cost() | ||||
| 
 | ||||
| 	def plan_operations(self): | ||||
| 		scheduled_datetime = self.planned_start_date | ||||
| 		for d in self.get('production_order_operations'): | ||||
| 			while getdate(scheduled_datetime) in self.get_holidays(d.workstation): | ||||
| 				scheduled_datetime = get_datetime(scheduled_datetime) + relativedelta(days=1) | ||||
| 
 | ||||
| 			d.planned_start_time = scheduled_datetime | ||||
| 			scheduled_datetime = get_datetime(scheduled_datetime) + relativedelta(minutes=d.time_in_mins) | ||||
| 			d.planned_end_time = scheduled_datetime | ||||
| 
 | ||||
| 		self.planned_end_date = scheduled_datetime | ||||
| 
 | ||||
| 
 | ||||
| 	def get_holidays(self, workstation): | ||||
| 		holiday_list = frappe.db.get_value("Workstation", workstation, "holiday_list") | ||||
| 
 | ||||
| 		if holiday_list not in self.holidays: | ||||
| 			holiday_list_days = [getdate(d[0]) for d in frappe.get_all("Holiday", fields=["holiday_date"], | ||||
| 				filters={"parent": holiday_list}, order_by="holiday_date", limit_page_length=0, as_list=1)] | ||||
| 
 | ||||
| 			self.holidays[holiday_list] = holiday_list_days | ||||
| 
 | ||||
| 		return self.holidays[holiday_list] | ||||
| 
 | ||||
| 	def update_operation_status(self): | ||||
| 		for d in self.get("production_order_operations"): | ||||
| 			if not d.completed_qty: | ||||
| 				d.status = "Pending" | ||||
| 			elif flt(d.completed_qty) < flt(self.qty): | ||||
| 				d.status = "Work in Progress" | ||||
| 			elif flt(d.completed_qty) == flt(self.qty): | ||||
| 				d.status = "Completed" | ||||
| 			else: | ||||
| 				frappe.throw(_("Completed Qty can not be greater than 'Qty to Manufacture'")) | ||||
| 
 | ||||
| 
 | ||||
| @frappe.whitelist() | ||||
| def get_item_details(item): | ||||
| 	res = frappe.db.sql("""select stock_uom, description | ||||
| @ -155,10 +220,7 @@ def get_item_details(item): | ||||
| 		return {} | ||||
| 
 | ||||
| 	res = res[0] | ||||
| 	bom = frappe.db.sql("""select name as bom_no,total_fixed_cost  from `tabBOM` where item=%s | ||||
| 		and ifnull(is_default, 0)=1""", item, as_dict=1) | ||||
| 	if bom: | ||||
| 		res.update(bom[0]) | ||||
| 	res["bom_no"] = frappe.db.get_value("BOM", filters={"item": item, "is_default": 1}) | ||||
| 	return res | ||||
| 
 | ||||
| @frappe.whitelist() | ||||
| @ -170,6 +232,7 @@ def make_stock_entry(production_order_id, purpose, qty=None): | ||||
| 	stock_entry.production_order = production_order_id | ||||
| 	stock_entry.company = production_order.company | ||||
| 	stock_entry.bom_no = production_order.bom_no | ||||
| 	stock_entry.additional_operating_cost = production_order.additional_operating_cost | ||||
| 	stock_entry.use_multi_level_bom = production_order.use_multi_level_bom | ||||
| 	stock_entry.fg_completed_qty = qty or (flt(production_order.qty) - flt(production_order.produced_qty)) | ||||
| 
 | ||||
| @ -196,12 +259,46 @@ def get_events(start, end, filters=None): | ||||
| 			if filters[key]: | ||||
| 				conditions += " and " + key + ' = "' + filters[key].replace('"', '\"') + '"' | ||||
| 
 | ||||
| 	data = frappe.db.sql("""select name,production_item, start_date,end_date from `tabProduction Order` | ||||
| 		where ((ifnull(start_date, '0000-00-00')!= '0000-00-00') \ | ||||
| 				and (start_date between %(start)s and %(end)s) \ | ||||
| 			or ((ifnull(start_date, '0000-00-00')!= '0000-00-00') \ | ||||
| 				and end_date between %(start)s and %(end)s)){conditions}""".format(conditions=conditions), { | ||||
| 	data = frappe.db.sql("""select name,production_item, production_start_date, production_end_date | ||||
| 		from `tabProduction Order` | ||||
| 		where ((ifnull(production_start_date, '0000-00-00')!= '0000-00-00') \ | ||||
| 				and (production_start_date between %(start)s and %(end)s) \ | ||||
| 			or ((ifnull(production_start_date, '0000-00-00')!= '0000-00-00') \ | ||||
| 				and production_end_date between %(start)s and %(end)s)) {conditions} | ||||
| 		""".format(conditions=conditions), { | ||||
| 			"start": start, | ||||
| 			"end": end | ||||
| 			}, as_dict=True, update={"allDay": 0}) | ||||
| 		}, as_dict=True, update={"allDay": 0}) | ||||
| 	return data | ||||
| 
 | ||||
| @frappe.whitelist() | ||||
| def make_time_log(name, operation, from_time, to_time, qty=None,  project=None, workstation=None): | ||||
| 	time_log =  frappe.new_doc("Time Log") | ||||
| 	time_log.time_log_for = 'Manufacturing' | ||||
| 	time_log.from_time = from_time | ||||
| 	time_log.to_time = to_time | ||||
| 	time_log.production_order = name | ||||
| 	time_log.project = project | ||||
| 	time_log.operation= operation | ||||
| 	time_log.workstation= workstation | ||||
| 	time_log.completed_qty = flt(qty) | ||||
| 	if from_time and to_time : | ||||
| 		time_log.calculate_total_hours() | ||||
| 	return time_log | ||||
| 
 | ||||
| @frappe.whitelist() | ||||
| def auto_make_time_log(production_order_id): | ||||
| 	if frappe.db.get_value("Time Log", filters={"production_order": production_order_id}): | ||||
| 		frappe.throw(_("Time logs already exists against this Production Order")) | ||||
| 
 | ||||
| 	time_logs = [] | ||||
| 	prod_order = frappe.get_doc("Production Order", production_order_id) | ||||
| 
 | ||||
| 	for d in prod_order.production_order_operations: | ||||
| 		operation = cstr(d.idx) + ". " + d.operation | ||||
| 		time_log = make_time_log(prod_order.name, operation, d.planned_start_time, d.planned_end_time, | ||||
| 			flt(prod_order.qty) - flt(d.completed_qty), prod_order.project_name, d.workstation) | ||||
| 		time_log.save() | ||||
| 		time_logs.append(time_log.name) | ||||
| 	if time_logs: | ||||
| 		frappe.msgprint(_("Time Logs created:") + "\n" + "\n".join(time_logs)) | ||||
|  | ||||
| @ -3,8 +3,8 @@ | ||||
| 
 | ||||
| frappe.views.calendar["Production Order"] = { | ||||
| 	field_map: { | ||||
| 		"start": "start_date", | ||||
| 		"end": "end_date", | ||||
| 		"start": "production_start_date", | ||||
| 		"end": "production_end_date", | ||||
| 		"id": "name", | ||||
| 		"title": "production_item", | ||||
| 		"allDay": "allDay" | ||||
|  | ||||
| @ -8,6 +8,7 @@ import frappe | ||||
| from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import set_perpetual_inventory | ||||
| from erpnext.manufacturing.doctype.production_order.production_order import make_stock_entry | ||||
| from erpnext.stock.doctype.stock_entry import test_stock_entry | ||||
| from erpnext.projects.doctype.time_log.time_log import OverProductionError | ||||
| 
 | ||||
| class TestProductionOrder(unittest.TestCase): | ||||
| 	def test_planned_qty(self): | ||||
| @ -58,4 +59,78 @@ class TestProductionOrder(unittest.TestCase): | ||||
| 
 | ||||
| 		self.assertRaises(StockOverProductionError, s.submit) | ||||
| 
 | ||||
| 	def test_make_time_log(self): | ||||
| 		prod_order = frappe.get_doc({ | ||||
| 			"doctype": "Production Order", | ||||
| 			"production_item": "_Test FG Item 2", | ||||
| 			"bom_no": "BOM/_Test FG Item 2/002", | ||||
| 			"qty": 1, | ||||
| 			"wip_warehouse": "_Test Warehouse - _TC", | ||||
| 			"fg_warehouse": "_Test Warehouse 1 - _TC" | ||||
| 		}) | ||||
| 
 | ||||
| 
 | ||||
| 		prod_order.set_production_order_operations() | ||||
| 		prod_order.production_order_operations[0].update({ | ||||
| 			"planned_start_time": "2014-11-25 00:00:00", | ||||
| 			"planned_end_time": "2014-11-25 10:00:00", | ||||
| 			"hour_rate": 10 | ||||
| 		}) | ||||
| 
 | ||||
| 		prod_order.insert() | ||||
| 
 | ||||
| 		d = prod_order.production_order_operations[0] | ||||
| 
 | ||||
| 		from erpnext.manufacturing.doctype.production_order.production_order import make_time_log | ||||
| 		from frappe.utils import cstr | ||||
| 		from frappe.utils import time_diff_in_hours | ||||
| 
 | ||||
| 		prod_order.submit() | ||||
| 
 | ||||
| 		time_log = make_time_log( prod_order.name, cstr(d.idx) + ". " + d.operation, \ | ||||
| 			d.planned_start_time, d.planned_end_time, prod_order.qty - d.qty_completed) | ||||
| 
 | ||||
| 		self.assertEqual(prod_order.name, time_log.production_order) | ||||
| 		self.assertEqual((prod_order.qty - d.qty_completed), time_log.qty) | ||||
| 		self.assertEqual(time_diff_in_hours(d.planned_end_time, d.planned_start_time),time_log.hours) | ||||
| 
 | ||||
| 		time_log.save() | ||||
| 		time_log.submit() | ||||
| 
 | ||||
| 		manufacturing_settings = frappe.get_doc({ | ||||
| 			"doctype": "Manufacturing Settings", | ||||
| 			"maximum_overtime": 30, | ||||
| 			"allow_production_on_holidays": 0 | ||||
| 		}) | ||||
| 
 | ||||
| 		manufacturing_settings.save() | ||||
| 
 | ||||
| 		prod_order.load_from_db() | ||||
| 		self.assertEqual(prod_order.production_order_operations[0].status, "Completed") | ||||
| 		self.assertEqual(prod_order.production_order_operations[0].qty_completed, prod_order.qty) | ||||
| 
 | ||||
| 		self.assertEqual(prod_order.production_order_operations[0].actual_start_time, time_log.from_time) | ||||
| 		self.assertEqual(prod_order.production_order_operations[0].actual_end_time, time_log.to_time) | ||||
| 
 | ||||
| 		self.assertEqual(prod_order.production_order_operations[0].actual_operation_time, 600) | ||||
| 		self.assertEqual(prod_order.production_order_operations[0].actual_operating_cost, 6000) | ||||
| 
 | ||||
| 		time_log.cancel() | ||||
| 
 | ||||
| 		prod_order.load_from_db() | ||||
| 		self.assertEqual(prod_order.production_order_operations[0].status, "Pending") | ||||
| 		self.assertEqual(prod_order.production_order_operations[0].qty_completed, 0) | ||||
| 
 | ||||
| 		self.assertEqual(prod_order.production_order_operations[0].actual_operation_time, 0) | ||||
| 		self.assertEqual(prod_order.production_order_operations[0].actual_operating_cost, 0) | ||||
| 
 | ||||
| 		time_log2 = frappe.copy_doc(time_log) | ||||
| 		time_log2.update({ | ||||
| 			"qty": 10, | ||||
| 			"from_time": "2014-11-26 00:00:00", | ||||
| 			"to_time": "2014-11-26 00:00:00", | ||||
| 			"docstatus": 0 | ||||
| 		}) | ||||
| 		self.assertRaises(OverProductionError, time_log2.save) | ||||
| 
 | ||||
| test_records = frappe.get_test_records('Production Order') | ||||
|  | ||||
| @ -0,0 +1,296 @@ | ||||
| { | ||||
|  "allow_copy": 0,  | ||||
|  "allow_import": 0,  | ||||
|  "allow_rename": 0,  | ||||
|  "creation": "2014-10-16 14:35:41.950175",  | ||||
|  "custom": 0,  | ||||
|  "docstatus": 0,  | ||||
|  "doctype": "DocType",  | ||||
|  "document_type": "",  | ||||
|  "fields": [ | ||||
|   { | ||||
|    "fieldname": "details",  | ||||
|    "fieldtype": "Section Break",  | ||||
|    "label": "Details",  | ||||
|    "permlevel": 0,  | ||||
|    "precision": "" | ||||
|   },  | ||||
|   { | ||||
|    "allow_on_submit": 0,  | ||||
|    "fieldname": "operation",  | ||||
|    "fieldtype": "Text",  | ||||
|    "hidden": 0,  | ||||
|    "ignore_user_permissions": 0,  | ||||
|    "in_filter": 0,  | ||||
|    "in_list_view": 1,  | ||||
|    "label": "Operation",  | ||||
|    "no_copy": 0,  | ||||
|    "oldfieldname": "operation_no",  | ||||
|    "oldfieldtype": "Data",  | ||||
|    "options": "",  | ||||
|    "permlevel": 0,  | ||||
|    "precision": "",  | ||||
|    "print_hide": 0,  | ||||
|    "read_only": 1,  | ||||
|    "report_hide": 0,  | ||||
|    "reqd": 1,  | ||||
|    "search_index": 0,  | ||||
|    "set_only_once": 0,  | ||||
|    "unique": 0 | ||||
|   },  | ||||
|   { | ||||
|    "allow_on_submit": 0,  | ||||
|    "fieldname": "opn_description",  | ||||
|    "fieldtype": "Text",  | ||||
|    "hidden": 0,  | ||||
|    "ignore_user_permissions": 0,  | ||||
|    "in_filter": 0,  | ||||
|    "in_list_view": 0,  | ||||
|    "label": "Operation Description",  | ||||
|    "no_copy": 0,  | ||||
|    "oldfieldname": "opn_description",  | ||||
|    "oldfieldtype": "Text",  | ||||
|    "permlevel": 0,  | ||||
|    "precision": "",  | ||||
|    "print_hide": 0,  | ||||
|    "read_only": 1,  | ||||
|    "report_hide": 0,  | ||||
|    "reqd": 1,  | ||||
|    "search_index": 0,  | ||||
|    "set_only_once": 0,  | ||||
|    "unique": 0 | ||||
|   },  | ||||
|   { | ||||
|    "allow_on_submit": 0,  | ||||
|    "fieldname": "col_break1",  | ||||
|    "fieldtype": "Column Break",  | ||||
|    "hidden": 0,  | ||||
|    "ignore_user_permissions": 0,  | ||||
|    "in_filter": 0,  | ||||
|    "in_list_view": 0,  | ||||
|    "no_copy": 0,  | ||||
|    "permlevel": 0,  | ||||
|    "precision": "",  | ||||
|    "print_hide": 0,  | ||||
|    "read_only": 0,  | ||||
|    "report_hide": 0,  | ||||
|    "reqd": 0,  | ||||
|    "search_index": 0,  | ||||
|    "set_only_once": 0,  | ||||
|    "unique": 0 | ||||
|   },  | ||||
|   { | ||||
|    "description": "Operation completed for how many finished goods?",  | ||||
|    "fieldname": "completed_qty",  | ||||
|    "fieldtype": "Float",  | ||||
|    "label": "Completed Qty",  | ||||
|    "no_copy": 1,  | ||||
|    "permlevel": 0,  | ||||
|    "precision": "",  | ||||
|    "read_only": 1 | ||||
|   },  | ||||
|   { | ||||
|    "default": "Pending",  | ||||
|    "fieldname": "status",  | ||||
|    "fieldtype": "Select",  | ||||
|    "in_list_view": 1,  | ||||
|    "label": "Status",  | ||||
|    "no_copy": 1,  | ||||
|    "options": "Pending\nWork in Progress\nCompleted",  | ||||
|    "permlevel": 0,  | ||||
|    "precision": "",  | ||||
|    "read_only": 1 | ||||
|   },  | ||||
|   { | ||||
|    "allow_on_submit": 0,  | ||||
|    "fieldname": "workstation",  | ||||
|    "fieldtype": "Link",  | ||||
|    "hidden": 0,  | ||||
|    "ignore_user_permissions": 0,  | ||||
|    "in_filter": 0,  | ||||
|    "in_list_view": 1,  | ||||
|    "label": "Workstation",  | ||||
|    "no_copy": 0,  | ||||
|    "oldfieldname": "workstation",  | ||||
|    "oldfieldtype": "Link",  | ||||
|    "options": "Workstation",  | ||||
|    "permlevel": 0,  | ||||
|    "precision": "",  | ||||
|    "print_hide": 0,  | ||||
|    "read_only": 1,  | ||||
|    "report_hide": 0,  | ||||
|    "reqd": 0,  | ||||
|    "search_index": 0,  | ||||
|    "set_only_once": 0,  | ||||
|    "unique": 0 | ||||
|   },  | ||||
|   { | ||||
|    "fieldname": "estimated_time_and_cost",  | ||||
|    "fieldtype": "Section Break",  | ||||
|    "label": "Estimated Time and Cost",  | ||||
|    "permlevel": 0,  | ||||
|    "precision": "" | ||||
|   },  | ||||
|   { | ||||
|    "fieldname": "planned_start_time",  | ||||
|    "fieldtype": "Datetime",  | ||||
|    "in_list_view": 0,  | ||||
|    "label": "Planned Start Time",  | ||||
|    "no_copy": 1,  | ||||
|    "permlevel": 0,  | ||||
|    "precision": "",  | ||||
|    "reqd": 1 | ||||
|   },  | ||||
|   { | ||||
|    "fieldname": "planned_end_time",  | ||||
|    "fieldtype": "Datetime",  | ||||
|    "in_list_view": 0,  | ||||
|    "label": "Planned End Time",  | ||||
|    "no_copy": 1,  | ||||
|    "permlevel": 0,  | ||||
|    "precision": "",  | ||||
|    "reqd": 1 | ||||
|   },  | ||||
|   { | ||||
|    "fieldname": "column_break_10",  | ||||
|    "fieldtype": "Column Break",  | ||||
|    "permlevel": 0,  | ||||
|    "precision": "" | ||||
|   },  | ||||
|   { | ||||
|    "allow_on_submit": 0,  | ||||
|    "description": "in Minutes",  | ||||
|    "fieldname": "time_in_mins",  | ||||
|    "fieldtype": "Float",  | ||||
|    "hidden": 0,  | ||||
|    "ignore_user_permissions": 0,  | ||||
|    "in_filter": 0,  | ||||
|    "in_list_view": 1,  | ||||
|    "label": "Operation Time",  | ||||
|    "no_copy": 0,  | ||||
|    "oldfieldname": "time_in_mins",  | ||||
|    "oldfieldtype": "Currency",  | ||||
|    "permlevel": 0,  | ||||
|    "precision": "",  | ||||
|    "print_hide": 0,  | ||||
|    "read_only": 1,  | ||||
|    "report_hide": 0,  | ||||
|    "reqd": 0,  | ||||
|    "search_index": 0,  | ||||
|    "set_only_once": 0,  | ||||
|    "unique": 0 | ||||
|   },  | ||||
|   { | ||||
|    "allow_on_submit": 0,  | ||||
|    "fieldname": "hour_rate",  | ||||
|    "fieldtype": "Float",  | ||||
|    "hidden": 0,  | ||||
|    "ignore_user_permissions": 0,  | ||||
|    "in_filter": 0,  | ||||
|    "in_list_view": 0,  | ||||
|    "label": "Hour Rate",  | ||||
|    "no_copy": 0,  | ||||
|    "oldfieldname": "hour_rate",  | ||||
|    "oldfieldtype": "Currency",  | ||||
|    "permlevel": 0,  | ||||
|    "precision": "",  | ||||
|    "print_hide": 0,  | ||||
|    "read_only": 1,  | ||||
|    "report_hide": 0,  | ||||
|    "reqd": 0,  | ||||
|    "search_index": 0,  | ||||
|    "set_only_once": 0,  | ||||
|    "unique": 0 | ||||
|   },  | ||||
|   { | ||||
|    "fieldname": "planned_operating_cost",  | ||||
|    "fieldtype": "Currency",  | ||||
|    "label": "Planned Operating Cost",  | ||||
|    "no_copy": 0,  | ||||
|    "options": "Company:company:default_currency",  | ||||
|    "permlevel": 0,  | ||||
|    "precision": "",  | ||||
|    "read_only": 1 | ||||
|   },  | ||||
|   { | ||||
|    "fieldname": "section_break_9",  | ||||
|    "fieldtype": "Section Break",  | ||||
|    "label": "Actual Time and Cost",  | ||||
|    "permlevel": 0,  | ||||
|    "precision": "" | ||||
|   },  | ||||
|   { | ||||
|    "fieldname": "actual_start_time",  | ||||
|    "fieldtype": "Datetime",  | ||||
|    "label": "Actual Start Time",  | ||||
|    "no_copy": 1,  | ||||
|    "permlevel": 0,  | ||||
|    "precision": "",  | ||||
|    "read_only": 1 | ||||
|   },  | ||||
|   { | ||||
|    "description": "Updated via 'Time Log'",  | ||||
|    "fieldname": "actual_end_time",  | ||||
|    "fieldtype": "Datetime",  | ||||
|    "label": "Actual End Time",  | ||||
|    "no_copy": 1,  | ||||
|    "permlevel": 0,  | ||||
|    "precision": "",  | ||||
|    "read_only": 1 | ||||
|   },  | ||||
|   { | ||||
|    "fieldname": "column_break_11",  | ||||
|    "fieldtype": "Column Break",  | ||||
|    "permlevel": 0,  | ||||
|    "precision": "" | ||||
|   },  | ||||
|   { | ||||
|    "description": "in Minutes\nUpdated via 'Time Log'",  | ||||
|    "fieldname": "actual_operation_time",  | ||||
|    "fieldtype": "Float",  | ||||
|    "label": "Actual Operation Time",  | ||||
|    "no_copy": 1,  | ||||
|    "permlevel": 0,  | ||||
|    "precision": "",  | ||||
|    "read_only": 1 | ||||
|   },  | ||||
|   { | ||||
|    "description": "Hour Rate * Actual Operating Cost",  | ||||
|    "fieldname": "actual_operating_cost",  | ||||
|    "fieldtype": "Currency",  | ||||
|    "label": "Actual Operating Cost",  | ||||
|    "no_copy": 1,  | ||||
|    "options": "Company:company:default_currency",  | ||||
|    "permlevel": 0,  | ||||
|    "precision": "",  | ||||
|    "read_only": 1 | ||||
|   },  | ||||
|   { | ||||
|    "allow_on_submit": 1,  | ||||
|    "depends_on": "eval:(doc.docstatus==1 && doc.status!=\"Completed\")",  | ||||
|    "fieldname": "make_time_log",  | ||||
|    "fieldtype": "Button",  | ||||
|    "label": "Make Time Log",  | ||||
|    "permlevel": 0,  | ||||
|    "precision": "" | ||||
|   } | ||||
|  ],  | ||||
|  "hide_heading": 0,  | ||||
|  "hide_toolbar": 0,  | ||||
|  "in_create": 0,  | ||||
|  "in_dialog": 0,  | ||||
|  "is_submittable": 0,  | ||||
|  "issingle": 0,  | ||||
|  "istable": 1,  | ||||
|  "modified": "2014-12-23 15:42:34.892964",  | ||||
|  "modified_by": "Administrator",  | ||||
|  "module": "Manufacturing",  | ||||
|  "name": "Production Order Operation",  | ||||
|  "name_case": "",  | ||||
|  "owner": "Administrator",  | ||||
|  "permissions": [],  | ||||
|  "read_only": 0,  | ||||
|  "read_only_onload": 0,  | ||||
|  "sort_field": "modified",  | ||||
|  "sort_order": "DESC" | ||||
| } | ||||
| @ -0,0 +1,9 @@ | ||||
| # Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors and contributors | ||||
| # For license information, please see license.txt | ||||
| 
 | ||||
| from __future__ import unicode_literals | ||||
| import frappe | ||||
| from frappe.model.document import Document | ||||
| 
 | ||||
| class ProductionOrderOperation(Document): | ||||
| 	pass | ||||
| @ -4,7 +4,13 @@ | ||||
| 		"name": "_Test Workstation 1", | ||||
| 		"workstation_name": "_Test Workstation 1", | ||||
| 		"warehouse": "_Test warehouse - _TC", | ||||
| 		"fixed_cycle_cost": 1000, | ||||
| 		"hour_rate":100 | ||||
| 		"hour_rate":100, | ||||
| 		"holiday_list": "_Test Holiday List", | ||||
| 		"workstation_operation_hours": [ | ||||
| 			{ | ||||
| 				"start_time": "10:00:00", | ||||
| 				"end_time": "20:00:00" | ||||
| 			} | ||||
| 		] | ||||
| 	} | ||||
| ] | ||||
|  | ||||
| @ -7,6 +7,9 @@ import unittest | ||||
| test_dependencies = ["Warehouse"] | ||||
| test_records = frappe.get_test_records('Workstation') | ||||
| 
 | ||||
| 
 | ||||
| class TestWorkstation(unittest.TestCase): | ||||
| 	pass | ||||
| 
 | ||||
| 	def test_validate_timings(self): | ||||
| 		wks = frappe.get_doc("Workstation", "_Test Workstation 1") | ||||
| 		self.assertEqual(1,wks.check_workstation_for_operation_time("2013-02-01 05:00:00", "2013-02-02 20:00:00")) | ||||
| 		self.assertEqual(None,wks.check_workstation_for_operation_time("2013-02-03 10:00:00", "2013-02-03 20:00:00")) | ||||
|  | ||||
| @ -5,9 +5,13 @@ | ||||
| 
 | ||||
| //--------- ONLOAD -------------
 | ||||
| cur_frm.cscript.onload = function(doc, cdt, cdn) { | ||||
|     | ||||
| } | ||||
| 
 | ||||
| cur_frm.cscript.refresh = function(doc, cdt, cdn) { | ||||
|     | ||||
|    frappe.call({ | ||||
| 	   type:"GET", | ||||
| 	   method:"erpnext.manufacturing.doctype.workstation.workstation.get_default_holiday_list", | ||||
| 	   callback: function(r) { | ||||
| 		   if(!r.exe && r.message){ | ||||
| 			   cur_frm.set_value("holiday_list", r.message); | ||||
| 		   } | ||||
| 	   } | ||||
|    }) | ||||
| } | ||||
| @ -7,6 +7,13 @@ | ||||
|  "doctype": "DocType",  | ||||
|  "document_type": "Master",  | ||||
|  "fields": [ | ||||
|   { | ||||
|    "fieldname": "description_and_warehouse",  | ||||
|    "fieldtype": "Section Break",  | ||||
|    "label": "Description and Warehouse",  | ||||
|    "permlevel": 0,  | ||||
|    "precision": "" | ||||
|   },  | ||||
|   { | ||||
|    "fieldname": "workstation_name",  | ||||
|    "fieldtype": "Data",  | ||||
| @ -17,17 +24,6 @@ | ||||
|    "permlevel": 0,  | ||||
|    "reqd": 1 | ||||
|   },  | ||||
|   { | ||||
|    "fieldname": "warehouse", | ||||
|    "fieldtype": "Link", | ||||
|    "in_list_view": 1, | ||||
|    "label": "Warehouse", | ||||
|    "oldfieldname": "warehouse", | ||||
|    "oldfieldtype": "Link", | ||||
|    "options": "Warehouse", | ||||
|    "permlevel": 0, | ||||
|    "reqd": 1 | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "description",  | ||||
|    "fieldtype": "Text",  | ||||
| @ -39,52 +35,40 @@ | ||||
|    "width": "300px" | ||||
|   },  | ||||
|   { | ||||
|    "fieldname": "capacity", | ||||
|    "fieldtype": "Data", | ||||
|    "hidden": 1, | ||||
|    "fieldname": "column_break_4",  | ||||
|    "fieldtype": "Column Break",  | ||||
|    "permlevel": 0,  | ||||
|    "precision": "" | ||||
|   },  | ||||
|   { | ||||
|    "fieldname": "warehouse",  | ||||
|    "fieldtype": "Link",  | ||||
|    "in_list_view": 1,  | ||||
|    "label": "Capacity", | ||||
|    "oldfieldname": "capacity", | ||||
|    "oldfieldtype": "Data", | ||||
|    "label": "Warehouse",  | ||||
|    "oldfieldname": "warehouse",  | ||||
|    "oldfieldtype": "Link",  | ||||
|    "options": "Warehouse",  | ||||
|    "permlevel": 0,  | ||||
|    "reqd": 0 | ||||
|    "reqd": 1 | ||||
|   },  | ||||
|   { | ||||
|    "fieldname": "capacity_units", | ||||
|    "fieldtype": "Select", | ||||
|    "hidden": 1, | ||||
|    "in_list_view": 1, | ||||
|    "label": "Capacity Units", | ||||
|    "oldfieldname": "capacity_units", | ||||
|    "oldfieldtype": "Select", | ||||
|    "options": "\nUnits/Shifts\nUnits/Hour", | ||||
|    "default": "",  | ||||
|    "fieldname": "holiday_list",  | ||||
|    "fieldtype": "Link",  | ||||
|    "label": "Holiday List",  | ||||
|    "options": "Holiday List",  | ||||
|    "permlevel": 0,  | ||||
|    "reqd": 0 | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "fixed_cycle_cost", | ||||
|    "fieldtype": "Float", | ||||
|    "label": "Fixed Cycle Cost", | ||||
|    "permlevel": 0 | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "hour_rate_labour", | ||||
|    "fieldtype": "Float", | ||||
|    "label": "Hour Rate Labour", | ||||
|    "oldfieldname": "hour_rate_labour", | ||||
|    "oldfieldtype": "Currency", | ||||
|    "permlevel": 0, | ||||
|    "reqd": 0 | ||||
|    "precision": "" | ||||
|   },  | ||||
|   { | ||||
|    "fieldname": "over_heads",  | ||||
|    "fieldtype": "Section Break",  | ||||
|    "label": "Overheads", | ||||
|    "label": "Operating Costs",  | ||||
|    "oldfieldtype": "Section Break",  | ||||
|    "permlevel": 0 | ||||
|   },  | ||||
|   { | ||||
|    "description": "Electricity cost per hour", | ||||
|    "description": "per hour",  | ||||
|    "fieldname": "hour_rate_electricity",  | ||||
|    "fieldtype": "Float",  | ||||
|    "label": "Electricity Cost",  | ||||
| @ -93,7 +77,7 @@ | ||||
|    "permlevel": 0 | ||||
|   },  | ||||
|   { | ||||
|    "description": "Consumable cost per hour", | ||||
|    "description": "per hour",  | ||||
|    "fieldname": "hour_rate_consumable",  | ||||
|    "fieldtype": "Float",  | ||||
|    "label": "Consumable Cost",  | ||||
| @ -102,7 +86,13 @@ | ||||
|    "permlevel": 0 | ||||
|   },  | ||||
|   { | ||||
|    "description": "Rent per hour", | ||||
|    "fieldname": "column_break_11",  | ||||
|    "fieldtype": "Column Break",  | ||||
|    "permlevel": 0,  | ||||
|    "precision": "" | ||||
|   },  | ||||
|   { | ||||
|    "description": "per hour",  | ||||
|    "fieldname": "hour_rate_rent",  | ||||
|    "fieldtype": "Float",  | ||||
|    "label": "Rent Cost",  | ||||
| @ -111,34 +101,44 @@ | ||||
|    "permlevel": 0 | ||||
|   },  | ||||
|   { | ||||
|    "fieldname": "overhead", | ||||
|    "description": "Wages per hour",  | ||||
|    "fieldname": "hour_rate_labour",  | ||||
|    "fieldtype": "Float",  | ||||
|    "label": "Overhead", | ||||
|    "oldfieldname": "overhead", | ||||
|    "label": "Wages",  | ||||
|    "oldfieldname": "hour_rate_labour",  | ||||
|    "oldfieldtype": "Currency",  | ||||
|    "permlevel": 0,  | ||||
|    "read_only": 1 | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "hour_rate_section_break", | ||||
|    "fieldtype": "Section Break", | ||||
|    "label": "Hour Rate", | ||||
|    "oldfieldtype": "Section Break", | ||||
|    "permlevel": 0 | ||||
|    "reqd": 0 | ||||
|   },  | ||||
|   { | ||||
|    "description": "per hour",  | ||||
|    "fieldname": "hour_rate",  | ||||
|    "fieldtype": "Float",  | ||||
|    "label": "Hour Rate", | ||||
|    "label": "Net Hour Rate",  | ||||
|    "oldfieldname": "hour_rate",  | ||||
|    "oldfieldtype": "Currency",  | ||||
|    "permlevel": 0,  | ||||
|    "read_only": 1 | ||||
|   },  | ||||
|   { | ||||
|    "fieldname": "operation_timings",  | ||||
|    "fieldtype": "Section Break",  | ||||
|    "label": "Operation Timings",  | ||||
|    "permlevel": 0,  | ||||
|    "precision": "" | ||||
|   },  | ||||
|   { | ||||
|    "fieldname": "workstation_operation_hours",  | ||||
|    "fieldtype": "Table",  | ||||
|    "label": "Workstation Operation Hours",  | ||||
|    "options": "Workstation Operation Hour",  | ||||
|    "permlevel": 0,  | ||||
|    "precision": "" | ||||
|   } | ||||
|  ],  | ||||
|  "icon": "icon-wrench",  | ||||
|  "idx": 1,  | ||||
|  "modified": "2014-09-15 10:59:07.960814", | ||||
|  "modified": "2014-12-23 15:27:58.477925",  | ||||
|  "modified_by": "Administrator",  | ||||
|  "module": "Manufacturing",  | ||||
|  "name": "Workstation",  | ||||
|  | ||||
| @ -3,10 +3,16 @@ | ||||
| 
 | ||||
| from __future__ import unicode_literals | ||||
| import frappe | ||||
| from frappe.utils import flt | ||||
| import datetime | ||||
| from frappe import _ | ||||
| from frappe.utils import flt, cint, getdate, formatdate, comma_and | ||||
| 
 | ||||
| from frappe.model.document import Document | ||||
| 
 | ||||
| class WorkstationHolidayError(frappe.ValidationError): pass | ||||
| class WorkstationIsClosedError(frappe.ValidationError): pass | ||||
| class OverlapError(frappe.ValidationError): pass | ||||
| 
 | ||||
| class Workstation(Document): | ||||
| 	def update_bom_operation(self): | ||||
| 		bom_list = frappe.db.sql("""select DISTINCT parent from `tabBOM Operation` | ||||
| @ -17,7 +23,57 @@ class Workstation(Document): | ||||
| 				(self.hour_rate, bom_no[0], self.name)) | ||||
| 
 | ||||
| 	def on_update(self): | ||||
| 		frappe.db.set(self, 'overhead', flt(self.hour_rate_electricity) +  | ||||
| 		flt(self.hour_rate_consumable) + flt(self.hour_rate_rent)) | ||||
| 		frappe.db.set(self, 'hour_rate', flt(self.hour_rate_labour) + flt(self.overhead)) | ||||
| 		self.validate_overlap_for_operation_timings() | ||||
| 
 | ||||
| 		frappe.db.set(self, 'hour_rate', flt(self.hour_rate_labour) + flt(self.hour_rate_electricity) + | ||||
| 			flt(self.hour_rate_consumable) + flt(self.hour_rate_rent)) | ||||
| 
 | ||||
| 		self.update_bom_operation() | ||||
| 
 | ||||
| 	def validate_overlap_for_operation_timings(self): | ||||
| 		for d in self.get("workstation_operation_hours"): | ||||
| 			existing = frappe.db.sql_list("""select idx from `tabWorkstation Operation Hours` | ||||
| 				where parent = %s and name != %s | ||||
| 					and ( | ||||
| 						(start_time between %s and %s) or | ||||
| 						(end_time between %s and %s) or | ||||
| 						(%s between start_time and end_time)) | ||||
| 				""", (self.name, d.name, d.start_time, d.end_time, d.start_time, d.end_time, d.start_time)) | ||||
| 
 | ||||
| 			if existing: | ||||
| 				frappe.throw(_("Row #{0}: Timings conflicts with row {1}").format(d.idx, comma_and(existing)), OverlapError) | ||||
| 
 | ||||
| @frappe.whitelist() | ||||
| def get_default_holiday_list(): | ||||
| 	return frappe.db.get_value("Company", frappe.defaults.get_user_default("company"), "default_holiday_list") | ||||
| 
 | ||||
| def check_if_within_operating_hours(workstation, from_datetime, to_datetime): | ||||
| 	if not is_within_operating_hours(workstation, from_datetime, to_datetime): | ||||
| 		frappe.throw(_("Time Log timings outside workstation operating hours"), WorkstationIsClosedError) | ||||
| 
 | ||||
| 	if not cint(frappe.db.get_value("Manufacturing Settings", "None", "allow_production_on_holidays")): | ||||
| 		check_workstation_for_holiday(workstation, from_datetime, to_datetime) | ||||
| 
 | ||||
| def is_within_operating_hours(workstation, from_datetime, to_datetime): | ||||
| 	if not cint(frappe.db.get_value("Manufacturing Settings", None, "dont_allow_overtime")): | ||||
| 		return True | ||||
| 
 | ||||
| 	start_time = datetime.datetime.strptime(from_datetime,'%Y-%m-%d %H:%M:%S').strftime('%H:%M:%S') | ||||
| 	end_time = datetime.datetime.strptime(to_datetime,'%Y-%m-%d %H:%M:%S').strftime('%H:%M:%S') | ||||
| 
 | ||||
| 	for d in frappe.db.sql("""select start_time, end_time from `tabWorkstation Operation Hours` | ||||
| 		where parent = %s and ifnull(enabled, 0) = 1""", workstation, as_dict=1): | ||||
| 			if d.end_time >= start_time >= d.start_time and d.end_time >= end_time >= d.start_time: | ||||
| 				return True | ||||
| 
 | ||||
| def check_workstation_for_holiday(workstation, from_datetime, to_datetime): | ||||
| 	holiday_list = frappe.db.get_value("Workstation", workstation, "holiday_list") | ||||
| 	if holiday_list: | ||||
| 		applicable_holidays = [] | ||||
| 		for d in frappe.db.sql("""select holiday_date from `tabHoliday` where parent = %s | ||||
| 			and holiday_date between %s and %s """, (holiday_list, getdate(from_datetime), getdate(to_datetime))): | ||||
| 				applicable_holidays.append(formatdate(d[0])) | ||||
| 
 | ||||
| 		if applicable_holidays: | ||||
| 			frappe.throw(_("Workstation is closed on the following dates as per Holiday List: {0}") | ||||
| 				.format(holiday_list) + "\n" + "\n".join(applicable_holidays), WorkstationHolidayError) | ||||
|  | ||||
| @ -0,0 +1,129 @@ | ||||
| { | ||||
|  "allow_copy": 0,  | ||||
|  "allow_import": 0,  | ||||
|  "allow_rename": 0,  | ||||
|  "creation": "2014-12-22 14:18:20.786493",  | ||||
|  "custom": 0,  | ||||
|  "docstatus": 0,  | ||||
|  "doctype": "DocType",  | ||||
|  "document_type": "",  | ||||
|  "fields": [ | ||||
|   { | ||||
|    "allow_on_submit": 0,  | ||||
|    "fieldname": "start_time",  | ||||
|    "fieldtype": "Time",  | ||||
|    "hidden": 0,  | ||||
|    "ignore_user_permissions": 0,  | ||||
|    "in_filter": 0,  | ||||
|    "in_list_view": 1,  | ||||
|    "label": "Start Time",  | ||||
|    "no_copy": 0,  | ||||
|    "permlevel": 0,  | ||||
|    "precision": "",  | ||||
|    "print_hide": 0,  | ||||
|    "read_only": 0,  | ||||
|    "report_hide": 0,  | ||||
|    "reqd": 0,  | ||||
|    "search_index": 0,  | ||||
|    "set_only_once": 0,  | ||||
|    "unique": 0 | ||||
|   },  | ||||
|   { | ||||
|    "allow_on_submit": 0,  | ||||
|    "fieldname": "column_break_2",  | ||||
|    "fieldtype": "Column Break",  | ||||
|    "hidden": 0,  | ||||
|    "ignore_user_permissions": 0,  | ||||
|    "in_filter": 0,  | ||||
|    "in_list_view": 0,  | ||||
|    "no_copy": 0,  | ||||
|    "permlevel": 0,  | ||||
|    "precision": "",  | ||||
|    "print_hide": 0,  | ||||
|    "read_only": 0,  | ||||
|    "report_hide": 0,  | ||||
|    "reqd": 0,  | ||||
|    "search_index": 0,  | ||||
|    "set_only_once": 0,  | ||||
|    "unique": 0 | ||||
|   },  | ||||
|   { | ||||
|    "allow_on_submit": 0,  | ||||
|    "fieldname": "end_time",  | ||||
|    "fieldtype": "Time",  | ||||
|    "hidden": 0,  | ||||
|    "ignore_user_permissions": 0,  | ||||
|    "in_filter": 0,  | ||||
|    "in_list_view": 1,  | ||||
|    "label": "End Time",  | ||||
|    "no_copy": 0,  | ||||
|    "permlevel": 0,  | ||||
|    "precision": "",  | ||||
|    "print_hide": 0,  | ||||
|    "read_only": 0,  | ||||
|    "report_hide": 0,  | ||||
|    "reqd": 0,  | ||||
|    "search_index": 0,  | ||||
|    "set_only_once": 0,  | ||||
|    "unique": 0 | ||||
|   },  | ||||
|   { | ||||
|    "allow_on_submit": 0,  | ||||
|    "fieldname": "section_break_2",  | ||||
|    "fieldtype": "Column Break",  | ||||
|    "hidden": 0,  | ||||
|    "ignore_user_permissions": 0,  | ||||
|    "in_filter": 0,  | ||||
|    "in_list_view": 0,  | ||||
|    "no_copy": 0,  | ||||
|    "permlevel": 0,  | ||||
|    "precision": "",  | ||||
|    "print_hide": 0,  | ||||
|    "read_only": 0,  | ||||
|    "report_hide": 0,  | ||||
|    "reqd": 0,  | ||||
|    "search_index": 0,  | ||||
|    "set_only_once": 0,  | ||||
|    "unique": 0 | ||||
|   },  | ||||
|   { | ||||
|    "allow_on_submit": 0,  | ||||
|    "default": "1",  | ||||
|    "fieldname": "enabled",  | ||||
|    "fieldtype": "Check",  | ||||
|    "hidden": 0,  | ||||
|    "ignore_user_permissions": 0,  | ||||
|    "in_filter": 0,  | ||||
|    "in_list_view": 1,  | ||||
|    "label": "Enabled",  | ||||
|    "no_copy": 0,  | ||||
|    "permlevel": 0,  | ||||
|    "precision": "",  | ||||
|    "print_hide": 0,  | ||||
|    "read_only": 0,  | ||||
|    "report_hide": 0,  | ||||
|    "reqd": 0,  | ||||
|    "search_index": 0,  | ||||
|    "set_only_once": 0,  | ||||
|    "unique": 0 | ||||
|   } | ||||
|  ],  | ||||
|  "hide_heading": 0,  | ||||
|  "hide_toolbar": 0,  | ||||
|  "in_create": 0,  | ||||
|  "in_dialog": 0,  | ||||
|  "is_submittable": 0,  | ||||
|  "issingle": 0,  | ||||
|  "istable": 1,  | ||||
|  "modified": "2014-12-22 14:18:31.091653",  | ||||
|  "modified_by": "Administrator",  | ||||
|  "module": "Manufacturing",  | ||||
|  "name": "Workstation Operation Hour",  | ||||
|  "name_case": "",  | ||||
|  "owner": "Administrator",  | ||||
|  "permissions": [],  | ||||
|  "read_only": 0,  | ||||
|  "read_only_onload": 0,  | ||||
|  "sort_field": "modified",  | ||||
|  "sort_order": "DESC" | ||||
| } | ||||
| @ -0,0 +1,9 @@ | ||||
| # Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors and contributors | ||||
| # For license information, please see license.txt | ||||
| 
 | ||||
| from __future__ import unicode_literals | ||||
| import frappe | ||||
| from frappe.model.document import Document | ||||
| 
 | ||||
| class WorkstationOperationHour(Document): | ||||
| 	pass | ||||
| @ -80,7 +80,6 @@ execute:frappe.delete_doc("DocType", "Landed Cost Wizard") | ||||
| erpnext.patches.v4_2.default_website_style | ||||
| erpnext.patches.v4_2.set_company_country | ||||
| erpnext.patches.v4_2.update_sales_order_invoice_field_name | ||||
| erpnext.patches.v4_2.cost_of_production_cycle | ||||
| erpnext.patches.v4_2.seprate_manufacture_and_repack | ||||
| execute:frappe.delete_doc("Report", "Warehouse-Wise Stock Balance") | ||||
| execute:frappe.delete_doc("DocType", "Purchase Request") | ||||
| @ -98,3 +97,4 @@ execute:frappe.reload_doc('stock', 'doctype', 'item') | ||||
| execute:frappe.db.sql("update `tabItem` i set apply_warehouse_wise_reorder_level=1, re_order_level=0, re_order_qty=0 where exists(select name from `tabItem Reorder` where parent=i.name)") | ||||
| execute:frappe.rename_doc("DocType", "Support Ticket", "Issue", force=True) | ||||
| erpnext.patches.v5_0.set_default_company_in_bom | ||||
| erpnext.patches.v5_0.capacity_planning | ||||
|  | ||||
| @ -1,9 +0,0 @@ | ||||
| # Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors | ||||
| # License: GNU General Public License v3. See license.txt | ||||
| 
 | ||||
| from __future__ import unicode_literals | ||||
| import frappe | ||||
| 
 | ||||
| def execute(): | ||||
| 	frappe.reload_doc("manufacturing", "doctype", "bom") | ||||
| 	frappe.db.sql("""update tabBOM set total_variable_cost = total_cost""") | ||||
							
								
								
									
										8
									
								
								erpnext/patches/v5_0/capacity_planning.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								erpnext/patches/v5_0/capacity_planning.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,8 @@ | ||||
| # Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors | ||||
| # License: GNU General Public License v3. See license.txt | ||||
| 
 | ||||
| import frappe | ||||
| 
 | ||||
| def execute(): | ||||
| 	frappe.reload_doc("stock", "doctype", "stock_entry") | ||||
| 	frappe.db.sql("update tabBOM set additional_operating_cost = total_fixed_cost") | ||||
| @ -1,10 +1,16 @@ | ||||
| # Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors | ||||
| # License: GNU General Public License v3. See license.txt | ||||
| 
 | ||||
| from __future__ import unicode_literals | ||||
| import frappe | ||||
| import unittest | ||||
| 
 | ||||
| from erpnext.projects.doctype.time_log.time_log import OverlapError | ||||
| from erpnext.projects.doctype.time_log.time_log import NotSubmittedError | ||||
| 
 | ||||
| from erpnext.manufacturing.doctype.workstation.workstation import WorkstationHolidayError | ||||
| from erpnext.manufacturing.doctype.workstation.workstation import WorkstationIsClosedError | ||||
| 
 | ||||
| from erpnext.projects.doctype.time_log_batch.test_time_log_batch import * | ||||
| 
 | ||||
| class TestTimeLog(unittest.TestCase): | ||||
| @ -17,5 +23,59 @@ class TestTimeLog(unittest.TestCase): | ||||
| 
 | ||||
| 		frappe.db.sql("delete from `tabTime Log`") | ||||
| 		 | ||||
| 	def test_production_order_status(self): | ||||
| 		prod_order = make_prod_order(self) | ||||
| 		 | ||||
| 		prod_order.save() | ||||
| 		 | ||||
| 		time_log = frappe.get_doc({ | ||||
| 			"doctype": "Time Log", | ||||
| 			"time_log_for": "Manufacturing", | ||||
| 			"production_order": prod_order.name, | ||||
| 			"qty": 1, | ||||
| 			"from_time": "2014-12-26 00:00:00", | ||||
| 			"to_time": "2014-12-26 00:00:00" | ||||
| 		}) | ||||
| 		 | ||||
| 		self.assertRaises(NotSubmittedError, time_log.save) | ||||
| 		 | ||||
| 	def test_time_log_on_holiday(self):	 | ||||
| 		prod_order = make_prod_order(self) | ||||
| 		 | ||||
| 		prod_order.save() | ||||
| 		prod_order.submit() | ||||
| 		 | ||||
| 		time_log = frappe.get_doc({ | ||||
| 			"doctype": "Time Log", | ||||
| 			"time_log_for": "Manufacturing", | ||||
| 			"production_order": prod_order.name, | ||||
| 			"qty": 1, | ||||
| 			"from_time": "2013-02-01 10:00:00", | ||||
| 			"to_time": "2013-02-01 20:00:00", | ||||
| 			"workstation": "_Test Workstation 1" | ||||
| 		}) | ||||
| 		self.assertRaises(WorkstationHolidayError , time_log.save) | ||||
| 		 | ||||
| 		time_log.update({ | ||||
| 			"from_time": "2013-02-02 09:00:00", | ||||
| 			"to_time": "2013-02-02 20:00:00" | ||||
| 		}) | ||||
| 		self.assertRaises(WorkstationIsClosedError , time_log.save) | ||||
| 		 | ||||
| 		time_log.from_time= "2013-02-02 09:30:00" | ||||
| 		time_log.save() | ||||
| 		time_log.submit() | ||||
| 		time_log.cancel() | ||||
| 		 | ||||
| def make_prod_order(self): | ||||
| 	return frappe.get_doc({ | ||||
| 			"doctype":"Production Order", | ||||
| 			"production_item": "_Test FG Item 2", | ||||
| 			"bom_no": "BOM/_Test FG Item 2/002", | ||||
| 			"qty": 1, | ||||
| 			"wip_warehouse": "_Test Warehouse - _TC", | ||||
| 			"fg_warehouse": "_Test Warehouse 1 - _TC" | ||||
| 		}) | ||||
| 		 | ||||
| test_records = frappe.get_test_records('Time Log') | ||||
| test_ignore = ["Time Log Batch", "Sales Invoice"] | ||||
|  | ||||
| @ -5,6 +5,16 @@ frappe.provide("erpnext.projects"); | ||||
| 
 | ||||
| frappe.ui.form.on("Time Log", "onload", function(frm) { | ||||
| 	frm.set_query("task", erpnext.queries.task); | ||||
| 	if (frm.doc.time_log_for == "Manufacturing") { | ||||
| 		frappe.ui.form.trigger("Time Log", "production_order"); | ||||
| 	} | ||||
| }); | ||||
| 
 | ||||
| frappe.ui.form.on("Time Log", "refresh", function(frm) { | ||||
| 	var is_manufacturing = frm.doc.time_log_for=="Manufacturing" ? true : false; | ||||
| 	frm.toggle_reqd("production_order", is_manufacturing); | ||||
| 	frm.toggle_reqd("operation", is_manufacturing); | ||||
| 	frm.toggle_reqd("completed_qty", is_manufacturing); | ||||
| }); | ||||
| 
 | ||||
| // set to time if hours is updated
 | ||||
| @ -26,4 +36,44 @@ frappe.ui.form.on("Time Log", "to_time", function(frm) { | ||||
| 		"hours")); | ||||
| }); | ||||
| 
 | ||||
| cur_frm.set_query("production_order", function(doc) { | ||||
| 	return { | ||||
| 		"filters": { | ||||
| 			"docstatus": 1 | ||||
| 		} | ||||
| 	}; | ||||
| }); | ||||
| 
 | ||||
| cur_frm.add_fetch('task','project','project'); | ||||
| 
 | ||||
| $.extend(cur_frm.cscript, { | ||||
| 	production_order: function(doc) { | ||||
| 		if (doc.production_order){ | ||||
| 			var operations = []; | ||||
| 			frappe.model.with_doc("Production Order", doc.production_order, function(pro) { | ||||
| 				doc = frappe.get_doc("Production Order",pro); | ||||
| 				$.each(doc.production_order_operations , function(i, row){ | ||||
| 					operations[i] = (i+1) +". "+ row.operation; | ||||
| 				}); | ||||
| 			frappe.meta.get_docfield("Time Log", "operation", me.frm.doc.name).options = "\n" + operations.join("\n"); | ||||
| 			refresh_field("operation"); | ||||
| 			}) | ||||
| 		} | ||||
| 	}, | ||||
| 
 | ||||
| 	operation: function(doc) { | ||||
| 		return frappe.call({ | ||||
| 			method: "erpnext.projects.doctype.time_log.time_log.get_workstation", | ||||
| 			args: { | ||||
| 				"production_order": doc.production_order, | ||||
| 				"operation": doc.operation | ||||
| 			}, | ||||
| 			callback: function(r) { | ||||
| 				if(!r.exc) { | ||||
| 					console.log(r.message) | ||||
| 					cur_frm.set_value("workstation", r.message) | ||||
| 				} | ||||
| 			} | ||||
| 		}); | ||||
| 	} | ||||
| }); | ||||
|  | ||||
| @ -16,6 +16,15 @@ | ||||
|    "read_only": 0,  | ||||
|    "reqd": 1 | ||||
|   },  | ||||
|   { | ||||
|    "fieldname": "time_log_for",  | ||||
|    "fieldtype": "Select",  | ||||
|    "label": "Time Log For",  | ||||
|    "options": "\nProject\nManufacturing",  | ||||
|    "permlevel": 0,  | ||||
|    "precision": "",  | ||||
|    "reqd": 0 | ||||
|   },  | ||||
|   { | ||||
|    "fieldname": "from_time",  | ||||
|    "fieldtype": "Datetime",  | ||||
| @ -59,6 +68,7 @@ | ||||
|    "reqd": 0 | ||||
|   },  | ||||
|   { | ||||
|    "depends_on": "eval:doc.time_log_for != 'Manufacturing'",  | ||||
|    "fieldname": "activity_type",  | ||||
|    "fieldtype": "Link",  | ||||
|    "in_list_view": 1,  | ||||
| @ -66,9 +76,10 @@ | ||||
|    "options": "Activity Type",  | ||||
|    "permlevel": 0,  | ||||
|    "read_only": 0,  | ||||
|    "reqd": 1 | ||||
|    "reqd": 0 | ||||
|   },  | ||||
|   { | ||||
|    "depends_on": "eval:doc.time_log_for != 'Manufacturing'",  | ||||
|    "fieldname": "task",  | ||||
|    "fieldtype": "Link",  | ||||
|    "label": "Task",  | ||||
| @ -76,6 +87,42 @@ | ||||
|    "permlevel": 0,  | ||||
|    "read_only": 0 | ||||
|   },  | ||||
|   { | ||||
|    "depends_on": "eval:doc.time_log_for == 'Manufacturing'",  | ||||
|    "fieldname": "production_order",  | ||||
|    "fieldtype": "Link",  | ||||
|    "label": "Production Order",  | ||||
|    "options": "Production Order",  | ||||
|    "permlevel": 0,  | ||||
|    "precision": "" | ||||
|   },  | ||||
|   { | ||||
|    "depends_on": "eval:doc.time_log_for == 'Manufacturing'",  | ||||
|    "fieldname": "operation",  | ||||
|    "fieldtype": "Select",  | ||||
|    "label": "Operation",  | ||||
|    "options": "",  | ||||
|    "permlevel": 0,  | ||||
|    "precision": "" | ||||
|   },  | ||||
|   { | ||||
|    "depends_on": "eval:doc.time_log_for == 'Manufacturing'",  | ||||
|    "fieldname": "workstation",  | ||||
|    "fieldtype": "Link",  | ||||
|    "label": "Workstation",  | ||||
|    "options": "Workstation",  | ||||
|    "permlevel": 0,  | ||||
|    "precision": "",  | ||||
|    "read_only": 1 | ||||
|   },  | ||||
|   { | ||||
|    "description": "Operation completed for how many finished goods?",  | ||||
|    "fieldname": "completed_qty",  | ||||
|    "fieldtype": "Float",  | ||||
|    "label": "Completed Qty",  | ||||
|    "permlevel": 0,  | ||||
|    "precision": "" | ||||
|   },  | ||||
|   { | ||||
|    "fieldname": "billable",  | ||||
|    "fieldtype": "Check",  | ||||
| @ -104,6 +151,7 @@ | ||||
|    "read_only": 0 | ||||
|   },  | ||||
|   { | ||||
|    "depends_on": "eval:doc.time_log_for",  | ||||
|    "fieldname": "project",  | ||||
|    "fieldtype": "Link",  | ||||
|    "in_list_view": 1,  | ||||
| @ -151,7 +199,7 @@ | ||||
|  "icon": "icon-time",  | ||||
|  "idx": 1,  | ||||
|  "is_submittable": 1,  | ||||
|  "modified": "2014-10-22 16:53:26.993828",  | ||||
|  "modified": "2014-12-22 15:22:00.664972",  | ||||
|  "modified_by": "Administrator",  | ||||
|  "module": "Projects",  | ||||
|  "name": "Time Log",  | ||||
|  | ||||
| @ -4,25 +4,37 @@ | ||||
| # For license information, please see license.txt | ||||
| 
 | ||||
| from __future__ import unicode_literals | ||||
| import frappe | ||||
| import frappe, json | ||||
| from frappe import _ | ||||
| from frappe.utils import cstr, comma_and | ||||
| 
 | ||||
| from frappe.utils import cstr, comma_and, flt | ||||
| 
 | ||||
| class OverlapError(frappe.ValidationError): pass | ||||
| class OverProductionError(frappe.ValidationError): pass | ||||
| class NotSubmittedError(frappe.ValidationError): pass | ||||
| 
 | ||||
| from frappe.model.document import Document | ||||
| 
 | ||||
| class TimeLog(Document): | ||||
| 
 | ||||
| 	def validate(self): | ||||
| 		self.set_status() | ||||
| 		self.validate_overlap() | ||||
| 		self.validate_timings() | ||||
| 		self.calculate_total_hours() | ||||
| 		self.validate_time_log_for() | ||||
| 		self.check_workstation_timings() | ||||
| 		self.validate_production_order() | ||||
| 
 | ||||
| 	def calculate_total_hours(self): | ||||
| 		from frappe.utils import time_diff_in_hours | ||||
| 		self.hours = time_diff_in_hours(self.to_time, self.from_time) | ||||
| 	def on_submit(self): | ||||
| 		self.update_production_order() | ||||
| 
 | ||||
| 	def on_cancel(self): | ||||
| 		self.update_production_order() | ||||
| 
 | ||||
| 	def before_update_after_submit(self): | ||||
| 		self.set_status() | ||||
| 
 | ||||
| 	def before_cancel(self): | ||||
| 		self.set_status() | ||||
| 
 | ||||
| 	def set_status(self): | ||||
| 		self.status = { | ||||
| @ -38,6 +50,7 @@ class TimeLog(Document): | ||||
| 			self.status="Billed" | ||||
| 
 | ||||
| 	def validate_overlap(self): | ||||
| 		"""Checks if 'Time Log' entries overlap each other. """ | ||||
| 		existing = frappe.db.sql_list("""select name from `tabTime Log` where owner=%s and | ||||
| 			( | ||||
| 				(from_time between %s and %s) or | ||||
| @ -53,30 +66,111 @@ class TimeLog(Document): | ||||
| 		if existing: | ||||
| 			frappe.throw(_("This Time Log conflicts with {0}").format(comma_and(existing)), OverlapError) | ||||
| 
 | ||||
| 	def before_cancel(self): | ||||
| 		self.set_status() | ||||
| 	def validate_timings(self): | ||||
| 		if self.to_time < self.from_time: | ||||
| 			frappe.throw(_("From Time cannot be greater than To Time")) | ||||
| 
 | ||||
| 	def before_update_after_submit(self): | ||||
| 		self.set_status() | ||||
| 	def calculate_total_hours(self): | ||||
| 		from frappe.utils import time_diff_in_seconds | ||||
| 		self.hours = flt(time_diff_in_seconds(self.to_time, self.from_time)) / 3600 | ||||
| 
 | ||||
| 	def validate_time_log_for(self): | ||||
| 		if self.time_log_for == "Project": | ||||
| 			for fld in ["production_order", "operation", "workstation", "completed_qty"]: | ||||
| 				self.set(fld, None) | ||||
| 
 | ||||
| 	def check_workstation_timings(self): | ||||
| 		"""Checks if **Time Log** is between operating hours of the **Workstation**.""" | ||||
| 		if self.workstation: | ||||
| 			from erpnext.manufacturing.doctype.workstation.workstation import check_if_within_operating_hours | ||||
| 			check_if_within_operating_hours(self.workstation, self.from_time, self.to_time) | ||||
| 
 | ||||
| 	def validate_production_order(self): | ||||
| 		"""Throws 'NotSubmittedError' if **production order** is not submitted. """ | ||||
| 		if self.production_order: | ||||
| 			if frappe.db.get_value("Production Order", self.production_order, "docstatus") != 1 : | ||||
| 				frappe.throw(_("You can make a time log only against a submitted production order"), NotSubmittedError) | ||||
| 
 | ||||
| 	def update_production_order(self): | ||||
| 		"""Updates `start_date`, `end_date`, `status` for operation in Production Order.""" | ||||
| 
 | ||||
| 		if self.time_log_for=="Manufacturing" and self.operation: | ||||
| 			operation = self.operation.split('. ',1) | ||||
| 
 | ||||
| 			dates = self.get_operation_start_end_time() | ||||
| 			tl = self.get_all_time_logs() | ||||
| 
 | ||||
| 
 | ||||
| 			frappe.db.sql("""update `tabProduction Order Operation` | ||||
| 				set actual_start_time = %s, actual_end_time = %s, completed_qty = %s, actual_operation_time = %s | ||||
| 				where parent=%s and idx=%s and operation = %s""", | ||||
| 				(dates.start_date, dates.end_date, tl.completed_qty, | ||||
| 					tl.hours, self.production_order, operation[0], operation[1])) | ||||
| 
 | ||||
| 			pro_order = frappe.get_doc("Production Order", self.production_order) | ||||
| 			pro_order.ignore_validate_update_after_submit = True | ||||
| 			pro_order.update_operation_status() | ||||
| 			pro_order.calculate_operating_cost() | ||||
| 			pro_order.save() | ||||
| 
 | ||||
| 	def get_operation_start_end_time(self): | ||||
| 		"""Returns Min From and Max To Dates of Time Logs against a specific Operation. """ | ||||
| 		return frappe.db.sql("""select min(from_time) as start_date, max(to_time) as end_date from `tabTime Log` | ||||
| 				where production_order = %s and operation = %s and docstatus=1""", | ||||
| 				(self.production_order, self.operation), as_dict=1)[0] | ||||
| 
 | ||||
| 	def get_all_time_logs(self): | ||||
| 		"""Returns 'Actual Operating Time'. """ | ||||
| 		return frappe.db.sql("""select | ||||
| 			sum(hours*60) as hours, sum(ifnull(completed_qty, 0)) as completed_qty | ||||
| 			from `tabTime Log` | ||||
| 			where production_order = %s and operation = %s and docstatus=1""", | ||||
| 			(self.production_order, self.operation), as_dict=1)[0] | ||||
| 
 | ||||
| @frappe.whitelist() | ||||
| def get_events(start, end): | ||||
| def get_workstation(production_order, operation): | ||||
| 	"""Returns workstation name from Production Order against an associated Operation. | ||||
| 
 | ||||
| 	:param production_order string | ||||
| 	:param operation string | ||||
| 	""" | ||||
| 	if operation: | ||||
| 		idx, operation = operation.split('. ',1) | ||||
| 
 | ||||
| 		workstation = frappe.db.sql("""select workstation from `tabProduction Order Operation` where idx=%s and | ||||
| 			parent=%s and operation = %s""", (idx, production_order, operation)) | ||||
| 		return workstation[0][0] if workstation else "" | ||||
| 
 | ||||
| @frappe.whitelist() | ||||
| def get_events(start, end, filters=None): | ||||
| 	"""Returns events for Gantt / Calendar view rendering. | ||||
| 
 | ||||
| 	:param start: Start date-time. | ||||
| 	:param end: End date-time. | ||||
| 	:param filters: Filters like workstation, project etc. | ||||
| 	""" | ||||
| 	from frappe.desk.reportview import build_match_conditions | ||||
| 	if not frappe.has_permission("Time Log"): | ||||
| 		frappe.msgprint(_("No Permission"), raise_exception=1) | ||||
| 
 | ||||
| 	match = build_match_conditions("Time Log") | ||||
| 	conditions = build_match_conditions("Time Log") | ||||
| 	conditions = conditions and (" and " + conditions) or "" | ||||
| 	if filters: | ||||
| 		filters = json.loads(filters) | ||||
| 		for key in filters: | ||||
| 			if filters[key]: | ||||
| 				conditions += " and " + key + ' = "' + filters[key].replace('"', '\"') + '"' | ||||
| 
 | ||||
| 	data = frappe.db.sql("""select name, from_time, to_time, | ||||
| 		activity_type, task, project from `tabTime Log` | ||||
| 		where from_time between '%(start)s' and '%(end)s' or to_time between '%(start)s' and '%(end)s' | ||||
| 		%(match)s""" % { | ||||
| 		activity_type, task, project, production_order, workstation from `tabTime Log` | ||||
| 		where ( from_time between %(start)s and %(end)s or to_time between %(start)s and %(end)s ) | ||||
| 		{conditions}""".format(conditions=conditions), { | ||||
| 			"start": start, | ||||
| 			"end": end, | ||||
| 			"match": match and (" and " + match) or "" | ||||
| 		}, as_dict=True, update={"allDay": 0}) | ||||
| 			"end": end | ||||
| 			}, as_dict=True, update={"allDay": 0}) | ||||
| 
 | ||||
| 	for d in data: | ||||
| 		d.title = d.name + ": " + (d.activity_type or "[Activity Type not set]") | ||||
| 		d.title = d.name + ": " + (d.activity_type or d.production_order or "") | ||||
| 		if d.task: | ||||
| 			d.title += " for Task: " + d.task | ||||
| 		if d.project: | ||||
|  | ||||
| @ -9,5 +9,14 @@ frappe.views.calendar["Time Log"] = { | ||||
| 		"title": "title", | ||||
| 		"allDay": "allDay" | ||||
| 	}, | ||||
| 	gantt: true, | ||||
| 	filters: [ | ||||
| 		{ | ||||
| 			"fieldtype": "Link", | ||||
| 			"fieldname": "workstation", | ||||
| 			"options": "Workstation", | ||||
| 			"label": __("Workstation") | ||||
| 		}, | ||||
| 	], | ||||
| 	get_events_method: "erpnext.projects.doctype.time_log.time_log.get_events" | ||||
| } | ||||
| @ -9,17 +9,31 @@ | ||||
| 				<i class="icon-money text-muted"></i> | ||||
| 			</span> | ||||
| 			{% } %} | ||||
| 			 | ||||
| 			{% if(doc.time_log_for == 'Manufacturing') { %} | ||||
| 			<span style="margin-right: 8px;" | ||||
| 				title="{%= __("Manufacturing") %}" class="filterable" | ||||
| 				data-filter="time_log_for,=,Manufacturing"> | ||||
| 				<i class="icon-cogs text-muted"></i> | ||||
| 			</span> | ||||
| 			{% } %} | ||||
| 			 | ||||
| 			{% if(doc.activity_type) { %} | ||||
| 			<span class="label label-info filterable" style="margin-right: 8px;" | ||||
| 				data-filter="activity_type,=,{%= doc.activity_type %}"> | ||||
| 				{%= doc.activity_type %}</span> | ||||
| 			<span style="margin-right: 8px;" class="text-muted"> | ||||
| 				({%= doc.hours + " " + __("hours") %}) | ||||
| 			</span> | ||||
| 			{% } %} | ||||
| 			 | ||||
| 			{% if(doc.project) { %} | ||||
| 			<span class="filterable" style="margin-right: 8px;" | ||||
| 				data-filter="project,=,{%= doc.project %}"> | ||||
| 				{%= doc.project %}</span> | ||||
| 			{% } %} | ||||
| 						 | ||||
| 			<span style="margin-right: 8px;" class="text-muted"> | ||||
| 			({%= doc.hours + " " + __("hours") %}) | ||||
| 			</span> | ||||
| 			 | ||||
| 		</div> | ||||
| 	</div> | ||||
| </div> | ||||
|  | ||||
| @ -3,7 +3,7 @@ | ||||
| 
 | ||||
| // render
 | ||||
| frappe.listview_settings['Time Log'] = { | ||||
| 	add_fields: ["status", "billable", "activity_type", "task", "project", "hours"], | ||||
| 	add_fields: ["status", "billable", "activity_type", "task", "project", "hours", "time_log_for"], | ||||
| 	selectable: true, | ||||
| 	onload: function(me) { | ||||
| 		me.appframe.add_primary_action(__("Make Time Log Batch"), function() { | ||||
|  | ||||
| @ -160,6 +160,14 @@ | ||||
|    "options": "Account",  | ||||
|    "permlevel": 0 | ||||
|   },  | ||||
|   { | ||||
|    "fieldname": "default_holiday_list",  | ||||
|    "fieldtype": "Link",  | ||||
|    "label": "Default Holiday List",  | ||||
|    "options": "Holiday List",  | ||||
|    "permlevel": 0,  | ||||
|    "precision": "" | ||||
|   },  | ||||
|   { | ||||
|    "fieldname": "column_break0",  | ||||
|    "fieldtype": "Column Break",  | ||||
| @ -356,7 +364,7 @@ | ||||
|  ],  | ||||
|  "icon": "icon-building",  | ||||
|  "idx": 1,  | ||||
|  "modified": "2014-08-29 15:50:18.539228",  | ||||
|  "modified": "2014-11-27 18:15:48.909416",  | ||||
|  "modified_by": "Administrator",  | ||||
|  "module": "Setup",  | ||||
|  "name": "Company",  | ||||
|  | ||||
| @ -82,10 +82,10 @@ class Company(Document): | ||||
| 
 | ||||
| 	def create_default_accounts(self): | ||||
| 		if not self.chart_of_accounts: | ||||
| 			frappe.throw(_("Please select Chart of Accounts")) | ||||
| 		else: | ||||
| 			from erpnext.accounts.doctype.account.chart_of_accounts.chart_of_accounts import create_charts | ||||
| 			create_charts(self.chart_of_accounts, self.name) | ||||
| 			self.chart_of_accounts = "Standard" | ||||
| 
 | ||||
| 		from erpnext.accounts.doctype.account.chart_of_accounts.chart_of_accounts import create_charts | ||||
| 		create_charts(self.chart_of_accounts, self.name) | ||||
| 
 | ||||
| 		frappe.db.set(self, "default_receivable_account", frappe.db.get_value("Account", | ||||
| 			{"company": self.name, "account_type": "Receivable"})) | ||||
|  | ||||
| @ -461,8 +461,6 @@ cur_frm.fields_dict.customer.get_query = function(doc, cdt, cdn) { | ||||
| cur_frm.fields_dict.supplier.get_query = function(doc, cdt, cdn) { | ||||
| 	return { query: "erpnext.controllers.queries.supplier_query" } | ||||
| } | ||||
| cur_frm.add_fetch('production_order', 'total_fixed_cost', 'total_fixed_cost'); | ||||
| cur_frm.add_fetch('bom_no', 'total_fixed_cost', 'total_fixed_cost'); | ||||
| 
 | ||||
| cur_frm.cscript.company = function(doc, cdt, cdn) { | ||||
| 	erpnext.get_fiscal_year(doc.company, doc.posting_date); | ||||
|  | ||||
| @ -299,9 +299,11 @@ | ||||
|   },  | ||||
|   { | ||||
|    "depends_on": "eval:inList([\"Manufacture\", \"Repack\"], doc.purpose)",  | ||||
|    "fieldname": "total_fixed_cost",  | ||||
|    "fieldtype": "Float",  | ||||
|    "label": "Total Fixed Cost",  | ||||
|    "fieldname": "additional_operating_cost",  | ||||
|    "fieldtype": "Currency",  | ||||
|    "label": "Additional Operating Cost",  | ||||
|    "no_copy": 1,  | ||||
|    "options": "Company:company:default_currency",  | ||||
|    "permlevel": 0,  | ||||
|    "read_only": 0 | ||||
|   },  | ||||
| @ -585,7 +587,7 @@ | ||||
|  "is_submittable": 1,  | ||||
|  "issingle": 0,  | ||||
|  "max_attachments": 0,  | ||||
|  "modified": "2014-10-03 14:55:44.916658",  | ||||
|  "modified": "2014-12-23 15:03:42.963697",  | ||||
|  "modified_by": "Administrator",  | ||||
|  "module": "Stock",  | ||||
|  "name": "Stock Entry",  | ||||
|  | ||||
| @ -171,10 +171,20 @@ class StockEntry(StockController): | ||||
| 			if not self.production_order: | ||||
| 				frappe.throw(_("Production order number is mandatory for stock entry purpose manufacture")) | ||||
| 			# check for double entry | ||||
| 			self.check_if_operations_completed() | ||||
| 			self.check_duplicate_entry_for_production_order() | ||||
| 		elif self.purpose != "Material Transfer": | ||||
| 			self.production_order = None | ||||
| 
 | ||||
| 	def check_if_operations_completed(self): | ||||
| 		prod_order = frappe.get_doc("Production Order", self.production_order) | ||||
| 		if prod_order.actual_operating_cost: | ||||
| 			for d in prod_order.get("production_order_operations"): | ||||
| 				total_completed_qty = flt(self.fg_completed_qty) + flt(prod_order.produced_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") | ||||
| 						.format(d.idx, d.operation, total_completed_qty, self.production_order)) | ||||
| 
 | ||||
| 	def check_duplicate_entry_for_production_order(self): | ||||
| 		other_ste = [t[0] for t in frappe.db.get_values("Stock Entry",  { | ||||
| 			"production_order": self.production_order, | ||||
| @ -258,14 +268,28 @@ class StockEntry(StockController): | ||||
| 			for d in self.get("mtn_details"): | ||||
| 				if d.bom_no or (d.t_warehouse and number_of_fg_items == 1): | ||||
| 					if not flt(d.incoming_rate) or force: | ||||
| 						operation_cost_per_unit = 0 | ||||
| 						if d.bom_no: | ||||
| 							bom = frappe.db.get_value("BOM", d.bom_no, ["operating_cost", "quantity"], as_dict=1) | ||||
| 							operation_cost_per_unit = flt(bom.operating_cost) / flt(bom.quantity) | ||||
| 						d.incoming_rate = operation_cost_per_unit + (raw_material_cost + flt(self.total_fixed_cost)) / flt(d.transfer_qty) | ||||
| 						operation_cost_per_unit = self.get_operation_cost_per_unit(d.bom_no, d.qty) | ||||
| 						d.incoming_rate = operation_cost_per_unit + (raw_material_cost / flt(d.transfer_qty)) | ||||
| 					d.amount = flt(d.transfer_qty) * flt(d.incoming_rate) | ||||
| 					break | ||||
| 
 | ||||
| 	def get_operation_cost_per_unit(self, bom_no, qty): | ||||
| 		operation_cost_per_unit = 0 | ||||
| 
 | ||||
| 		if self.production_order: | ||||
| 			pro_order = frappe.get_doc("Production Order", self.production_order) | ||||
| 			for d in pro_order.get("production_order_operations"): | ||||
| 				if flt(d.completed_qty): | ||||
| 					operation_cost_per_unit += flt(d.actual_operating_cost) / flt(d.completed_qty) | ||||
| 				else: | ||||
| 					operation_cost_per_unit += flt(d.planned_operating_cost) / flt(self.qty) | ||||
| 
 | ||||
| 		if not operation_cost_per_unit and bom_no: | ||||
| 			bom = frappe.db.get_value("BOM", bom_no, ["operating_cost", "quantity"], as_dict=1) | ||||
| 			operation_cost_per_unit = flt(bom.operating_cost) / flt(bom.quantity) | ||||
| 
 | ||||
| 		return operation_cost_per_unit + flt(self.additional_operating_cost) / flt(qty) | ||||
| 
 | ||||
| 	def get_incoming_rate(self, args): | ||||
| 		incoming_rate = 0 | ||||
| 		if self.purpose == "Sales Return": | ||||
| @ -643,10 +667,12 @@ def get_party_details(ref_dt, ref_dn): | ||||
| 
 | ||||
| @frappe.whitelist() | ||||
| def get_production_order_details(production_order): | ||||
| 	result = frappe.db.sql("""select bom_no, | ||||
| 		ifnull(qty, 0) - ifnull(produced_qty, 0) as fg_completed_qty, use_multi_level_bom, | ||||
| 		wip_warehouse from `tabProduction Order` where name = %s""", production_order, as_dict=1) | ||||
| 	return result and result[0] or {} | ||||
| 	res = frappe.db.sql("""select bom_no, use_multi_level_bom, wip_warehouse, | ||||
| 		ifnull(qty, 0) - ifnull(produced_qty, 0) as fg_completed_qty, | ||||
| 		(infull(additional_operating_cost, 0) / qty)*(ifnull(qty, 0) - ifnull(produced_qty, 0)) as additional_operating_cost | ||||
| 		from `tabProduction Order` where name = %s""", production_order, as_dict=1) | ||||
| 
 | ||||
| 	return res and res[0] or {} | ||||
| 
 | ||||
| def query_sales_return_doc(doctype, txt, searchfield, start, page_len, filters): | ||||
| 	conditions = "" | ||||
|  | ||||
| @ -918,7 +918,7 @@ class TestStockEntry(unittest.TestCase): | ||||
| 			"production_order": production_order.name, | ||||
| 			"bom_no": bom_no, | ||||
| 			"fg_completed_qty": "1", | ||||
| 			"total_fixed_cost": 1000 | ||||
| 			"additional_operating_cost": 1000 | ||||
| 		}) | ||||
| 		stock_entry.get_items() | ||||
| 
 | ||||
|  | ||||
| @ -115,7 +115,7 @@ def get_item_bom_rate(): | ||||
| 
 | ||||
| 	item_bom_map = {} | ||||
| 
 | ||||
| 	for b in frappe.db.sql("""select item, (total_variable_cost/quantity) as bom_rate | ||||
| 	for b in frappe.db.sql("""select item, (total_cost/quantity) as bom_rate | ||||
| 		from `tabBOM` where is_active=1 and is_default=1""", as_dict=1): | ||||
| 			item_bom_map.setdefault(b.item, flt(b.bom_rate)) | ||||
| 
 | ||||
|  | ||||
| @ -40,7 +40,8 @@ | ||||
| 		<div class="col-sm-2 text-right"> | ||||
| 			{%= doc.get_formatted("amount") %} | ||||
| 			<div class="small text-muted"> | ||||
| 				{%= doc.get_formatted("incoming_rate") %}</div> | ||||
| 				{%= doc.get_formatted("incoming_rate") %} | ||||
| 			</div> | ||||
| 		</div> | ||||
| 	</div> | ||||
| {% } %} | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user