feat(repairs): add repair management foundation
This commit is contained in:
parent
22e54f212c
commit
e9ec207617
37 changed files with 2149 additions and 7 deletions
|
|
@ -18,6 +18,7 @@ class DashboardSummary(BaseModel):
|
|||
latest_customers: list[CustomerResponse] = []
|
||||
users: list[MetricCard] = []
|
||||
roles: list[MetricCard] = []
|
||||
repairs: list[MetricCard] = []
|
||||
activities: EmptyWidget
|
||||
tasks: EmptyWidget
|
||||
tickets: EmptyWidget
|
||||
|
|
|
|||
162
backend/hermes/app/schemas/repair.py
Normal file
162
backend/hermes/app/schemas/repair.py
Normal file
|
|
@ -0,0 +1,162 @@
|
|||
from datetime import datetime
|
||||
from typing import Literal
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, EmailStr, Field, field_validator, model_validator
|
||||
|
||||
RepairStatus = Literal[
|
||||
"new",
|
||||
"accepted",
|
||||
"diagnosis",
|
||||
"estimate",
|
||||
"waiting_for_customer",
|
||||
"approved",
|
||||
"repair",
|
||||
"final_test",
|
||||
"ready_for_pickup",
|
||||
"shipped",
|
||||
"completed",
|
||||
"cancelled",
|
||||
]
|
||||
RepairPriority = Literal["low", "normal", "high", "urgent"]
|
||||
RepairSource = Literal["manual", "website", "customer_portal", "email", "phone"]
|
||||
|
||||
|
||||
def normalize_text(value: object) -> str:
|
||||
if value is None:
|
||||
return ""
|
||||
return str(value).strip()
|
||||
|
||||
|
||||
class RepairBase(BaseModel):
|
||||
customer_id: int | None = None
|
||||
contact_id: int | None = None
|
||||
status: RepairStatus = "new"
|
||||
priority: RepairPriority = "normal"
|
||||
source: RepairSource = "manual"
|
||||
source_reference: str | None = Field(default=None, max_length=120)
|
||||
customer_name: str = Field(default="", max_length=255)
|
||||
customer_email: EmailStr | None = None
|
||||
customer_phone: str = Field(default="", max_length=80)
|
||||
device_manufacturer: str = Field(min_length=1, max_length=160)
|
||||
device_model: str = Field(min_length=1, max_length=160)
|
||||
device_serial_number: str | None = Field(default=None, max_length=160)
|
||||
device_type: str = Field(default="", max_length=120)
|
||||
accessories: str = ""
|
||||
fault_description: str = Field(min_length=1)
|
||||
previous_work: str = ""
|
||||
device_opened: bool = False
|
||||
intake_notes: str = ""
|
||||
diagnosis_notes: str = ""
|
||||
repair_notes: str = ""
|
||||
estimate_notes: str = ""
|
||||
estimated_cost_cents: int | None = Field(default=None, ge=0)
|
||||
|
||||
@field_validator(
|
||||
"source_reference",
|
||||
"customer_name",
|
||||
"customer_phone",
|
||||
"device_manufacturer",
|
||||
"device_model",
|
||||
"device_serial_number",
|
||||
"device_type",
|
||||
"accessories",
|
||||
"fault_description",
|
||||
"previous_work",
|
||||
"intake_notes",
|
||||
"diagnosis_notes",
|
||||
"repair_notes",
|
||||
"estimate_notes",
|
||||
mode="before",
|
||||
)
|
||||
@classmethod
|
||||
def normalize_strings(cls, value: object) -> str | None:
|
||||
if value is None:
|
||||
return None
|
||||
return normalize_text(value)
|
||||
|
||||
@model_validator(mode="after")
|
||||
def validate_customer_identity(self):
|
||||
if self.customer_id is None and not self.customer_name:
|
||||
raise ValueError("Kundenname ist erforderlich, wenn kein Kunde ausgewählt ist")
|
||||
return self
|
||||
|
||||
|
||||
class RepairCreate(RepairBase):
|
||||
pass
|
||||
|
||||
|
||||
class RepairUpdate(RepairBase):
|
||||
pass
|
||||
|
||||
|
||||
class RepairStatusUpdate(BaseModel):
|
||||
status: RepairStatus
|
||||
note: str | None = None
|
||||
|
||||
@field_validator("note", mode="before")
|
||||
@classmethod
|
||||
def normalize_note(cls, value: object) -> str | None:
|
||||
if value is None:
|
||||
return None
|
||||
text = normalize_text(value)
|
||||
return text or None
|
||||
|
||||
|
||||
class RepairResponse(RepairBase):
|
||||
id: int
|
||||
repair_number: str
|
||||
customer_email: str
|
||||
device_serial_number: str | None
|
||||
approved_at: datetime | None
|
||||
completed_at: datetime | None
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
|
||||
class RepairListResponse(BaseModel):
|
||||
items: list[RepairResponse]
|
||||
total: int
|
||||
limit: int
|
||||
offset: int
|
||||
|
||||
|
||||
class RepairStatusHistoryResponse(BaseModel):
|
||||
id: int
|
||||
repair_id: int
|
||||
old_status: str | None
|
||||
new_status: str
|
||||
note: str | None
|
||||
actor_user_id: int | None
|
||||
created_at: datetime
|
||||
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
|
||||
class RepairIntakePayload(BaseModel):
|
||||
name: str = Field(min_length=1, max_length=255)
|
||||
email: EmailStr | None = None
|
||||
phone: str = Field(default="", max_length=80)
|
||||
manufacturer: str = Field(min_length=1, max_length=160)
|
||||
model: str = Field(min_length=1, max_length=160)
|
||||
serial_number: str | None = Field(default=None, max_length=160)
|
||||
device_type: str = Field(default="", max_length=120)
|
||||
accessories: str = ""
|
||||
fault_description: str = Field(min_length=1)
|
||||
previous_work: str = ""
|
||||
device_opened: bool = False
|
||||
source_reference: str | None = Field(default=None, max_length=120)
|
||||
|
||||
@field_validator("*", mode="before")
|
||||
@classmethod
|
||||
def normalize_payload(cls, value: object) -> object:
|
||||
if isinstance(value, str):
|
||||
return value.strip()
|
||||
return value
|
||||
|
||||
|
||||
class RepairIntakeResponse(BaseModel):
|
||||
repair_number: str
|
||||
status: RepairStatus
|
||||
message: str
|
||||
Loading…
Add table
Add a link
Reference in a new issue