feat(repairs): add repair management foundation
This commit is contained in:
parent
22e54f212c
commit
e9ec207617
37 changed files with 2149 additions and 7 deletions
157
backend/hermes/app/services/repair_service.py
Normal file
157
backend/hermes/app/services/repair_service.py
Normal file
|
|
@ -0,0 +1,157 @@
|
|||
from datetime import UTC, datetime
|
||||
|
||||
from fastapi import HTTPException, status
|
||||
from sqlalchemy.exc import IntegrityError
|
||||
from sqlalchemy.orm import Session
|
||||
from starlette.requests import Request
|
||||
|
||||
from app.models.repair import Repair
|
||||
from app.models.user import User
|
||||
from app.repositories.customer_repository import CustomerRepository
|
||||
from app.repositories.repair_repository import RepairRepository
|
||||
from app.schemas.repair import RepairCreate, RepairIntakePayload, RepairStatusUpdate, RepairUpdate
|
||||
from app.services.audit_service import sanitize, write_audit_log
|
||||
|
||||
|
||||
def repair_label(repair: Repair) -> str:
|
||||
return f"{repair.repair_number} · {repair.customer_name}"
|
||||
|
||||
|
||||
class RepairService:
|
||||
@staticmethod
|
||||
def create(db: Session, payload: RepairCreate, *, actor: User | None, request: Request | None = None) -> Repair:
|
||||
RepairService._validate_customer(db, payload.customer_id)
|
||||
repair_number = RepairRepository.get_next_number(db, datetime.now(UTC).year)
|
||||
try:
|
||||
repair = RepairRepository.create(db, payload, repair_number=repair_number)
|
||||
except IntegrityError:
|
||||
db.rollback()
|
||||
repair_number = RepairRepository.get_next_number(db, datetime.now(UTC).year)
|
||||
repair = RepairRepository.create(db, payload, repair_number=repair_number)
|
||||
write_audit_log(
|
||||
db,
|
||||
action="repairs.create",
|
||||
entity_type="repairs",
|
||||
entity_id=repair.id,
|
||||
entity_label=repair_label(repair),
|
||||
actor=actor,
|
||||
request=request,
|
||||
after_data=repair,
|
||||
)
|
||||
return repair
|
||||
|
||||
@staticmethod
|
||||
def update(db: Session, repair: Repair, payload: RepairUpdate, *, actor: User, request: Request) -> Repair:
|
||||
RepairService._validate_customer(db, payload.customer_id)
|
||||
before_data = sanitize(repair)
|
||||
updated = RepairRepository.update(db, repair, payload)
|
||||
write_audit_log(
|
||||
db,
|
||||
action="repairs.update",
|
||||
entity_type="repairs",
|
||||
entity_id=updated.id,
|
||||
entity_label=repair_label(updated),
|
||||
actor=actor,
|
||||
request=request,
|
||||
before_data=before_data,
|
||||
after_data=updated,
|
||||
)
|
||||
return updated
|
||||
|
||||
@staticmethod
|
||||
def update_status(db: Session, repair: Repair, payload: RepairStatusUpdate, *, actor: User, request: Request) -> Repair:
|
||||
before_data = sanitize(repair)
|
||||
updated = RepairRepository.update_status(db, repair, payload, actor_user_id=actor.id)
|
||||
write_audit_log(
|
||||
db,
|
||||
action="repairs.status_update",
|
||||
entity_type="repairs",
|
||||
entity_id=updated.id,
|
||||
entity_label=repair_label(updated),
|
||||
actor=actor,
|
||||
request=request,
|
||||
before_data=before_data,
|
||||
after_data=updated,
|
||||
metadata={"old_status": before_data.get("status") if isinstance(before_data, dict) else None, "new_status": updated.status},
|
||||
)
|
||||
return updated
|
||||
|
||||
@staticmethod
|
||||
def cancel(db: Session, repair: Repair, *, actor: User, request: Request) -> Repair:
|
||||
before_data = sanitize(repair)
|
||||
updated = RepairRepository.cancel(db, repair, actor_user_id=actor.id)
|
||||
write_audit_log(
|
||||
db,
|
||||
action="repairs.cancel",
|
||||
entity_type="repairs",
|
||||
entity_id=updated.id,
|
||||
entity_label=repair_label(updated),
|
||||
actor=actor,
|
||||
request=request,
|
||||
before_data=before_data,
|
||||
after_data=updated,
|
||||
)
|
||||
return updated
|
||||
|
||||
@staticmethod
|
||||
def create_from_intake(db: Session, payload: RepairIntakePayload, *, request: Request | None = None) -> Repair:
|
||||
event = RepairRepository.create_intake_event(
|
||||
db,
|
||||
external_source="website",
|
||||
external_reference=payload.source_reference,
|
||||
payload=payload.model_dump(mode="json"),
|
||||
status="received",
|
||||
)
|
||||
try:
|
||||
repair_payload = RepairCreate(
|
||||
customer_id=None,
|
||||
contact_id=None,
|
||||
status="new",
|
||||
priority="normal",
|
||||
source="website",
|
||||
source_reference=payload.source_reference,
|
||||
customer_name=payload.name,
|
||||
customer_email=payload.email,
|
||||
customer_phone=payload.phone,
|
||||
device_manufacturer=payload.manufacturer,
|
||||
device_model=payload.model,
|
||||
device_serial_number=payload.serial_number,
|
||||
device_type=payload.device_type,
|
||||
accessories=payload.accessories,
|
||||
fault_description=payload.fault_description,
|
||||
previous_work=payload.previous_work,
|
||||
device_opened=payload.device_opened,
|
||||
intake_notes="Website Intake",
|
||||
)
|
||||
repair = RepairService.create(db, repair_payload, actor=None, request=request)
|
||||
RepairRepository.update_intake_event(db, event, status="processed", repair_id=repair.id)
|
||||
write_audit_log(
|
||||
db,
|
||||
action="repairs.intake_processed",
|
||||
entity_type="repairs",
|
||||
entity_id=repair.id,
|
||||
entity_label=repair_label(repair),
|
||||
request=request,
|
||||
metadata={"external_source": "website", "external_reference": payload.source_reference},
|
||||
)
|
||||
return repair
|
||||
except Exception as exc:
|
||||
RepairRepository.update_intake_event(db, event, status="failed", error_message="Intake konnte nicht verarbeitet werden")
|
||||
write_audit_log(
|
||||
db,
|
||||
action="repairs.intake_failed",
|
||||
entity_type="repairs",
|
||||
request=request,
|
||||
metadata={"external_source": "website", "external_reference": payload.source_reference},
|
||||
)
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="Reparaturannahme konnte nicht verarbeitet werden",
|
||||
) from exc
|
||||
|
||||
@staticmethod
|
||||
def _validate_customer(db: Session, customer_id: int | None) -> None:
|
||||
if customer_id is None:
|
||||
return
|
||||
if CustomerRepository.get_by_id(db, customer_id) is None:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Kunde nicht gefunden")
|
||||
Loading…
Add table
Add a link
Reference in a new issue