"""家庭成员管理服务函数。""" 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)