diff --git a/backend/src/backend/platform.py b/backend/src/backend/platform.py index 9c0e3bb..871e05d 100644 --- a/backend/src/backend/platform.py +++ b/backend/src/backend/platform.py @@ -854,18 +854,34 @@ async def add_member( status.HTTP_422_UNPROCESSABLE_ENTITY, "用户的平台角色行不存在或已被删除", ) - duplicate = await session.scalar( - select(WorkspaceMembers.user_id).where( + # ``WorkspaceMembers`` 的主键是 ``(workspace_id, user_id)`` 复合 PK, + # 而 ``remove_member`` / ``delete_platform_employee`` 都是软删除 (保留行, + # 仅置 ``is_deleted=1``). 因此这里必须按主键查整行,而不是只看活跃行: + # 否则软删行会被 active-duplicate 检查漏过,然后 INSERT 直接撞 PK. + existing = await session.scalar( + select(WorkspaceMembers).where( WorkspaceMembers.workspace_id == workspace_id, WorkspaceMembers.user_id == payload.user_id, - WorkspaceMembers.is_deleted == 0, ) ) - if duplicate is not None: - raise HTTPException( - status.HTTP_409_CONFLICT, - "用户已是该 workspace 成员;workspace 角色继承自平台角色," - "要变更请 PATCH /api/v1/platform/employees/{user_id} 修改 role_code", + if existing is not None: + if existing.is_deleted == 0: + raise HTTPException( + status.HTTP_409_CONFLICT, + "用户已是该 workspace 成员;workspace 角色继承自平台角色," + "要变更请 PATCH /api/v1/platform/employees/{user_id} 修改 role_code", + ) + # 复活软删除行. 保留 ``joined_at`` 作为历史记录;``role_id`` 重新继承 + # 当前用户的平台角色 (用户在中间可能改过 platform_role);清掉 + # ``deleted_at`` 标记本轮已不在软删状态. + existing.is_deleted = 0 + existing.deleted_at = None + existing.role_id = role.role_id + existing.member_status = "active" + await session.flush() + await session.refresh(existing) + return _envelope( + context.request_id, member_payload(user, role, existing), ) membership = WorkspaceMembers( workspace_id=workspace_id,