목록 진입 시 "지금 할 일"이 먼저 보이도록 기본 정렬을 개선하고, 사용자가 마감일로 직접 훑어볼 수 있는 전체 정렬 모드를 추가한다. - 기본(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>
64 lines
3.3 KiB
Python
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))
|