- MockAgentClient/use_mock 분기와 mock 전용 turn/count_bot_messages 제거 (항상 HttpAgentClient 위임) - agent RL state 입력을 실 DB 로 연동: anchor_price=quotation_settings.anchoring_value 기반 계산, partner_count=견적당 세션 수 (quotation_settings 모델 추가) - 재견적 배송형태(delivery_type) 캡처: 배송형태선택 단계 선택값을 summaryCM 요약에 반영 (DeliveryType enum 추가) - 표현 계약 passthrough: agent 응답의 bot_chat_type/indicator_value 를 우선 사용하고 없으면 step+qt_type 으로 폴백, indicator_value 컬럼 영속/전달 - 턴당 중복 조회 제거: item/last_price 를 1회 로드해 재사용 - 관련 테스트 추가(배송형태 캡처, passthrough) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
219 lines
20 KiB
Python
219 lines
20 KiB
Python
from sqlalchemy.orm import declarative_base
|
|
from sqlalchemy import Column, Integer, String, Boolean, DateTime, SmallInteger, BigInteger, Numeric
|
|
from sqlalchemy.dialects.postgresql import UUID, JSONB
|
|
from sqlalchemy.sql import text
|
|
|
|
from common.enums import DBType
|
|
|
|
# 모든 ORM 모델의 베이스. insert 시 isinstance 체크에도 사용된다.
|
|
MAIN_BASE = declarative_base()
|
|
|
|
|
|
class supplier_users(MAIN_BASE):
|
|
# 이 프로젝트의 기본 유저. 실제 테이블은 negosium_db 의 supplier 스키마(supplier_users).
|
|
@staticmethod
|
|
def DBType():
|
|
return DBType.USER.value
|
|
|
|
__tablename__ = "supplier_users"
|
|
__table_args__ = {"schema": "supplier"}
|
|
|
|
# gen_random_uuid() 는 pgcrypto 확장 기준. 코드값(status/role/type)은 SMALLINT 정수 코드(앱 enum 매핑).
|
|
su_id = Column(UUID(as_uuid=True), primary_key=True, server_default=text("gen_random_uuid()")) # 유저 식별자(PK)
|
|
supplier_id = Column(UUID(as_uuid=True), nullable=False) # 소속 공급사(partner.suppliers.supplier_id)
|
|
id = Column(String(20), nullable=False) # 로그인 ID
|
|
password = Column(String(255), nullable=False) # 해시된 비밀번호이어야 함
|
|
name = Column(String(50), nullable=True) # 이름
|
|
email = Column(String(255), nullable=True) # 이메일
|
|
contact_number = Column(String(20), nullable=True) # 연락처
|
|
last_accessed_at = Column(DateTime(timezone=True), nullable=False) # 마지막 접속 시각
|
|
status = Column(SmallInteger, nullable=False, server_default=text("1")) # 상태: 1=active, 2=inactive
|
|
role = Column(SmallInteger, nullable=False, server_default=text("1")) # 권한: 1=user, 2=manager
|
|
created_at = Column(DateTime(timezone=True), nullable=False, server_default=text("(now() AT TIME ZONE 'utc')")) # 생성 시각(UTC)
|
|
updated_at = Column(DateTime(timezone=True), nullable=False, server_default=text("(now() AT TIME ZONE 'utc')")) # 수정 시각(UTC, 앱에서 갱신)
|
|
deleted = Column(Boolean, nullable=False, server_default=text("false")) # 소프트 삭제 여부
|
|
|
|
|
|
class suppliers(MAIN_BASE):
|
|
# partner.suppliers (공급사 회사). 공급사명(name) 조회용. partner 도메인이라 DBType 은 PARTNER.
|
|
@staticmethod
|
|
def DBType():
|
|
return DBType.PARTNER.value
|
|
|
|
__tablename__ = "suppliers"
|
|
__table_args__ = {"schema": "partner"}
|
|
|
|
supplier_id = Column(UUID(as_uuid=True), primary_key=True, server_default=text("gen_random_uuid()")) # 공급사 식별자(PK)
|
|
company_id = Column(UUID(as_uuid=True), nullable=False) # 소속 회사(company.companies.company_id)
|
|
user_id = Column(UUID(as_uuid=True), nullable=False) # 등록 유저(company.users.user_id)
|
|
name = Column(String(100), nullable=False) # 공급사명
|
|
code = Column(String(20), nullable=True) # 공급사 코드
|
|
manager_name = Column(String(50), nullable=True) # 담당자명
|
|
manager_email = Column(String(255), nullable=True) # 담당자 이메일
|
|
manager_contact_number = Column(String(20), nullable=True) # 담당자 연락처
|
|
priority = Column(String(10), nullable=True) # 우선순위 (고객사별 문자열 값 가능)
|
|
created_at = Column(DateTime(timezone=True), nullable=False, server_default=text("(now() AT TIME ZONE 'utc')")) # 생성 시각(UTC)
|
|
updated_at = Column(DateTime(timezone=True), nullable=False, server_default=text("(now() AT TIME ZONE 'utc')")) # 수정 시각(UTC, 앱에서 갱신)
|
|
deleted = Column(Boolean, nullable=False, server_default=text("false")) # 소프트 삭제 여부
|
|
|
|
|
|
class items(MAIN_BASE):
|
|
# partner.items (상품).
|
|
@staticmethod
|
|
def DBType():
|
|
return DBType.PARTNER.value
|
|
|
|
__tablename__ = "items"
|
|
__table_args__ = {"schema": "partner"}
|
|
|
|
item_id = Column(UUID(as_uuid=True), primary_key=True, server_default=text("gen_random_uuid()")) # 상품 식별자(PK)
|
|
company_id = Column(UUID(as_uuid=True), nullable=False) # 소속 회사(company.companies.company_id)
|
|
user_id = Column(UUID(as_uuid=True), nullable=False) # 등록 유저(company.users.user_id)
|
|
name = Column(String(100), nullable=False) # 상품명
|
|
code = Column(String(30), nullable=True) # 상품 코드
|
|
price = Column(BigInteger, nullable=True) # 가격(원)
|
|
category = Column(String(255), nullable=True) # 카테고리
|
|
image_url = Column(String(255), nullable=True) # 이미지 URL
|
|
model_name = Column(String(100), nullable=True) # 모델명
|
|
spec = Column(String(255), nullable=True) # 규격
|
|
moq = Column(String(50), nullable=True) # 최소 주문 수량
|
|
lead_time = Column(SmallInteger, nullable=True) # 배송 리드타임
|
|
manufacturer = Column(String(50), nullable=True) # 제조사
|
|
made_in = Column(String(100), nullable=True) # 원산지
|
|
quantity_unit = Column(SmallInteger, nullable=True) # 취급 단위 (코드, 앱 enum 매핑)
|
|
delivery_type = Column(SmallInteger, nullable=True) # 배송 유형 (코드, 앱 enum 매핑)
|
|
vat_yn = Column(Boolean, nullable=True) # 부가세 포함 여부
|
|
delivery_fee_yn = Column(Boolean, nullable=True) # 배송비 포함 여부
|
|
internet_lowest_price_yn = Column(Boolean, nullable=False, server_default=text("false")) # 최저가 솔루션 보조 컬럼
|
|
category_type = Column(Integer, nullable=False, server_default=text("1")) # 카테고리 조회용 자동 증가 숫자
|
|
created_at = Column(DateTime(timezone=True), nullable=False, server_default=text("(now() AT TIME ZONE 'utc')")) # 생성 시각(UTC)
|
|
updated_at = Column(DateTime(timezone=True), nullable=False, server_default=text("(now() AT TIME ZONE 'utc')")) # 수정 시각(UTC, 앱에서 갱신)
|
|
deleted = Column(Boolean, nullable=False, server_default=text("false")) # 소프트 삭제 여부
|
|
|
|
|
|
class sessions(MAIN_BASE):
|
|
# negotiation.sessions (협상 세션).
|
|
@staticmethod
|
|
def DBType():
|
|
return DBType.NEGOTIATION.value
|
|
|
|
__tablename__ = "sessions"
|
|
__table_args__ = {"schema": "negotiation"}
|
|
|
|
session_id = Column(UUID(as_uuid=True), primary_key=True, server_default=text("gen_random_uuid()")) # 협상 세션 식별자(PK)
|
|
quotation_id = Column(UUID(as_uuid=True), nullable=False) # 소속 견적(quotation.quotations.qt_id)
|
|
item_id = Column(UUID(as_uuid=True), nullable=False) # 대상 상품(partner.items.item_id)
|
|
supplier_id = Column(UUID(as_uuid=True), nullable=False) # 대상 공급사(partner.suppliers.supplier_id)
|
|
qt_number = Column(String(30), nullable=False) # 견적번호(스냅샷)
|
|
qt_round = Column(Integer, nullable=False) # 견적 라운드(스냅샷)
|
|
qt_type = Column(SmallInteger, nullable=False) # 견적 유형: 1=재협상, 2=재견적 (QtType)
|
|
target_price = Column(BigInteger, nullable=False) # 목표가(원)
|
|
status = Column(SmallInteger, nullable=False) # 진행 상태 (SessionStatus 코드)
|
|
bid_price = Column(BigInteger, nullable=True) # 입찰가(원)
|
|
bid_at = Column(DateTime(timezone=True), nullable=True) # 입찰 시각
|
|
end_time = Column(DateTime(timezone=True), nullable=False) # 세션 종료(마감) 시각
|
|
reject_reason = Column(String(255), nullable=True) # 거절 사유
|
|
reject_price = Column(BigInteger, nullable=True) # 거절 시 제시가(원)
|
|
reject_delivery_type = Column(SmallInteger, nullable=True) # 거절 시 배송 유형 (코드)
|
|
created_at = Column(DateTime(timezone=True), nullable=False, server_default=text("(now() AT TIME ZONE 'utc')")) # 생성 시각(UTC)
|
|
updated_at = Column(DateTime(timezone=True), nullable=False, server_default=text("(now() AT TIME ZONE 'utc')")) # 수정 시각(UTC, 앱에서 갱신)
|
|
deleted = Column(Boolean, nullable=False, server_default=text("false")) # 소프트 삭제 여부
|
|
|
|
|
|
class quotations(MAIN_BASE):
|
|
# quotation.quotations (견적).
|
|
@staticmethod
|
|
def DBType():
|
|
return DBType.QUOTATION.value
|
|
|
|
__tablename__ = "quotations"
|
|
__table_args__ = {"schema": "quotation"}
|
|
|
|
qt_id = Column(UUID(as_uuid=True), primary_key=True, server_default=text("gen_random_uuid()")) # 견적 식별자(PK)
|
|
user_id = Column(UUID(as_uuid=True), nullable=False) # 생성 유저(company.users.user_id)
|
|
qt_setting_id = Column(UUID(as_uuid=True), nullable=False) # 견적 설정(quotation.quotation_settings.qt_setting_id)
|
|
version_id = Column(UUID(as_uuid=True), nullable=False) # 버전(card.versions.version_id)
|
|
name = Column(String(50), nullable=False) # 견적명
|
|
number = Column(String(30), nullable=False) # 견적번호
|
|
type = Column(SmallInteger, nullable=False) # 견적 유형: 1=재협상, 2=재견적 (QtType)
|
|
round = Column(Integer, nullable=False, server_default=text("1")) # 재견적 회차
|
|
status = Column(SmallInteger, nullable=False) # 진행 상태 (QuotationStatus 코드)
|
|
start_time = Column(DateTime(timezone=True), nullable=False) # 견적 시작 시각
|
|
end_time = Column(DateTime(timezone=True), nullable=False) # 견적 종료(마감) 시각
|
|
manager_name = Column(String(50), nullable=True) # 담당자명
|
|
manager_email = Column(String(255), nullable=True) # 담당자 이메일
|
|
manager_contact_number = Column(String(20), nullable=True) # 담당자 연락처
|
|
memo = Column(String(100), nullable=True) # 메모
|
|
iteration = Column(Integer, nullable=False, server_default=text("0")) # 반복 횟수
|
|
preferred_sp_yn = Column(Boolean, nullable=True) # 선호 공급사 지정 여부
|
|
preferred_sp_id = Column(UUID(as_uuid=True), nullable=True) # 선호 공급사(partner.suppliers.supplier_id)
|
|
preferred_sp_name = Column(String(20), nullable=True) # 선호 공급사명(스냅샷)
|
|
equal_bid_yn = Column(Boolean, nullable=True) # 동일가 입찰 발생 여부
|
|
equal_bid_data = Column(JSONB, nullable=True) # 동일가 입찰 상세(JSON)
|
|
created_at = Column(DateTime(timezone=True), nullable=False, server_default=text("(now() AT TIME ZONE 'utc')")) # 생성 시각(UTC)
|
|
updated_at = Column(DateTime(timezone=True), nullable=False, server_default=text("(now() AT TIME ZONE 'utc')")) # 수정 시각(UTC, 앱에서 갱신)
|
|
deleted = Column(Boolean, nullable=False, server_default=text("false")) # 소프트 삭제 여부
|
|
|
|
|
|
class quotation_settings(MAIN_BASE):
|
|
# quotation.quotation_settings (견적 설정). 앵커링값(anchoring_value) 조회용 — agent RL state 입력.
|
|
@staticmethod
|
|
def DBType():
|
|
return DBType.QUOTATION.value
|
|
|
|
__tablename__ = "quotation_settings"
|
|
__table_args__ = {"schema": "quotation"}
|
|
|
|
qt_setting_id = Column(UUID(as_uuid=True), primary_key=True, server_default=text("gen_random_uuid()")) # 견적 설정 식별자(PK)
|
|
user_id = Column(UUID(as_uuid=True), nullable=False) # 생성 유저(company.users.user_id)
|
|
target_margin_rate = Column(Numeric(8, 6), nullable=False) # 목표 마진율
|
|
anchoring_value = Column(Numeric(8, 6), nullable=False, server_default=text("0.01")) # 앵커링 값(비율) — anchor=round(target*(1-value))
|
|
card_count = Column(Integer, nullable=False, server_default=text("3")) # 협상 내 협상카드 사용 횟수
|
|
created_at = Column(DateTime(timezone=True), nullable=False, server_default=text("(now() AT TIME ZONE 'utc')")) # 생성 시각(UTC)
|
|
updated_at = Column(DateTime(timezone=True), nullable=False, server_default=text("(now() AT TIME ZONE 'utc')")) # 수정 시각(UTC, 앱에서 갱신)
|
|
deleted = Column(Boolean, nullable=False, server_default=text("false")) # 소프트 삭제 여부
|
|
|
|
|
|
class chats(MAIN_BASE):
|
|
# negotiation.chats (협상 채팅 메시지 로그). session 1 : N chats. (session_id, seq) 유니크.
|
|
@staticmethod
|
|
def DBType():
|
|
return DBType.NEGOTIATION.value
|
|
|
|
__tablename__ = "chats"
|
|
__table_args__ = {"schema": "negotiation"}
|
|
|
|
chat_id = Column(UUID(as_uuid=True), primary_key=True, server_default=text("gen_random_uuid()")) # 채팅 식별자(PK)
|
|
session_id = Column(UUID(as_uuid=True), nullable=False) # 소속 세션(negotiation.sessions.session_id)
|
|
card_id = Column(UUID(as_uuid=True), nullable=True) # 사용된 카드(card.nego_cards/wild_cards)
|
|
seq = Column(Integer, nullable=False, server_default=text("1")) # 세션 내 메시지 순번
|
|
sender = Column(SmallInteger, nullable=False) # 발신자 (ChatSender: 1=BOT, 2=USER)
|
|
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
|
|
meta = Column(JSONB, nullable=True) # 말풍선 표현 데이터(script/step/client_step/input_mode/input_options/chat_end)
|
|
created_at = Column(DateTime(timezone=True), nullable=False, server_default=text("(now() AT TIME ZONE 'utc')")) # 생성 시각(UTC)
|
|
updated_at = Column(DateTime(timezone=True), nullable=False, server_default=text("(now() AT TIME ZONE 'utc')")) # 수정 시각(UTC, 앱에서 갱신)
|
|
deleted = Column(Boolean, nullable=False, server_default=text("false")) # 소프트 삭제 여부
|
|
|
|
|
|
class supplier_user_tokens(MAIN_BASE):
|
|
# 유저 인증 토큰. supplier_users 1 : N tokens.
|
|
@staticmethod
|
|
def DBType():
|
|
return DBType.USER.value
|
|
|
|
__tablename__ = "supplier_user_tokens"
|
|
__table_args__ = {"schema": "supplier"}
|
|
|
|
sut_id = Column(UUID(as_uuid=True), primary_key=True, server_default=text("gen_random_uuid()")) # 토큰 식별자(PK)
|
|
su_id = Column(UUID(as_uuid=True), nullable=False) # 소유 유저(supplier.supplier_users.su_id)
|
|
type = Column(SmallInteger, nullable=False) # 토큰 종류 (코드, 앱 enum 매핑)
|
|
token = Column(JSONB, nullable=False) # 토큰 본문(JSON)
|
|
issued_at = Column(DateTime(timezone=True), nullable=False) # 발급 시각
|
|
expired_at = Column(DateTime(timezone=True), nullable=False) # 만료 시각
|
|
created_at = Column(DateTime(timezone=True), nullable=False, server_default=text("(now() AT TIME ZONE 'utc')")) # 생성 시각(UTC)
|
|
updated_at = Column(DateTime(timezone=True), nullable=False, server_default=text("(now() AT TIME ZONE 'utc')")) # 수정 시각(UTC, 앱에서 갱신)
|
|
deleted = Column(Boolean, nullable=False, server_default=text("false")) # 소프트 삭제 여부
|