[refactor] negodata/backend: 공용 PageParams(page/size/skip) + 상태 enum·모델 정비
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
ece24f4067
commit
4587be5844
@ -25,29 +25,14 @@ class _DBTypeMixin:
|
|||||||
return DBType.MAIN.value
|
return DBType.MAIN.value
|
||||||
|
|
||||||
|
|
||||||
# ERD 공통 컬럼(created/updated/deleted). negodata 도메인 테이블은 단일 MAIN DB 에서 soft-delete 를 쓴다.
|
# ERD 공통 컬럼
|
||||||
class MainTableMixin(_DBTypeMixin):
|
class MainTableMixin(_DBTypeMixin):
|
||||||
created_at = Column(DateTime, nullable=False, server_default=_utc_now_sql())
|
created_at = Column(DateTime, nullable=False, server_default=_utc_now_sql())
|
||||||
updated_at = Column(DateTime, nullable=False, server_default=_utc_now_sql(), onupdate=_utc_now_sql())
|
updated_at = Column(DateTime, nullable=False, server_default=_utc_now_sql(), onupdate=_utc_now_sql())
|
||||||
deleted = Column(Boolean, nullable=False, server_default=text("false"), default=False)
|
deleted = Column(Boolean, nullable=False, server_default=text("false"), default=False)
|
||||||
|
|
||||||
|
|
||||||
# 스캐폴드 예시 계정 (negosium 골격 유산)
|
# ERD 도메인 모델
|
||||||
# negodata 인증은 ERD 의 users/companies 를 쓴다(아래). tbl_account 는 스캐폴드
|
|
||||||
# 테스트 호환을 위해 남겨둔다 (negodata_db 의 postgres-init 이 생성).
|
|
||||||
class tbl_account(_DBTypeMixin, MAIN_BASE):
|
|
||||||
__tablename__ = "tbl_account"
|
|
||||||
|
|
||||||
uid = Column(Integer, primary_key=True, autoincrement=True)
|
|
||||||
id = Column(String(45), nullable=False, unique=True)
|
|
||||||
pw = Column(String(255), nullable=False, default="")
|
|
||||||
nickname = Column(String(45), nullable=False, default="")
|
|
||||||
is_blocked = Column(Boolean, nullable=False, default=False)
|
|
||||||
last_login_at = Column(DateTime, nullable=False, server_default=_utc_now_sql())
|
|
||||||
create_at = Column(DateTime, server_default=_utc_now_sql())
|
|
||||||
|
|
||||||
|
|
||||||
# ERD 도메인 모델 (negosium.sql 기준, UUID PK)
|
|
||||||
class companies(MainTableMixin, MAIN_BASE):
|
class companies(MainTableMixin, MAIN_BASE):
|
||||||
__tablename__ = "companies"
|
__tablename__ = "companies"
|
||||||
|
|
||||||
@ -122,6 +107,31 @@ class suppliers(MainTableMixin, MAIN_BASE):
|
|||||||
priority = Column(String(10), nullable=True) # True/False 가 아닌 string value 가능
|
priority = Column(String(10), nullable=True) # True/False 가 아닌 string value 가능
|
||||||
|
|
||||||
|
|
||||||
|
class nego_cards(MainTableMixin, MAIN_BASE):
|
||||||
|
__tablename__ = "nego_cards"
|
||||||
|
|
||||||
|
nego_card_id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
||||||
|
user_id = Column(UUID(as_uuid=True), nullable=True, index=True) # 등록 유저(o2o 기본 카드는 NULL)
|
||||||
|
name = Column(String(20), nullable=True) # 카드명
|
||||||
|
number = Column(String(10), nullable=True) # 식별번호(카드코드)
|
||||||
|
script = Column(String(255), nullable=True) # 협상 스크립트(평문 미리보기)
|
||||||
|
edit_script = Column(JSONB, nullable=True) # 편집된 스크립트(Slate JSON)
|
||||||
|
|
||||||
|
|
||||||
|
class wild_cards(MainTableMixin, MAIN_BASE):
|
||||||
|
__tablename__ = "wild_cards"
|
||||||
|
|
||||||
|
wild_card_id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
||||||
|
user_id = Column(UUID(as_uuid=True), nullable=True, index=True) # 등록 유저(o2o 기본 카드는 NULL)
|
||||||
|
name = Column(String(20), nullable=True) # 카드명
|
||||||
|
number = Column(String(10), nullable=True) # 식별번호(카드코드)
|
||||||
|
script = Column(String(255), nullable=True) # 협상 스크립트(평문 미리보기)
|
||||||
|
edit_script = Column(JSONB, nullable=True) # 편집된 스크립트(Slate JSON)
|
||||||
|
condition = Column(String(255), nullable=True) # 사용 조건(트리거)
|
||||||
|
available = Column(Boolean, nullable=False, default=False) # 수동 협상 적용 여부(ACTIVE/INACTIVE 매핑)
|
||||||
|
memo = Column(String(255), nullable=True) # 자유 메모
|
||||||
|
|
||||||
|
|
||||||
class quotation_settings(MainTableMixin, MAIN_BASE):
|
class quotation_settings(MainTableMixin, MAIN_BASE):
|
||||||
__tablename__ = "quotation_settings"
|
__tablename__ = "quotation_settings"
|
||||||
|
|
||||||
@ -159,3 +169,39 @@ class quotations(MainTableMixin, MAIN_BASE):
|
|||||||
preferred_sp_name = Column(String(20), nullable=True)
|
preferred_sp_name = Column(String(20), nullable=True)
|
||||||
equal_bid_yn = Column(Boolean, nullable=True)
|
equal_bid_yn = Column(Boolean, nullable=True)
|
||||||
equal_bid_data = Column(JSONB, nullable=True)
|
equal_bid_data = Column(JSONB, nullable=True)
|
||||||
|
|
||||||
|
|
||||||
|
class sessions(MainTableMixin, MAIN_BASE):
|
||||||
|
__tablename__ = "sessions"
|
||||||
|
|
||||||
|
session_id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
||||||
|
quotation_id = Column(UUID(as_uuid=True), nullable=False, index=True) # 소속 견적(quotations.qt_id)
|
||||||
|
item_id = Column(UUID(as_uuid=True), nullable=False, index=True) # 대상 상품(items.item_id)
|
||||||
|
supplier_id = Column(UUID(as_uuid=True), nullable=False, index=True) # 대상 공급사(suppliers.supplier_id)
|
||||||
|
|
||||||
|
qt_number = Column(String(30), nullable=False) # 견적번호 스냅샷
|
||||||
|
qt_round = Column(Integer, nullable=False) # 견적 라운드 스냅샷
|
||||||
|
qt_type = Column(SmallInteger, nullable=False) # QuotationType 스냅샷
|
||||||
|
target_price = Column(BigInteger, nullable=False) # 목표가(원)
|
||||||
|
status = Column(SmallInteger, nullable=False) # SessionStatus 코드
|
||||||
|
bid_price = Column(BigInteger, nullable=True) # 입찰가(원)
|
||||||
|
bid_at = Column(DateTime, nullable=True) # 입찰 시각
|
||||||
|
end_time = Column(DateTime, nullable=False) # 세션 종료 시각
|
||||||
|
reject_reason = Column(String(255), nullable=True)
|
||||||
|
reject_price = Column(BigInteger, nullable=True)
|
||||||
|
reject_delivery_type = Column(SmallInteger, nullable=True) # DeliveryType 코드
|
||||||
|
|
||||||
|
|
||||||
|
class chats(MainTableMixin, MAIN_BASE):
|
||||||
|
__tablename__ = "chats"
|
||||||
|
|
||||||
|
chat_id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
||||||
|
session_id = Column(UUID(as_uuid=True), nullable=False, index=True) # 소속 세션(sessions.session_id)
|
||||||
|
card_id = Column(UUID(as_uuid=True), nullable=True) # 사용된 카드(nego_cards.nego_card_id)
|
||||||
|
|
||||||
|
seq = Column(Integer, nullable=False, default=1) # 세션 내 메시지 순번(프로토콜 index)
|
||||||
|
sender = Column(SmallInteger, nullable=False) # ChatSender 코드(1=bot, 2=partner)
|
||||||
|
target_price = Column(BigInteger, nullable=False) # 제시 목표가(원)
|
||||||
|
card_used_yn = Column(Boolean, nullable=True) # 카드 사용 여부
|
||||||
|
indicator_value = Column(Numeric(8, 6), nullable=True)
|
||||||
|
card_type = Column(SmallInteger, nullable=True) # 1=nego_card, 2=wild_card
|
||||||
|
|||||||
@ -38,9 +38,11 @@ class ErrorType(Enum):
|
|||||||
|
|
||||||
# 상품 관련 에러
|
# 상품 관련 에러
|
||||||
ITEM_NOT_FOUND = 1300
|
ITEM_NOT_FOUND = 1300
|
||||||
|
ITEM_CODE_DUPLICATE = auto()
|
||||||
|
|
||||||
# 협력사 관련 에러
|
# 협력사 관련 에러
|
||||||
SUPPLIER_NOT_FOUND = 1400
|
SUPPLIER_NOT_FOUND = 1400
|
||||||
|
SUPPLIER_CODE_DUPLICATE = auto()
|
||||||
|
|
||||||
# 견적 관련 에러
|
# 견적 관련 에러
|
||||||
QUOTATION_NOT_FOUND = 1500
|
QUOTATION_NOT_FOUND = 1500
|
||||||
@ -48,6 +50,9 @@ class ErrorType(Enum):
|
|||||||
# 견적 설정 관련 에러
|
# 견적 설정 관련 에러
|
||||||
QUOTATION_SETTING_NOT_FOUND = 1600
|
QUOTATION_SETTING_NOT_FOUND = 1600
|
||||||
|
|
||||||
|
# 협상카드 관련 에러
|
||||||
|
CARD_NOT_FOUND = 1700
|
||||||
|
|
||||||
|
|
||||||
# ErrorType 의 HTTP_* 값과 status_code 를 맞춰 router 단에서 raise 한다.
|
# 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_INVALID_CLIENT_REQUEST = HTTPException(status_code=ErrorType.HTTP_INVALID_CLIENT_REQUEST.value, detail=ErrorType.HTTP_INVALID_CLIENT_REQUEST.name)
|
||||||
@ -102,6 +107,30 @@ class QuotationType(Enum):
|
|||||||
REQUOTE = 2
|
REQUOTE = 2
|
||||||
|
|
||||||
|
|
||||||
|
class QuotationStatus(Enum):
|
||||||
|
"""quotations.status 코드값(SMALLINT). 프론트 견적상태 뱃지와 매핑된다."""
|
||||||
|
|
||||||
|
CREATED = 1 # 견적생성
|
||||||
|
ACTIVE = 2 # 견적진행중
|
||||||
|
CLOSED = 3 # 견적마감
|
||||||
|
ON_HOLD = 4 # 협상보류
|
||||||
|
|
||||||
|
|
||||||
|
class SessionStatus(Enum):
|
||||||
|
"""negotiation.sessions.status 코드값. 협력사별 협상 세션 진행 상태."""
|
||||||
|
|
||||||
|
NEGOTIATING = 1 # 협상중
|
||||||
|
COMPLETED = 2 # 협상종료
|
||||||
|
REJECTED = 3 # 협상거부
|
||||||
|
|
||||||
|
|
||||||
|
class ChatSender(Enum):
|
||||||
|
"""negotiation.chats.sender 코드값. 채팅 발신 주체."""
|
||||||
|
|
||||||
|
BOT = 1 # 구매대행 봇
|
||||||
|
PARTNER = 2 # 협력사
|
||||||
|
|
||||||
|
|
||||||
class DeliveryType(Enum):
|
class DeliveryType(Enum):
|
||||||
"""items.delivery_type 코드값. 협상 채팅의 배송형태 선택지와 동일 집합."""
|
"""items.delivery_type 코드값. 협상 채팅의 배송형태 선택지와 동일 집합."""
|
||||||
|
|
||||||
@ -110,6 +139,13 @@ class DeliveryType(Enum):
|
|||||||
PICKUP = 3 # 픽업배송
|
PICKUP = 3 # 픽업배송
|
||||||
|
|
||||||
|
|
||||||
|
class CardStatus(Enum):
|
||||||
|
"""nego_cards.status 코드값. 와일드카드의 협상 적용 여부(수동 승인). 일반 협상카드는 상시 ACTIVE."""
|
||||||
|
|
||||||
|
ACTIVE = 1
|
||||||
|
INACTIVE = 2
|
||||||
|
|
||||||
|
|
||||||
# 도메인 enum 한글 라벨. 프론트 드롭다운 표시는 이 라벨을 쓴다(값=코드).
|
# 도메인 enum 한글 라벨. 프론트 드롭다운 표시는 이 라벨을 쓴다(값=코드).
|
||||||
ENUM_LABELS = {
|
ENUM_LABELS = {
|
||||||
UserStatus.ACTIVE: "활성",
|
UserStatus.ACTIVE: "활성",
|
||||||
@ -120,9 +156,20 @@ ENUM_LABELS = {
|
|||||||
CompanyStatus.INACTIVE: "비활성",
|
CompanyStatus.INACTIVE: "비활성",
|
||||||
QuotationType.RENEGO: "재협상",
|
QuotationType.RENEGO: "재협상",
|
||||||
QuotationType.REQUOTE: "재견적",
|
QuotationType.REQUOTE: "재견적",
|
||||||
|
QuotationStatus.CREATED: "견적생성",
|
||||||
|
QuotationStatus.ACTIVE: "견적진행중",
|
||||||
|
QuotationStatus.CLOSED: "견적마감",
|
||||||
|
QuotationStatus.ON_HOLD: "협상보류",
|
||||||
|
SessionStatus.NEGOTIATING: "협상중",
|
||||||
|
SessionStatus.COMPLETED: "협상종료",
|
||||||
|
SessionStatus.REJECTED: "협상거부",
|
||||||
|
ChatSender.BOT: "봇",
|
||||||
|
ChatSender.PARTNER: "협력사",
|
||||||
DeliveryType.PARTNER: "협력사배송",
|
DeliveryType.PARTNER: "협력사배송",
|
||||||
DeliveryType.COURIER: "지정택배배송",
|
DeliveryType.COURIER: "지정택배배송",
|
||||||
DeliveryType.PICKUP: "픽업배송",
|
DeliveryType.PICKUP: "픽업배송",
|
||||||
|
CardStatus.ACTIVE: "적용",
|
||||||
|
CardStatus.INACTIVE: "대기",
|
||||||
}
|
}
|
||||||
|
|
||||||
# 프론트로 내려주는 도메인 코드 enum 모음. 새 코드 enum 추가 시 여기에 등록한다.
|
# 프론트로 내려주는 도메인 코드 enum 모음. 새 코드 enum 추가 시 여기에 등록한다.
|
||||||
@ -131,5 +178,9 @@ DOMAIN_ENUMS = {
|
|||||||
"user_role": UserRole,
|
"user_role": UserRole,
|
||||||
"company_status": CompanyStatus,
|
"company_status": CompanyStatus,
|
||||||
"quotation_type": QuotationType,
|
"quotation_type": QuotationType,
|
||||||
|
"quotation_status": QuotationStatus,
|
||||||
|
"session_status": SessionStatus,
|
||||||
|
"chat_sender": ChatSender,
|
||||||
"delivery_type": DeliveryType,
|
"delivery_type": DeliveryType,
|
||||||
|
"card_status": CardStatus,
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import json
|
import json
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
|
from fastapi import Query
|
||||||
from pydantic import BaseModel, Field
|
from pydantic import BaseModel, Field
|
||||||
|
|
||||||
from common.enums import ErrorType
|
from common.enums import ErrorType
|
||||||
@ -45,6 +46,24 @@ class Res_WebPacketProtocol(WebPacketProtocol):
|
|||||||
msg: Optional[str] = None
|
msg: Optional[str] = None
|
||||||
|
|
||||||
|
|
||||||
|
class Res_PageProtocol(Res_WebPacketProtocol):
|
||||||
|
# 목록 응답 공용 페이지 메타.
|
||||||
|
total: int = 0
|
||||||
|
page: int = 0
|
||||||
|
size: int = 0
|
||||||
|
|
||||||
|
|
||||||
|
class PageParams:
|
||||||
|
# 목록 엔드포인트 공용 쿼리 파라미터. 라우터에서 Depends() 로 주입한다.
|
||||||
|
def __init__(self, page: int = Query(1, ge=1), size: int = Query(20, ge=1, le=100)):
|
||||||
|
self.page = page
|
||||||
|
self.size = size
|
||||||
|
|
||||||
|
@property
|
||||||
|
def skip(self) -> int:
|
||||||
|
return (self.page - 1) * self.size
|
||||||
|
|
||||||
|
|
||||||
class UserInfo(StructModel):
|
class UserInfo(StructModel):
|
||||||
"""JWT subject 로 인코딩되는 유저 식별 정보."""
|
"""JWT subject 로 인코딩되는 유저 식별 정보."""
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user