"""业务分组 — LICA 部门内部的数据范围隔离(生产大组 / 维修大组) 系统已有 lifecycle_phase(生产制造 / 售后回流),但此前**只用于展示与校验**, 没有任何权限含义。本模块补上「数据范围」这一层:组决定成员能看到哪个阶段的数据。 为什么要两级(大组 > 小组): 生产大组下可能再分「生产小组」「测试小组」,它们看的是同一片数据(都属生产 阶段),彼此互通;维修大组则与生产隔离。用 parent_id 表达归属,子组默认继承 父组的可见范围 —— 这样「生产大组配一次 PRODUCTION,下面的小组都不用再配」。 为什么组存在 Track 库而不是 MOM: 组是 Track 自己的业务概念(对应 lifecycle_phase),MOM 里没有对应表,且组名要 能由业务随时改。只有「人」是 String(64) 逻辑外键指向 MOM sys_user。 为什么可见范围是独立的 business_group_phases 表而不是一个字段: 需求明确要求「范围可配置、不要写死」,且一个组可能同时要看生产和售后 —— 单列存不下多选。 """ from datetime import datetime from sqlalchemy import Boolean, DateTime, ForeignKey, Integer, String, UniqueConstraint from sqlalchemy.orm import Mapped, mapped_column, relationship from app.models.base import Base from app.core.time_utils import get_beijing_time class BusinessGroup(Base): """业务分组定义(两级:parent_id 为空即大组)""" __tablename__ = "business_groups" id: Mapped[int] = mapped_column( primary_key=True, autoincrement=True, comment="分组ID(数字,对外稳定不变;显示名可改而引用不变)", ) parent_id: Mapped[int | None] = mapped_column( Integer, ForeignKey("business_groups.id", ondelete="CASCADE"), nullable=True, index=True, comment="上级大组ID(为空=大组;有值=挂在某大组下的小组,默认继承其可见范围)", ) name: Mapped[str] = mapped_column( String(50), nullable=False, unique=True, comment="分组显示名(可修改,如 生产小组/维修组)", ) description: Mapped[str | None] = mapped_column( String(200), nullable=True, comment="备注说明", ) sort_order: Mapped[int] = mapped_column( Integer, nullable=False, default=0, server_default="0", comment="同层排序(升序)", ) is_active: Mapped[bool] = mapped_column( Boolean, nullable=False, default=True, server_default="true", comment="是否启用。⚠️ 停用后该组所有成员立即退回「未分组」状态(等同被移出所有组)", ) created_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), default=get_beijing_time, comment="创建时间", ) updated_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), default=get_beijing_time, onupdate=get_beijing_time, comment="最后修改时间", ) def __repr__(self) -> str: return f"" class BusinessGroupPhase(Base): """分组的可见范围 —— 该组成员能看到哪些 lifecycle_phase 的数据。 ⚠️ 这是「不写死」的落点:给不给某个组看售后,完全由管理员在这里配置, 不在代码里硬编码任何「生产组只能看生产」这类规则。 """ __tablename__ = "business_group_phases" group_id: Mapped[int] = mapped_column( Integer, ForeignKey("business_groups.id", ondelete="CASCADE"), primary_key=True, comment="分组ID", ) phase: Mapped[str] = mapped_column( String(20), primary_key=True, comment="可见的生命周期阶段: PRODUCTION(生产制造) | AFTER_SALES(售后回流)", ) def __repr__(self) -> str: return f"" class BusinessGroupMember(Base): """分组成员。一人可属多个组 —— 这是「让某人同时看生产+维修」的实现方式。""" __tablename__ = "business_group_members" __table_args__ = ( UniqueConstraint("group_id", "user_id", name="uq_business_group_members_group_user"), ) id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True) group_id: Mapped[int] = mapped_column( Integer, ForeignKey("business_groups.id", ondelete="CASCADE"), nullable=False, index=True, comment="所属分组ID", ) user_id: Mapped[str] = mapped_column( String(64), nullable=False, index=True, comment="成员账号(逻辑外键→MOM sys_user,与 Task.assignee_id / JWT.username 同口径)", ) is_leader: Mapped[bool] = mapped_column( Boolean, nullable=False, default=False, server_default="false", comment="是否组长(可管理本组成员;数据范围与组员相同)", ) created_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), default=get_beijing_time, comment="加入时间", ) def __repr__(self) -> str: return f""