Connect physical elevators and staff commutes to construction, saves, and cutaway UI

This commit is contained in:
Takeshi
2026-09-09 12:58:40 +09:00
parent 4d72de8973
commit 0f12c748e9
46 changed files with 3267 additions and 165 deletions
+42 -5
View File
@@ -49,6 +49,7 @@ var _item_reserver := Callable()
var _item_releaser := Callable()
var _travel_estimator := Callable()
var _external_task_driver := Callable()
var _attendance_driver := Callable()
func configure_callbacks(
@@ -69,6 +70,17 @@ func configure_external_task_driver(driver: Callable = Callable()) -> void:
_external_task_driver = driver
## Runtime attendance uses the employee's real position. Component consumers
## and legacy one-off shifts retain their existing scheduling contract.
func configure_attendance_driver(driver: Callable = Callable()) -> void:
_attendance_driver = driver
func requires_attendance(staff_id: StringName) -> bool:
return _attendance_driver.is_valid() and _work_assignments.has(staff_id) \
and not String(_work_assignments[staff_id][&"facility_id"]).is_empty()
func is_externally_driven_task(task_id: StringName) -> bool:
return _external_task_driver.is_valid() and bool(_external_task_driver.call(task_id))
@@ -461,6 +473,17 @@ func set_work_assignment(staff_id: StringName, facility_id: StringName, template
if shift.cancelled or shift.end_at <= minute:
continue
var replacement := ShiftInstanceType.from_snapshot(shift.create_snapshot())
# Attendance can contain gaps, so clamping by scheduled duration alone
# cannot remove an already recorded paused-edit boundary minute. The
# current AVAILABLE employee recorded that minute; its replacement plan
# records it only if the employee is physically at the new workplace.
if requires_attendance(staff_id) and minute == current_minute \
and staff.state == StaffRuntimeType.State.AVAILABLE \
and staff.shift_id == shift.shift_instance_id \
and shift.contains_minute(minute) and shift.worked_minutes > 0:
replacement.worked_minutes -= 1
if replacement.worked_minutes == 0 and replacement.overtime_minutes == 0:
replacement.actual_started_at = -1
if replacement.start_at >= minute:
replacement.cancelled = true
else:
@@ -496,6 +519,8 @@ func set_work_assignment(staff_id: StringName, facility_id: StringName, template
staff.shift_id = &""
staff.assigned_facility_id = &""
staff.is_overtime = false
if String(staff.absence_reason).begins_with("COMMUTE_"):
staff.absence_reason = &""
staff.transition_to(StaffRuntimeType.State.OFF_DUTY, &"WORK_ASSIGNMENT_CHANGED", minute)
if new_shift != null:
_shifts[new_shift.shift_instance_id] = new_shift
@@ -1379,6 +1404,8 @@ func _update_shift_states(absolute_minute: int, only_staff: StringName = &"", re
task_reopened.emit(current_task.task_id, &"SHIFT_ENDED")
staff.current_task_id = &""
staff.is_overtime = false
if String(staff.absence_reason).begins_with("COMMUTE_"):
staff.absence_reason = &""
staff.shift_id = &""
if staff.assigned_facility_id == shift.assigned_facility_id:
staff.assigned_facility_id = &""
@@ -1410,16 +1437,26 @@ func _update_shift_states(absolute_minute: int, only_staff: StringName = &"", re
continue
if shift.contains_minute(absolute_minute):
staff.is_overtime = false
var attendance_required := requires_attendance(staff.staff_id)
var attended := not attendance_required or bool(_attendance_driver.call(staff, shift, absolute_minute))
if attendance_required and attended and String(staff.absence_reason).begins_with("COMMUTE_"):
staff.absence_reason = &""
if staff.current_task_id.is_empty() and staff.state in [
StaffRuntimeType.State.OFF_DUTY,
StaffRuntimeType.State.COMMUTING,
StaffRuntimeType.State.AVAILABLE,
]:
staff.transition_to(StaffRuntimeType.State.AVAILABLE, &"SHIFT_STARTED", absolute_minute)
shift_state_changed.emit(staff.staff_id, shift.shift_instance_id, staff.state)
if record_duty:
var next_state := StaffRuntimeType.State.AVAILABLE if attended else StaffRuntimeType.State.COMMUTING
if staff.state != next_state:
var reason := &"SHIFT_STARTED" if attended else (&"RETURN_TO_POST" if shift.actual_started_at >= 0 else &"SHIFT_COMMUTE")
staff.transition_to(next_state, reason, absolute_minute)
shift_state_changed.emit(staff.staff_id, shift.shift_instance_id, staff.state)
if record_duty and attended:
shift.record_duty_minute(absolute_minute)
if absolute_minute >= shift.start_at + 15 and staff.state == StaffRuntimeType.State.COMMUTING:
shift.mark_late(absolute_minute, DEFAULT_LATE_REASON)
if absolute_minute >= shift.start_at + 15 and staff.state == StaffRuntimeType.State.COMMUTING \
and (not attendance_required or shift.actual_started_at < 0):
var late_reason := staff.absence_reason if attendance_required and not staff.absence_reason.is_empty() else DEFAULT_LATE_REASON
shift.mark_late(absolute_minute, late_reason)
func _find_relevant_shift(staff_id: StringName, absolute_minute: int) -> ShiftInstance: