feat: add demand page modules and admin endpoints
Add full backend support for a demand request page, including: - New database models for demand hero, feature cards, form configuration, and product recommendations - Alembic migration for the new tables - Default seed data for demand page components - Public site config endpoint exposing demand data - Admin CRUD operations and configuration for demand modules - Extended lead query filters for source page, keyword, and date range - Updated Pydantic schemas for lead query parameters - Comprehensive test coverage for all new endpoints
This commit is contained in:
@@ -207,6 +207,7 @@ class Product(Base):
|
||||
images: Mapped[list["ProductImage"]] = relationship(back_populates="product", cascade="all, delete-orphan")
|
||||
campaignLinks: Mapped[list["CampaignProduct"]] = relationship(back_populates="product", cascade="all, delete-orphan")
|
||||
routeSectionLinks: Mapped[list["RouteSectionProduct"]] = relationship(back_populates="product", cascade="all, delete-orphan")
|
||||
demandRecommendationLinks: Mapped[list["DemandRecommendationProduct"]] = relationship(back_populates="product", cascade="all, delete-orphan")
|
||||
leads: Mapped[list["Lead"]] = relationship(back_populates="sourceProduct")
|
||||
orders: Mapped[list["Order"]] = relationship(back_populates="product")
|
||||
|
||||
@@ -279,6 +280,74 @@ class RouteSectionProduct(Base):
|
||||
product: Mapped[Product] = relationship(back_populates="routeSectionLinks")
|
||||
|
||||
|
||||
class DemandHero(Base):
|
||||
__tablename__ = "DemandHero"
|
||||
|
||||
id: Mapped[str] = mapped_column(String, primary_key=True, default=new_id)
|
||||
title: Mapped[str] = mapped_column(String, nullable=False)
|
||||
kicker: Mapped[str | None] = mapped_column(String)
|
||||
description: Mapped[str | None] = mapped_column(Text)
|
||||
steps: Mapped[list[str]] = mapped_column(ARRAY(String), default=list, nullable=False)
|
||||
sortOrder: Mapped[int] = mapped_column(Integer, default=0, nullable=False)
|
||||
isActive: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False)
|
||||
createdAt: Mapped[datetime] = mapped_column(DateTime, default=utc_now, nullable=False)
|
||||
updatedAt: Mapped[datetime] = mapped_column(DateTime, default=utc_now, onupdate=utc_now, nullable=False)
|
||||
|
||||
|
||||
class DemandFeatureCard(Base):
|
||||
__tablename__ = "DemandFeatureCard"
|
||||
|
||||
id: Mapped[str] = mapped_column(String, primary_key=True, default=new_id)
|
||||
title: Mapped[str] = mapped_column(String, nullable=False)
|
||||
description: Mapped[str | None] = mapped_column(Text)
|
||||
sortOrder: Mapped[int] = mapped_column(Integer, default=0, nullable=False)
|
||||
isActive: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False)
|
||||
createdAt: Mapped[datetime] = mapped_column(DateTime, default=utc_now, nullable=False)
|
||||
updatedAt: Mapped[datetime] = mapped_column(DateTime, default=utc_now, onupdate=utc_now, nullable=False)
|
||||
|
||||
|
||||
class DemandForm(Base):
|
||||
__tablename__ = "DemandForm"
|
||||
|
||||
id: Mapped[str] = mapped_column(String, primary_key=True, default=new_id)
|
||||
destinationLabel: Mapped[str] = mapped_column(String, default="目的地/玩法", nullable=False)
|
||||
destinationPlaceholder: Mapped[str | None] = mapped_column(String)
|
||||
phoneLabel: Mapped[str] = mapped_column(String, default="联系方式", nullable=False)
|
||||
phonePlaceholder: Mapped[str | None] = mapped_column(String)
|
||||
noteLabel: Mapped[str] = mapped_column(String, default="补充说明", nullable=False)
|
||||
notePlaceholder: Mapped[str | None] = mapped_column(String)
|
||||
submitLabel: Mapped[str] = mapped_column(String, default="提交出行需求", nullable=False)
|
||||
chips: Mapped[list[str]] = mapped_column(ARRAY(String), default=list, nullable=False)
|
||||
isActive: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False)
|
||||
createdAt: Mapped[datetime] = mapped_column(DateTime, default=utc_now, nullable=False)
|
||||
updatedAt: Mapped[datetime] = mapped_column(DateTime, default=utc_now, onupdate=utc_now, nullable=False)
|
||||
|
||||
|
||||
class DemandRecommendation(Base):
|
||||
__tablename__ = "DemandRecommendation"
|
||||
|
||||
id: Mapped[str] = mapped_column(String, primary_key=True, default=new_id)
|
||||
title: Mapped[str] = mapped_column(String, nullable=False)
|
||||
subtitle: Mapped[str | None] = mapped_column(Text)
|
||||
sortOrder: Mapped[int] = mapped_column(Integer, default=0, nullable=False)
|
||||
isActive: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False)
|
||||
createdAt: Mapped[datetime] = mapped_column(DateTime, default=utc_now, nullable=False)
|
||||
updatedAt: Mapped[datetime] = mapped_column(DateTime, default=utc_now, onupdate=utc_now, nullable=False)
|
||||
|
||||
products: Mapped[list["DemandRecommendationProduct"]] = relationship(back_populates="recommendation", cascade="all, delete-orphan")
|
||||
|
||||
|
||||
class DemandRecommendationProduct(Base):
|
||||
__tablename__ = "DemandRecommendationProduct"
|
||||
|
||||
recommendationId: Mapped[str] = mapped_column(String, ForeignKey("DemandRecommendation.id", ondelete="CASCADE"), primary_key=True)
|
||||
productId: Mapped[str] = mapped_column(String, ForeignKey("Product.id", ondelete="CASCADE"), primary_key=True)
|
||||
sortOrder: Mapped[int] = mapped_column(Integer, default=0, nullable=False)
|
||||
|
||||
recommendation: Mapped[DemandRecommendation] = relationship(back_populates="products")
|
||||
product: Mapped[Product] = relationship(back_populates="demandRecommendationLinks")
|
||||
|
||||
|
||||
class Lead(Base):
|
||||
__tablename__ = "Lead"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user