- 新增数据库迁移脚本,为详情表添加四个价格相关字段 - 完善后端schema校验、序列化逻辑,增加价格相关的数据清理与验证规则 - 新增价格配置校验逻辑,设置起售价时必须选择对应的计价单位 - 在Admin UI详情编辑器中新增价格配置模块,支持配置起售价、计价单位、适用人数范围和价格说明 - 更新小程序端详情页面,支持展示配置的参考价格信息 - 补充相关测试用例,更新API文档与类型定义
170 lines
5.1 KiB
Python
170 lines
5.1 KiB
Python
from datetime import datetime
|
|
from sqlalchemy.inspection import inspect
|
|
|
|
from .media_urls import resolve_media_url
|
|
|
|
|
|
def encode_value(value):
|
|
if isinstance(value, datetime):
|
|
return value.isoformat()
|
|
if isinstance(value, list):
|
|
return [encode_value(item) for item in value]
|
|
if isinstance(value, dict):
|
|
return {key: encode_value(item) for key, item in value.items()}
|
|
return value
|
|
|
|
|
|
def model_dict(instance, include: dict[str, object] | None = None) -> dict:
|
|
data = {column.key: encode_value(getattr(instance, column.key)) for column in inspect(instance).mapper.column_attrs}
|
|
if include:
|
|
for key, value in include.items():
|
|
data[key] = encode_value(value)
|
|
return data
|
|
|
|
|
|
def media_model_dict(instance) -> dict:
|
|
return resolve_media_fields(model_dict(instance))
|
|
|
|
|
|
def resolve_media_fields(data: dict) -> dict:
|
|
result = dict(data)
|
|
for field in ("image", "avatar", "qrImage", "url"):
|
|
if field in result:
|
|
result[field] = resolve_media_url(result[field])
|
|
for field in ("gallery", "images"):
|
|
if field in result and isinstance(result[field], list):
|
|
result[field] = [resolve_media_url(value) for value in result[field]]
|
|
return result
|
|
|
|
|
|
def lead_dict(lead) -> dict:
|
|
return model_dict(
|
|
lead,
|
|
{"assignedUser": {"id": lead.assignedUser.id, "name": lead.assignedUser.name} if lead.assignedUser else None},
|
|
)
|
|
|
|
|
|
def public_wanfa_route_dict(route) -> dict:
|
|
return {
|
|
"id": route.id,
|
|
"title": route.title,
|
|
"subtitle": route.subtitle,
|
|
"image": resolve_media_url(route.image),
|
|
"routeCount": route.routeCount,
|
|
"demandKeyword": route.demandKeyword,
|
|
}
|
|
|
|
|
|
def public_wanfa_category_dict(category) -> dict:
|
|
return {
|
|
"id": category.id,
|
|
"label": category.label,
|
|
"routes": [public_wanfa_route_dict(route) for route in category.routes],
|
|
}
|
|
|
|
|
|
def detail_record_dict(detail) -> dict:
|
|
return media_model_dict(detail)
|
|
|
|
|
|
def public_detail_dict(detail, concierge_advisor=None) -> dict:
|
|
return {
|
|
"key": detail.key,
|
|
"eyebrow": detail.eyebrow,
|
|
"duration": detail.duration,
|
|
"title": detail.title,
|
|
"subtitle": detail.subtitle,
|
|
"intro": detail.intro,
|
|
"highlights": detail.highlights or [],
|
|
"included": detail.included or [],
|
|
"excluded": detail.excluded or [],
|
|
"notes": detail.notes or [],
|
|
"gallery": [resolve_media_url(image) for image in (detail.gallery or [])],
|
|
"priceStartingValue": detail.priceStartingValue,
|
|
"priceUnit": detail.priceUnit,
|
|
"pricePeopleRange": detail.pricePeopleRange,
|
|
"priceDescription": detail.priceDescription,
|
|
"conciergeAdvisor": public_concierge_advisor_dict(concierge_advisor) if concierge_advisor else None,
|
|
}
|
|
|
|
|
|
def public_home_wanfa_recommendation_dict(item) -> dict:
|
|
return {
|
|
"id": item.id,
|
|
"categoryId": item.category.id,
|
|
"label": item.category.label,
|
|
"routes": [public_wanfa_route_dict(route) for route in item.category.routes],
|
|
}
|
|
|
|
|
|
def concierge_advisor_dict(advisor) -> dict:
|
|
return media_model_dict(advisor)
|
|
|
|
|
|
def public_concierge_advisor_dict(advisor) -> dict:
|
|
return {
|
|
"avatar": resolve_media_url(advisor.avatar),
|
|
"name": advisor.name,
|
|
"role": advisor.role,
|
|
"details": advisor.details or [],
|
|
"qrImage": resolve_media_url(advisor.qrImage),
|
|
}
|
|
|
|
|
|
def public_home_experience_dict(item) -> dict:
|
|
return {
|
|
"id": item.id,
|
|
"badge": item.badge,
|
|
"category": item.category,
|
|
"title": item.title,
|
|
"englishTitle": item.englishTitle,
|
|
"image": resolve_media_url(item.image),
|
|
"demandKeyword": item.demandKeyword,
|
|
}
|
|
|
|
|
|
def public_home_team_building_dict(item) -> dict:
|
|
return {
|
|
"id": item.id,
|
|
"tag": item.tag,
|
|
"title": item.title,
|
|
"description": item.description,
|
|
"image": resolve_media_url(item.image),
|
|
"demandKeyword": item.demandKeyword,
|
|
}
|
|
|
|
|
|
def public_home_team_building_detail_dict(item) -> dict:
|
|
paragraphs = [
|
|
paragraph.strip()
|
|
for paragraph in (getattr(item, "detailParagraphs", None) or [])
|
|
if isinstance(paragraph, str) and paragraph.strip()
|
|
]
|
|
if not paragraphs:
|
|
paragraphs = [item.description]
|
|
return {
|
|
**public_home_team_building_dict(item),
|
|
"detailSubtitle": (getattr(item, "detailSubtitle", None) or item.description).strip(),
|
|
"detailParagraphs": paragraphs,
|
|
}
|
|
|
|
|
|
def public_home_wild_archive_dict(item) -> dict:
|
|
return {
|
|
"id": item.id,
|
|
"title": item.title,
|
|
"image": resolve_media_url(item.image),
|
|
"demandKeyword": item.demandKeyword,
|
|
"photoCount": len(item.images or [item.image]),
|
|
}
|
|
|
|
|
|
def public_home_wild_archive_detail_dict(item) -> dict:
|
|
images = [image for image in (item.images or []) if isinstance(image, str) and image.strip()]
|
|
if not images:
|
|
images = [item.image]
|
|
return {
|
|
**public_home_wild_archive_dict(item),
|
|
"images": [resolve_media_url(image) for image in images],
|
|
}
|