o2o-negosium-original/backend/router/v1/negotiation/session.py
민헌 2480a47efe feat(backend): 협상 세션 목록 + 참여 기능
- GET /v1/negotiation/sessions: 로그인 공급사의 세션 목록(필터/정렬/페이지네이션)
  · qt_end_time 은 견적(quotation.end_time) 기준, sessions⨝items⨝quotations 조인
  · status/qt_type 은 정수 코드로 응답(라벨 매핑은 프론트)
- POST /v1/negotiation/sessions/{session_id}/participate: 협상 참여
  · 검증: 소유(공급사 대조)→세션상태→견적마감→마감시간, 에러코드 1300~1304
  · 협상생성→협상중, 견적→견적진행중 (협상중/완료는 무변경 진입)
  · 마감초과 시 협상생성 세션만 미참여로 정리
- DBType.NEGOTIATION/QUOTATION, items/sessions/quotations 모델
- QtType/SessionStatus/QuotationStatus enum, AuthService.authenticate 공통화
- 협상 e2e 테스트(test_negotiation.py)

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

48 lines
2.1 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 Res_Participate, 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))