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:
duanshuwen
2026-07-07 22:03:57 +08:00
parent d11c361aa2
commit fe85197e68
9 changed files with 749 additions and 15 deletions

View File

@@ -1,6 +1,7 @@
from sqlalchemy import select
from sqlalchemy.orm import Session, selectinload
from ..models import Campaign, CtaBanner, Destination, DestinationHero, DestinationRegion, HeroSlide, HotelGroup, MapImage, ThemeCard, VehicleOption
from ..models import Campaign, CtaBanner, DemandFeatureCard, DemandForm, DemandHero, Destination, DestinationHero, DestinationRegion, HeroSlide, HotelGroup, MapImage, ThemeCard, VehicleOption
from ..demand_recommendations import demand_recommendation_dict, demand_recommendation_query
from ..route_sections import route_section_dict, route_section_query
from ..serializers import destination_dict, hotel_group_dict, model_dict
@@ -15,6 +16,9 @@ def site_config(db: Session, active_only: bool, include_public_extras: bool = Tr
cta_stmt = select(CtaBanner).order_by(CtaBanner.sortOrder.asc())
hotel_stmt = select(HotelGroup).order_by(HotelGroup.sortOrder.asc())
vehicle_stmt = select(VehicleOption).order_by(VehicleOption.sortOrder.asc())
demand_hero_stmt = select(DemandHero).order_by(DemandHero.sortOrder.asc())
demand_feature_card_stmt = select(DemandFeatureCard).order_by(DemandFeatureCard.sortOrder.asc())
demand_form_stmt = select(DemandForm).order_by(DemandForm.createdAt.asc())
if active_only:
hero_stmt = hero_stmt.where(HeroSlide.isActive.is_(True))
destination_stmt = destination_stmt.where(Destination.isActive.is_(True))
@@ -25,6 +29,9 @@ def site_config(db: Session, active_only: bool, include_public_extras: bool = Tr
cta_stmt = cta_stmt.where(CtaBanner.isActive.is_(True))
hotel_stmt = hotel_stmt.where(HotelGroup.isActive.is_(True), HotelGroup.status == "published")
vehicle_stmt = vehicle_stmt.where(VehicleOption.isActive.is_(True))
demand_hero_stmt = demand_hero_stmt.where(DemandHero.isActive.is_(True))
demand_feature_card_stmt = demand_feature_card_stmt.where(DemandFeatureCard.isActive.is_(True))
demand_form_stmt = demand_form_stmt.where(DemandForm.isActive.is_(True))
hero_slides = db.scalars(hero_stmt).all()
destinations = db.scalars(destination_stmt).all()
@@ -51,4 +58,11 @@ def site_config(db: Session, active_only: bool, include_public_extras: bool = Tr
result["routeSections"] = [route_section_dict(item, public=True) for item in route_sections]
result["hotelGroups"] = [hotel_group_dict(item) for item in db.scalars(hotel_stmt).all()]
result["vehicleOptions"] = [model_dict(item) for item in db.scalars(vehicle_stmt).all()]
result["demandHero"] = [model_dict(item) for item in db.scalars(demand_hero_stmt).all()]
result["demandFeatureCards"] = [model_dict(item) for item in db.scalars(demand_feature_card_stmt).all()]
result["demandForm"] = [model_dict(item) for item in db.scalars(demand_form_stmt).all()]
demand_recommendations = db.scalars(demand_recommendation_query()).all()
if active_only:
demand_recommendations = [item for item in demand_recommendations if item.isActive]
result["demandRecommendations"] = [demand_recommendation_dict(item, public=active_only) for item in demand_recommendations]
return result