feat: add repair parts tracking
This commit is contained in:
1 parent
6e550c1c13
commit
eab9646960
23 files changed
+2386
-4
No files matched your search
@@ -1,10 +1,12 @@
|
||||
"""Typed contracts for the Repairs domain."""
|
||||
|
||||
from datetime import datetime
|
||||
from decimal import Decimal
|
||||
from typing import Any, Literal
|
||||
from uuid import UUID
|
||||
|
||||
from pydantic import AwareDatetime, BaseModel, ConfigDict, Field
|
||||
from pydantic import AwareDatetime, BaseModel, ConfigDict, Field, model_validator
|
||||
from app.components.models import ComponentType
|
||||
|
||||
|
||||
RepairReason = Literal[
|
||||
@@ -33,6 +35,8 @@ RepairEventType = Literal[
|
||||
"INITIAL_NOTE",
|
||||
"REPAIR_COMPLETED",
|
||||
"CONTROL_BOARD_REPLACED",
|
||||
"REPAIR_PART_RECORDED",
|
||||
"REPAIR_PART_VOIDED",
|
||||
]
|
||||
RepairEligibleLifecycle = Literal["active", "spare"]
|
||||
RepairPostLifecycle = Literal["active", "spare"]
|
||||
@@ -194,3 +198,91 @@ class RepairTimelineResponse(BaseModel):
|
||||
data: RepairTimeline
|
||||
meta: dict[str, object] = Field(default_factory=dict)
|
||||
error: None = None
|
||||
|
||||
|
||||
ConsumableUnit = Literal["each", "gram", "millilitre", "metre", "centimetre"]
|
||||
RepairPartEntryKind = Literal["consumable", "tracked_component"]
|
||||
|
||||
|
||||
class ConsumableInput(BaseModel):
|
||||
model_config = ConfigDict(extra="forbid", str_strip_whitespace=True)
|
||||
|
||||
description: str = Field(min_length=1, max_length=255)
|
||||
quantity: Decimal = Field(gt=0, max_digits=12, decimal_places=3)
|
||||
unit: ConsumableUnit
|
||||
|
||||
@model_validator(mode="after")
|
||||
def validate_each_quantity(self) -> "ConsumableInput":
|
||||
if self.unit == "each" and self.quantity != self.quantity.to_integral_value():
|
||||
raise ValueError("quantity for unit 'each' must be a whole number")
|
||||
return self
|
||||
|
||||
|
||||
class RepairPartRecordRequest(BaseModel):
|
||||
model_config = ConfigDict(extra="forbid", str_strip_whitespace=True)
|
||||
|
||||
consumable: ConsumableInput | None = None
|
||||
tracked_component_uuid: UUID | None = None
|
||||
notes: str | None = Field(default=None, max_length=4000)
|
||||
|
||||
@model_validator(mode="after")
|
||||
def validate_entry_shape(self) -> "RepairPartRecordRequest":
|
||||
if (self.consumable is None) == (self.tracked_component_uuid is None):
|
||||
raise ValueError(
|
||||
"exactly one of consumable and tracked_component_uuid is required"
|
||||
)
|
||||
return self
|
||||
|
||||
|
||||
class RepairPartVoidRequest(BaseModel):
|
||||
model_config = ConfigDict(extra="forbid", str_strip_whitespace=True)
|
||||
|
||||
reason: str = Field(min_length=1, max_length=1000)
|
||||
|
||||
|
||||
class RepairPartConsumable(BaseModel):
|
||||
description: str
|
||||
quantity: Decimal
|
||||
unit: ConsumableUnit
|
||||
|
||||
|
||||
class RepairPartComponentSnapshot(BaseModel):
|
||||
component_type: ComponentType
|
||||
asset_id: str | None = None
|
||||
manufacturer: str | None = None
|
||||
model: str | None = None
|
||||
manufacturer_serial: str | None = None
|
||||
|
||||
|
||||
class RepairPartDetail(BaseModel):
|
||||
repair_part_id: UUID
|
||||
repair_id: UUID
|
||||
entry_kind: RepairPartEntryKind
|
||||
consumable: RepairPartConsumable | None = None
|
||||
component_id: UUID | None = None
|
||||
component_snapshot: RepairPartComponentSnapshot | None = None
|
||||
notes: str | None = None
|
||||
recorded_by: UUID
|
||||
recorded_at: AwareDatetime
|
||||
recorded_event_id: UUID
|
||||
voided_by: UUID | None = None
|
||||
voided_at: AwareDatetime | None = None
|
||||
void_reason: str | None = None
|
||||
void_event_id: UUID | None = None
|
||||
|
||||
|
||||
class RepairPartList(BaseModel):
|
||||
repair_id: UUID
|
||||
entries: list[RepairPartDetail] = Field(default_factory=list)
|
||||
|
||||
|
||||
class RepairPartResponse(BaseModel):
|
||||
data: RepairPartDetail
|
||||
meta: dict[str, object] = Field(default_factory=dict)
|
||||
error: None = None
|
||||
|
||||
|
||||
class RepairPartListResponse(BaseModel):
|
||||
data: RepairPartList
|
||||
meta: dict[str, object] = Field(default_factory=dict)
|
||||
error: None = None
|
||||
Reference in new issue
Block a user