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()
|
||||
|
||||
|
@ -1,15 +1,15 @@
|
||||
[
|
||||
{
|
||||
"doctype": "Holiday List",
|
||||
"doctype": "Holiday List",
|
||||
"fiscal_year": "_Test Fiscal Year 2013",
|
||||
"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",
|
||||
|
@ -1,147 +1,147 @@
|
||||
{
|
||||
"creation": "2013-02-22 01:27:49",
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"creation": "2013-02-22 01:27:49",
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "operation_no",
|
||||
"fieldtype": "Select",
|
||||
"in_list_view": 1,
|
||||
"label": "Operation No",
|
||||
"oldfieldname": "operation_no",
|
||||
"oldfieldtype": "Data",
|
||||
"permlevel": 0,
|
||||
"fieldname": "operation",
|
||||
"fieldtype": "Select",
|
||||
"in_list_view": 1,
|
||||
"label": "Operation",
|
||||
"oldfieldname": "operation_no",
|
||||
"oldfieldtype": "Data",
|
||||
"permlevel": 0,
|
||||
"reqd": 0
|
||||
},
|
||||
},
|
||||
{
|
||||
"fieldname": "item_code",
|
||||
"fieldtype": "Link",
|
||||
"in_filter": 1,
|
||||
"in_list_view": 1,
|
||||
"label": "Item Code",
|
||||
"oldfieldname": "item_code",
|
||||
"oldfieldtype": "Link",
|
||||
"options": "Item",
|
||||
"permlevel": 0,
|
||||
"reqd": 1,
|
||||
"fieldname": "item_code",
|
||||
"fieldtype": "Link",
|
||||
"in_filter": 1,
|
||||
"in_list_view": 1,
|
||||
"label": "Item Code",
|
||||
"oldfieldname": "item_code",
|
||||
"oldfieldtype": "Link",
|
||||
"options": "Item",
|
||||
"permlevel": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 1
|
||||
},
|
||||
},
|
||||
{
|
||||
"fieldname": "bom_no",
|
||||
"fieldtype": "Link",
|
||||
"in_filter": 1,
|
||||
"in_list_view": 1,
|
||||
"label": "BOM No",
|
||||
"oldfieldname": "bom_no",
|
||||
"oldfieldtype": "Link",
|
||||
"options": "BOM",
|
||||
"permlevel": 0,
|
||||
"print_width": "150px",
|
||||
"reqd": 0,
|
||||
"search_index": 1,
|
||||
"fieldname": "bom_no",
|
||||
"fieldtype": "Link",
|
||||
"in_filter": 1,
|
||||
"in_list_view": 1,
|
||||
"label": "BOM No",
|
||||
"oldfieldname": "bom_no",
|
||||
"oldfieldtype": "Link",
|
||||
"options": "BOM",
|
||||
"permlevel": 0,
|
||||
"print_width": "150px",
|
||||
"reqd": 0,
|
||||
"search_index": 1,
|
||||
"width": "150px"
|
||||
},
|
||||
},
|
||||
{
|
||||
"fieldname": "col_break1",
|
||||
"fieldtype": "Column Break",
|
||||
"fieldname": "col_break1",
|
||||
"fieldtype": "Column Break",
|
||||
"permlevel": 0
|
||||
},
|
||||
},
|
||||
{
|
||||
"fieldname": "description",
|
||||
"fieldtype": "Text",
|
||||
"label": "Item Description",
|
||||
"oldfieldname": "description",
|
||||
"oldfieldtype": "Text",
|
||||
"permlevel": 0,
|
||||
"print_width": "250px",
|
||||
"reqd": 0,
|
||||
"fieldname": "description",
|
||||
"fieldtype": "Text",
|
||||
"label": "Item Description",
|
||||
"oldfieldname": "description",
|
||||
"oldfieldtype": "Text",
|
||||
"permlevel": 0,
|
||||
"print_width": "250px",
|
||||
"reqd": 0,
|
||||
"width": "250px"
|
||||
},
|
||||
},
|
||||
{
|
||||
"fieldname": "quantity_and_rate",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Quantity and Rate",
|
||||
"fieldname": "quantity_and_rate",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Quantity and Rate",
|
||||
"permlevel": 0
|
||||
},
|
||||
},
|
||||
{
|
||||
"fieldname": "qty",
|
||||
"fieldtype": "Float",
|
||||
"in_list_view": 1,
|
||||
"label": "Qty",
|
||||
"oldfieldname": "qty",
|
||||
"oldfieldtype": "Currency",
|
||||
"permlevel": 0,
|
||||
"fieldname": "qty",
|
||||
"fieldtype": "Float",
|
||||
"in_list_view": 1,
|
||||
"label": "Qty",
|
||||
"oldfieldname": "qty",
|
||||
"oldfieldtype": "Currency",
|
||||
"permlevel": 0,
|
||||
"reqd": 1
|
||||
},
|
||||
},
|
||||
{
|
||||
"description": "See \"Rate Of Materials Based On\" in Costing Section",
|
||||
"fieldname": "rate",
|
||||
"fieldtype": "Currency",
|
||||
"in_list_view": 1,
|
||||
"label": "Rate",
|
||||
"options": "Company:company:default_currency",
|
||||
"permlevel": 0,
|
||||
"description": "See \"Rate Of Materials Based On\" in Costing Section",
|
||||
"fieldname": "rate",
|
||||
"fieldtype": "Currency",
|
||||
"in_list_view": 1,
|
||||
"label": "Rate",
|
||||
"options": "Company:company:default_currency",
|
||||
"permlevel": 0,
|
||||
"reqd": 1
|
||||
},
|
||||
},
|
||||
{
|
||||
"fieldname": "col_break2",
|
||||
"fieldtype": "Column Break",
|
||||
"fieldname": "col_break2",
|
||||
"fieldtype": "Column Break",
|
||||
"permlevel": 0
|
||||
},
|
||||
},
|
||||
{
|
||||
"fieldname": "stock_uom",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 0,
|
||||
"label": "Stock UOM",
|
||||
"oldfieldname": "stock_uom",
|
||||
"oldfieldtype": "Data",
|
||||
"options": "UOM",
|
||||
"permlevel": 0,
|
||||
"read_only": 1,
|
||||
"fieldname": "stock_uom",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 0,
|
||||
"label": "Stock UOM",
|
||||
"oldfieldname": "stock_uom",
|
||||
"oldfieldtype": "Data",
|
||||
"options": "UOM",
|
||||
"permlevel": 0,
|
||||
"read_only": 1,
|
||||
"reqd": 1
|
||||
},
|
||||
},
|
||||
{
|
||||
"fieldname": "amount",
|
||||
"fieldtype": "Currency",
|
||||
"in_list_view": 1,
|
||||
"label": "Amount",
|
||||
"oldfieldname": "amount_as_per_mar",
|
||||
"oldfieldtype": "Currency",
|
||||
"options": "Company:company:default_currency",
|
||||
"permlevel": 0,
|
||||
"print_width": "150px",
|
||||
"read_only": 1,
|
||||
"fieldname": "amount",
|
||||
"fieldtype": "Currency",
|
||||
"in_list_view": 1,
|
||||
"label": "Amount",
|
||||
"oldfieldname": "amount_as_per_mar",
|
||||
"oldfieldtype": "Currency",
|
||||
"options": "Company:company:default_currency",
|
||||
"permlevel": 0,
|
||||
"print_width": "150px",
|
||||
"read_only": 1,
|
||||
"width": "150px"
|
||||
},
|
||||
},
|
||||
{
|
||||
"fieldname": "scrap",
|
||||
"fieldtype": "Float",
|
||||
"label": "Scrap %",
|
||||
"oldfieldname": "scrap",
|
||||
"oldfieldtype": "Currency",
|
||||
"permlevel": 0,
|
||||
"fieldname": "scrap",
|
||||
"fieldtype": "Float",
|
||||
"label": "Scrap %",
|
||||
"oldfieldname": "scrap",
|
||||
"oldfieldtype": "Currency",
|
||||
"permlevel": 0,
|
||||
"print_hide": 1
|
||||
},
|
||||
},
|
||||
{
|
||||
"fieldname": "qty_consumed_per_unit",
|
||||
"fieldtype": "Float",
|
||||
"hidden": 1,
|
||||
"label": "Qty Consumed Per Unit",
|
||||
"oldfieldname": "qty_consumed_per_unit",
|
||||
"oldfieldtype": "Float",
|
||||
"permlevel": 0,
|
||||
"print_hide": 1,
|
||||
"fieldname": "qty_consumed_per_unit",
|
||||
"fieldtype": "Float",
|
||||
"hidden": 1,
|
||||
"label": "Qty Consumed Per Unit",
|
||||
"oldfieldname": "qty_consumed_per_unit",
|
||||
"oldfieldtype": "Float",
|
||||
"permlevel": 0,
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"idx": 1,
|
||||
"istable": 1,
|
||||
"modified": "2014-12-12 11:15:43.798755",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Manufacturing",
|
||||
"name": "BOM Item",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"sort_field": "modified",
|
||||
],
|
||||
"idx": 1,
|
||||
"istable": 1,
|
||||
"modified": "2014-12-12 11:15:43.798755",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Manufacturing",
|
||||
"name": "BOM Item",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC"
|
||||
}
|
||||
}
|
||||
|
@ -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"
|
||||
@ -21,7 +21,7 @@ frappe.views.calendar["Production Order"] = {
|
||||
"fieldtype": "Link",
|
||||
"fieldname": "production_item",
|
||||
"options": "Item",
|
||||
"label": __("Production Item")
|
||||
"label": __("Production Item")
|
||||
},
|
||||
{
|
||||
"fieldtype": "Link",
|
||||
@ -31,4 +31,4 @@ frappe.views.calendar["Production Order"] = {
|
||||
}
|
||||
],
|
||||
get_events_method: "erpnext.manufacturing.doctype.production_order.production_order.get_events"
|
||||
}
|
||||
}
|
||||
|
@ -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"))
|
||||
|
@ -1,13 +1,17 @@
|
||||
// Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors
|
||||
// License: GNU General Public License v3. See license.txt
|
||||
|
||||
|
||||
|
||||
|
||||
//--------- ONLOAD -------------
|
||||
cur_frm.cscript.onload = 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);
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
cur_frm.cscript.refresh = function(doc, cdt, cdn) {
|
||||
|
||||
}
|
@ -1,161 +1,161 @@
|
||||
{
|
||||
"allow_import": 1,
|
||||
"allow_rename": 1,
|
||||
"autoname": "field:workstation_name",
|
||||
"creation": "2013-01-10 16:34:17",
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "Master",
|
||||
"allow_import": 1,
|
||||
"allow_rename": 1,
|
||||
"autoname": "field:workstation_name",
|
||||
"creation": "2013-01-10 16:34:17",
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "Master",
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "workstation_name",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Workstation Name",
|
||||
"oldfieldname": "workstation_name",
|
||||
"oldfieldtype": "Data",
|
||||
"permlevel": 0,
|
||||
"reqd": 1
|
||||
},
|
||||
"fieldname": "description_and_warehouse",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Description and Warehouse",
|
||||
"permlevel": 0,
|
||||
"precision": ""
|
||||
},
|
||||
{
|
||||
"fieldname": "warehouse",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Warehouse",
|
||||
"oldfieldname": "warehouse",
|
||||
"oldfieldtype": "Link",
|
||||
"options": "Warehouse",
|
||||
"permlevel": 0,
|
||||
"fieldname": "workstation_name",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Workstation Name",
|
||||
"oldfieldname": "workstation_name",
|
||||
"oldfieldtype": "Data",
|
||||
"permlevel": 0,
|
||||
"reqd": 1
|
||||
},
|
||||
},
|
||||
{
|
||||
"fieldname": "description",
|
||||
"fieldtype": "Text",
|
||||
"in_list_view": 1,
|
||||
"label": "Description",
|
||||
"oldfieldname": "description",
|
||||
"oldfieldtype": "Text",
|
||||
"permlevel": 0,
|
||||
"fieldname": "description",
|
||||
"fieldtype": "Text",
|
||||
"in_list_view": 1,
|
||||
"label": "Description",
|
||||
"oldfieldname": "description",
|
||||
"oldfieldtype": "Text",
|
||||
"permlevel": 0,
|
||||
"width": "300px"
|
||||
},
|
||||
},
|
||||
{
|
||||
"fieldname": "capacity",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 1,
|
||||
"in_list_view": 1,
|
||||
"label": "Capacity",
|
||||
"oldfieldname": "capacity",
|
||||
"oldfieldtype": "Data",
|
||||
"permlevel": 0,
|
||||
"fieldname": "column_break_4",
|
||||
"fieldtype": "Column Break",
|
||||
"permlevel": 0,
|
||||
"precision": ""
|
||||
},
|
||||
{
|
||||
"fieldname": "warehouse",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Warehouse",
|
||||
"oldfieldname": "warehouse",
|
||||
"oldfieldtype": "Link",
|
||||
"options": "Warehouse",
|
||||
"permlevel": 0,
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"default": "",
|
||||
"fieldname": "holiday_list",
|
||||
"fieldtype": "Link",
|
||||
"label": "Holiday List",
|
||||
"options": "Holiday List",
|
||||
"permlevel": 0,
|
||||
"precision": ""
|
||||
},
|
||||
{
|
||||
"fieldname": "over_heads",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Operating Costs",
|
||||
"oldfieldtype": "Section Break",
|
||||
"permlevel": 0
|
||||
},
|
||||
{
|
||||
"description": "per hour",
|
||||
"fieldname": "hour_rate_electricity",
|
||||
"fieldtype": "Float",
|
||||
"label": "Electricity Cost",
|
||||
"oldfieldname": "hour_rate_electricity",
|
||||
"oldfieldtype": "Currency",
|
||||
"permlevel": 0
|
||||
},
|
||||
{
|
||||
"description": "per hour",
|
||||
"fieldname": "hour_rate_consumable",
|
||||
"fieldtype": "Float",
|
||||
"label": "Consumable Cost",
|
||||
"oldfieldname": "hour_rate_consumable",
|
||||
"oldfieldtype": "Currency",
|
||||
"permlevel": 0
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_11",
|
||||
"fieldtype": "Column Break",
|
||||
"permlevel": 0,
|
||||
"precision": ""
|
||||
},
|
||||
{
|
||||
"description": "per hour",
|
||||
"fieldname": "hour_rate_rent",
|
||||
"fieldtype": "Float",
|
||||
"label": "Rent Cost",
|
||||
"oldfieldname": "hour_rate_rent",
|
||||
"oldfieldtype": "Currency",
|
||||
"permlevel": 0
|
||||
},
|
||||
{
|
||||
"description": "Wages per hour",
|
||||
"fieldname": "hour_rate_labour",
|
||||
"fieldtype": "Float",
|
||||
"label": "Wages",
|
||||
"oldfieldname": "hour_rate_labour",
|
||||
"oldfieldtype": "Currency",
|
||||
"permlevel": 0,
|
||||
"reqd": 0
|
||||
},
|
||||
},
|
||||
{
|
||||
"fieldname": "capacity_units",
|
||||
"fieldtype": "Select",
|
||||
"hidden": 1,
|
||||
"in_list_view": 1,
|
||||
"label": "Capacity Units",
|
||||
"oldfieldname": "capacity_units",
|
||||
"oldfieldtype": "Select",
|
||||
"options": "\nUnits/Shifts\nUnits/Hour",
|
||||
"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
|
||||
},
|
||||
{
|
||||
"fieldname": "over_heads",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Overheads",
|
||||
"oldfieldtype": "Section Break",
|
||||
"permlevel": 0
|
||||
},
|
||||
{
|
||||
"description": "Electricity cost per hour",
|
||||
"fieldname": "hour_rate_electricity",
|
||||
"fieldtype": "Float",
|
||||
"label": "Electricity Cost",
|
||||
"oldfieldname": "hour_rate_electricity",
|
||||
"oldfieldtype": "Currency",
|
||||
"permlevel": 0
|
||||
},
|
||||
{
|
||||
"description": "Consumable cost per hour",
|
||||
"fieldname": "hour_rate_consumable",
|
||||
"fieldtype": "Float",
|
||||
"label": "Consumable Cost",
|
||||
"oldfieldname": "hour_rate_consumable",
|
||||
"oldfieldtype": "Currency",
|
||||
"permlevel": 0
|
||||
},
|
||||
{
|
||||
"description": "Rent per hour",
|
||||
"fieldname": "hour_rate_rent",
|
||||
"fieldtype": "Float",
|
||||
"label": "Rent Cost",
|
||||
"oldfieldname": "hour_rate_rent",
|
||||
"oldfieldtype": "Currency",
|
||||
"permlevel": 0
|
||||
},
|
||||
{
|
||||
"fieldname": "overhead",
|
||||
"fieldtype": "Float",
|
||||
"label": "Overhead",
|
||||
"oldfieldname": "overhead",
|
||||
"oldfieldtype": "Currency",
|
||||
"permlevel": 0,
|
||||
"description": "per hour",
|
||||
"fieldname": "hour_rate",
|
||||
"fieldtype": "Float",
|
||||
"label": "Net Hour Rate",
|
||||
"oldfieldname": "hour_rate",
|
||||
"oldfieldtype": "Currency",
|
||||
"permlevel": 0,
|
||||
"read_only": 1
|
||||
},
|
||||
},
|
||||
{
|
||||
"fieldname": "hour_rate_section_break",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Hour Rate",
|
||||
"oldfieldtype": "Section Break",
|
||||
"permlevel": 0
|
||||
},
|
||||
"fieldname": "operation_timings",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Operation Timings",
|
||||
"permlevel": 0,
|
||||
"precision": ""
|
||||
},
|
||||
{
|
||||
"fieldname": "hour_rate",
|
||||
"fieldtype": "Float",
|
||||
"label": "Hour Rate",
|
||||
"oldfieldname": "hour_rate",
|
||||
"oldfieldtype": "Currency",
|
||||
"permlevel": 0,
|
||||
"read_only": 1
|
||||
"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_by": "Administrator",
|
||||
"module": "Manufacturing",
|
||||
"name": "Workstation",
|
||||
"owner": "Administrator",
|
||||
],
|
||||
"icon": "icon-wrench",
|
||||
"idx": 1,
|
||||
"modified": "2014-12-23 15:27:58.477925",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Manufacturing",
|
||||
"name": "Workstation",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"apply_user_permissions": 1,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"permlevel": 0,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Manufacturing User",
|
||||
"submit": 0,
|
||||
"apply_user_permissions": 1,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"permlevel": 0,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Manufacturing User",
|
||||
"submit": 0,
|
||||
"write": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
@ -3,21 +3,77 @@
|
||||
|
||||
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`
|
||||
bom_list = frappe.db.sql("""select DISTINCT parent from `tabBOM Operation`
|
||||
where workstation = %s""", self.name)
|
||||
for bom_no in bom_list:
|
||||
frappe.db.sql("""update `tabBOM Operation` set hour_rate = %s
|
||||
where parent = %s and workstation = %s""",
|
||||
frappe.db.sql("""update `tabBOM Operation` set hour_rate = %s
|
||||
where parent = %s and workstation = %s""",
|
||||
(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.update_bom_operation()
|
||||
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):
|
||||
@ -16,6 +22,60 @@ class TestTimeLog(unittest.TestCase):
|
||||
self.assertRaises(OverlapError, ts.insert)
|
||||
|
||||
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…
Reference in New Issue
Block a user