Improve passport layout and deletion flows
Some checks failed
ci / test (push) Has been cancelled

This commit is contained in:
VPN SaaS Dev
2026-05-16 11:38:29 +09:00
parent 8aa6640308
commit 01a69fc42d
8 changed files with 347 additions and 38 deletions

View File

@@ -93,6 +93,23 @@ async def _appointment_for_sto(session: AsyncSession, appointment_id: int, user:
return appointment
def _ensure_appointment_deletable(appointment: ServiceAppointment) -> None:
if appointment.linked_work_order_id or appointment.status in {"converted_to_work_order", "completed"}:
raise HTTPException(status_code=409, detail="Appointment already has a work order and cannot be deleted")
async def _restore_recommendation_after_appointment_delete(
session: AsyncSession,
appointment: ServiceAppointment,
) -> None:
if not appointment.source_recommendation_id:
return
recommendation = await session.get(MaintenanceRecommendation, appointment.source_recommendation_id)
if recommendation is not None and recommendation.status == "booked":
recommendation.status = "active"
recommendation.source_appointment_id = None
@router.get("/sto/catalog", response_model=list[ServiceCatalogItem])
async def get_sto_catalog(
city: str | None = None,
@@ -276,6 +293,34 @@ async def cancel_appointment(
return appointment
@router.delete("/appointments/{appointment_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_appointment_by_owner(
appointment_id: int,
session: AsyncSession = Depends(get_session),
current_user: User = Depends(get_current_telegram_user),
) -> None:
appointment = await _appointment_for_owner(session, appointment_id, current_user)
_ensure_appointment_deletable(appointment)
await _restore_recommendation_after_appointment_delete(session, appointment)
await notify_service_staff(
session,
service_center_id=appointment.service_center_id,
notification_type="appointment.deleted_by_owner",
title="Клиент удалил запись",
body=f"{appointment.service_name}: {appointment.requested_start_at:%Y-%m-%d %H:%M}",
appointment_id=None,
)
await log_audit(
session,
actor=current_user,
action="appointment.delete_by_owner",
target_type="service_appointment",
target_id=appointment_id,
)
await session.delete(appointment)
await session.commit()
@router.post("/appointments/{appointment_id}/accept-proposed-time", response_model=AppointmentRead)
async def accept_proposed_time(
appointment_id: int,
@@ -515,6 +560,36 @@ async def reject_appointment(
return appointment
@router.delete("/sto/appointments/{appointment_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_appointment_by_sto(
appointment_id: int,
session: AsyncSession = Depends(get_session),
current_user: User = Depends(get_current_telegram_user),
) -> None:
appointment = await _appointment_for_sto(session, appointment_id, current_user)
_ensure_appointment_deletable(appointment)
await _restore_recommendation_after_appointment_delete(session, appointment)
await create_service_notification(
session,
recipient_user_id=appointment.owner_id,
service_center_id=appointment.service_center_id,
appointment_id=None,
notification_type="appointment.deleted_by_sto",
title="СТО удалило запись",
body=f"{appointment.service_name}: {appointment.requested_start_at:%Y-%m-%d %H:%M}",
idempotency_key=f"appointment:{appointment.id}:deleted_by_sto",
)
await log_audit(
session,
actor=current_user,
action="appointment.delete_by_sto",
target_type="service_appointment",
target_id=appointment_id,
)
await session.delete(appointment)
await session.commit()
@router.post("/sto/appointments/{appointment_id}/propose-time", response_model=AppointmentRead)
async def propose_appointment_time(
appointment_id: int,
@@ -570,6 +645,9 @@ async def create_work_order_from_appointment(
if visit is None:
raise HTTPException(status_code=404, detail="Linked work order not found")
return visit
vehicle = await session.get(Car, appointment.vehicle_id)
if vehicle is None:
raise HTTPException(status_code=404, detail="Vehicle not found")
visit = ServiceVisit(
service_center_id=appointment.service_center_id,
vehicle_id=appointment.vehicle_id,
@@ -577,7 +655,7 @@ async def create_work_order_from_appointment(
created_by_employee_id=employee.id,
assigned_employee_id=employee.id,
visit_date=(appointment.confirmed_start_at or appointment.requested_start_at).date(),
odometer=payload.odometer,
odometer=payload.odometer if payload.odometer is not None else vehicle.current_odometer,
status="draft",
customer_complaint=appointment.customer_comment,
notes=payload.notes or appointment.customer_comment,