class_name ServiceInstance extends RefCounted enum State { ACTIVE, PAUSED, WAITING_FOR_STAFF, COMPLETED, FAILED, CANCELLED, } const NO_DEADLINE := -1 var service_id: StringName = &"" var request_id: StringName = &"" var facility_id: StringName = &"" var started_at := 0 var finish_at := 1 var guest_ids: Array[StringName] = [] var staff_ids: Array[StringName] = [] var quality_snapshot := 0 var price_snapshot := 0 var policy_snapshot: Dictionary = {} var inventory_reservations: Array[StringName] = [] var interruption_policy: Dictionary = {} var duration_minutes := 1 var elapsed_minutes := 0 var state: State = State.ACTIVE var interruption_reason: StringName = &"" var interrupted_at := -1 var staff_grace_deadline := NO_DEADLINE var completed_at := -1 var failure_reason: StringName = &"" func configure( p_service_id: StringName, p_request_id: StringName, p_facility_id: StringName, p_started_at: int, p_duration_minutes: int, p_guest_ids: Array[StringName], p_staff_ids: Array[StringName], options: Dictionary = {} ) -> ServiceInstance: service_id = p_service_id request_id = p_request_id facility_id = p_facility_id started_at = maxi(p_started_at, 0) duration_minutes = maxi(p_duration_minutes, 1) finish_at = started_at + duration_minutes guest_ids.assign(p_guest_ids) staff_ids.assign(p_staff_ids) quality_snapshot = int(_value(options, &"quality_snapshot", 0)) price_snapshot = maxi(int(_value(options, &"price_snapshot", 0)), 0) policy_snapshot = _dictionary_copy(_value(options, &"policy_snapshot", {})) inventory_reservations = _string_name_array( _value(options, &"inventory_reservations", []) ) interruption_policy = _dictionary_copy(_value(options, &"interruption_policy", {})) return self func is_valid() -> bool: return not service_id.is_empty() \ and not request_id.is_empty() \ and not facility_id.is_empty() \ and not guest_ids.is_empty() \ and duration_minutes > 0 \ and elapsed_minutes >= 0 \ and elapsed_minutes <= duration_minutes func is_terminal() -> bool: return state in [State.COMPLETED, State.FAILED, State.CANCELLED] func can_progress() -> bool: return state == State.ACTIVE and elapsed_minutes < duration_minutes func advance_one_minute() -> bool: if not can_progress(): return false elapsed_minutes = mini(elapsed_minutes + 1, duration_minutes) return elapsed_minutes >= duration_minutes func pause(reason: StringName, absolute_minute: int) -> bool: if state != State.ACTIVE: return false state = State.PAUSED interruption_reason = reason interrupted_at = absolute_minute return true func wait_for_staff(reason: StringName, absolute_minute: int, grace_minutes := 5) -> bool: if state not in [State.ACTIVE, State.PAUSED]: return false state = State.WAITING_FOR_STAFF interruption_reason = reason interrupted_at = absolute_minute staff_grace_deadline = absolute_minute + maxi(grace_minutes, 0) return true func resume() -> bool: if state not in [State.PAUSED, State.WAITING_FOR_STAFF]: return false state = State.ACTIVE interruption_reason = &"" interrupted_at = -1 staff_grace_deadline = NO_DEADLINE return true func mark_completed(absolute_minute: int) -> void: elapsed_minutes = duration_minutes state = State.COMPLETED completed_at = absolute_minute interruption_reason = &"" failure_reason = &"" staff_grace_deadline = NO_DEADLINE func mark_failed(reason: StringName, absolute_minute: int) -> void: state = State.FAILED failure_reason = reason completed_at = absolute_minute staff_grace_deadline = NO_DEADLINE func mark_cancelled(reason: StringName, absolute_minute: int) -> void: state = State.CANCELLED failure_reason = reason completed_at = absolute_minute staff_grace_deadline = NO_DEADLINE func remaining_minutes() -> int: return maxi(duration_minutes - elapsed_minutes, 0) func create_snapshot() -> Dictionary: return { &"service_id": String(service_id), &"request_id": String(request_id), &"facility_id": String(facility_id), &"started_at": started_at, &"finish_at": finish_at, &"guest_ids": _names_to_strings(guest_ids), &"staff_ids": _names_to_strings(staff_ids), &"quality_snapshot": quality_snapshot, &"price_snapshot": price_snapshot, &"policy_snapshot": policy_snapshot.duplicate(true), &"inventory_reservations": _names_to_strings(inventory_reservations), &"interruption_policy": interruption_policy.duplicate(true), &"duration_minutes": duration_minutes, &"elapsed_minutes": elapsed_minutes, &"state": State.keys()[state], &"interruption_reason": String(interruption_reason), &"interrupted_at": interrupted_at, &"staff_grace_deadline": staff_grace_deadline, &"completed_at": completed_at, &"failure_reason": String(failure_reason), } static func from_snapshot(snapshot: Dictionary) -> ServiceInstance: if not snapshot.get(&"guest_ids", null) is Array: return null if not snapshot.get(&"staff_ids", null) is Array: return null if not snapshot.get(&"policy_snapshot", null) is Dictionary: return null if not snapshot.get(&"inventory_reservations", null) is Array: return null if not snapshot.get(&"interruption_policy", null) is Dictionary: return null var service := ServiceInstance.new().configure( StringName(snapshot.get(&"service_id", "")), StringName(snapshot.get(&"request_id", "")), StringName(snapshot.get(&"facility_id", "")), int(snapshot.get(&"started_at", 0)), int(snapshot.get(&"duration_minutes", 1)), _string_name_array(snapshot.get(&"guest_ids", [])), _string_name_array(snapshot.get(&"staff_ids", [])), { &"quality_snapshot": snapshot.get(&"quality_snapshot", 0), &"price_snapshot": snapshot.get(&"price_snapshot", 0), &"policy_snapshot": snapshot.get(&"policy_snapshot", {}), &"inventory_reservations": snapshot.get(&"inventory_reservations", []), &"interruption_policy": snapshot.get(&"interruption_policy", {}), } ) var state_index := State.keys().find(String(snapshot.get(&"state", "ACTIVE"))) if state_index < 0: return null service.state = state_index as State service.finish_at = int(snapshot.get(&"finish_at", service.started_at + service.duration_minutes)) service.elapsed_minutes = int(snapshot.get(&"elapsed_minutes", 0)) service.interruption_reason = StringName(snapshot.get(&"interruption_reason", "")) service.interrupted_at = int(snapshot.get(&"interrupted_at", -1)) service.staff_grace_deadline = int(snapshot.get(&"staff_grace_deadline", NO_DEADLINE)) service.completed_at = int(snapshot.get(&"completed_at", -1)) service.failure_reason = StringName(snapshot.get(&"failure_reason", "")) return service if service.is_valid() else null static func _value(source: Dictionary, key: StringName, fallback: Variant) -> Variant: return source.get(key, source.get(String(key), fallback)) static func _dictionary_copy(source: Variant) -> Dictionary: return source.duplicate(true) if source is Dictionary else {} static func _string_name_array(source: Variant) -> Array[StringName]: var values: Array[StringName] = [] if source is Array: for value: Variant in source: values.append(StringName(value)) return values static func _names_to_strings(source: Array[StringName]) -> Array[String]: var values: Array[String] = [] for value: StringName in source: values.append(String(value)) return values