o2o-negosium-original/backend/router/v1/negotiation/session.py
민헌 d199c1dec1 feat(backend): 협상 거부(reject) 기능
- POST /v1/negotiation/sessions/{session_id}/reject (Req_Reject/Res_Reject)
- 참여와 공통 검증(인증/소유/세션상태/견적마감/마감시간)을
  _load_actionable_session 헬퍼로 추출해 participate·reject 재사용
- 거부 불가 상태: 협상완료/미참여/협상거부 → NEGO_NOT_PARTICIPABLE
- 성공 시 세션을 협상거부(5)로 전이하고 reject_reason 저장(최대 255자)
- 빈 사유는 INVALID_REQUEST_DATA

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 13:20:29 +09:00

64 lines
2.7 KiB
Python

from typing import Optional
from fastapi import APIRouter, Depends, Query
from fastapi.security import HTTPAuthorizationCredentials
from common.models.gmodel import UserInfo
from router.v1.validator.dependencies import IsValidAccessToken, RemoveNoneResponse, security
from services.negotiation_service import NegotiationService
from .protocol import Req_Reject, Res_Participate, Res_Reject, Res_SessionList
router = APIRouter(prefix="/v1/negotiation", tags=["Negotiation"], responses={404: {"description": "Not found"}})
@router.get(
path="/sessions",
response_model=Res_SessionList,
summary="협상 세션 목록",
description="로그인한 공급사의 협상 세션 목록. 필터(status/qt_type, 정수 코드)·마감일 정렬·페이지네이션 지원.",
)
async def list_sessions(
user_info: UserInfo = Depends(IsValidAccessToken),
credentials: HTTPAuthorizationCredentials = Depends(security),
service: NegotiationService = Depends(),
status: Optional[int] = Query(None, description="세션 상태 코드 (SessionStatus)"),
qt_type: Optional[int] = Query(None, description="견적 유형 코드 (QtType: 1=재협상, 2=재견적)"),
order: str = Query("asc", description="마감일 정렬: asc(임박순)/desc"),
page: int = Query(1, ge=1),
page_size: int = Query(20, ge=1, le=100),
):
return RemoveNoneResponse(
await service.list_sessions(user_info, credentials.credentials, status, qt_type, order, page, page_size)
)
@router.post(
path="/sessions/{session_id}/participate",
response_model=Res_Participate,
summary="협상 참여",
description="세션에 참여한다. 소유(공급사)·세션상태·견적마감·마감시간 검증 후 협상생성→협상중, 견적→견적진행중으로 전이.",
)
async def participate(
session_id: str,
user_info: UserInfo = Depends(IsValidAccessToken),
credentials: HTTPAuthorizationCredentials = Depends(security),
service: NegotiationService = Depends(),
):
return RemoveNoneResponse(await service.participate(user_info, credentials.credentials, session_id))
@router.post(
path="/sessions/{session_id}/reject",
response_model=Res_Reject,
summary="협상 거부",
description="세션 참여를 거부한다. 소유(공급사)·세션상태(완료/미참여/거부 불가)·견적마감·마감시간 검증 후 협상거부로 전이하고 사유를 저장.",
)
async def reject(
session_id: str,
req: Req_Reject,
user_info: UserInfo = Depends(IsValidAccessToken),
credentials: HTTPAuthorizationCredentials = Depends(security),
service: NegotiationService = Depends(),
):
return RemoveNoneResponse(await service.reject(user_info, credentials.credentials, session_id, req.reject_reason))