import uuid from sqlalchemy.orm import declarative_base from sqlalchemy import Column, Integer, SmallInteger, BigInteger, Numeric, Float, String, Boolean, DateTime from sqlalchemy.dialects.postgresql import UUID, JSONB from sqlalchemy.sql import text from common.enums import DBType, UserStatus, UserRole, CompanyStatus # 모든 ORM 모델의 베이스. insert 시 isinstance 체크에도 사용된다. MAIN_BASE = declarative_base() # 공통 mixin # DB 계약(_DBTypeMixin)과 ERD 공통 컬럼(MainTableMixin)을 분리해 둔다. def _utc_now_sql(): return text("(now() AT TIME ZONE 'utc')") class _DBTypeMixin: """모델이 자신이 속한 논리 DB 를 알려준다 (람다 실행 시 DBType 으로 세션 선택).""" @staticmethod def DBType(): return DBType.MAIN.value # ERD 공통 컬럼(created/updated/deleted). negodata 도메인 테이블은 단일 MAIN DB 에서 soft-delete 를 쓴다. class MainTableMixin(_DBTypeMixin): 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()) deleted = Column(Boolean, nullable=False, server_default=text("false"), default=False) # 스캐폴드 예시 계정 (negosium 골격 유산) # 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): __tablename__ = "companies" company_id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) name = Column(String(100), nullable=False) business_number = Column(String(30), nullable=True) code = Column(Integer, nullable=True) # 내부 인덱스용 representative_name = Column(String(50), nullable=True) email = Column(String(255), nullable=True) contact_number = Column(String(20), nullable=True) website_url = Column(String(255), nullable=True) industry = Column(SmallInteger, nullable=True) # 업종 코드 (스키마 SMALLINT) status = Column(SmallInteger, nullable=False, default=CompanyStatus.ACTIVE.value) # CompanyStatus class users(MainTableMixin, MAIN_BASE): __tablename__ = "users" user_id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) company_id = Column(UUID(as_uuid=True), nullable=False, index=True) id = Column(String(20), nullable=False, unique=True, index=True) # 로그인 아이디 password = Column(String(255), nullable=False) # bcrypt 해시 (ERD VARCHAR(30)→255 확장) name = Column(String(50), nullable=True) email = Column(String(255), nullable=True) contact_number = Column(String(20), nullable=True) last_accessed_at = Column(DateTime, nullable=False, server_default=_utc_now_sql()) status = Column(SmallInteger, nullable=False, default=UserStatus.ACTIVE.value) # UserStatus role = Column(SmallInteger, nullable=False, default=UserRole.USER.value) # UserRole class items(MainTableMixin, MAIN_BASE): __tablename__ = "items" item_id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) company_id = Column(UUID(as_uuid=True), nullable=False, index=True) user_id = Column(UUID(as_uuid=True), nullable=False, index=True) # 등록한 유저 name = Column(String(100), nullable=False) code = Column(String(30), nullable=True) category = Column(String(255), nullable=True) category_type = Column(Integer, nullable=False, default=1) # 카테고리 탐색용 자동 증가 숫자 image_url = Column(String(255), nullable=True) model_name = Column(String(100), nullable=True) spec = Column(String(255), nullable=True) manufacturer = Column(String(50), nullable=True) made_in = Column(String(100), nullable=True) price = Column(BigInteger, nullable=True) # 금액(원), 스키마 BIGINT internet_lowest_price_yn = Column(Boolean, nullable=False, default=False) # 최저가 솔루션 원자성 보존용 moq = Column(String(50), nullable=True) # 최소 주문 수량 lead_time = Column(SmallInteger, nullable=True) # 주문 후 배송 도착까지 시간 quantity_unit = Column(String(50), nullable=True) # 단위 라벨(자유입력): EA/BOX/SET/ROLL ... delivery_type = Column(SmallInteger, nullable=True) # 배송 유형 코드 vat_yn = Column(Boolean, nullable=True) delivery_fee_yn = Column(Boolean, nullable=True) class suppliers(MainTableMixin, MAIN_BASE): __tablename__ = "suppliers" supplier_id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) company_id = Column(UUID(as_uuid=True), nullable=False, index=True) user_id = Column(UUID(as_uuid=True), nullable=False, index=True) # 등록한 유저 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) # ERD 오타(manger) 교정 priority = Column(String(10), nullable=True) # True/False 가 아닌 string value 가능 class quotation_settings(MainTableMixin, MAIN_BASE): __tablename__ = "quotation_settings" qt_setting_id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) user_id = Column(UUID(as_uuid=True), nullable=True, index=True) # 설정 소유 유저 target_margin_rate = Column(Numeric(8, 6), nullable=False) anchoring_value = Column(Numeric(8, 6), nullable=False, default=0.01) card_count = Column(Integer, nullable=False, default=3) # 한 협상 내 협상카드 사용 횟수 class quotations(MainTableMixin, MAIN_BASE): __tablename__ = "quotations" qt_id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) user_id = Column(UUID(as_uuid=True), nullable=False, index=True) qt_setting_id = Column(UUID(as_uuid=True), nullable=False, index=True) version_id = Column(UUID(as_uuid=True), nullable=False, index=True) name = Column(String(50), nullable=False) number = Column(String(30), nullable=False) type = Column(SmallInteger, nullable=False) # QuotationType: 1=renego(1:1) / 2=requote(1:N) round = Column(Integer, nullable=False, default=1) # 재견적 진행 시 증가 status = Column(SmallInteger, nullable=False) # 진행 상태 코드 start_time = Column(DateTime, nullable=False) end_time = Column(DateTime, 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, default=0) preferred_sp_yn = Column(Boolean, nullable=True) preferred_sp_id = Column(UUID(as_uuid=True), nullable=True) preferred_sp_name = Column(String(20), nullable=True) equal_bid_yn = Column(Boolean, nullable=True) equal_bid_data = Column(JSONB, nullable=True)