o2o-negosium-original/backend/common/enums.py
민헌 1a933409a3 [refactor] backend services/router 정리 — chat 분리·staticmethod 집약·protocol 문서화·테스트 보강
- router: 채팅 라우터/프로토콜을 negotiation/ 에서 chat/ 으로 분리(git mv, URL 유지).
  chat_protocol.py → chat/protocol.py (폴더당 protocol.py convention 복원), router 등록·import 갱신.
- chat_service: 순수 헬퍼/매퍼를 모듈 함수 → 클래스 @staticmethod 로 이동하고 클래스 상단에 집약.
- protocol: Req 모델 + 코드/enum 필드에 Field(description=...) 추가(Swagger 노출),
  Req 문자열 필드에 max_length(DB 컬럼 정합) 추가 → 초과 입력이 DB 오류 대신 422.
- models: updated_at 에 onupdate 추가 → UPDATE 시 자동 갱신(negodata convention 일치).
- tests: negotiation reject e2e 6케이스 + chat 순수 헬퍼 단위 4케이스 추가(45→55 passed).
- enums: 장황한 주석/docstring 축약(코드값 변경 없음).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 11:07:54 +09:00

148 lines
5.8 KiB
Python

from enum import Enum, auto
from fastapi import HTTPException
class ErrorType(Enum):
"""서버 전역 결과 코드. Res_WebPacketProtocol.result 에 담겨 클라이언트로 전달된다.
HTTP status 와 겹치지 않도록 구간을 분리해서 관리한다.
"""
SUCCESS = 0
FAIL = 1
# DB / Redis 에러
DB_RUN_FAILED = 10
DB_ALREADY_SAME_KEY = auto()
DB_INVALID_KEY = auto()
DB_EMPTY_DATA = auto()
DB_INVALID_TYPE = auto()
# 요청/직렬화 에러
JSON_PARSE_ERROR = 100
INVALID_REQUEST_DATA = auto()
INTERNAL_EXCEPTION = auto()
# http 에러 코드와 겹치지 않게 설정 - router 전용 예외 발생 옵션
HTTP_INVALID_CLIENT_REQUEST = 419
HTTP_TO_MANY_REQUEST = 429
HTTP_INVALID_CLIENT_ACCESS = 433
HTTP_ACCESS_TOKEN_EXPIRED = 434
HTTP_REFRESH_TOKEN_EXPIRED = 435
HTTP_INVALID_TOKEN_ACCESS = 436
# 계정 관련 에러
ACCOUNT_INVALID_INFO = 1200
ACCOUNT_ALREADY_EXIST = auto()
ACCOUNT_BLOCKED_USER = auto()
TOKEN_REVOKED = auto() # 제시된 토큰이 저장된 토큰과 불일치(로그아웃/타기기 로그인으로 교체됨)
# 협상(negotiation) 관련 에러 — 프론트 toast 용 코드
NEGO_FORBIDDEN = 1300 # 공급사 불일치(권한 없음)
NEGO_NOT_PARTICIPABLE = auto() # 1301 세션 상태가 미참여/협상거부라 참여 불가
NEGO_QUOTATION_CLOSED = auto() # 1302 견적 마감 상태
NEGO_DEADLINE_PASSED = auto() # 1303 견적 마감 시간 초과
NEGO_NOT_FOUND = auto() # 1304 세션/견적 없음
# 채팅(chat) 관련 에러
CHAT_NOT_IN_PROGRESS = 1400 # 협상중 상태가 아니라 대화 불가(미참여/완료/거부)
CHAT_PRICE_OUT_OF_RANGE = auto() # 1401 제시가가 허용 범위를 벗어남
CHAT_AGENT_UNAVAILABLE = auto() # 1402 협상 에이전트(agent) 호출 실패
CHAT_IN_PROGRESS = auto() # 1403 직전 턴 처리 중(동시 전송 가드)
CHAT_INPUT_MODE_MISMATCH = auto() # 1404 직전 봇이 요구한 입력 모드와 보낸 입력이 불일치(잘못된 버튼/타입) → 화면 리싱크 필요
CHAT_AGENT_TIMEOUT = auto() # 1405 agent 응답 타임아웃(처리됐을 수 있음 — 롤백/재시도 시 desync 위험)
# ErrorType 의 HTTP_* 값과 status_code 를 맞춰 router 단에서 raise 한다.
EXCEPTION_INVALID_CLIENT_REQUEST = HTTPException(status_code=ErrorType.HTTP_INVALID_CLIENT_REQUEST.value, detail=ErrorType.HTTP_INVALID_CLIENT_REQUEST.name)
EXCEPTION_TO_MANY_REQUEST = HTTPException(status_code=ErrorType.HTTP_TO_MANY_REQUEST.value, detail=ErrorType.HTTP_TO_MANY_REQUEST.name)
EXCEPTION_INVALID_CLIENT_ACCESS = HTTPException(status_code=ErrorType.HTTP_INVALID_CLIENT_ACCESS.value, detail=ErrorType.HTTP_INVALID_CLIENT_ACCESS.name)
EXCEPTION_ACCESS_TOKEN_EXPIRED = HTTPException(status_code=ErrorType.HTTP_ACCESS_TOKEN_EXPIRED.value, detail=ErrorType.HTTP_ACCESS_TOKEN_EXPIRED.name)
EXCEPTION_REFRESH_TOKEN_EXPIRED = HTTPException(status_code=ErrorType.HTTP_REFRESH_TOKEN_EXPIRED.value, detail=ErrorType.HTTP_REFRESH_TOKEN_EXPIRED.name)
EXCEPTION_HTTP_INVALID_TOKEN_ACCESS = HTTPException(status_code=ErrorType.HTTP_INVALID_TOKEN_ACCESS.value, detail=ErrorType.HTTP_INVALID_TOKEN_ACCESS.name)
class DBType(Enum):
"""논리 DB 구분. 모델마다 DBType() 으로 자신이 속한 DB 를 반환한다.
DB 가 늘어나면 여기에 추가하고 db_session_manager 의 맵에 등록만 하면 된다.
물리적으로 같은 negosium_db 라도 도메인별 논리 구분으로 나눠 둘 수 있다(커넥션 config 는 재사용).
"""
USER = 1 # 기본 유저 (supplier_users 테이블)
PARTNER = 2 # partner 도메인 (partner.suppliers, partner.items 등)
NEGOTIATION = 3 # negotiation 도메인 (negotiation.sessions 등)
QUOTATION = 4 # quotation 도메인 (quotation.quotations 등)
class DBWRType(Enum):
"""Read / Write 접속 구분. 조회는 DB_READ, 변경은 DB_WRITE 엔진을 사용한다."""
DB_READ = 1
DB_WRITE = 2
class AccountStatus(Enum):
"""계정 상태 코드. company.users / supplier.supplier_users 의 status 컬럼."""
ACTIVE = 1 # 활성
INACTIVE = 2 # 비활성
class UserRole(Enum):
"""유저 권한 코드. company.users / supplier.supplier_users 의 role 컬럼."""
USER = 1 # 일반 유저
MANAGER = 2 # 매니저
class TokenType(Enum):
"""토큰 종류 코드. supplier.supplier_user_tokens 의 type 컬럼."""
ACCESS = 1
REFRESH = 2
class QtType(Enum):
"""견적/세션 유형 코드. quotation.quotations.type / negotiation.sessions.qt_type."""
RENEGO = 1 # 재협상(1:1)
REQUOTE = 2 # 재견적(1:N)
class SessionStatus(Enum):
"""협상 세션 진행 상태 코드. negotiation.sessions.status"""
CREATED = 1 # 협상생성
IN_PROGRESS = 2 # 협상중
DONE = 3 # 협상완료
NOT_PARTICIPATED = 4 # 미참여
REJECTED = 5 # 협상거부
class QuotationStatus(Enum):
"""견적 진행 상태 코드. quotation.quotations.status """
CREATED = 1 # 견적생성
IN_PROGRESS = 2 # 견적진행중
CLOSED = 3 # 견적마감
class ChatSender(Enum):
"""채팅 발신자 코드. negotiation.chats.sender """
BOT = 1 # 공급사(갑) — bot 메시지
USER = 2 # 협력사(을) — user 입력
class DeliveryType(Enum):
"""배송 유형 코드. partner.items.delivery_type / negotiation.sessions.reject_delivery_type """
SUPPLIER = 1 # 협력사배송
COURIER = 2 # 지정택배배송
PICKUP = 3 # 픽업배송
@classmethod
def label_of(cls, code) -> str:
"""코드(1~3) → 한글 라벨. 알 수 없으면 빈 문자열."""
return {1: "협력사배송", 2: "지정택배배송", 3: "픽업배송"}.get(code, "")