You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

201 lines
6.1 KiB

"""家庭成员管理服务函数。"""
from __future__ import annotations
from collections.abc import Sequence
from sqlalchemy import Select, func
from sqlalchemy.orm import selectinload
from app.extensions import db
from app.models import HouseholdMember
# 成员性别选项
GENDER_OPTIONS = ("male", "female", "other")
GENDER_LABELS = {
"male": "",
"female": "",
"other": "其他",
}
# 年龄段选项
AGE_GROUP_OPTIONS = ("child", "teen", "adult", "senior")
AGE_GROUP_LABELS = {
"child": "儿童(0-12岁)",
"teen": "青少年(13-17岁)",
"adult": "成人(18-59岁)",
"senior": "长者(60岁以上)",
}
# 与户主关系选项
RELATION_TO_HEAD_OPTIONS = (
"户主",
"配偶",
"子女",
"儿媳/女婿",
"孙辈",
"父母",
"岳父母/公婆",
"兄弟姐妹",
"其他亲属",
"朋友",
"其他",
)
MEMBER_VALUE_LABELS = {
"gender": GENDER_LABELS,
"age_group": AGE_GROUP_LABELS,
}
def member_query(household_id: int | None = None) -> Select[tuple[HouseholdMember]]:
"""构建家庭成员查询。"""
query = db.select(HouseholdMember).options(
selectinload(HouseholdMember.household),
)
if household_id is not None:
query = query.where(HouseholdMember.household_id == household_id)
return query.order_by(HouseholdMember.sort_order.asc(), HouseholdMember.id.asc())
def list_members(household_id: int) -> Sequence[HouseholdMember]:
"""列出指定户的所有成员。"""
result = db.session.execute(
member_query(household_id=household_id),
)
return result.scalars().all()
def get_member_or_none(member_id: int) -> HouseholdMember | None:
"""根据 ID 获取成员,不存在返回 None。"""
result = db.session.execute(
member_query().where(HouseholdMember.id == member_id),
)
return result.scalar_one_or_none()
def get_member_count(household_id: int) -> int:
"""获取指定户的成员数量。"""
result = db.session.execute(
db.select(func.count(HouseholdMember.id)).where(
HouseholdMember.household_id == household_id,
),
)
return result.scalar_one() or 0
def suggest_next_sort_order(household_id: int) -> int:
"""建议下一个排序序号。"""
result = db.session.execute(
db.select(func.max(HouseholdMember.sort_order)).where(
HouseholdMember.household_id == household_id,
),
)
max_order = result.scalar_one()
return (max_order or 0) + 10
def build_new_member_draft(household_id: int) -> HouseholdMember:
"""构建新成员草稿对象。"""
member = HouseholdMember()
member.household_id = household_id
member.name = ""
member.relation_to_head = None
member.gender = None
member.age_group = None
member.is_child = False
member.needs_red_packet = False
member.expected_to_attend = True
member.actually_attended = False
member.sort_order = suggest_next_sort_order(household_id)
member.note = None
return member
def parse_member_form(
form: dict[str, str],
*,
household_id: int,
) -> tuple[dict[str, object], list[str]]:
"""解析成员表单数据,返回有效载荷和错误列表。"""
errors: list[str] = []
# 必填字段:姓名
name = form.get("name", "").strip()
if not name:
errors.append("成员姓名不能为空。")
# 与户主关系
relation_to_head = form.get("relation_to_head", "").strip() or None
if relation_to_head and relation_to_head not in RELATION_TO_HEAD_OPTIONS:
errors.append("与户主关系选项不合法。")
# 性别
gender = form.get("gender", "").strip() or None
if gender and gender not in GENDER_OPTIONS:
errors.append("性别选项不合法。")
# 年龄段
age_group = form.get("age_group", "").strip() or None
if age_group and age_group not in AGE_GROUP_OPTIONS:
errors.append("年龄段选项不合法。")
# 是否儿童
is_child_raw = form.get("is_child", "false").strip().lower()
is_child = is_child_raw in ("true", "1", "yes", "on")
# 是否需要红包
needs_red_packet_raw = form.get("needs_red_packet", "false").strip().lower()
needs_red_packet = needs_red_packet_raw in ("true", "1", "yes", "on")
# 预期是否到场
expected_to_attend_raw = form.get("expected_to_attend", "true").strip().lower()
expected_to_attend = expected_to_attend_raw in ("true", "1", "yes", "on")
# 实际是否到场
actually_attended_raw = form.get("actually_attended", "false").strip().lower()
actually_attended = actually_attended_raw in ("true", "1", "yes", "on")
if errors:
return {}, errors
return {
"household_id": household_id,
"name": name,
"relation_to_head": relation_to_head,
"gender": gender,
"age_group": age_group,
"is_child": is_child,
"needs_red_packet": needs_red_packet,
"expected_to_attend": expected_to_attend,
"actually_attended": actually_attended,
"sort_order": suggest_next_sort_order(household_id),
"note": form.get("note", "").strip() or None,
}, []
def serialize_member_snapshot(member: HouseholdMember) -> dict[str, object]:
"""序列化成员快照,用于审计日志。"""
return {
"id": member.id,
"household_id": member.household_id,
"name": member.name,
"relation_to_head": member.relation_to_head,
"gender": member.gender,
"age_group": member.age_group,
"is_child": member.is_child,
"needs_red_packet": member.needs_red_packet,
"expected_to_attend": member.expected_to_attend,
"actually_attended": member.actually_attended,
"sort_order": member.sort_order,
"note": member.note,
}
def member_value_label(field_name: str, value: str | None) -> str:
"""获取成员字段的中文标签。"""
if value is None:
return "-"
normalized_value = value.strip()
if not normalized_value:
return "-"
return MEMBER_VALUE_LABELS.get(field_name, {}).get(normalized_value, normalized_value)