feat: add return to service workflow
This commit is contained in:
1 parent
72ad19a071
commit
f2b736b08e
19 files changed
+2883
-46
No files matched your search
+115
-24
@@ -6,15 +6,22 @@ from app.repairs.exceptions import (
|
||||
ActiveRepairConflict,
|
||||
IncompatibleMinerLifecycle,
|
||||
InconsistentRepairState,
|
||||
RepairActiveMismatch,
|
||||
RepairActorRequired,
|
||||
RepairCompletionActorRequired,
|
||||
RepairMinerNotFound,
|
||||
RepairMinerNotInRepair,
|
||||
RepairNotActive,
|
||||
RepairNotFound,
|
||||
RepairPersistenceError,
|
||||
)
|
||||
from app.repairs.models import (
|
||||
BeginRepairRequest,
|
||||
BeginRepairResult,
|
||||
InitialRepairNotePayload,
|
||||
RepairActor,
|
||||
RepairCompleteRequest,
|
||||
RepairCompletedPayload,
|
||||
RepairDetail,
|
||||
RepairTimeline,
|
||||
RepairTimelineEvent,
|
||||
@@ -24,6 +31,9 @@ from app.repairs.repository import RepairRepository
|
||||
|
||||
|
||||
ELIGIBLE_LIFECYCLE_STATUSES = frozenset({"active", "spare"})
|
||||
ACTIVE_REPAIR_STATUSES = frozenset(
|
||||
{"OPEN", "IN_PROGRESS", "WAITING_FOR_PARTS", "READY_FOR_TEST"}
|
||||
)
|
||||
REPAIR_LIFECYCLE_STATUS = "in_repair"
|
||||
|
||||
|
||||
@@ -40,6 +50,32 @@ class RepairService:
|
||||
normalized = str(note).strip()
|
||||
return normalized or None
|
||||
|
||||
@staticmethod
|
||||
def _map_repair_detail(repair: dict) -> RepairDetail:
|
||||
closed_by = None
|
||||
if repair["closed_by_user_id"] is not None:
|
||||
closed_by = RepairActor(
|
||||
user_id=repair["closed_by_user_id"],
|
||||
display_name=repair["closed_by_display_name"],
|
||||
)
|
||||
|
||||
return RepairDetail(
|
||||
repair_id=repair["repair_id"],
|
||||
miner_id=repair["miner_id"],
|
||||
miner_asset_id=repair["miner_asset_id"],
|
||||
status=repair["status"],
|
||||
reason=repair["reason"],
|
||||
opened_at=repair["opened_at"],
|
||||
opened_by=RepairActor(
|
||||
user_id=repair["opened_by_user_id"],
|
||||
display_name=repair["opened_by_display_name"],
|
||||
),
|
||||
closed_at=repair["closed_at"],
|
||||
closed_by=closed_by,
|
||||
miner_lifecycle=repair["miner_lifecycle"],
|
||||
latest_event_sequence=repair["latest_event_sequence"],
|
||||
)
|
||||
|
||||
def begin_repair(
|
||||
self,
|
||||
request: BeginRepairRequest,
|
||||
@@ -107,34 +143,89 @@ class RepairService:
|
||||
opened_by=repair["opened_by"],
|
||||
)
|
||||
|
||||
def complete_repair(
|
||||
self,
|
||||
repair_id: UUID,
|
||||
request: RepairCompleteRequest,
|
||||
*,
|
||||
actor_user_id: UUID | None,
|
||||
) -> RepairDetail:
|
||||
if actor_user_id is None:
|
||||
raise RepairCompletionActorRequired()
|
||||
|
||||
completion_summary = self.normalize_note(request.completion_summary)
|
||||
|
||||
with self.repository.transaction() as repository:
|
||||
repair_identity = repository.get_repair_identity(repair_id)
|
||||
if repair_identity is None:
|
||||
raise RepairNotFound(repair_id)
|
||||
|
||||
miner_id = repair_identity["miner_id"]
|
||||
# Begin Repair also locks the miner first. Keeping this order makes
|
||||
# competing lifecycle workflows serialize on the durable asset.
|
||||
miner = repository.lock_miner_for_update(miner_id)
|
||||
if miner is None:
|
||||
raise RepairMinerNotFound(miner_id)
|
||||
|
||||
repair = repository.lock_repair_for_update(repair_id)
|
||||
if repair is None:
|
||||
raise RepairNotFound(repair_id)
|
||||
if repair["miner_id"] != miner_id:
|
||||
raise RepairActiveMismatch(repair_id, miner_id)
|
||||
if repair["status"] not in ACTIVE_REPAIR_STATUSES:
|
||||
raise RepairNotActive(repair_id, repair["status"])
|
||||
|
||||
active_repair = repository.get_active_repair(miner_id)
|
||||
if active_repair is None or active_repair["id"] != repair_id:
|
||||
raise RepairActiveMismatch(repair_id, miner_id)
|
||||
|
||||
lifecycle_status = str(miner["lifecycle_status"])
|
||||
if lifecycle_status != REPAIR_LIFECYCLE_STATUS:
|
||||
raise RepairMinerNotInRepair(miner_id, lifecycle_status)
|
||||
|
||||
next_sequence = repository.get_next_event_sequence(repair_id)
|
||||
pre_repair_lifecycle = repository.get_pre_repair_lifecycle(
|
||||
repair_id
|
||||
)
|
||||
completed = repository.complete_repair(
|
||||
repair_id,
|
||||
completed_by=actor_user_id,
|
||||
completion_summary=completion_summary,
|
||||
resulting_lifecycle=request.target_lifecycle,
|
||||
)
|
||||
if completed is None:
|
||||
raise RepairNotActive(repair_id, repair["status"])
|
||||
|
||||
repository.set_miner_lifecycle(
|
||||
miner_id,
|
||||
request.target_lifecycle,
|
||||
)
|
||||
payload = RepairCompletedPayload(
|
||||
resulting_miner_lifecycle=request.target_lifecycle,
|
||||
pre_repair_lifecycle=pre_repair_lifecycle,
|
||||
completion_summary=completion_summary,
|
||||
)
|
||||
repository.create_event(
|
||||
repair_id,
|
||||
sequence=next_sequence,
|
||||
event_type="REPAIR_COMPLETED",
|
||||
created_by=actor_user_id,
|
||||
payload=payload.model_dump(mode="json", exclude_none=True),
|
||||
)
|
||||
|
||||
detail = repository.get_repair_detail(repair_id)
|
||||
if detail is None:
|
||||
raise RepairPersistenceError(
|
||||
"Completed repair could not be read in its transaction"
|
||||
)
|
||||
|
||||
return self._map_repair_detail(detail)
|
||||
|
||||
def get_repair(self, repair_id: UUID) -> RepairDetail:
|
||||
repair = self.repository.get_repair_detail(repair_id)
|
||||
if repair is None:
|
||||
raise RepairNotFound(repair_id)
|
||||
|
||||
closed_by = None
|
||||
if repair["closed_by_user_id"] is not None:
|
||||
closed_by = RepairActor(
|
||||
user_id=repair["closed_by_user_id"],
|
||||
display_name=repair["closed_by_display_name"],
|
||||
)
|
||||
|
||||
return RepairDetail(
|
||||
repair_id=repair["repair_id"],
|
||||
miner_id=repair["miner_id"],
|
||||
miner_asset_id=repair["miner_asset_id"],
|
||||
status=repair["status"],
|
||||
reason=repair["reason"],
|
||||
opened_at=repair["opened_at"],
|
||||
opened_by=RepairActor(
|
||||
user_id=repair["opened_by_user_id"],
|
||||
display_name=repair["opened_by_display_name"],
|
||||
),
|
||||
closed_at=repair["closed_at"],
|
||||
closed_by=closed_by,
|
||||
miner_lifecycle=repair["miner_lifecycle"],
|
||||
latest_event_sequence=repair["latest_event_sequence"],
|
||||
)
|
||||
return self._map_repair_detail(repair)
|
||||
|
||||
def get_repair_timeline(self, repair_id: UUID) -> RepairTimeline:
|
||||
events = self.repository.get_repair_events(repair_id)
|
||||
|
||||
Reference in new issue
Block a user