113 lines
5.1 KiB
Python
113 lines
5.1 KiB
Python
|
|
"""业务分组 — 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"<BusinessGroup {self.id} {self.name}>"
|
|||
|
|
|
|||
|
|
|
|||
|
|
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"<BusinessGroupPhase g={self.group_id} {self.phase}>"
|
|||
|
|
|
|||
|
|
|
|||
|
|
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"<BusinessGroupMember g={self.group_id} u={self.user_id}>"
|