o2o-negosium-original/backend/router/v1/negotiation/session.py
민헌 5519c6e73b feat(negotiation): 세션 목록 기본 그룹 정렬 + order 지정 시 전체 정렬
목록 진입 시 "지금 할 일"이 먼저 보이도록 기본 정렬을 개선하고,
사용자가 마감일로 직접 훑어볼 수 있는 전체 정렬 모드를 추가한다.

- 기본(order 미지정): 협상생성·협상중을 상단 그룹으로 마감 임박순,
  종료(완료·미참여·거부)는 하단 그룹으로 최근 마감순. 동일 마감은
  session_id 로 tie-break 하여 페이지네이션 안정화.
- order=asc/desc: 그룹 구분 없이 전체를 마감(qt_end_time) 기준으로 정렬.
- router: order 기본값을 "asc" → None(Optional) 로 변경해 "그룹 기본"과
  "명시적 전체 정렬"을 구분.
- frontend: SessionListParams.order 주석을 새 계약에 맞게 갱신(타입 무변경).
- tests: 기본=그룹 / 파라미터=전체 정렬 검증으로 분리(21 passed).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-08 14:12:56 +09:00

64 lines
3.3 KiB
Python

from typing import Optional
from fastapi import APIRouter, Depends, Path, 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, 정수 코드)·페이지네이션 지원. 기본 정렬(order 미지정)은 '할 일(협상생성·협상중) 우선 + 마감 임박순', 종료(완료·미참여·거부)는 하단·최근순. order 를 주면 그룹 없이 전체 마감순으로 정렬.",
)
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=재견적, 3=신규협상, 4=신규견적)"),
order: Optional[str] = Query(None, description="마감일 전체 정렬: asc(임박순)/desc(여유순). 미지정 시 기본 그룹 정렬('할 일' 우선 → 종료는 하단·최근순). 지정하면 그룹 없이 전체를 마감 기준으로 정렬."),
page: int = Query(1, ge=1, description="페이지 (1부터)"),
page_size: int = Query(20, ge=1, le=100, description="페이지당 건수 (1~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 = Path(description="대상 협상 세션 uuid"),
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 = Path(description="대상 협상 세션 uuid"),
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))