o2o-negosium-original/negodata/backend/router/v1/quotation/quotation.py

128 lines
7.2 KiB
Python

from datetime import datetime
from uuid import UUID
from fastapi import APIRouter, Depends, Query
from common.models.gmodel import PageParams, UserInfo
from router.v1.validator.dependencies import IsValidAccessToken, RemoveNoneResponse
from services.quotation_service import QuotationService
from .protocol import (
Req_AwardQuotation,
Req_CreateQuotation,
Req_RegenerateQuotation,
Res_CreateQuotation,
Res_DeleteQuotation,
Res_NotifySessions,
Res_Quotation,
Res_QuotationCards,
Res_QuotationList,
Res_QuotationResult,
Res_QuotationSessions,
Res_QuotationStatus,
Res_SessionChat,
Res_TargetBreakdown,
)
# 라우터(컨트롤러). 인증(Depends(IsValidAccessToken))으로 UserInfo 를 받아 company_id 로 스코프(작성자 회사 기준).
# 라우팅 주의: 정적/하위 경로(/list, /create, /{qt_id}/status ...)를 /{qt_id} 보다 먼저 선언해야
# /{qt_id} 가 /list 등을 가로채지 않는다.
router = APIRouter(prefix="/v1/quotation", tags=["Quotation"], responses={404: {"description": "Not found"}})
# ----- 실제 엔드포인트 (DB) -----
@router.get(path="/list", response_model=Res_QuotationList, summary="견적 목록")
async def list_quotations(
service: QuotationService = Depends(),
user_info: UserInfo = Depends(IsValidAccessToken),
search: str | None = Query(None, description="견적명/견적번호 검색"),
status: str | None = Query(None, description="상태 필터(정확히 일치)"),
type: str | None = Query(None, description="유형 필터(정확히 일치)"),
start_from: datetime | None = Query(None, description="시작일시 이후(ISO)"),
start_to: datetime | None = Query(None, description="시작일시 이전(ISO)"),
mine: bool = Query(False, description="내 견적만 보기(작성자=로그인 유저)"),
pg: PageParams = Depends(),
):
owner = user_info.user_id if mine else None
return RemoveNoneResponse(await service.list_quotations(user_info.company_id, owner, search, status, type, start_from, start_to, pg))
@router.post(path="/create", response_model=Res_CreateQuotation, summary="견적 생성")
async def create_quotation(
req: Req_CreateQuotation, service: QuotationService = Depends(), user_info: UserInfo = Depends(IsValidAccessToken)
):
return RemoveNoneResponse(await service.create_quotation(user_info.user_id, req))
@router.post(path="/stop/{qt_id}", response_model=Res_Quotation, summary="견적 마감")
async def stop_quotation(qt_id: UUID, service: QuotationService = Depends(), user_info: UserInfo = Depends(IsValidAccessToken)):
return RemoveNoneResponse(await service.stop_quotation(str(qt_id), user_info.company_id, user_info.user_id, user_info.role))
@router.post(path="/award/{qt_id}", response_model=Res_Quotation, summary="개찰 견적 직접 낙찰")
async def award_quotation(
qt_id: UUID, req: Req_AwardQuotation, service: QuotationService = Depends(), user_info: UserInfo = Depends(IsValidAccessToken)
):
# 권한(본인 견적만/OWNER 예외)은 서비스에서 판정하도록 호출자 user_id·role 을 넘긴다.
return RemoveNoneResponse(
await service.award_quotation(str(qt_id), user_info.company_id, user_info.user_id, user_info.role, req.winner_supplier_id)
)
@router.post(path="/regenerate/{qt_id}", response_model=Res_CreateQuotation, summary="견적 재생성(다음 라운드)")
async def regenerate_quotation(
qt_id: UUID, req: Req_RegenerateQuotation, service: QuotationService = Depends(), user_info: UserInfo = Depends(IsValidAccessToken)
):
return RemoveNoneResponse(await service.regenerate_quotation(str(qt_id), user_info.company_id, req.supplier_ids, user_info.user_id, user_info.role))
# ----- 견적 상세 (FK로 연결된 하위 데이터 / 일부는 모델 미존재로 스텁) -----
@router.get(path="/{qt_id}/status", response_model=Res_QuotationStatus, summary="견적 상태 조회")
async def get_quotation_status(qt_id: UUID, service: QuotationService = Depends(), user_info: UserInfo = Depends(IsValidAccessToken)):
return RemoveNoneResponse(await service.get_status(str(qt_id), user_info.company_id))
@router.get(path="/{qt_id}/sessions", response_model=Res_QuotationSessions, summary="참여현황")
async def get_quotation_sessions(qt_id: UUID, service: QuotationService = Depends(), user_info: UserInfo = Depends(IsValidAccessToken)):
return RemoveNoneResponse(await service.list_sessions(str(qt_id), user_info.company_id))
@router.post(path="/{qt_id}/notify", response_model=Res_NotifySessions, summary="협상 초청 메일 발송")
async def notify_quotation(qt_id: UUID, service: QuotationService = Depends(), user_info: UserInfo = Depends(IsValidAccessToken)):
return RemoveNoneResponse(await service.notify_sessions(str(qt_id), user_info.company_id, user_info.user_id, user_info.role))
@router.get(path="/session/{session_id}/chat", response_model=Res_SessionChat, summary="채팅 상세")
async def get_session_chat(session_id: UUID, service: QuotationService = Depends(), user_info: UserInfo = Depends(IsValidAccessToken)):
return RemoveNoneResponse(await service.list_chats(str(session_id), user_info.company_id))
@router.get(path="/session/{session_id}/target-breakdown", response_model=Res_TargetBreakdown, summary="세션 목표가 산정내역")
async def get_target_breakdown(session_id: UUID, service: QuotationService = Depends(), user_info: UserInfo = Depends(IsValidAccessToken)):
return RemoveNoneResponse(await service.get_target_breakdown(str(session_id), user_info.company_id))
@router.post(path="/session/{session_id}/notify", response_model=Res_NotifySessions, summary="세션 초청 메일 재발송")
async def notify_session(session_id: UUID, service: QuotationService = Depends(), user_info: UserInfo = Depends(IsValidAccessToken)):
return RemoveNoneResponse(await service.notify_session(str(session_id), user_info.company_id, user_info.user_id, user_info.role))
@router.get(path="/{qt_id}/result", response_model=Res_QuotationResult, summary="낙찰 결과")
async def get_quotation_result(qt_id: UUID, service: QuotationService = Depends(), user_info: UserInfo = Depends(IsValidAccessToken)):
return RemoveNoneResponse(await service.get_result(str(qt_id), user_info.company_id))
@router.get(path="/{qt_id}/cards", response_model=Res_QuotationCards, summary="견적 사용 카드")
async def get_quotation_cards(qt_id: UUID, service: QuotationService = Depends(), user_info: UserInfo = Depends(IsValidAccessToken)):
return RemoveNoneResponse(await service.list_cards(str(qt_id), user_info.company_id))
@router.delete(path="/delete/{qt_id}", response_model=Res_DeleteQuotation, summary="견적 삭제")
async def delete_quotation(qt_id: UUID, service: QuotationService = Depends(), user_info: UserInfo = Depends(IsValidAccessToken)):
return RemoveNoneResponse(await service.delete_quotation(str(qt_id), user_info.company_id, user_info.user_id, user_info.role))
# ----- 단건 조회 (정적/하위 경로 뒤에 선언) -----
@router.get(path="/{qt_id}", response_model=Res_Quotation, summary="견적 조회")
async def get_quotation(qt_id: UUID, service: QuotationService = Depends(), user_info: UserInfo = Depends(IsValidAccessToken)):
return RemoveNoneResponse(await service.get_quotation(str(qt_id), user_info.company_id))