feat(weibo): 增加强制分配权限与账号详情
This commit is contained in:
@@ -45,6 +45,7 @@ PERMISSIONS = {
|
||||
"weibo:view": "查看微博账号",
|
||||
"weibo:workbench": "使用个人微博工作台",
|
||||
"weibo:assign": "查看全部微博账号并分配客服",
|
||||
"weibo:assign:force": "查看全部微博账号并强制改分配",
|
||||
"weibo:import": "导入/刷新微博 CK",
|
||||
"weibo:manage": "编辑、停用和启用微博账号",
|
||||
"weibo:account:ck_export": "复制微博外部 CK 账号的 CK(写剪贴板)",
|
||||
|
||||
+115
-20
@@ -31,6 +31,7 @@ from ..schemas import (
|
||||
WeiboExchangeRecordItem,
|
||||
WeiboExchangeRecordsOut,
|
||||
WeiboExchangeRequest,
|
||||
WeiboForceAssignRequest,
|
||||
WeiboGoodsOut,
|
||||
WeiboImportDuplicateOut,
|
||||
WeiboImportError,
|
||||
@@ -99,6 +100,32 @@ def _visible_weibo_accounts_query(query, current: User):
|
||||
return query.filter(WeiboAccount.assigned_to == current.id)
|
||||
|
||||
|
||||
def _active_support_target(db: Session, user_id: int) -> User | None:
|
||||
return (
|
||||
db.query(User)
|
||||
.filter(
|
||||
User.id == user_id,
|
||||
User.role == "support",
|
||||
User.is_active.is_(True),
|
||||
)
|
||||
.first()
|
||||
)
|
||||
|
||||
|
||||
def _replace_exclusive_workbench_assignments(
|
||||
db: Session,
|
||||
account_ids: set[int],
|
||||
target: User | None,
|
||||
) -> None:
|
||||
query = db.query(WeiboWorkbenchAccount).filter(
|
||||
WeiboWorkbenchAccount.account_id.in_(account_ids),
|
||||
WeiboWorkbenchAccount.exclusive_slot.is_not(None),
|
||||
)
|
||||
if target is not None:
|
||||
query = query.filter(WeiboWorkbenchAccount.user_id != target.id)
|
||||
query.delete(synchronize_session=False)
|
||||
|
||||
|
||||
def _get_account(
|
||||
db: Session,
|
||||
account_id: int,
|
||||
@@ -844,29 +871,23 @@ def batch_assign_accounts(
|
||||
raise HTTPException(status_code=404, detail=f"账号不存在: {missing}")
|
||||
target = None
|
||||
if req.assigned_to is not None:
|
||||
target = (
|
||||
db.query(User)
|
||||
.filter(
|
||||
User.id == req.assigned_to,
|
||||
User.role == "support",
|
||||
User.is_active.is_(True),
|
||||
)
|
||||
.first()
|
||||
)
|
||||
target = _active_support_target(db, req.assigned_to)
|
||||
if target is None:
|
||||
raise HTTPException(status_code=404, detail="目标客服不存在或已停用")
|
||||
cross_owner_accounts = [
|
||||
account
|
||||
for account in accounts
|
||||
if account.assigned_to is not None and account.assigned_to != target.id
|
||||
]
|
||||
if cross_owner_accounts and not user_has_permission(
|
||||
current, "weibo:assign:force"
|
||||
):
|
||||
raise HTTPException(
|
||||
status_code=403,
|
||||
detail="包含已分配给其他客服的账号,强制改归属需要专属权限",
|
||||
)
|
||||
previous_assignments = {account.id: account.assigned_to for account in accounts}
|
||||
if target is None:
|
||||
db.query(WeiboWorkbenchAccount).filter(
|
||||
WeiboWorkbenchAccount.account_id.in_(actual_ids),
|
||||
WeiboWorkbenchAccount.exclusive_slot.is_not(None),
|
||||
).delete(synchronize_session=False)
|
||||
else:
|
||||
db.query(WeiboWorkbenchAccount).filter(
|
||||
WeiboWorkbenchAccount.account_id.in_(actual_ids),
|
||||
WeiboWorkbenchAccount.user_id != target.id,
|
||||
WeiboWorkbenchAccount.exclusive_slot.is_not(None),
|
||||
).delete(synchronize_session=False)
|
||||
_replace_exclusive_workbench_assignments(db, actual_ids, target)
|
||||
for account in accounts:
|
||||
account.assigned_to = target.id if target else None
|
||||
db.commit()
|
||||
@@ -891,6 +912,80 @@ def batch_assign_accounts(
|
||||
}
|
||||
|
||||
|
||||
@router.post("/assignments/force")
|
||||
def force_assign_accounts(
|
||||
req: WeiboForceAssignRequest,
|
||||
db: Session = Depends(get_db),
|
||||
current: User = Depends(get_current_user),
|
||||
):
|
||||
"""把勾选账号或某个标签下的全部账号强制转给指定客服。"""
|
||||
|
||||
_require_weibo_perm(current, "weibo:assign:force")
|
||||
target = _active_support_target(db, req.assigned_to)
|
||||
if target is None:
|
||||
raise HTTPException(status_code=404, detail="目标客服不存在或已停用")
|
||||
|
||||
tag = req.tag.strip()
|
||||
if tag:
|
||||
accounts = (
|
||||
db.query(WeiboAccount)
|
||||
.filter(WeiboAccount.tag == tag)
|
||||
.order_by(WeiboAccount.id.asc())
|
||||
.all()
|
||||
)
|
||||
source = "tag"
|
||||
account_ids: list[int] = []
|
||||
else:
|
||||
account_ids = sorted(set(req.account_ids))
|
||||
if len(account_ids) != req.expected_count:
|
||||
raise HTTPException(status_code=409, detail="账号数量已变化,请刷新后重试")
|
||||
accounts = (
|
||||
db.query(WeiboAccount)
|
||||
.filter(WeiboAccount.id.in_(account_ids))
|
||||
.order_by(WeiboAccount.id.asc())
|
||||
.all()
|
||||
)
|
||||
actual_ids = {account.id for account in accounts}
|
||||
missing = sorted(set(account_ids) - actual_ids)
|
||||
if missing:
|
||||
raise HTTPException(status_code=404, detail=f"账号不存在: {missing}")
|
||||
source = "selection"
|
||||
|
||||
if len(accounts) != req.expected_count:
|
||||
raise HTTPException(status_code=409, detail="账号数量已变化,请刷新后重试")
|
||||
|
||||
actual_ids = {account.id for account in accounts}
|
||||
previous_assignments = {account.id: account.assigned_to for account in accounts}
|
||||
_replace_exclusive_workbench_assignments(db, actual_ids, target)
|
||||
for account in accounts:
|
||||
account.assigned_to = target.id
|
||||
db.commit()
|
||||
|
||||
assigned_ids = sorted(actual_ids)
|
||||
_record_audit_safely(
|
||||
db,
|
||||
current,
|
||||
action="weibo:account:force_assign",
|
||||
target=f"weibo_accounts:{len(assigned_ids)}",
|
||||
detail={
|
||||
"source": source,
|
||||
"tag": tag,
|
||||
"account_ids": assigned_ids,
|
||||
"assigned_to": target.id,
|
||||
"assigned_username": target.username,
|
||||
"previous_assignments": previous_assignments,
|
||||
},
|
||||
)
|
||||
return {
|
||||
"updated": len(assigned_ids),
|
||||
"assigned_to": target.id,
|
||||
"assigned_username": target.username,
|
||||
"source": source,
|
||||
"tag": tag,
|
||||
"success": True,
|
||||
}
|
||||
|
||||
|
||||
@router.get("/assignments/summary")
|
||||
def assignments_summary(
|
||||
db: Session = Depends(get_db),
|
||||
|
||||
+18
-1
@@ -3,7 +3,7 @@
|
||||
from datetime import UTC, datetime
|
||||
from typing import Any
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field, model_serializer
|
||||
from pydantic import BaseModel, ConfigDict, Field, model_serializer, model_validator
|
||||
|
||||
from .huya_defaults import (
|
||||
HUYA_DEFAULT_BIND_ACT_ID,
|
||||
@@ -571,6 +571,23 @@ class WeiboBatchAssignRequest(BaseModel):
|
||||
assigned_to: int | None = None
|
||||
|
||||
|
||||
class WeiboForceAssignRequest(BaseModel):
|
||||
"""超级管理员或获授权用户强制改归属;按 ID 或标签二选一。"""
|
||||
|
||||
assigned_to: int = Field(..., gt=0)
|
||||
account_ids: list[int] = Field(default_factory=list)
|
||||
tag: str = Field("", max_length=64)
|
||||
expected_count: int = Field(..., ge=1)
|
||||
|
||||
@model_validator(mode="after")
|
||||
def _validate_scope(self):
|
||||
has_ids = bool(self.account_ids)
|
||||
has_tag = bool(self.tag.strip())
|
||||
if has_ids == has_tag:
|
||||
raise ValueError("account_ids 和 tag 必须且只能填写一项")
|
||||
return self
|
||||
|
||||
|
||||
class WeiboAccountDisableRequest(BaseModel):
|
||||
reason: str = Field("", max_length=256)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user