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. 신규/재 × 협상(1:1)/견적(1:N). 1·2(재)는 기존 데이터 보존 위해 고정, 신규는 3·4.""" RENEGO = 1 # 재협상(1:1) REQUOTE = 2 # 재견적(1:N) NEW_NEGO = 3 # 신규협상(1:1) NEW_QUOTE = 4 # 신규견적(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 CloseReason(Enum): """견적 마감 사유. quotation.quotations.close_reason 낙찰(AWARDED) 외 OPEN_* 는 낙찰자 미정으로 마감된 '결렬' 건 — 공급사 재협상 요청 대상.""" AWARDED = 1 # 낙찰 OPEN_PRICE = 5 # 개찰: 낙찰 기준 미달 OPEN_EQUAL = 6 # 개찰: 동가 OPEN_NOSHOW = 7 # 개찰: 전원 미응찰 OPEN_REJECT = 8 # 개찰: 협상거부 존재 # 재협상 요청 가능한 마감 사유(낙찰 건은 제외). RENEGOTIABLE_CLOSE_REASONS = ( CloseReason.OPEN_PRICE.value, CloseReason.OPEN_EQUAL.value, CloseReason.OPEN_NOSHOW.value, CloseReason.OPEN_REJECT.value, ) class RenegotiationStatus(Enum): """sessions.custom.renegotiation.status — 공급사 재협상 요청 상태(IMK #15).""" PENDING = 1 # 접수, 담당자 심사 대기 APPROVED = 2 # 승인 — 다음 라운드 생성됨 REJECTED = 3 # 반려 CANCELED = 4 # 공급사 철회 class NotificationType(Enum): """company.notifications.type — negodata 담당자 인박스. 포털에서 만드는 건 재협상 요청뿐.""" RENEGO_REQUESTED = 5 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, "")