refactor: separate table added to track scheduling in the job card
This commit is contained in:
parent
9a993b0364
commit
497c83eb7e
@ -9,39 +9,40 @@
|
|||||||
"naming_series",
|
"naming_series",
|
||||||
"work_order",
|
"work_order",
|
||||||
"bom_no",
|
"bom_no",
|
||||||
|
"production_item",
|
||||||
|
"employee",
|
||||||
"column_break_4",
|
"column_break_4",
|
||||||
"posting_date",
|
"posting_date",
|
||||||
"company",
|
"company",
|
||||||
"production_section",
|
|
||||||
"production_item",
|
|
||||||
"item_name",
|
|
||||||
"for_quantity",
|
"for_quantity",
|
||||||
"serial_and_batch_bundle",
|
|
||||||
"serial_no",
|
|
||||||
"column_break_12",
|
|
||||||
"wip_warehouse",
|
|
||||||
"quality_inspection_template",
|
|
||||||
"quality_inspection",
|
|
||||||
"project",
|
|
||||||
"batch_no",
|
|
||||||
"operation_section_section",
|
|
||||||
"operation",
|
|
||||||
"operation_row_number",
|
|
||||||
"column_break_18",
|
|
||||||
"workstation_type",
|
|
||||||
"workstation",
|
|
||||||
"employee",
|
|
||||||
"section_break_21",
|
|
||||||
"sub_operations",
|
|
||||||
"timing_detail",
|
|
||||||
"expected_start_date",
|
|
||||||
"expected_end_date",
|
|
||||||
"time_logs",
|
|
||||||
"section_break_13",
|
|
||||||
"total_completed_qty",
|
"total_completed_qty",
|
||||||
"process_loss_qty",
|
"process_loss_qty",
|
||||||
"column_break_15",
|
"scheduled_time_section",
|
||||||
|
"expected_start_date",
|
||||||
|
"time_required",
|
||||||
|
"column_break_jkir",
|
||||||
|
"expected_end_date",
|
||||||
|
"section_break_05am",
|
||||||
|
"scheduled_time_logs",
|
||||||
|
"timing_detail",
|
||||||
|
"time_logs",
|
||||||
|
"section_break_13",
|
||||||
|
"actual_start_date",
|
||||||
"total_time_in_mins",
|
"total_time_in_mins",
|
||||||
|
"column_break_15",
|
||||||
|
"actual_end_date",
|
||||||
|
"production_section",
|
||||||
|
"operation",
|
||||||
|
"wip_warehouse",
|
||||||
|
"column_break_12",
|
||||||
|
"workstation_type",
|
||||||
|
"workstation",
|
||||||
|
"quality_inspection_section",
|
||||||
|
"quality_inspection_template",
|
||||||
|
"column_break_fcmp",
|
||||||
|
"quality_inspection",
|
||||||
|
"section_break_21",
|
||||||
|
"sub_operations",
|
||||||
"section_break_8",
|
"section_break_8",
|
||||||
"items",
|
"items",
|
||||||
"scrap_items_section",
|
"scrap_items_section",
|
||||||
@ -53,18 +54,25 @@
|
|||||||
"hour_rate",
|
"hour_rate",
|
||||||
"for_operation",
|
"for_operation",
|
||||||
"more_information",
|
"more_information",
|
||||||
"operation_id",
|
"project",
|
||||||
"sequence_id",
|
"item_name",
|
||||||
"transferred_qty",
|
"transferred_qty",
|
||||||
"requested_qty",
|
"requested_qty",
|
||||||
"status",
|
"status",
|
||||||
"column_break_20",
|
"column_break_20",
|
||||||
|
"operation_row_number",
|
||||||
|
"operation_id",
|
||||||
|
"sequence_id",
|
||||||
"remarks",
|
"remarks",
|
||||||
|
"serial_and_batch_bundle",
|
||||||
|
"batch_no",
|
||||||
|
"serial_no",
|
||||||
"barcode",
|
"barcode",
|
||||||
"job_started",
|
"job_started",
|
||||||
"started_time",
|
"started_time",
|
||||||
"current_time",
|
"current_time",
|
||||||
"amended_from"
|
"amended_from",
|
||||||
|
"connections_tab"
|
||||||
],
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
@ -134,7 +142,7 @@
|
|||||||
{
|
{
|
||||||
"fieldname": "timing_detail",
|
"fieldname": "timing_detail",
|
||||||
"fieldtype": "Section Break",
|
"fieldtype": "Section Break",
|
||||||
"label": "Timing Detail"
|
"label": "Actual Time"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 1,
|
"allow_bulk_edit": 1,
|
||||||
@ -167,7 +175,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "section_break_8",
|
"fieldname": "section_break_8",
|
||||||
"fieldtype": "Section Break",
|
"fieldtype": "Tab Break",
|
||||||
"label": "Raw Materials"
|
"label": "Raw Materials"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -179,7 +187,7 @@
|
|||||||
{
|
{
|
||||||
"collapsible": 1,
|
"collapsible": 1,
|
||||||
"fieldname": "more_information",
|
"fieldname": "more_information",
|
||||||
"fieldtype": "Section Break",
|
"fieldtype": "Tab Break",
|
||||||
"label": "More Information"
|
"label": "More Information"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -264,10 +272,9 @@
|
|||||||
"reqd": 1
|
"reqd": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"collapsible": 1,
|
|
||||||
"fieldname": "production_section",
|
"fieldname": "production_section",
|
||||||
"fieldtype": "Section Break",
|
"fieldtype": "Tab Break",
|
||||||
"label": "Production"
|
"label": "Operation & Workstation"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "column_break_12",
|
"fieldname": "column_break_12",
|
||||||
@ -331,19 +338,11 @@
|
|||||||
"options": "Job Card Operation",
|
"options": "Job Card Operation",
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"fieldname": "operation_section_section",
|
|
||||||
"fieldtype": "Section Break",
|
|
||||||
"label": "Operation Section"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldname": "column_break_18",
|
|
||||||
"fieldtype": "Column Break"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"fieldname": "section_break_21",
|
"fieldname": "section_break_21",
|
||||||
"fieldtype": "Section Break",
|
"fieldtype": "Tab Break",
|
||||||
"hide_border": 1
|
"hide_border": 1,
|
||||||
|
"label": "Sub Operations"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"depends_on": "is_corrective_job_card",
|
"depends_on": "is_corrective_job_card",
|
||||||
@ -355,7 +354,7 @@
|
|||||||
"collapsible": 1,
|
"collapsible": 1,
|
||||||
"depends_on": "is_corrective_job_card",
|
"depends_on": "is_corrective_job_card",
|
||||||
"fieldname": "corrective_operation_section",
|
"fieldname": "corrective_operation_section",
|
||||||
"fieldtype": "Section Break",
|
"fieldtype": "Tab Break",
|
||||||
"label": "Corrective Operation"
|
"label": "Corrective Operation"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -408,7 +407,7 @@
|
|||||||
{
|
{
|
||||||
"collapsible": 1,
|
"collapsible": 1,
|
||||||
"fieldname": "scrap_items_section",
|
"fieldname": "scrap_items_section",
|
||||||
"fieldtype": "Section Break",
|
"fieldtype": "Tab Break",
|
||||||
"label": "Scrap Items"
|
"label": "Scrap Items"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -451,15 +450,68 @@
|
|||||||
"print_hide": 1
|
"print_hide": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"depends_on": "process_loss_qty",
|
||||||
"fieldname": "process_loss_qty",
|
"fieldname": "process_loss_qty",
|
||||||
"fieldtype": "Float",
|
"fieldtype": "Float",
|
||||||
"label": "Process Loss Qty",
|
"label": "Process Loss Qty",
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "connections_tab",
|
||||||
|
"fieldtype": "Tab Break",
|
||||||
|
"label": "Connections",
|
||||||
|
"show_dashboard": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "scheduled_time_section",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"label": "Scheduled Time"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_jkir",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "time_required",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"label": "Expected Time Required (In Mins)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "section_break_05am",
|
||||||
|
"fieldtype": "Section Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "scheduled_time_logs",
|
||||||
|
"fieldtype": "Table",
|
||||||
|
"label": "Scheduled Time Logs",
|
||||||
|
"options": "Job Card Scheduled Time",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "actual_start_date",
|
||||||
|
"fieldtype": "Datetime",
|
||||||
|
"label": "Actual Start Date",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "actual_end_date",
|
||||||
|
"fieldtype": "Datetime",
|
||||||
|
"label": "Actual End Date",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "quality_inspection_section",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"label": "Quality Inspection"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_fcmp",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2023-06-09 12:04:55.534264",
|
"modified": "2023-06-28 19:23:14.345214",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Manufacturing",
|
"module": "Manufacturing",
|
||||||
"name": "Job Card",
|
"name": "Job Card",
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
# For license information, please see license.txt
|
# For license information, please see license.txt
|
||||||
import datetime
|
import datetime
|
||||||
import json
|
import json
|
||||||
from typing import Optional
|
from collections import OrderedDict
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe import _, bold
|
from frappe import _, bold
|
||||||
@ -164,10 +164,40 @@ class JobCard(Document):
|
|||||||
self.total_completed_qty += row.completed_qty
|
self.total_completed_qty += row.completed_qty
|
||||||
|
|
||||||
def get_overlap_for(self, args, check_next_available_slot=False):
|
def get_overlap_for(self, args, check_next_available_slot=False):
|
||||||
production_capacity = 1
|
time_logs = []
|
||||||
|
|
||||||
|
time_logs.extend(self.get_time_logs(args, "Job Card Time Log", check_next_available_slot))
|
||||||
|
|
||||||
|
time_logs.extend(self.get_time_logs(args, "Job Card Scheduled Time", check_next_available_slot))
|
||||||
|
|
||||||
|
if not time_logs:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
time_logs = sorted(time_logs, key=lambda x: x.get("to_time"))
|
||||||
|
|
||||||
|
production_capacity = 1
|
||||||
|
if self.workstation:
|
||||||
|
production_capacity = (
|
||||||
|
frappe.get_cached_value("Workstation", self.workstation, "production_capacity") or 1
|
||||||
|
)
|
||||||
|
|
||||||
|
if args.get("employee"):
|
||||||
|
# override capacity for employee
|
||||||
|
production_capacity = 1
|
||||||
|
|
||||||
|
if time_logs and production_capacity > len(time_logs):
|
||||||
|
return {}
|
||||||
|
|
||||||
|
if self.workstation_type and time_logs:
|
||||||
|
if workstation_time := self.get_workstation_based_on_available_slot(time_logs):
|
||||||
|
self.workstation = workstation_time.get("workstation")
|
||||||
|
return workstation_time
|
||||||
|
|
||||||
|
return time_logs[-1]
|
||||||
|
|
||||||
|
def get_time_logs(self, args, doctype, check_next_available_slot=False):
|
||||||
jc = frappe.qb.DocType("Job Card")
|
jc = frappe.qb.DocType("Job Card")
|
||||||
jctl = frappe.qb.DocType("Job Card Time Log")
|
jctl = frappe.qb.DocType(doctype)
|
||||||
|
|
||||||
time_conditions = [
|
time_conditions = [
|
||||||
((jctl.from_time < args.from_time) & (jctl.to_time > args.from_time)),
|
((jctl.from_time < args.from_time) & (jctl.to_time > args.from_time)),
|
||||||
@ -181,7 +211,7 @@ class JobCard(Document):
|
|||||||
query = (
|
query = (
|
||||||
frappe.qb.from_(jctl)
|
frappe.qb.from_(jctl)
|
||||||
.from_(jc)
|
.from_(jc)
|
||||||
.select(jc.name.as_("name"), jctl.to_time, jc.workstation, jc.workstation_type)
|
.select(jc.name.as_("name"), jctl.from_time, jctl.to_time, jc.workstation, jc.workstation_type)
|
||||||
.where(
|
.where(
|
||||||
(jctl.parent == jc.name)
|
(jctl.parent == jc.name)
|
||||||
& (Criterion.any(time_conditions))
|
& (Criterion.any(time_conditions))
|
||||||
@ -189,42 +219,51 @@ class JobCard(Document):
|
|||||||
& (jc.name != f"{args.parent or 'No Name'}")
|
& (jc.name != f"{args.parent or 'No Name'}")
|
||||||
& (jc.docstatus < 2)
|
& (jc.docstatus < 2)
|
||||||
)
|
)
|
||||||
.orderby(jctl.to_time, order=frappe.qb.desc)
|
.orderby(jctl.to_time)
|
||||||
)
|
)
|
||||||
|
|
||||||
if self.workstation_type:
|
if self.workstation_type:
|
||||||
query = query.where(jc.workstation_type == self.workstation_type)
|
query = query.where(jc.workstation_type == self.workstation_type)
|
||||||
|
|
||||||
if self.workstation:
|
if self.workstation:
|
||||||
production_capacity = (
|
|
||||||
frappe.get_cached_value("Workstation", self.workstation, "production_capacity") or 1
|
|
||||||
)
|
|
||||||
query = query.where(jc.workstation == self.workstation)
|
query = query.where(jc.workstation == self.workstation)
|
||||||
|
|
||||||
if args.get("employee"):
|
if args.get("employee") and doctype == "Job Card Time Log":
|
||||||
# override capacity for employee
|
|
||||||
production_capacity = 1
|
|
||||||
query = query.where(jctl.employee == args.get("employee"))
|
query = query.where(jctl.employee == args.get("employee"))
|
||||||
|
|
||||||
existing = query.run(as_dict=True)
|
if doctype != "Job Card Time Log":
|
||||||
|
query = query.where(jc.total_time_in_mins == 0)
|
||||||
|
|
||||||
if existing and production_capacity > len(existing):
|
time_logs = query.run(as_dict=True)
|
||||||
return
|
|
||||||
|
|
||||||
if self.workstation_type:
|
return time_logs
|
||||||
if workstation := self.get_workstation_based_on_available_slot(existing):
|
|
||||||
self.workstation = workstation
|
|
||||||
return None
|
|
||||||
|
|
||||||
return existing[0] if existing else None
|
def get_workstation_based_on_available_slot(self, existing_time_logs) -> dict:
|
||||||
|
|
||||||
def get_workstation_based_on_available_slot(self, existing) -> Optional[str]:
|
|
||||||
workstations = get_workstations(self.workstation_type)
|
workstations = get_workstations(self.workstation_type)
|
||||||
if workstations:
|
if workstations:
|
||||||
busy_workstations = [row.workstation for row in existing]
|
busy_workstations = self.time_slot_wise_busy_workstations(existing_time_logs)
|
||||||
for workstation in workstations:
|
for time_slot in busy_workstations:
|
||||||
if workstation not in busy_workstations:
|
available_workstations = sorted(list(set(workstations) - set(busy_workstations[time_slot])))
|
||||||
return workstation
|
if available_workstations:
|
||||||
|
return frappe._dict(
|
||||||
|
{
|
||||||
|
"workstation": available_workstations[0],
|
||||||
|
"planned_start_time": get_datetime(time_slot[0]),
|
||||||
|
"to_time": get_datetime(time_slot[1]),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
return frappe._dict({})
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def time_slot_wise_busy_workstations(existing_time_logs) -> dict:
|
||||||
|
time_slot = OrderedDict()
|
||||||
|
for row in existing_time_logs:
|
||||||
|
from_time = get_datetime(row.from_time).strftime("%Y-%m-%d %H:%M")
|
||||||
|
to_time = get_datetime(row.to_time).strftime("%Y-%m-%d %H:%M")
|
||||||
|
time_slot.setdefault((from_time, to_time), []).append(row.workstation)
|
||||||
|
|
||||||
|
return time_slot
|
||||||
|
|
||||||
def schedule_time_logs(self, row):
|
def schedule_time_logs(self, row):
|
||||||
row.remaining_time_in_mins = row.time_in_mins
|
row.remaining_time_in_mins = row.time_in_mins
|
||||||
@ -237,11 +276,17 @@ class JobCard(Document):
|
|||||||
def validate_overlap_for_workstation(self, args, row):
|
def validate_overlap_for_workstation(self, args, row):
|
||||||
# get the last record based on the to time from the job card
|
# get the last record based on the to time from the job card
|
||||||
data = self.get_overlap_for(args, check_next_available_slot=True)
|
data = self.get_overlap_for(args, check_next_available_slot=True)
|
||||||
if data:
|
if not self.workstation:
|
||||||
if not self.workstation:
|
workstations = get_workstations(self.workstation_type)
|
||||||
self.workstation = data.workstation
|
if workstations:
|
||||||
|
# Get the first workstation
|
||||||
|
self.workstation = workstations[0]
|
||||||
|
|
||||||
row.planned_start_time = get_datetime(data.to_time + get_mins_between_operations())
|
if data:
|
||||||
|
if data.get("planned_start_time"):
|
||||||
|
row.planned_start_time = get_datetime(data.planned_start_time)
|
||||||
|
else:
|
||||||
|
row.planned_start_time = get_datetime(data.to_time + get_mins_between_operations())
|
||||||
|
|
||||||
def check_workstation_time(self, row):
|
def check_workstation_time(self, row):
|
||||||
workstation_doc = frappe.get_cached_doc("Workstation", self.workstation)
|
workstation_doc = frappe.get_cached_doc("Workstation", self.workstation)
|
||||||
@ -410,7 +455,7 @@ class JobCard(Document):
|
|||||||
|
|
||||||
def update_time_logs(self, row):
|
def update_time_logs(self, row):
|
||||||
self.append(
|
self.append(
|
||||||
"time_logs",
|
"scheduled_time_logs",
|
||||||
{
|
{
|
||||||
"from_time": row.planned_start_time,
|
"from_time": row.planned_start_time,
|
||||||
"to_time": row.planned_end_time,
|
"to_time": row.planned_end_time,
|
||||||
@ -452,6 +497,7 @@ class JobCard(Document):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def before_save(self):
|
def before_save(self):
|
||||||
|
self.set_expected_and_actual_time()
|
||||||
self.set_process_loss()
|
self.set_process_loss()
|
||||||
|
|
||||||
def on_submit(self):
|
def on_submit(self):
|
||||||
@ -510,6 +556,32 @@ class JobCard(Document):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def set_expected_and_actual_time(self):
|
||||||
|
for child_table, start_field, end_field, time_required in [
|
||||||
|
("scheduled_time_logs", "expected_start_date", "expected_end_date", "time_required"),
|
||||||
|
("time_logs", "actual_start_date", "actual_end_date", "total_time_in_mins"),
|
||||||
|
]:
|
||||||
|
if not self.get(child_table):
|
||||||
|
continue
|
||||||
|
|
||||||
|
time_list = []
|
||||||
|
time_in_mins = 0.0
|
||||||
|
for row in self.get(child_table):
|
||||||
|
time_in_mins += flt(row.get("time_in_mins"))
|
||||||
|
for field in ["from_time", "to_time"]:
|
||||||
|
if row.get(field):
|
||||||
|
time_list.append(get_datetime(row.get(field)))
|
||||||
|
|
||||||
|
if time_list:
|
||||||
|
self.set(start_field, min(time_list))
|
||||||
|
if end_field == "actual_end_date" and not self.time_logs[-1].to_time:
|
||||||
|
self.set(end_field, "")
|
||||||
|
return
|
||||||
|
|
||||||
|
self.set(end_field, max(time_list))
|
||||||
|
|
||||||
|
self.set(time_required, time_in_mins)
|
||||||
|
|
||||||
def set_process_loss(self):
|
def set_process_loss(self):
|
||||||
precision = self.precision("total_completed_qty")
|
precision = self.precision("total_completed_qty")
|
||||||
|
|
||||||
|
@ -541,6 +541,16 @@ class TestJobCard(FrappeTestCase):
|
|||||||
)[0].name
|
)[0].name
|
||||||
|
|
||||||
jc = frappe.get_doc("Job Card", first_job_card)
|
jc = frappe.get_doc("Job Card", first_job_card)
|
||||||
|
for row in jc.scheduled_time_logs:
|
||||||
|
jc.append(
|
||||||
|
"time_logs",
|
||||||
|
{
|
||||||
|
"from_time": row.from_time,
|
||||||
|
"to_time": row.to_time,
|
||||||
|
"time_in_mins": row.time_in_mins,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
jc.time_logs[0].completed_qty = 8
|
jc.time_logs[0].completed_qty = 8
|
||||||
jc.save()
|
jc.save()
|
||||||
jc.submit()
|
jc.submit()
|
||||||
@ -557,11 +567,30 @@ class TestJobCard(FrappeTestCase):
|
|||||||
)[0].name
|
)[0].name
|
||||||
|
|
||||||
jc2 = frappe.get_doc("Job Card", second_job_card)
|
jc2 = frappe.get_doc("Job Card", second_job_card)
|
||||||
|
for row in jc2.scheduled_time_logs:
|
||||||
|
jc2.append(
|
||||||
|
"time_logs",
|
||||||
|
{
|
||||||
|
"from_time": row.from_time,
|
||||||
|
"to_time": row.to_time,
|
||||||
|
"time_in_mins": row.time_in_mins,
|
||||||
|
},
|
||||||
|
)
|
||||||
jc2.time_logs[0].completed_qty = 10
|
jc2.time_logs[0].completed_qty = 10
|
||||||
|
|
||||||
self.assertRaises(frappe.ValidationError, jc2.save)
|
self.assertRaises(frappe.ValidationError, jc2.save)
|
||||||
|
|
||||||
jc2.load_from_db()
|
jc2.load_from_db()
|
||||||
|
for row in jc2.scheduled_time_logs:
|
||||||
|
jc2.append(
|
||||||
|
"time_logs",
|
||||||
|
{
|
||||||
|
"from_time": row.from_time,
|
||||||
|
"to_time": row.to_time,
|
||||||
|
"time_in_mins": row.time_in_mins,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
jc2.time_logs[0].completed_qty = 8
|
jc2.time_logs[0].completed_qty = 8
|
||||||
jc2.save()
|
jc2.save()
|
||||||
jc2.submit()
|
jc2.submit()
|
||||||
|
@ -0,0 +1,45 @@
|
|||||||
|
{
|
||||||
|
"actions": [],
|
||||||
|
"allow_rename": 1,
|
||||||
|
"creation": "2023-06-14 15:23:54.673262",
|
||||||
|
"doctype": "DocType",
|
||||||
|
"editable_grid": 1,
|
||||||
|
"engine": "InnoDB",
|
||||||
|
"field_order": [
|
||||||
|
"from_time",
|
||||||
|
"to_time",
|
||||||
|
"time_in_mins"
|
||||||
|
],
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldname": "from_time",
|
||||||
|
"fieldtype": "Datetime",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "From Time"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "to_time",
|
||||||
|
"fieldtype": "Datetime",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "To Time"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "time_in_mins",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Time (In Mins)"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"index_web_pages_for_search": 1,
|
||||||
|
"istable": 1,
|
||||||
|
"links": [],
|
||||||
|
"modified": "2023-06-14 15:27:03.203045",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "Manufacturing",
|
||||||
|
"name": "Job Card Scheduled Time",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"permissions": [],
|
||||||
|
"sort_field": "modified",
|
||||||
|
"sort_order": "DESC",
|
||||||
|
"states": []
|
||||||
|
}
|
@ -0,0 +1,9 @@
|
|||||||
|
# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
# import frappe
|
||||||
|
from frappe.model.document import Document
|
||||||
|
|
||||||
|
|
||||||
|
class JobCardScheduledTime(Document):
|
||||||
|
pass
|
@ -38,6 +38,16 @@ class TestRouting(FrappeTestCase):
|
|||||||
"Job Card", filters={"work_order": wo_doc.name}, order_by="sequence_id desc"
|
"Job Card", filters={"work_order": wo_doc.name}, order_by="sequence_id desc"
|
||||||
):
|
):
|
||||||
job_card_doc = frappe.get_doc("Job Card", data.name)
|
job_card_doc = frappe.get_doc("Job Card", data.name)
|
||||||
|
for row in job_card_doc.scheduled_time_logs:
|
||||||
|
job_card_doc.append(
|
||||||
|
"time_logs",
|
||||||
|
{
|
||||||
|
"from_time": row.from_time,
|
||||||
|
"to_time": row.to_time,
|
||||||
|
"time_in_mins": row.time_in_mins,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
job_card_doc.time_logs[0].completed_qty = 10
|
job_card_doc.time_logs[0].completed_qty = 10
|
||||||
if job_card_doc.sequence_id != 1:
|
if job_card_doc.sequence_id != 1:
|
||||||
self.assertRaises(OperationSequenceError, job_card_doc.save)
|
self.assertRaises(OperationSequenceError, job_card_doc.save)
|
||||||
|
@ -487,6 +487,16 @@ class TestWorkOrder(FrappeTestCase):
|
|||||||
|
|
||||||
for i, job_card in enumerate(job_cards):
|
for i, job_card in enumerate(job_cards):
|
||||||
doc = frappe.get_doc("Job Card", job_card)
|
doc = frappe.get_doc("Job Card", job_card)
|
||||||
|
for row in doc.scheduled_time_logs:
|
||||||
|
doc.append(
|
||||||
|
"time_logs",
|
||||||
|
{
|
||||||
|
"from_time": row.from_time,
|
||||||
|
"to_time": row.to_time,
|
||||||
|
"time_in_mins": row.time_in_mins,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
doc.time_logs[0].completed_qty = 1
|
doc.time_logs[0].completed_qty = 1
|
||||||
doc.submit()
|
doc.submit()
|
||||||
|
|
||||||
@ -957,7 +967,7 @@ class TestWorkOrder(FrappeTestCase):
|
|||||||
item=item, company=company, planned_start_date=add_days(now(), 60), qty=20, skip_transfer=1
|
item=item, company=company, planned_start_date=add_days(now(), 60), qty=20, skip_transfer=1
|
||||||
)
|
)
|
||||||
job_card = frappe.db.get_value("Job Card", {"work_order": wo_order.name}, "name")
|
job_card = frappe.db.get_value("Job Card", {"work_order": wo_order.name}, "name")
|
||||||
update_job_card(job_card, 10)
|
update_job_card(job_card, 10, 1)
|
||||||
|
|
||||||
stock_entry = frappe.get_doc(make_stock_entry(wo_order.name, "Manufacture", 10))
|
stock_entry = frappe.get_doc(make_stock_entry(wo_order.name, "Manufacture", 10))
|
||||||
for row in stock_entry.items:
|
for row in stock_entry.items:
|
||||||
@ -975,7 +985,7 @@ class TestWorkOrder(FrappeTestCase):
|
|||||||
|
|
||||||
make_job_card(wo_order.name, operations)
|
make_job_card(wo_order.name, operations)
|
||||||
job_card = frappe.db.get_value("Job Card", {"work_order": wo_order.name, "docstatus": 0}, "name")
|
job_card = frappe.db.get_value("Job Card", {"work_order": wo_order.name, "docstatus": 0}, "name")
|
||||||
update_job_card(job_card, 10)
|
update_job_card(job_card, 10, 2)
|
||||||
|
|
||||||
stock_entry = frappe.get_doc(make_stock_entry(wo_order.name, "Manufacture", 10))
|
stock_entry = frappe.get_doc(make_stock_entry(wo_order.name, "Manufacture", 10))
|
||||||
for row in stock_entry.items:
|
for row in stock_entry.items:
|
||||||
@ -1671,9 +1681,32 @@ class TestWorkOrder(FrappeTestCase):
|
|||||||
)
|
)
|
||||||
job_card = frappe.db.get_value("Job Card", {"work_order": wo_order.name}, "name")
|
job_card = frappe.db.get_value("Job Card", {"work_order": wo_order.name}, "name")
|
||||||
job_card_doc = frappe.get_doc("Job Card", job_card)
|
job_card_doc = frappe.get_doc("Job Card", job_card)
|
||||||
|
for row in job_card_doc.scheduled_time_logs:
|
||||||
|
job_card_doc.append(
|
||||||
|
"time_logs",
|
||||||
|
{
|
||||||
|
"from_time": row.from_time,
|
||||||
|
"to_time": row.to_time,
|
||||||
|
"time_in_mins": row.time_in_mins,
|
||||||
|
"completed_qty": 20,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
job_card_doc.save()
|
||||||
|
|
||||||
# Make another Job Card for the same Work Order
|
# Make another Job Card for the same Work Order
|
||||||
job_card2 = frappe.copy_doc(job_card_doc)
|
job_card2 = frappe.copy_doc(job_card_doc)
|
||||||
|
job_card2.append(
|
||||||
|
"time_logs",
|
||||||
|
{
|
||||||
|
"from_time": row.from_time,
|
||||||
|
"to_time": row.to_time,
|
||||||
|
"time_in_mins": row.time_in_mins,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
job_card2.time_logs[0].completed_qty = 20
|
||||||
|
|
||||||
self.assertRaises(frappe.ValidationError, job_card2.save)
|
self.assertRaises(frappe.ValidationError, job_card2.save)
|
||||||
|
|
||||||
frappe.db.set_single_value(
|
frappe.db.set_single_value(
|
||||||
@ -1841,7 +1874,7 @@ def prepare_data_for_backflush_based_on_materials_transferred():
|
|||||||
make_bom(item=item.name, source_warehouse="Stores - _TC", raw_materials=[sn_batch_item_doc.name])
|
make_bom(item=item.name, source_warehouse="Stores - _TC", raw_materials=[sn_batch_item_doc.name])
|
||||||
|
|
||||||
|
|
||||||
def update_job_card(job_card, jc_qty=None):
|
def update_job_card(job_card, jc_qty=None, days=None):
|
||||||
employee = frappe.db.get_value("Employee", {"status": "Active"}, "name")
|
employee = frappe.db.get_value("Employee", {"status": "Active"}, "name")
|
||||||
job_card_doc = frappe.get_doc("Job Card", job_card)
|
job_card_doc = frappe.get_doc("Job Card", job_card)
|
||||||
job_card_doc.set(
|
job_card_doc.set(
|
||||||
@ -1855,15 +1888,32 @@ def update_job_card(job_card, jc_qty=None):
|
|||||||
if jc_qty:
|
if jc_qty:
|
||||||
job_card_doc.for_quantity = jc_qty
|
job_card_doc.for_quantity = jc_qty
|
||||||
|
|
||||||
job_card_doc.append(
|
for row in job_card_doc.scheduled_time_logs:
|
||||||
"time_logs",
|
job_card_doc.append(
|
||||||
{
|
"time_logs",
|
||||||
"from_time": now(),
|
{
|
||||||
"employee": employee,
|
"from_time": row.from_time,
|
||||||
"time_in_mins": 60,
|
"to_time": row.to_time,
|
||||||
"completed_qty": job_card_doc.for_quantity,
|
"employee": employee,
|
||||||
},
|
"time_in_mins": 60,
|
||||||
)
|
"completed_qty": 0.0,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
if not job_card_doc.time_logs and days:
|
||||||
|
planned_start_time = add_days(now(), days=days)
|
||||||
|
job_card_doc.append(
|
||||||
|
"time_logs",
|
||||||
|
{
|
||||||
|
"from_time": planned_start_time,
|
||||||
|
"to_time": add_to_date(planned_start_time, minutes=60),
|
||||||
|
"employee": employee,
|
||||||
|
"time_in_mins": 60,
|
||||||
|
"completed_qty": 0.0,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
job_card_doc.time_logs[0].completed_qty = job_card_doc.for_quantity
|
||||||
|
|
||||||
job_card_doc.submit()
|
job_card_doc.submit()
|
||||||
|
|
||||||
|
@ -519,8 +519,8 @@ class WorkOrder(Document):
|
|||||||
)
|
)
|
||||||
|
|
||||||
if enable_capacity_planning and job_card_doc:
|
if enable_capacity_planning and job_card_doc:
|
||||||
row.planned_start_time = job_card_doc.time_logs[-1].from_time
|
row.planned_start_time = job_card_doc.scheduled_time_logs[-1].from_time
|
||||||
row.planned_end_time = job_card_doc.time_logs[-1].to_time
|
row.planned_end_time = job_card_doc.scheduled_time_logs[-1].to_time
|
||||||
|
|
||||||
if date_diff(row.planned_start_time, original_start_time) > plan_days:
|
if date_diff(row.planned_start_time, original_start_time) > plan_days:
|
||||||
frappe.message_log.pop()
|
frappe.message_log.pop()
|
||||||
|
@ -20,6 +20,8 @@ class WorkstationType(Document):
|
|||||||
|
|
||||||
|
|
||||||
def get_workstations(workstation_type):
|
def get_workstations(workstation_type):
|
||||||
workstations = frappe.get_all("Workstation", filters={"workstation_type": workstation_type})
|
workstations = frappe.get_all(
|
||||||
|
"Workstation", filters={"workstation_type": workstation_type}, order_by="creation"
|
||||||
|
)
|
||||||
|
|
||||||
return [workstation.name for workstation in workstations]
|
return [workstation.name for workstation in workstations]
|
||||||
|
Loading…
Reference in New Issue
Block a user