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",
|
||||
"work_order",
|
||||
"bom_no",
|
||||
"production_item",
|
||||
"employee",
|
||||
"column_break_4",
|
||||
"posting_date",
|
||||
"company",
|
||||
"production_section",
|
||||
"production_item",
|
||||
"item_name",
|
||||
"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",
|
||||
"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",
|
||||
"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",
|
||||
"items",
|
||||
"scrap_items_section",
|
||||
@ -53,18 +54,25 @@
|
||||
"hour_rate",
|
||||
"for_operation",
|
||||
"more_information",
|
||||
"operation_id",
|
||||
"sequence_id",
|
||||
"project",
|
||||
"item_name",
|
||||
"transferred_qty",
|
||||
"requested_qty",
|
||||
"status",
|
||||
"column_break_20",
|
||||
"operation_row_number",
|
||||
"operation_id",
|
||||
"sequence_id",
|
||||
"remarks",
|
||||
"serial_and_batch_bundle",
|
||||
"batch_no",
|
||||
"serial_no",
|
||||
"barcode",
|
||||
"job_started",
|
||||
"started_time",
|
||||
"current_time",
|
||||
"amended_from"
|
||||
"amended_from",
|
||||
"connections_tab"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
@ -134,7 +142,7 @@
|
||||
{
|
||||
"fieldname": "timing_detail",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Timing Detail"
|
||||
"label": "Actual Time"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 1,
|
||||
@ -167,7 +175,7 @@
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_8",
|
||||
"fieldtype": "Section Break",
|
||||
"fieldtype": "Tab Break",
|
||||
"label": "Raw Materials"
|
||||
},
|
||||
{
|
||||
@ -179,7 +187,7 @@
|
||||
{
|
||||
"collapsible": 1,
|
||||
"fieldname": "more_information",
|
||||
"fieldtype": "Section Break",
|
||||
"fieldtype": "Tab Break",
|
||||
"label": "More Information"
|
||||
},
|
||||
{
|
||||
@ -264,10 +272,9 @@
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"fieldname": "production_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Production"
|
||||
"fieldtype": "Tab Break",
|
||||
"label": "Operation & Workstation"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_12",
|
||||
@ -331,19 +338,11 @@
|
||||
"options": "Job Card Operation",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "operation_section_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Operation Section"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_18",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_21",
|
||||
"fieldtype": "Section Break",
|
||||
"hide_border": 1
|
||||
"fieldtype": "Tab Break",
|
||||
"hide_border": 1,
|
||||
"label": "Sub Operations"
|
||||
},
|
||||
{
|
||||
"depends_on": "is_corrective_job_card",
|
||||
@ -355,7 +354,7 @@
|
||||
"collapsible": 1,
|
||||
"depends_on": "is_corrective_job_card",
|
||||
"fieldname": "corrective_operation_section",
|
||||
"fieldtype": "Section Break",
|
||||
"fieldtype": "Tab Break",
|
||||
"label": "Corrective Operation"
|
||||
},
|
||||
{
|
||||
@ -408,7 +407,7 @@
|
||||
{
|
||||
"collapsible": 1,
|
||||
"fieldname": "scrap_items_section",
|
||||
"fieldtype": "Section Break",
|
||||
"fieldtype": "Tab Break",
|
||||
"label": "Scrap Items"
|
||||
},
|
||||
{
|
||||
@ -451,15 +450,68 @@
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "process_loss_qty",
|
||||
"fieldname": "process_loss_qty",
|
||||
"fieldtype": "Float",
|
||||
"label": "Process Loss Qty",
|
||||
"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,
|
||||
"links": [],
|
||||
"modified": "2023-06-09 12:04:55.534264",
|
||||
"modified": "2023-06-28 19:23:14.345214",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Manufacturing",
|
||||
"name": "Job Card",
|
||||
|
@ -2,7 +2,7 @@
|
||||
# For license information, please see license.txt
|
||||
import datetime
|
||||
import json
|
||||
from typing import Optional
|
||||
from collections import OrderedDict
|
||||
|
||||
import frappe
|
||||
from frappe import _, bold
|
||||
@ -164,10 +164,40 @@ class JobCard(Document):
|
||||
self.total_completed_qty += row.completed_qty
|
||||
|
||||
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")
|
||||
jctl = frappe.qb.DocType("Job Card Time Log")
|
||||
jctl = frappe.qb.DocType(doctype)
|
||||
|
||||
time_conditions = [
|
||||
((jctl.from_time < args.from_time) & (jctl.to_time > args.from_time)),
|
||||
@ -181,7 +211,7 @@ class JobCard(Document):
|
||||
query = (
|
||||
frappe.qb.from_(jctl)
|
||||
.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(
|
||||
(jctl.parent == jc.name)
|
||||
& (Criterion.any(time_conditions))
|
||||
@ -189,42 +219,51 @@ class JobCard(Document):
|
||||
& (jc.name != f"{args.parent or 'No Name'}")
|
||||
& (jc.docstatus < 2)
|
||||
)
|
||||
.orderby(jctl.to_time, order=frappe.qb.desc)
|
||||
.orderby(jctl.to_time)
|
||||
)
|
||||
|
||||
if self.workstation_type:
|
||||
query = query.where(jc.workstation_type == self.workstation_type)
|
||||
|
||||
if self.workstation:
|
||||
production_capacity = (
|
||||
frappe.get_cached_value("Workstation", self.workstation, "production_capacity") or 1
|
||||
)
|
||||
query = query.where(jc.workstation == self.workstation)
|
||||
|
||||
if args.get("employee"):
|
||||
# override capacity for employee
|
||||
production_capacity = 1
|
||||
if args.get("employee") and doctype == "Job Card Time Log":
|
||||
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):
|
||||
return
|
||||
time_logs = query.run(as_dict=True)
|
||||
|
||||
if self.workstation_type:
|
||||
if workstation := self.get_workstation_based_on_available_slot(existing):
|
||||
self.workstation = workstation
|
||||
return None
|
||||
return time_logs
|
||||
|
||||
return existing[0] if existing else None
|
||||
|
||||
def get_workstation_based_on_available_slot(self, existing) -> Optional[str]:
|
||||
def get_workstation_based_on_available_slot(self, existing_time_logs) -> dict:
|
||||
workstations = get_workstations(self.workstation_type)
|
||||
if workstations:
|
||||
busy_workstations = [row.workstation for row in existing]
|
||||
for workstation in workstations:
|
||||
if workstation not in busy_workstations:
|
||||
return workstation
|
||||
busy_workstations = self.time_slot_wise_busy_workstations(existing_time_logs)
|
||||
for time_slot in busy_workstations:
|
||||
available_workstations = sorted(list(set(workstations) - set(busy_workstations[time_slot])))
|
||||
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):
|
||||
row.remaining_time_in_mins = row.time_in_mins
|
||||
@ -237,11 +276,17 @@ class JobCard(Document):
|
||||
def validate_overlap_for_workstation(self, args, row):
|
||||
# get the last record based on the to time from the job card
|
||||
data = self.get_overlap_for(args, check_next_available_slot=True)
|
||||
if data:
|
||||
if not self.workstation:
|
||||
self.workstation = data.workstation
|
||||
if not self.workstation:
|
||||
workstations = get_workstations(self.workstation_type)
|
||||
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):
|
||||
workstation_doc = frappe.get_cached_doc("Workstation", self.workstation)
|
||||
@ -410,7 +455,7 @@ class JobCard(Document):
|
||||
|
||||
def update_time_logs(self, row):
|
||||
self.append(
|
||||
"time_logs",
|
||||
"scheduled_time_logs",
|
||||
{
|
||||
"from_time": row.planned_start_time,
|
||||
"to_time": row.planned_end_time,
|
||||
@ -452,6 +497,7 @@ class JobCard(Document):
|
||||
)
|
||||
|
||||
def before_save(self):
|
||||
self.set_expected_and_actual_time()
|
||||
self.set_process_loss()
|
||||
|
||||
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):
|
||||
precision = self.precision("total_completed_qty")
|
||||
|
||||
|
@ -541,6 +541,16 @@ class TestJobCard(FrappeTestCase):
|
||||
)[0].name
|
||||
|
||||
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.save()
|
||||
jc.submit()
|
||||
@ -557,11 +567,30 @@ class TestJobCard(FrappeTestCase):
|
||||
)[0].name
|
||||
|
||||
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
|
||||
|
||||
self.assertRaises(frappe.ValidationError, jc2.save)
|
||||
|
||||
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.save()
|
||||
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_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
|
||||
if job_card_doc.sequence_id != 1:
|
||||
self.assertRaises(OperationSequenceError, job_card_doc.save)
|
||||
|
@ -487,6 +487,16 @@ class TestWorkOrder(FrappeTestCase):
|
||||
|
||||
for i, job_card in enumerate(job_cards):
|
||||
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.submit()
|
||||
|
||||
@ -957,7 +967,7 @@ class TestWorkOrder(FrappeTestCase):
|
||||
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")
|
||||
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))
|
||||
for row in stock_entry.items:
|
||||
@ -975,7 +985,7 @@ class TestWorkOrder(FrappeTestCase):
|
||||
|
||||
make_job_card(wo_order.name, operations)
|
||||
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))
|
||||
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_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
|
||||
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)
|
||||
|
||||
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])
|
||||
|
||||
|
||||
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")
|
||||
job_card_doc = frappe.get_doc("Job Card", job_card)
|
||||
job_card_doc.set(
|
||||
@ -1855,15 +1888,32 @@ def update_job_card(job_card, jc_qty=None):
|
||||
if jc_qty:
|
||||
job_card_doc.for_quantity = jc_qty
|
||||
|
||||
job_card_doc.append(
|
||||
"time_logs",
|
||||
{
|
||||
"from_time": now(),
|
||||
"employee": employee,
|
||||
"time_in_mins": 60,
|
||||
"completed_qty": job_card_doc.for_quantity,
|
||||
},
|
||||
)
|
||||
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,
|
||||
"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()
|
||||
|
||||
|
@ -519,8 +519,8 @@ class WorkOrder(Document):
|
||||
)
|
||||
|
||||
if enable_capacity_planning and job_card_doc:
|
||||
row.planned_start_time = job_card_doc.time_logs[-1].from_time
|
||||
row.planned_end_time = job_card_doc.time_logs[-1].to_time
|
||||
row.planned_start_time = job_card_doc.scheduled_time_logs[-1].from_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:
|
||||
frappe.message_log.pop()
|
||||
|
@ -20,6 +20,8 @@ class WorkstationType(Document):
|
||||
|
||||
|
||||
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]
|
||||
|
Loading…
Reference in New Issue
Block a user