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")