fix: total time calculation
This commit is contained in:
parent
57307443f0
commit
2330c41cca
@ -71,7 +71,6 @@ frappe.ui.form.on("BOM", {
|
||||
|
||||
refresh: function(frm) {
|
||||
frm.toggle_enable("item", frm.doc.__islocal);
|
||||
toggle_operations(frm);
|
||||
|
||||
frm.set_indicator_formatter('item_code',
|
||||
function(doc) {
|
||||
@ -651,15 +650,8 @@ frappe.ui.form.on("BOM Item", "items_remove", function(frm) {
|
||||
erpnext.bom.calculate_total(frm.doc);
|
||||
});
|
||||
|
||||
var toggle_operations = function(frm) {
|
||||
frm.toggle_display("operations_section", cint(frm.doc.with_operations) == 1);
|
||||
frm.toggle_display("transfer_material_against", cint(frm.doc.with_operations) == 1);
|
||||
frm.toggle_reqd("transfer_material_against", cint(frm.doc.with_operations) == 1);
|
||||
};
|
||||
|
||||
frappe.ui.form.on("BOM", "with_operations", function(frm) {
|
||||
if(!cint(frm.doc.with_operations)) {
|
||||
frm.set_value("operations", []);
|
||||
}
|
||||
toggle_operations(frm);
|
||||
});
|
@ -193,6 +193,7 @@
|
||||
},
|
||||
{
|
||||
"default": "Work Order",
|
||||
"depends_on": "with_operations",
|
||||
"fieldname": "transfer_material_against",
|
||||
"fieldtype": "Select",
|
||||
"label": "Transfer Material Against",
|
||||
@ -235,6 +236,7 @@
|
||||
{
|
||||
"fieldname": "operations_section",
|
||||
"fieldtype": "Section Break",
|
||||
"hide_border": 1,
|
||||
"oldfieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
@ -245,6 +247,7 @@
|
||||
"options": "Routing"
|
||||
},
|
||||
{
|
||||
"depends_on": "with_operations",
|
||||
"fieldname": "operations",
|
||||
"fieldtype": "Table",
|
||||
"label": "Operations",
|
||||
@ -517,7 +520,7 @@
|
||||
"image_field": "image",
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-05-21 12:29:32.634952",
|
||||
"modified": "2021-03-16 12:25:09.081968",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Manufacturing",
|
||||
"name": "BOM",
|
||||
|
@ -590,7 +590,7 @@ class BOM(WebsiteGenerator):
|
||||
self.get_routing()
|
||||
|
||||
def validate_operations(self):
|
||||
if self.with_operations and not self.get('operations'):
|
||||
if self.with_operations and not self.get('operations') and self.docstatus == 1:
|
||||
frappe.throw(_("Operations cannot be left blank"))
|
||||
|
||||
if self.with_operations:
|
||||
|
@ -42,7 +42,7 @@ frappe.ui.form.on('Job Card', {
|
||||
}
|
||||
|
||||
if (frm.doc.docstatus == 1 && !frm.doc.is_corrective_job_card) {
|
||||
frm.trigger('setup_corrective_job_card')
|
||||
frm.trigger('setup_corrective_job_card');
|
||||
}
|
||||
|
||||
frm.set_query("quality_inspection", function() {
|
||||
@ -71,15 +71,27 @@ frappe.ui.form.on('Job Card', {
|
||||
let fields = [
|
||||
{
|
||||
fieldtype: 'Link', label: __('Corrective Operation'), options: 'Operation',
|
||||
fieldname: 'operation', get_query() { return { filters: { "is_corrective_operation": 1 }}}
|
||||
fieldname: 'operation', get_query() {
|
||||
return {
|
||||
filters: {
|
||||
"is_corrective_operation": 1
|
||||
}
|
||||
};
|
||||
}
|
||||
}, {
|
||||
fieldtype: 'Link', label: __('For Operation'), options: 'Operation',
|
||||
fieldname: 'for_operation', get_query() { return { filters: { "name": ["in", operations] }}}
|
||||
fieldname: 'for_operation', get_query() {
|
||||
return {
|
||||
filters: {
|
||||
"name": ["in", operations]
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
frappe.prompt(fields, d => {
|
||||
frm.events.make_corrective_job_card(frm, d.operation, d.for_operation);
|
||||
frm.events.make_corrective_job_card(frm, d.operation, d.for_operation);
|
||||
}, __("Select Corrective Operation"));
|
||||
}, __('Make'));
|
||||
},
|
||||
@ -152,14 +164,18 @@ frappe.ui.form.on('Job Card', {
|
||||
|
||||
if (!frm.doc.started_time && !frm.doc.current_time) {
|
||||
frm.add_custom_button(__("Start Job"), () => {
|
||||
frappe.prompt({fieldtype: 'Table MultiSelect', label: __('Select Employees'),
|
||||
options: "Job Card Time Log", fieldname: 'employees'}, d => {
|
||||
if ((frm.doc.employee && !frm.doc.employee.length) || !frm.doc.employee) {
|
||||
frappe.prompt({fieldtype: 'Table MultiSelect', label: __('Select Employees'),
|
||||
options: "Job Card Time Log", fieldname: 'employees'}, d => {
|
||||
frm.events.start_job(frm, "Work In Progress", d.employees);
|
||||
}, __("Assign Job to Employee"));
|
||||
}, __("Assign Job to Employee"));
|
||||
} else {
|
||||
frm.events.start_job(frm, "Work In Progress", frm.doc.employee);
|
||||
}
|
||||
}).addClass("btn-primary");
|
||||
} else if (frm.doc.status == "On Hold") {
|
||||
frm.add_custom_button(__("Resume Job"), () => {
|
||||
frm.events.start_job(frm, "Resume Job");
|
||||
frm.events.start_job(frm, "Resume Job", frm.doc.employee);
|
||||
}).addClass("btn-primary");
|
||||
} else {
|
||||
frm.add_custom_button(__("Pause Job"), () => {
|
||||
@ -167,10 +183,26 @@ frappe.ui.form.on('Job Card', {
|
||||
});
|
||||
|
||||
frm.add_custom_button(__("Complete Job"), () => {
|
||||
frappe.prompt({fieldtype: 'Float', label: __('Completed Quantity'),
|
||||
fieldname: 'qty', default: frm.doc.for_quantity}, data => {
|
||||
var sub_operations = frm.doc.sub_operations;
|
||||
|
||||
let set_qty = true;
|
||||
if (sub_operations && sub_operations.length > 1) {
|
||||
set_qty = false;
|
||||
let last_op_row = sub_operations[sub_operations.length - 2];
|
||||
|
||||
if (last_op_row.status == 'Complete') {
|
||||
set_qty = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (set_qty) {
|
||||
frappe.prompt({fieldtype: 'Float', label: __('Completed Quantity'),
|
||||
fieldname: 'qty', default: frm.doc.for_quantity}, data => {
|
||||
frm.events.complete_job(frm, "Complete", data.qty);
|
||||
}, __("Enter Value"));
|
||||
} else {
|
||||
frm.events.complete_job(frm, "Complete", 0.0);
|
||||
}
|
||||
}).addClass("btn-primary");
|
||||
}
|
||||
},
|
||||
@ -204,11 +236,11 @@ frappe.ui.form.on('Job Card', {
|
||||
args: args
|
||||
},
|
||||
freeze: true,
|
||||
callback: function (r) {
|
||||
callback: function () {
|
||||
frm.reload_doc();
|
||||
frm.trigger("make_dashboard");
|
||||
}
|
||||
})
|
||||
});
|
||||
},
|
||||
|
||||
update_sub_operation: function(frm, args) {
|
||||
|
@ -16,15 +16,18 @@
|
||||
"production_item",
|
||||
"item_name",
|
||||
"for_quantity",
|
||||
"serial_no",
|
||||
"column_break_12",
|
||||
"wip_warehouse",
|
||||
"quality_inspection",
|
||||
"project",
|
||||
"batch_no",
|
||||
"operation_section_section",
|
||||
"operation",
|
||||
"operation_row_number",
|
||||
"column_break_18",
|
||||
"workstation",
|
||||
"employee",
|
||||
"section_break_21",
|
||||
"sub_operations",
|
||||
"timing_detail",
|
||||
@ -163,8 +166,7 @@
|
||||
"fieldname": "items",
|
||||
"fieldtype": "Table",
|
||||
"label": "Items",
|
||||
"options": "Job Card Item",
|
||||
"read_only": 1
|
||||
"options": "Job Card Item"
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
@ -373,11 +375,28 @@
|
||||
"fieldtype": "Link",
|
||||
"label": "For Operation",
|
||||
"options": "Operation"
|
||||
},
|
||||
{
|
||||
"fieldname": "employee",
|
||||
"fieldtype": "Table MultiSelect",
|
||||
"label": "Employee",
|
||||
"options": "Job Card Time Log"
|
||||
},
|
||||
{
|
||||
"fieldname": "serial_no",
|
||||
"fieldtype": "Small Text",
|
||||
"label": "Serial No"
|
||||
},
|
||||
{
|
||||
"fieldname": "batch_no",
|
||||
"fieldtype": "Link",
|
||||
"label": "Batch No",
|
||||
"options": "Batch"
|
||||
}
|
||||
],
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2021-02-03 20:36:51.826944",
|
||||
"modified": "2021-03-16 15:59:32.766484",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Manufacturing",
|
||||
"name": "Job Card",
|
||||
|
@ -4,9 +4,9 @@
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
import datetime, json
|
||||
import datetime
|
||||
import json
|
||||
from frappe import _, bold
|
||||
from six import string_types
|
||||
from frappe.model.mapper import get_mapped_doc
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils import (flt, cint, time_diff_in_hours, get_datetime, getdate,
|
||||
@ -33,11 +33,10 @@ class JobCard(Document):
|
||||
if self.operation:
|
||||
self.sub_operations = []
|
||||
for row in frappe.get_all("Sub Operation",
|
||||
filters = {"parent": self.operation}, fields=["operation"]):
|
||||
self.append("sub_operations", {
|
||||
"sub_operation": row.operation,
|
||||
"status": "Pending"
|
||||
})
|
||||
filters = {"parent": self.operation}, fields=["operation", "idx"]):
|
||||
row.status = "Pending"
|
||||
row.sub_operation = row.operation
|
||||
self.append("sub_operations", row)
|
||||
|
||||
def validate_time_logs(self):
|
||||
self.total_time_in_mins = 0.0
|
||||
@ -57,11 +56,14 @@ class JobCard(Document):
|
||||
d.time_in_mins = time_diff_in_hours(d.to_time, d.from_time) * 60
|
||||
self.total_time_in_mins += d.time_in_mins
|
||||
|
||||
if d.completed_qty:
|
||||
if d.completed_qty and not self.sub_operations:
|
||||
self.total_completed_qty += d.completed_qty
|
||||
|
||||
self.total_completed_qty = flt(self.total_completed_qty, self.precision("total_completed_qty"))
|
||||
|
||||
for row in self.sub_operations:
|
||||
self.total_completed_qty += row.completed_qty
|
||||
|
||||
def get_overlap_for(self, args, check_next_available_slot=False):
|
||||
production_capacity = 1
|
||||
|
||||
@ -173,6 +175,10 @@ class JobCard(Document):
|
||||
|
||||
def add_time_log(self, args):
|
||||
last_row = []
|
||||
employees = args.employees
|
||||
if isinstance(employees, str):
|
||||
employees = json.loads(employees)
|
||||
|
||||
if self.time_logs and len(self.time_logs) > 0:
|
||||
last_row = self.time_logs[-1]
|
||||
|
||||
@ -186,13 +192,7 @@ class JobCard(Document):
|
||||
"completed_qty": args.get("completed_qty") or 0.0
|
||||
})
|
||||
elif args.get("start_time"):
|
||||
employees = args.employees
|
||||
print(args)
|
||||
if isinstance(employees, string_types):
|
||||
employees = json.loads(employees)
|
||||
|
||||
for name in employees:
|
||||
print(name.get('employee'))
|
||||
self.append("time_logs", {
|
||||
"from_time": get_datetime(args.get("start_time")),
|
||||
"employee": name.get('employee'),
|
||||
@ -200,11 +200,21 @@ class JobCard(Document):
|
||||
"completed_qty": 0.0
|
||||
})
|
||||
|
||||
if not self.employee:
|
||||
self.set_employees(employees)
|
||||
|
||||
if self.status == "On Hold":
|
||||
self.current_time = time_diff_in_seconds(last_row.to_time, last_row.from_time)
|
||||
|
||||
self.save()
|
||||
|
||||
def set_employees(self, employees):
|
||||
for name in employees:
|
||||
self.append('employee', {
|
||||
'employee': name.get('employee'),
|
||||
'completed_qty': 0.0
|
||||
})
|
||||
|
||||
def reset_timer_value(self, args):
|
||||
self.started_time = None
|
||||
|
||||
@ -221,24 +231,41 @@ class JobCard(Document):
|
||||
self.status = args.get("status")
|
||||
|
||||
def update_sub_operation_status(self):
|
||||
if not (self.sub_operations and self.time_logs): return
|
||||
if not (self.sub_operations and self.time_logs):
|
||||
return
|
||||
|
||||
operation_wise_completed_time = {}
|
||||
for time_log in self.time_logs:
|
||||
if time_log.operation not in operation_wise_completed_time:
|
||||
operation_wise_completed_time.setdefault(time_log.operation,
|
||||
frappe._dict({"status": "Pending", "completed_time": 0.0}))
|
||||
frappe._dict({"status": "Pending", "completed_qty":0.0, "completed_time": 0.0, "employee": []}))
|
||||
|
||||
op_row = operation_wise_completed_time[time_log.operation]
|
||||
op_row.status = "Work In Progress" if not time_log.time_in_mins else "Complete"
|
||||
if self.status == 'On Hold':
|
||||
op_row.status = 'Pause'
|
||||
|
||||
op_row.employee.append(time_log.employee)
|
||||
if time_log.time_in_mins:
|
||||
op_row.completed_time += time_log.time_in_mins
|
||||
op_row.completed_qty += time_log.completed_qty
|
||||
|
||||
for row in self.sub_operations:
|
||||
operation_deatils = operation_wise_completed_time.get(row.sub_operation)
|
||||
if operation_deatils:
|
||||
row.status = operation_deatils.status
|
||||
if row.status != 'Complete':
|
||||
row.status = operation_deatils.status
|
||||
|
||||
row.completed_time = operation_deatils.completed_time
|
||||
if operation_deatils.employee:
|
||||
row.completed_time = row.completed_time / len(set(operation_deatils.employee))
|
||||
|
||||
if operation_deatils.completed_qty:
|
||||
row.completed_qty = operation_deatils.completed_qty / len(set(operation_deatils.employee))
|
||||
else:
|
||||
row.status = 'Pending'
|
||||
row.completed_time = 0.0
|
||||
row.completed_qty = 0.0
|
||||
|
||||
def update_time_logs(self, row):
|
||||
self.append("time_logs", {
|
||||
@ -275,6 +302,7 @@ class JobCard(Document):
|
||||
})
|
||||
|
||||
def on_submit(self):
|
||||
self.validate_transfer_qty()
|
||||
self.validate_job_card()
|
||||
self.update_work_order()
|
||||
self.set_transferred_qty()
|
||||
@ -283,7 +311,16 @@ class JobCard(Document):
|
||||
self.update_work_order()
|
||||
self.set_transferred_qty()
|
||||
|
||||
def validate_transfer_qty(self):
|
||||
if self.items and self.transferred_qty < self.for_quantity:
|
||||
frappe.throw(_('Materials needs to be transferred to the work in progress warehouse for the job card {0}')
|
||||
.format(self.name))
|
||||
|
||||
def validate_job_card(self):
|
||||
if self.work_order and frappe.get_cached_value('Work Order', self.work_order, 'status') == 'Stopped':
|
||||
frappe.throw(_("Transaction not allowed against stopped Work Order {0}")
|
||||
.format(get_link_to_form('Work Order', self.work_order)))
|
||||
|
||||
if not self.time_logs:
|
||||
frappe.throw(_("Time logs are required for {0} {1}")
|
||||
.format(bold("Job Card"), get_link_to_form("Job Card", self.name)))
|
||||
@ -299,6 +336,10 @@ class JobCard(Document):
|
||||
if not self.work_order:
|
||||
return
|
||||
|
||||
if self.is_corrective_job_card and not cint(frappe.db.get_single_value('Manufacturing Settings',
|
||||
'add_corrective_operation_cost_in_finished_good_valuation')):
|
||||
return
|
||||
|
||||
for_quantity, time_in_mins = 0, 0
|
||||
from_time_list, to_time_list = [], []
|
||||
|
||||
@ -346,8 +387,8 @@ class JobCard(Document):
|
||||
min(from_time) as start_time, max(to_time) as end_time
|
||||
FROM `tabJob Card` jc, `tabJob Card Time Log` jctl
|
||||
WHERE
|
||||
jctl.parent = jc.name and jc.work_order = %s
|
||||
and jc.operation_id = %s and jc.docstatus = 1
|
||||
jctl.parent = jc.name and jc.work_order = %s and jc.operation_id = %s
|
||||
and jc.docstatus = 1 and IFNULL(jc.is_corrective_job_card, 0) = 0
|
||||
""", (self.work_order, self.operation_id), as_dict=1)
|
||||
|
||||
for data in wo.operations:
|
||||
@ -453,9 +494,11 @@ class JobCard(Document):
|
||||
.format(bold(self.operation), work_order), OperationMismatchError)
|
||||
|
||||
def validate_sequence_id(self):
|
||||
if self.is_corrective_job_card: return
|
||||
if self.is_corrective_job_card:
|
||||
return
|
||||
|
||||
if not (self.work_order and self.sequence_id): return
|
||||
if not (self.work_order and self.sequence_id):
|
||||
return
|
||||
|
||||
current_operation_qty = 0.0
|
||||
data = self.get_current_operation_data()
|
||||
@ -480,7 +523,7 @@ class JobCard(Document):
|
||||
|
||||
@frappe.whitelist()
|
||||
def make_time_log(args):
|
||||
if isinstance(args, string_types):
|
||||
if isinstance(args, str):
|
||||
args = json.loads(args)
|
||||
|
||||
args = frappe._dict(args)
|
||||
@ -632,6 +675,8 @@ def make_corrective_job_card(source_name, operation=None, for_operation=None, ta
|
||||
target.for_operation = for_operation
|
||||
|
||||
target.set('time_logs', [])
|
||||
target.set('employee', [])
|
||||
target.set('items', [])
|
||||
target.get_sub_operations()
|
||||
target.get_required_items()
|
||||
target.validate_time_logs()
|
||||
|
@ -17,8 +17,6 @@
|
||||
"required_qty",
|
||||
"column_break_9",
|
||||
"transferred_qty",
|
||||
"rate",
|
||||
"amount",
|
||||
"allow_alternative_item"
|
||||
],
|
||||
"fields": [
|
||||
@ -27,8 +25,7 @@
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Item Code",
|
||||
"options": "Item",
|
||||
"read_only": 1
|
||||
"options": "Item"
|
||||
},
|
||||
{
|
||||
"fieldname": "source_warehouse",
|
||||
@ -69,8 +66,7 @@
|
||||
"fieldname": "required_qty",
|
||||
"fieldtype": "Float",
|
||||
"in_list_view": 1,
|
||||
"label": "Required Qty",
|
||||
"read_only": 1
|
||||
"label": "Required Qty"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_9",
|
||||
@ -102,25 +98,14 @@
|
||||
"fieldtype": "Float",
|
||||
"label": "Transferred Qty",
|
||||
"no_copy": 1,
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "rate",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Rate",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "amount",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Amount",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2021-02-11 13:50:13.804108",
|
||||
"modified": "2021-04-22 18:50:00.003444",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Manufacturing",
|
||||
"name": "Job Card Item",
|
||||
|
@ -7,7 +7,8 @@
|
||||
"field_order": [
|
||||
"sub_operation",
|
||||
"completed_time",
|
||||
"status"
|
||||
"status",
|
||||
"completed_qty"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
@ -34,12 +35,18 @@
|
||||
"label": "Operation",
|
||||
"options": "Operation",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "completed_qty",
|
||||
"fieldtype": "Float",
|
||||
"label": "Completed Qty",
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-12-14 17:08:25.992957",
|
||||
"modified": "2021-03-16 18:24:35.399593",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Manufacturing",
|
||||
"name": "Job Card Operation",
|
||||
|
@ -27,6 +27,7 @@
|
||||
"overproduction_percentage_for_work_order",
|
||||
"other_settings_section",
|
||||
"update_bom_costs_automatically",
|
||||
"add_corrective_operation_cost_in_finished_good_valuation",
|
||||
"column_break_23",
|
||||
"make_serial_no_batch_from_work_order"
|
||||
],
|
||||
@ -168,13 +169,19 @@
|
||||
"fieldname": "make_serial_no_batch_from_work_order",
|
||||
"fieldtype": "Check",
|
||||
"label": "Make Serial No / Batch from Work Order"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "add_corrective_operation_cost_in_finished_good_valuation",
|
||||
"fieldtype": "Check",
|
||||
"label": "Add Corrective Operation Cost in Finished Good Valuation"
|
||||
}
|
||||
],
|
||||
"icon": "icon-wrench",
|
||||
"index_web_pages_for_search": 1,
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2020-12-08 13:37:40.325838",
|
||||
"modified": "2021-03-16 15:54:38.967341",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Manufacturing",
|
||||
"name": "Manufacturing Settings",
|
||||
|
@ -2,5 +2,13 @@
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on('Operation', {
|
||||
|
||||
setup: function(frm) {
|
||||
frm.set_query('operation', 'sub_operations', function() {
|
||||
return {
|
||||
filters: {
|
||||
'name': ['not in', [frm.doc.name]]
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
});
|
@ -5,7 +5,6 @@ from __future__ import unicode_literals
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.utils import flt
|
||||
from frappe.model.document import Document
|
||||
|
||||
class Operation(Document):
|
||||
|
@ -189,35 +189,41 @@ frappe.ui.form.on("Work Order", {
|
||||
const dialog = frappe.prompt({fieldname: 'operations', fieldtype: 'Table', label: __('Operations'),
|
||||
fields: [
|
||||
{
|
||||
fieldtype:'Link',
|
||||
fieldname:'operation',
|
||||
fieldtype: 'Link',
|
||||
fieldname: 'operation',
|
||||
label: __('Operation'),
|
||||
read_only:1,
|
||||
in_list_view:1
|
||||
read_only: 1,
|
||||
in_list_view: 1
|
||||
},
|
||||
{
|
||||
fieldtype:'Link',
|
||||
fieldname:'workstation',
|
||||
fieldtype: 'Link',
|
||||
fieldname: 'workstation',
|
||||
label: __('Workstation'),
|
||||
read_only:1,
|
||||
in_list_view:1
|
||||
read_only: 1,
|
||||
in_list_view: 1
|
||||
},
|
||||
{
|
||||
fieldtype:'Data',
|
||||
fieldname:'name',
|
||||
fieldtype: 'Data',
|
||||
fieldname: 'name',
|
||||
label: __('Operation Id')
|
||||
},
|
||||
{
|
||||
fieldtype:'Float',
|
||||
fieldname:'pending_qty',
|
||||
fieldtype: 'Float',
|
||||
fieldname: 'pending_qty',
|
||||
label: __('Pending Qty'),
|
||||
},
|
||||
{
|
||||
fieldtype:'Float',
|
||||
fieldname:'qty',
|
||||
fieldtype: 'Float',
|
||||
fieldname: 'qty',
|
||||
label: __('Quantity to Manufacture'),
|
||||
read_only:0,
|
||||
in_list_view:1,
|
||||
read_only: 0,
|
||||
in_list_view: 1,
|
||||
},
|
||||
{
|
||||
fieldtype: 'Float',
|
||||
fieldname: 'batch_size',
|
||||
label: __('Batch Size'),
|
||||
read_only: 1
|
||||
},
|
||||
],
|
||||
data: operations_data,
|
||||
@ -228,9 +234,13 @@ frappe.ui.form.on("Work Order", {
|
||||
}, function(data) {
|
||||
frappe.call({
|
||||
method: "erpnext.manufacturing.doctype.work_order.work_order.make_job_card",
|
||||
freeze: true,
|
||||
args: {
|
||||
work_order: frm.doc.name,
|
||||
operations: data.operations,
|
||||
},
|
||||
callback: function() {
|
||||
frm.reload_doc();
|
||||
}
|
||||
});
|
||||
}, __("Job Card"), __("Create"));
|
||||
@ -247,6 +257,7 @@ frappe.ui.form.on("Work Order", {
|
||||
'name': data.name,
|
||||
'operation': data.operation,
|
||||
'workstation': data.workstation,
|
||||
'batch_size': data.batch_size,
|
||||
'qty': pending_qty,
|
||||
'pending_qty': pending_qty
|
||||
});
|
||||
|
@ -527,7 +527,8 @@
|
||||
"depends_on": "has_serial_no",
|
||||
"fieldname": "serial_no",
|
||||
"fieldtype": "Small Text",
|
||||
"label": "Serial Nos"
|
||||
"label": "Serial Nos",
|
||||
"no_copy": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
@ -552,7 +553,7 @@
|
||||
"image_field": "image",
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2021-03-16 13:27:51.116484",
|
||||
"modified": "2021-06-20 15:19:14.902699",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Manufacturing",
|
||||
"name": "Work Order",
|
||||
|
@ -27,9 +27,8 @@ class CapacityError(frappe.ValidationError): pass
|
||||
class StockOverProductionError(frappe.ValidationError): pass
|
||||
class OperationTooLongError(frappe.ValidationError): pass
|
||||
class ItemHasVariantError(frappe.ValidationError): pass
|
||||
class SerialNoQtyError(frappe.ValidationError): pass
|
||||
|
||||
from six import string_types
|
||||
class SerialNoQtyError(frappe.ValidationError):
|
||||
pass
|
||||
|
||||
form_grid_templates = {
|
||||
"operations": "templates/form_grid/work_order_grid.html"
|
||||
@ -248,7 +247,7 @@ class WorkOrder(Document):
|
||||
frappe.throw(_("Work-in-Progress Warehouse is required before Submit"))
|
||||
if not self.fg_warehouse:
|
||||
frappe.throw(_("For Warehouse is required before Submit"))
|
||||
|
||||
|
||||
if self.production_plan and frappe.db.exists('Production Plan Item Reference',{'parent':self.production_plan}):
|
||||
self.update_work_order_qty_in_combined_so()
|
||||
else:
|
||||
@ -268,7 +267,7 @@ class WorkOrder(Document):
|
||||
self.update_work_order_qty_in_combined_so()
|
||||
else:
|
||||
self.update_work_order_qty_in_so()
|
||||
|
||||
|
||||
self.delete_job_card()
|
||||
self.update_completed_qty_in_material_request()
|
||||
self.update_planned_qty()
|
||||
@ -277,10 +276,11 @@ class WorkOrder(Document):
|
||||
self.delete_auto_created_batch_and_serial_no()
|
||||
|
||||
def create_serial_no_batch_no(self):
|
||||
if not (self.has_serial_no or self.has_batch_no): return
|
||||
if not (self.has_serial_no or self.has_batch_no):
|
||||
return
|
||||
|
||||
if not cint(frappe.db.get_single_value("Manufacturing Settings",
|
||||
"make_serial_no_batch_from_work_order")): return
|
||||
if not cint(frappe.db.get_single_value("Manufacturing Settings", "make_serial_no_batch_from_work_order")):
|
||||
return
|
||||
|
||||
if self.has_batch_no:
|
||||
self.create_batch_for_finished_good()
|
||||
@ -346,29 +346,17 @@ class WorkOrder(Document):
|
||||
|
||||
for index, row in enumerate(self.operations):
|
||||
qty = self.qty
|
||||
i=0
|
||||
while qty > 0:
|
||||
i += 1
|
||||
if not cint(frappe.db.get_value("Operation",
|
||||
row.operation, "create_job_card_based_on_batch_size")):
|
||||
row.batch_size = self.qty
|
||||
|
||||
job_card_qty = row.batch_size
|
||||
if row.batch_size and qty >= row.batch_size:
|
||||
qty -= row.batch_size
|
||||
elif qty > 0:
|
||||
job_card_qty = qty
|
||||
qty = 0
|
||||
|
||||
if job_card_qty > 0:
|
||||
self.prepare_data_for_job_card(row, job_card_qty, index,
|
||||
qty = split_qty_based_on_batch_size(self, row, qty)
|
||||
if row.job_card_qty > 0:
|
||||
self.prepare_data_for_job_card(row, index,
|
||||
plan_days, enable_capacity_planning)
|
||||
|
||||
planned_end_date = self.operations and self.operations[-1].planned_end_time
|
||||
if planned_end_date:
|
||||
self.db_set("planned_end_date", planned_end_date)
|
||||
|
||||
def prepare_data_for_job_card(self, row, job_card_qty, index, plan_days, enable_capacity_planning):
|
||||
def prepare_data_for_job_card(self, row, index, plan_days, enable_capacity_planning):
|
||||
self.set_operation_start_end_time(index, row)
|
||||
|
||||
if not row.workstation:
|
||||
@ -376,8 +364,8 @@ class WorkOrder(Document):
|
||||
.format(row.idx, row.operation))
|
||||
|
||||
original_start_time = row.planned_start_time
|
||||
job_card_doc = create_job_card(self, row, qty=job_card_qty,
|
||||
enable_capacity_planning=enable_capacity_planning, auto_create=True)
|
||||
job_card_doc = create_job_card(self, row, auto_create=True,
|
||||
enable_capacity_planning=enable_capacity_planning)
|
||||
|
||||
if enable_capacity_planning and job_card_doc:
|
||||
row.planned_start_time = job_card_doc.time_logs[-1].from_time
|
||||
@ -456,7 +444,7 @@ class WorkOrder(Document):
|
||||
work_order_qty = qty[0][0] if qty and qty[0][0] else 0
|
||||
frappe.db.set_value('Sales Order Item',
|
||||
self.sales_order_item, 'work_order_qty', flt(work_order_qty/total_bundle_qty, 2))
|
||||
|
||||
|
||||
def update_work_order_qty_in_combined_so(self):
|
||||
total_bundle_qty = 1
|
||||
if self.product_bundle_item:
|
||||
@ -469,7 +457,7 @@ class WorkOrder(Document):
|
||||
|
||||
prod_plan = frappe.get_doc('Production Plan', self.production_plan)
|
||||
item_reference = frappe.get_value('Production Plan Item', self.production_plan_item, 'sales_order_item')
|
||||
|
||||
|
||||
for plan_reference in prod_plan.prod_plan_references:
|
||||
work_order_qty = 0.0
|
||||
if plan_reference.item_reference == item_reference:
|
||||
@ -477,7 +465,7 @@ class WorkOrder(Document):
|
||||
work_order_qty = flt(plan_reference.qty) / total_bundle_qty
|
||||
frappe.db.set_value('Sales Order Item',
|
||||
plan_reference.sales_order_item, 'work_order_qty', work_order_qty)
|
||||
|
||||
|
||||
def update_completed_qty_in_material_request(self):
|
||||
if self.material_request:
|
||||
frappe.get_doc("Material Request", self.material_request).update_completed_qty([self.material_request_item])
|
||||
@ -761,8 +749,8 @@ class WorkOrder(Document):
|
||||
return bom
|
||||
|
||||
def update_batch_produced_qty(self, stock_entry_doc):
|
||||
if not cint(frappe.db.get_single_value("Manufacturing Settings",
|
||||
"make_serial_no_batch_from_work_order")): return
|
||||
if not cint(frappe.db.get_single_value("Manufacturing Settings", "make_serial_no_batch_from_work_order")):
|
||||
return
|
||||
|
||||
for row in stock_entry_doc.items:
|
||||
if row.batch_no and (row.is_finished_item or row.is_scrap_item):
|
||||
@ -848,7 +836,7 @@ def make_work_order(bom_no, item, qty=0, project=None, variant_items=None):
|
||||
return wo_doc
|
||||
|
||||
def add_variant_item(variant_items, wo_doc, bom_no, table_name="items"):
|
||||
if isinstance(variant_items, string_types):
|
||||
if isinstance(variant_items, str):
|
||||
variant_items = json.loads(variant_items)
|
||||
|
||||
for item in variant_items:
|
||||
@ -970,13 +958,47 @@ def query_sales_order(production_item):
|
||||
|
||||
@frappe.whitelist()
|
||||
def make_job_card(work_order, operations):
|
||||
if isinstance(operations, string_types):
|
||||
if isinstance(operations, str):
|
||||
operations = json.loads(operations)
|
||||
|
||||
work_order = frappe.get_doc('Work Order', work_order)
|
||||
for row in operations:
|
||||
row = frappe._dict(row)
|
||||
validate_operation_data(row)
|
||||
create_job_card(work_order, row, row.get("qty"), auto_create=True)
|
||||
qty = row.get("qty")
|
||||
while qty > 0:
|
||||
qty = split_qty_based_on_batch_size(work_order, row, qty)
|
||||
if row.job_card_qty > 0:
|
||||
create_job_card(work_order, row, auto_create=True)
|
||||
|
||||
def split_qty_based_on_batch_size(wo_doc, row, qty):
|
||||
if not cint(frappe.db.get_value("Operation",
|
||||
row.operation, "create_job_card_based_on_batch_size")):
|
||||
row.batch_size = row.get("qty") or wo_doc.qty
|
||||
|
||||
row.job_card_qty = row.batch_size
|
||||
if row.batch_size and qty >= row.batch_size:
|
||||
qty -= row.batch_size
|
||||
elif qty > 0:
|
||||
row.job_card_qty = qty
|
||||
qty = 0
|
||||
|
||||
get_serial_nos_for_job_card(row, wo_doc)
|
||||
|
||||
return qty
|
||||
|
||||
def get_serial_nos_for_job_card(row, wo_doc):
|
||||
if not wo_doc.serial_no:
|
||||
return
|
||||
|
||||
serial_nos = get_serial_nos(wo_doc.serial_no)
|
||||
used_serial_nos = []
|
||||
for d in frappe.get_all('Job Card', fields=['serial_no'],
|
||||
filters={'docstatus': ('<', 2), 'work_order': wo_doc.name, 'operation_id': row.name}):
|
||||
used_serial_nos.extend(get_serial_nos(d.serial_no))
|
||||
|
||||
serial_nos = sorted(list(set(serial_nos) - set(used_serial_nos)))
|
||||
row.serial_no = '\n'.join(serial_nos[0:row.job_card_qty])
|
||||
|
||||
def validate_operation_data(row):
|
||||
if row.get("qty") <= 0:
|
||||
@ -995,21 +1017,22 @@ def validate_operation_data(row):
|
||||
)
|
||||
)
|
||||
|
||||
def create_job_card(work_order, row, qty=0, enable_capacity_planning=False, auto_create=False):
|
||||
def create_job_card(work_order, row, enable_capacity_planning=False, auto_create=False):
|
||||
doc = frappe.new_doc("Job Card")
|
||||
doc.update({
|
||||
'work_order': work_order.name,
|
||||
'operation': row.get("operation"),
|
||||
'workstation': row.get("workstation"),
|
||||
'posting_date': nowdate(),
|
||||
'for_quantity': qty or work_order.get('qty', 0),
|
||||
'for_quantity': row.job_card_qty or work_order.get('qty', 0),
|
||||
'operation_id': row.get("name"),
|
||||
'bom_no': work_order.bom_no,
|
||||
'project': work_order.project,
|
||||
'company': work_order.company,
|
||||
'sequence_id': row.get("sequence_id"),
|
||||
'wip_warehouse': work_order.wip_warehouse,
|
||||
"hour_rate": row.get("hour_rate")
|
||||
'hour_rate': row.get("hour_rate"),
|
||||
'serial_no': row.get("serial_no")
|
||||
})
|
||||
|
||||
if work_order.transfer_material_against == 'Job Card' and not work_order.skip_transfer:
|
||||
|
@ -65,5 +65,41 @@ frappe.query_reports["Cost of Poor Quality Report"] = {
|
||||
fieldtype: "Link",
|
||||
options: "Workstation"
|
||||
},
|
||||
{
|
||||
label: __("Item"),
|
||||
fieldname: "production_item",
|
||||
fieldtype: "Link",
|
||||
options: "Item"
|
||||
},
|
||||
{
|
||||
label: __("Serial No"),
|
||||
fieldname: "serial_no",
|
||||
fieldtype: "Link",
|
||||
options: "Serial No",
|
||||
depends_on: "eval: doc.production_item",
|
||||
get_query: function() {
|
||||
var item_code = frappe.query_report.get_filter_value('production_item');
|
||||
return {
|
||||
filters: {
|
||||
item_code: item_code
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
label: __("Batch No"),
|
||||
fieldname: "batch_no",
|
||||
fieldtype: "Link",
|
||||
options: "Batch No",
|
||||
depends_on: "eval: doc.production_item",
|
||||
get_query: function() {
|
||||
var item_code = frappe.query_report.get_filter_value('production_item');
|
||||
return {
|
||||
filters: {
|
||||
item: item_code
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
]
|
||||
};
|
||||
|
@ -20,7 +20,7 @@ def get_data(report_filters):
|
||||
if operations:
|
||||
operations = [d.name for d in operations]
|
||||
fields = ["production_item as item_code", "item_name", "work_order", "operation",
|
||||
"workstation", "total_time_in_mins", "name", "hour_rate"]
|
||||
"workstation", "total_time_in_mins", "name", "hour_rate", "serial_no", "batch_no"]
|
||||
|
||||
filters = get_filters(report_filters, operations)
|
||||
|
||||
@ -30,15 +30,18 @@ def get_data(report_filters):
|
||||
for row in job_cards:
|
||||
row.operating_cost = flt(row.hour_rate) * (flt(row.total_time_in_mins) / 60.0)
|
||||
update_raw_material_cost(row, report_filters)
|
||||
update_time_details(row, report_filters, data)
|
||||
data.append(row)
|
||||
|
||||
return data
|
||||
|
||||
def get_filters(report_filters, operations):
|
||||
filters = {"docstatus": 1, "operation": ("in", operations), "is_corrective_job_card": 1}
|
||||
for field in ["name", "work_order", "operation", "workstation", "company"]:
|
||||
for field in ["name", "work_order", "operation", "workstation", "company", "serial_no", "batch_no", "production_item"]:
|
||||
if report_filters.get(field):
|
||||
filters[field] = report_filters.get(field)
|
||||
if field != 'serial_no':
|
||||
filters[field] = report_filters.get(field)
|
||||
else:
|
||||
filters[field] = ('like', '% {} %'.format(report_filters.get(field)))
|
||||
|
||||
return filters
|
||||
|
||||
@ -48,24 +51,6 @@ def update_raw_material_cost(row, filters):
|
||||
filters={"parent": row.name, "docstatus": 1}):
|
||||
row.rm_cost += data.amount
|
||||
|
||||
def update_time_details(row, filters, data):
|
||||
args = frappe._dict({"item_code": "", "item_name": "", "name": "", "work_order":"",
|
||||
"operation": "", "workstation":"", "operating_cost": "", "rm_cost": "", "total_time_in_mins": ""})
|
||||
|
||||
i=0
|
||||
for time_log in frappe.get_all("Job Card Time Log",
|
||||
fields = ["from_time", "to_time", "time_in_mins"],
|
||||
filters={"parent": row.name, "docstatus": 1,
|
||||
"from_time": (">=", filters.from_date), "to_time": ("<=", filters.to_date)}):
|
||||
|
||||
if i==0:
|
||||
i += 1
|
||||
row.update(time_log)
|
||||
data.append(row)
|
||||
else:
|
||||
args.update(time_log)
|
||||
data.append(args)
|
||||
|
||||
def get_columns(filters):
|
||||
return [
|
||||
{
|
||||
@ -102,6 +87,18 @@ def get_columns(filters):
|
||||
"options": "Operation",
|
||||
"width": "100"
|
||||
},
|
||||
{
|
||||
"label": _("Serial No"),
|
||||
"fieldtype": "Data",
|
||||
"fieldname": "serial_no",
|
||||
"width": "100"
|
||||
},
|
||||
{
|
||||
"label": _("Batch No"),
|
||||
"fieldtype": "Data",
|
||||
"fieldname": "batch_no",
|
||||
"width": "100"
|
||||
},
|
||||
{
|
||||
"label": _("Workstation"),
|
||||
"fieldtype": "Link",
|
||||
@ -126,23 +123,5 @@ def get_columns(filters):
|
||||
"fieldtype": "Float",
|
||||
"fieldname": "total_time_in_mins",
|
||||
"width": "100"
|
||||
},
|
||||
{
|
||||
"label": _("From Time"),
|
||||
"fieldtype": "Datetime",
|
||||
"fieldname": "from_time",
|
||||
"width": "100"
|
||||
},
|
||||
{
|
||||
"label": _("To Time"),
|
||||
"fieldtype": "Datetime",
|
||||
"fieldname": "to_time",
|
||||
"width": "100"
|
||||
},
|
||||
{
|
||||
"label": _("Time in Mins"),
|
||||
"fieldtype": "Float",
|
||||
"fieldname": "time_in_mins",
|
||||
"width": "100"
|
||||
},
|
||||
}
|
||||
]
|
@ -1097,7 +1097,8 @@ class StockEntry(StockController):
|
||||
|
||||
def set_batchwise_finished_goods(self, args, item):
|
||||
qty = flt(self.fg_completed_qty)
|
||||
filters = {"reference_name": self.pro_doc.name,
|
||||
filters = {
|
||||
"reference_name": self.pro_doc.name,
|
||||
"reference_doctype": self.pro_doc.doctype,
|
||||
"qty_to_produce": (">", 0)
|
||||
}
|
||||
@ -1106,7 +1107,8 @@ class StockEntry(StockController):
|
||||
|
||||
for row in frappe.get_all("Batch", filters = filters, fields = fields, order_by="creation asc"):
|
||||
batch_qty = flt(row.qty) - flt(row.produced_qty)
|
||||
if not batch_qty: continue
|
||||
if not batch_qty:
|
||||
continue
|
||||
|
||||
if qty <=0:
|
||||
break
|
||||
@ -1701,6 +1703,10 @@ def get_operating_cost_per_unit(work_order=None, bom_no=None):
|
||||
if bom.quantity:
|
||||
operating_cost_per_unit = flt(bom.operating_cost) / flt(bom.quantity)
|
||||
|
||||
if work_order and work_order.produced_qty and cint(frappe.db.get_single_value('Manufacturing Settings',
|
||||
'add_corrective_operation_cost_in_finished_good_valuation')):
|
||||
operating_cost_per_unit += flt(work_order.corrective_operation_cost) / flt(work_order.produced_qty)
|
||||
|
||||
return operating_cost_per_unit
|
||||
|
||||
def get_used_alternative_items(purchase_order=None, work_order=None):
|
||||
|
@ -18,6 +18,7 @@
|
||||
"col_break2",
|
||||
"is_finished_item",
|
||||
"is_scrap_item",
|
||||
"quality_inspection",
|
||||
"subcontracted_item",
|
||||
"section_break_8",
|
||||
"description",
|
||||
@ -69,7 +70,6 @@
|
||||
"putaway_rule",
|
||||
"column_break_51",
|
||||
"reference_purchase_receipt",
|
||||
"quality_inspection",
|
||||
"job_card_item"
|
||||
],
|
||||
"fields": [
|
||||
@ -548,7 +548,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2021-02-11 13:47:50.158754",
|
||||
"modified": "2021-04-22 20:08:23.799715",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Stock Entry Detail",
|
||||
|
Loading…
x
Reference in New Issue
Block a user