fix: total time calculation

This commit is contained in:
Rohit Waghchaure 2021-03-17 14:03:12 +05:30
parent 57307443f0
commit 2330c41cca
18 changed files with 324 additions and 171 deletions

View File

@ -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);
});

View File

@ -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",

View File

@ -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:

View File

@ -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) {

View File

@ -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",

View File

@ -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()

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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]]
}
};
});
}
});

View File

@ -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):

View File

@ -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
});

View File

@ -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",

View File

@ -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:

View File

@ -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
}
}
}
},
]
};

View File

@ -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"
},
}
]

View File

@ -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):

View File

@ -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",