feat: add manual revision and dashboard list apis
This commit is contained in:
@@ -3,9 +3,11 @@
|
||||
from fastapi import HTTPException, status
|
||||
from sqlalchemy import select
|
||||
|
||||
from app.api.schemas.revision import ConfirmRevisionRequest, ConfirmRevisionResponse
|
||||
from app.api.schemas.review import PendingReviewResponse, SubmitReviewRequest, SubmitReviewResponse
|
||||
from app.application.services.revision_service import RevisionService
|
||||
from app.application.services.workflow_service import WorkflowService
|
||||
from app.domain.enums import OrderStatus, ReviewTaskStatus
|
||||
from app.domain.enums import OrderStatus, ReviewDecision, 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
|
||||
@@ -18,6 +20,7 @@ class ReviewService:
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.workflow_service = WorkflowService()
|
||||
self.revision_service = RevisionService()
|
||||
|
||||
async def list_pending_reviews(self, session) -> list[PendingReviewResponse]:
|
||||
"""Return all pending review tasks."""
|
||||
@@ -25,20 +28,29 @@ class ReviewService:
|
||||
result = await session.execute(
|
||||
select(ReviewTaskORM, WorkflowRunORM)
|
||||
.join(WorkflowRunORM, WorkflowRunORM.order_id == ReviewTaskORM.order_id)
|
||||
.where(ReviewTaskORM.status == ReviewTaskStatus.PENDING)
|
||||
.where(ReviewTaskORM.status.in_([ReviewTaskStatus.PENDING, ReviewTaskStatus.REVISION_UPLOADED]))
|
||||
.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,
|
||||
pending_reviews = []
|
||||
for review_task, workflow_run in result.all():
|
||||
snapshot = await self.revision_service.get_revision_snapshot(session, review_task.order_id)
|
||||
pending_reviews.append(
|
||||
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,
|
||||
review_task_status=review_task.status,
|
||||
latest_revision_asset_id=snapshot.latest_revision_asset_id,
|
||||
current_revision_asset_id=snapshot.current_revision_asset_id,
|
||||
latest_revision_version=snapshot.latest_revision_version,
|
||||
revision_count=snapshot.revision_count,
|
||||
pending_manual_confirm=snapshot.pending_manual_confirm,
|
||||
created_at=review_task.created_at,
|
||||
)
|
||||
)
|
||||
for review_task, workflow_run in result.all()
|
||||
]
|
||||
return pending_reviews
|
||||
|
||||
async def submit_review(self, session, order_id: int, payload: SubmitReviewRequest) -> SubmitReviewResponse:
|
||||
"""Persist the review submission and signal the Temporal workflow."""
|
||||
@@ -71,7 +83,7 @@ class ReviewService:
|
||||
select(ReviewTaskORM)
|
||||
.where(
|
||||
ReviewTaskORM.order_id == order_id,
|
||||
ReviewTaskORM.status == ReviewTaskStatus.PENDING,
|
||||
ReviewTaskORM.status.in_([ReviewTaskStatus.PENDING, ReviewTaskStatus.REVISION_UPLOADED]),
|
||||
)
|
||||
.order_by(ReviewTaskORM.created_at.desc())
|
||||
)
|
||||
@@ -80,10 +92,22 @@ class ReviewService:
|
||||
review_task = ReviewTaskORM(order_id=order_id, status=ReviewTaskStatus.SUBMITTED)
|
||||
session.add(review_task)
|
||||
|
||||
current_task_status = review_task.status
|
||||
selected_asset_id = payload.selected_asset_id
|
||||
if selected_asset_id is None and current_task_status == ReviewTaskStatus.REVISION_UPLOADED:
|
||||
selected_asset_id = review_task.resume_asset_id or review_task.latest_revision_asset_id
|
||||
if selected_asset_id is not None:
|
||||
asset = await session.get(AssetORM, 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",
|
||||
)
|
||||
|
||||
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.selected_asset_id = selected_asset_id
|
||||
review_task.comment = payload.comment
|
||||
await session.commit()
|
||||
|
||||
@@ -93,7 +117,7 @@ class ReviewService:
|
||||
ReviewSignalPayload(
|
||||
decision=payload.decision,
|
||||
reviewer_id=payload.reviewer_id,
|
||||
selected_asset_id=payload.selected_asset_id,
|
||||
selected_asset_id=selected_asset_id,
|
||||
comment=payload.comment,
|
||||
),
|
||||
)
|
||||
@@ -110,3 +134,88 @@ class ReviewService:
|
||||
status="submitted",
|
||||
)
|
||||
|
||||
async def confirm_revision_continue(
|
||||
self,
|
||||
session,
|
||||
order_id: int,
|
||||
payload: ConfirmRevisionRequest,
|
||||
) -> ConfirmRevisionResponse:
|
||||
"""Confirm the latest revision and resume the existing 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")
|
||||
|
||||
review_result = await session.execute(
|
||||
select(ReviewTaskORM)
|
||||
.where(
|
||||
ReviewTaskORM.order_id == order_id,
|
||||
ReviewTaskORM.status.in_([ReviewTaskStatus.PENDING, ReviewTaskStatus.REVISION_UPLOADED]),
|
||||
)
|
||||
.order_by(ReviewTaskORM.created_at.desc(), ReviewTaskORM.id.desc())
|
||||
)
|
||||
review_task = review_result.scalars().first()
|
||||
if review_task is None:
|
||||
raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail="No active review task")
|
||||
if review_task.status != ReviewTaskStatus.REVISION_UPLOADED:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_409_CONFLICT,
|
||||
detail="No uploaded revision to confirm",
|
||||
)
|
||||
|
||||
revision_asset_id = review_task.resume_asset_id or review_task.latest_revision_asset_id
|
||||
if revision_asset_id is None:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_409_CONFLICT,
|
||||
detail="No uploaded revision to confirm",
|
||||
)
|
||||
|
||||
revision_asset = await session.get(AssetORM, revision_asset_id)
|
||||
if revision_asset is None or revision_asset.order_id != order_id:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="Revision asset does not belong to the order",
|
||||
)
|
||||
|
||||
review_task.status = ReviewTaskStatus.SUBMITTED
|
||||
review_task.decision = ReviewDecision.APPROVE
|
||||
review_task.reviewer_id = payload.reviewer_id
|
||||
review_task.selected_asset_id = revision_asset_id
|
||||
review_task.comment = payload.comment
|
||||
await session.commit()
|
||||
|
||||
try:
|
||||
await self.workflow_service.signal_review(
|
||||
workflow_run.workflow_id,
|
||||
ReviewSignalPayload(
|
||||
decision=ReviewDecision.APPROVE,
|
||||
reviewer_id=payload.reviewer_id,
|
||||
selected_asset_id=revision_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 ConfirmRevisionResponse(
|
||||
order_id=order_id,
|
||||
workflow_id=workflow_run.workflow_id,
|
||||
revision_asset_id=revision_asset_id,
|
||||
decision=ReviewDecision.APPROVE,
|
||||
status="submitted",
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user