import uuid from datetime import datetime, timezone from fastapi import Depends from common.database.db_session_manager import DB_SESSION_MNG from common.database.model.models import sessions from common.enums import DBWRType, ErrorType, QuotationStatus, SessionStatus from common.models.gmodel import UserInfo from crud.session_crud import ISessionCRUD, SessionCRUD from router.v1.negotiation.protocol import ListItem, Res_Participate, Res_SessionList from services.auth_service import AuthService class NegotiationService: """협상 도메인 비즈니스 로직. - 인증(계정 활성 + 저장 토큰 대조)은 AuthService.authenticate 로 위임(재사용). - 목록은 로그인 유저의 supplier_id 로만 조회한다. """ def __init__(self, auth: AuthService = Depends(AuthService), session_crud: ISessionCRUD = Depends(SessionCRUD)): self.auth = auth self.session_crud = session_crud async def list_sessions(self, user_info: UserInfo, access_token: str, status, qt_type, order: str, page: int, page_size: int) -> Res_SessionList: res = Res_SessionList() # 1) 인증 (활성 + 저장된 access 토큰 대조) err_type, info = await self.auth.authenticate(user_info, access_token) if err_type != ErrorType.SUCCESS: res.result.SetResult(err_type) return res supplier_id = uuid.UUID(info.supplier_id) offset = (page - 1) * page_size # 2) 목록 조회 (NEGOTIATION Read 세션, sessions ⨝ items) err_type, rows = await DB_SESSION_MNG.execute_lambda( sessions.DBType(), DBWRType.DB_READ.value, lambda s: self.session_crud.list_by_supplier(s, supplier_id, status, qt_type, order, offset, page_size), ) if err_type != ErrorType.SUCCESS: res.result.SetResult(err_type) return res # 3) 총개수 (페이지네이션용) err_type, total = await DB_SESSION_MNG.execute_lambda( sessions.DBType(), DBWRType.DB_READ.value, lambda s: self.session_crud.count_by_supplier(s, supplier_id, status, qt_type), ) if err_type != ErrorType.SUCCESS: res.result.SetResult(err_type) return res res.items = [ ListItem( session_id=str(r[0]), session_status=r[1], qt_type=r[2], qt_number=r[3], qt_end_time=r[4].isoformat(timespec="seconds") if r[4] else "", item_code=r[5] or "", item_name=r[6] or "", model_name=r[7] or "", maker_name=r[8] or "", ) for r in rows ] res.total = total res.page = page res.page_size = page_size return res async def participate(self, user_info: UserInfo, access_token: str, session_id_str: str) -> Res_Participate: res = Res_Participate() # 1) 인증 err_type, info = await self.auth.authenticate(user_info, access_token) if err_type != ErrorType.SUCCESS: res.result.SetResult(err_type) return res try: session_id = uuid.UUID(session_id_str) except (ValueError, TypeError): res.result.SetResult(ErrorType.NEGO_NOT_FOUND) return res # 2) 세션 조회 err_type, sess = await DB_SESSION_MNG.execute_lambda( sessions.DBType(), DBWRType.DB_READ.value, lambda s: self.session_crud.get_session_by_id(s, session_id), ) if err_type != ErrorType.SUCCESS or sess is None: res.result.SetResult(ErrorType.NEGO_NOT_FOUND) return res # 3) 소유 검증 (세션 공급사 == 접속 유저 공급사) if str(sess.supplier_id) != info.supplier_id: res.result.SetResult(ErrorType.NEGO_FORBIDDEN) return res # 4) 세션 상태 검증 (미참여/협상거부는 참여 불가) if sess.status in (SessionStatus.NOT_PARTICIPATED.value, SessionStatus.REJECTED.value): res.result.SetResult(ErrorType.NEGO_NOT_PARTICIPABLE) return res # 5) 견적 조회 + 마감 상태 err_type, quote = await DB_SESSION_MNG.execute_lambda( sessions.DBType(), DBWRType.DB_READ.value, lambda s: self.session_crud.get_quotation_by_id(s, sess.quotation_id), ) if err_type != ErrorType.SUCCESS or quote is None: res.result.SetResult(ErrorType.NEGO_NOT_FOUND) return res if quote.status == QuotationStatus.CLOSED.value: res.result.SetResult(ErrorType.NEGO_QUOTATION_CLOSED) return res # 6) 마감 시간 초과 (견적 end_time < 현재). 협상생성(1)일 때만 session→미참여로 정리. end = quote.end_time if end is not None and end.tzinfo is None: end = end.replace(tzinfo=timezone.utc) if end is not None and end < datetime.now(timezone.utc): if sess.status == SessionStatus.CREATED.value: await DB_SESSION_MNG.execute_lambda_run( [sessions.DBType()], [lambda s: self.session_crud.update_session_status(s, session_id, SessionStatus.NOT_PARTICIPATED.value)], ) res.result.SetResult(ErrorType.NEGO_DEADLINE_PASSED) return res # 7) 참여 성공 — 협상생성(1)일 때만 상태 전이(협상중/완료는 무변경 진입) if sess.status == SessionStatus.CREATED.value: err_type = await DB_SESSION_MNG.execute_lambda_run( [sessions.DBType()], [ lambda s: self.session_crud.update_session_status(s, session_id, SessionStatus.IN_PROGRESS.value), lambda s: self.session_crud.update_quotation_status(s, sess.quotation_id, QuotationStatus.IN_PROGRESS.value), ], ) if err_type != ErrorType.SUCCESS: res.result.SetResult(err_type) return res res.session_id = str(sess.session_id) return res