"""Review application service.""" from fastapi import HTTPException, status from sqlalchemy import select from app.api.schemas.review import PendingReviewResponse, SubmitReviewRequest, SubmitReviewResponse from app.application.services.workflow_service import WorkflowService from app.domain.enums import OrderStatus, ReviewTaskStatus from app.infra.db.models.asset import AssetORM from app.infra.db.models.order import OrderORM from app.infra.db.models.review_task import ReviewTaskORM from app.infra.db.models.workflow_run import WorkflowRunORM from app.workers.workflows.types import ReviewSignalPayload class ReviewService: """Application service for review flows.""" def __init__(self) -> None: self.workflow_service = WorkflowService() async def list_pending_reviews(self, session) -> list[PendingReviewResponse]: """Return all pending review tasks.""" result = await session.execute( select(ReviewTaskORM, WorkflowRunORM) .join(WorkflowRunORM, WorkflowRunORM.order_id == ReviewTaskORM.order_id) .where(ReviewTaskORM.status == ReviewTaskStatus.PENDING) .order_by(ReviewTaskORM.created_at.asc()) ) return [ PendingReviewResponse( review_task_id=review_task.id, order_id=review_task.order_id, workflow_id=workflow_run.workflow_id, current_step=workflow_run.current_step, created_at=review_task.created_at, ) for review_task, workflow_run in result.all() ] async def submit_review(self, session, order_id: int, payload: SubmitReviewRequest) -> SubmitReviewResponse: """Persist the review submission and signal the Temporal workflow.""" order = await session.get(OrderORM, order_id) if order is None: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Order not found") if order.status != OrderStatus.WAITING_REVIEW: raise HTTPException( status_code=status.HTTP_409_CONFLICT, detail="Order is not waiting for review", ) workflow_result = await session.execute( select(WorkflowRunORM).where(WorkflowRunORM.order_id == order_id) ) workflow_run = workflow_result.scalar_one_or_none() if workflow_run is None: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Workflow not found") if payload.selected_asset_id is not None: asset = await session.get(AssetORM, payload.selected_asset_id) if asset is None or asset.order_id != order_id: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="Selected asset does not belong to the order", ) pending_result = await session.execute( select(ReviewTaskORM) .where( ReviewTaskORM.order_id == order_id, ReviewTaskORM.status == ReviewTaskStatus.PENDING, ) .order_by(ReviewTaskORM.created_at.desc()) ) review_task = pending_result.scalars().first() if review_task is None: review_task = ReviewTaskORM(order_id=order_id, status=ReviewTaskStatus.SUBMITTED) session.add(review_task) review_task.status = ReviewTaskStatus.SUBMITTED review_task.decision = payload.decision review_task.reviewer_id = payload.reviewer_id review_task.selected_asset_id = payload.selected_asset_id review_task.comment = payload.comment await session.commit() try: await self.workflow_service.signal_review( workflow_run.workflow_id, ReviewSignalPayload( decision=payload.decision, reviewer_id=payload.reviewer_id, selected_asset_id=payload.selected_asset_id, comment=payload.comment, ), ) except Exception as exc: raise HTTPException( status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail=f"Failed to signal Temporal workflow: {exc}", ) from exc return SubmitReviewResponse( order_id=order_id, workflow_id=workflow_run.workflow_id, decision=payload.decision, status="submitted", )