"""商品主表:对齐 Ozon ImportProductsV3 字段 + 本地扩展(raw/pricing/copy)。""" from __future__ import annotations import uuid from datetime import datetime from sqlalchemy import BigInteger, DateTime, Enum, Float, Integer, String, Text, Uuid, func from sqlalchemy.orm import Mapped, mapped_column from db import Base from models.enums import Stage from models.types import JSONType class Product(Base): __tablename__ = "products" id: Mapped[uuid.UUID] = mapped_column(Uuid(as_uuid=True), primary_key=True, default=uuid.uuid4) user_id: Mapped[uuid.UUID | None] = mapped_column(Uuid(as_uuid=True), nullable=True) shop_id: Mapped[uuid.UUID | None] = mapped_column(Uuid(as_uuid=True), nullable=True) # 上架店铺 stage: Mapped[Stage] = mapped_column( Enum(Stage, native_enum=False, length=16), default=Stage.collected, index=True ) # 采集溯源 source_platform: Mapped[str | None] = mapped_column(String(16), nullable=True) source_item_id: Mapped[str | None] = mapped_column(String(64), nullable=True, index=True) source_url: Mapped[str | None] = mapped_column(Text, nullable=True) # ── Ozon 字段(对齐 ImportProductsV3)── offer_id: Mapped[str] = mapped_column(String(255), default="", index=True) ozon_product_id: Mapped[int | None] = mapped_column(BigInteger, nullable=True, index=True) ozon_sku: Mapped[int | None] = mapped_column(BigInteger, nullable=True) name: Mapped[str] = mapped_column(Text, default="") description: Mapped[str] = mapped_column(Text, default="") description_category_id: Mapped[int | None] = mapped_column(BigInteger, nullable=True) type_id: Mapped[int | None] = mapped_column(BigInteger, nullable=True) price: Mapped[float | None] = mapped_column(Float, nullable=True) old_price: Mapped[float | None] = mapped_column(Float, nullable=True) # 币种固定人民币(跨境卖家 CNY 计价);vat 恒为 0(简化税制,无 НДС),前端不再展示 currency_code: Mapped[str] = mapped_column(String(3), default="CNY", server_default="CNY") vat: Mapped[str] = mapped_column(String(8), default="0", server_default="0") depth: Mapped[float | None] = mapped_column(Float, nullable=True) width: Mapped[float | None] = mapped_column(Float, nullable=True) height: Mapped[float | None] = mapped_column(Float, nullable=True) dimension_unit: Mapped[str] = mapped_column(String(4), default="mm", server_default="mm") weight: Mapped[float | None] = mapped_column(Float, nullable=True) weight_unit: Mapped[str] = mapped_column(String(4), default="g", server_default="g") barcode: Mapped[str | None] = mapped_column(String(64), nullable=True) # 图片(有序公网 URL,≤15) images: Mapped[list | None] = mapped_column(JSONType, nullable=True) primary_image: Mapped[str | None] = mapped_column(Text, nullable=True) images360: Mapped[list | None] = mapped_column(JSONType, nullable=True) color_image: Mapped[str | None] = mapped_column(Text, nullable=True) pdf_list: Mapped[list | None] = mapped_column(JSONType, nullable=True) promotions: Mapped[list | None] = mapped_column(JSONType, nullable=True) # 动态属性(工作台映射后填) attributes: Mapped[list | None] = mapped_column(JSONType, nullable=True) complex_attributes: Mapped[list | None] = mapped_column(JSONType, nullable=True) # ── 本地扩展(提交 Ozon 前剥离)── raw: Mapped[dict | None] = mapped_column(JSONType, nullable=True) # 采集原文 + texts pricing: Mapped[dict | None] = mapped_column(JSONType, nullable=True) # 计价结果 copy: Mapped[dict | None] = mapped_column(JSONType, nullable=True) # AI 文案结果 fx_rate: Mapped[float | None] = mapped_column(Float, nullable=True) published_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True) created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now()) updated_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), server_default=func.now(), onupdate=func.now(), index=True ) # 采集素材数量(冗余,供列表快速展示;由 service 维护) asset_counts: Mapped[dict | None] = mapped_column(JSONType, nullable=True)