Files
zkxw/backend/app/crud/production_task_step_assignee.py
T
zlwuandCursor 5e563a6669 fix: 子表保存改为就地同步,避免整批换 id 打断外键。
协议样本、生产产出和盘点明细按行更新并保留原 id;补齐存储证明质检预填、离职人员显示名,以及样本采集通知模板字段。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-28 10:51:53 +08:00

239 lines
7.5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""生产任务步骤负责人 CRUD"""
from __future__ import annotations
from datetime import datetime
from typing import Dict, List, Optional, Set
from sqlalchemy.orm import Session
from app.core.db_collation import eq_unicode
from app.core.utils import generate_uuid
from app.models.models import BizProductionTaskStep, BizProductionTaskStepAssignee, SysUser
from app.services.web_auth_roles import list_user_ids_with_effective_role_name
PRODUCTION_STEP_ASSIGNEE_ROLE = "实验专员"
def _active_users_in_tenant(
db: Session, user_ids: List[str], tenant_no: str
) -> Set[str]:
ids = [x.strip() for x in user_ids if (x or "").strip()]
if not ids:
return set()
rows = (
db.query(SysUser.id)
.filter(
SysUser.id.in_(ids),
SysUser.tenant_no == tenant_no,
SysUser.del_flag == 0,
SysUser.status == "1",
)
.all()
)
return {r[0] for r in rows}
def _active_users_with_role_in_tenant(
db: Session, user_ids: List[str], tenant_no: str, role_name: str
) -> Set[str]:
"""校验候选用户是否拥有指定角色(直接绑定 ∪ 职位继承)。"""
ids = [x.strip() for x in user_ids if (x or "").strip()]
rn = (role_name or "").strip()
if not ids or not rn:
return set()
return list_user_ids_with_effective_role_name(
db,
tenant_no=tenant_no,
role_name=rn,
candidate_user_ids=ids,
)
def validate_assignee_user_ids(
db: Session,
user_ids: List[str],
tenant_no: str,
*,
required_role_name: Optional[str] = PRODUCTION_STEP_ASSIGNEE_ROLE,
) -> List[str]:
"""去重并校验用户有效,返回有序列表。"""
seen: Set[str] = set()
ordered: List[str] = []
for uid in user_ids:
u = (uid or "").strip()
if not u or u in seen:
continue
seen.add(u)
ordered.append(u)
if not ordered:
raise ValueError("请至少指定一名负责人")
valid = _active_users_in_tenant(db, ordered, tenant_no)
invalid = [u for u in ordered if u not in valid]
if invalid:
raise ValueError("存在无效或已停用的负责人用户")
rn = (required_role_name or "").strip()
if rn:
role_valid = _active_users_with_role_in_tenant(db, ordered, tenant_no, rn)
role_invalid = [u for u in ordered if u not in role_valid]
if role_invalid:
raise ValueError(f"负责人须为「{rn}」角色用户")
return ordered
def sync_assignees_for_step(
db: Session,
step_id: str,
user_ids: List[str],
*,
tenant_no: str,
operator_id: Optional[str] = None,
create_dept_id: str = "",
) -> None:
"""全量替换步骤负责人(软删旧记录 + 插入新记录)。"""
normalized = validate_assignee_user_ids(db, user_ids, tenant_no)
now = datetime.now()
existing = (
db.query(BizProductionTaskStepAssignee)
.filter(
BizProductionTaskStepAssignee.step_id == step_id,
BizProductionTaskStepAssignee.tenant_no == tenant_no,
BizProductionTaskStepAssignee.del_flag == 0,
)
.all()
)
keep_ids = set(normalized)
for row in existing:
if row.user_id not in keep_ids:
row.del_flag = 1
row.update_by = operator_id
row.update_time = now
db.add(row)
existing_map = {r.user_id: r for r in existing if r.del_flag == 0}
for idx, uid in enumerate(normalized):
if uid in existing_map:
row = existing_map[uid]
row.order_num = idx + 1
row.update_by = operator_id
row.update_time = now
db.add(row)
continue
db.add(
BizProductionTaskStepAssignee(
id=generate_uuid(),
step_id=step_id,
user_id=uid,
tenant_no=tenant_no,
order_num=idx + 1,
create_by=operator_id,
create_dept_id=create_dept_id or "",
create_time=now,
update_by=operator_id,
update_time=now,
del_flag=0,
)
)
def enrich_step_assignees(
db: Session, steps: List[Dict], *, tenant_no: str
) -> None:
"""批量填充 assigneeUserIds / assigneeNames / assignees。"""
step_ids = [s["id"] for s in steps if s.get("id")]
if not step_ids:
return
rows = (
db.query(BizProductionTaskStepAssignee, SysUser)
.outerjoin(SysUser, eq_unicode(SysUser.id, BizProductionTaskStepAssignee.user_id))
.filter(
BizProductionTaskStepAssignee.step_id.in_(step_ids),
BizProductionTaskStepAssignee.tenant_no == tenant_no,
BizProductionTaskStepAssignee.del_flag == 0,
)
.order_by(
BizProductionTaskStepAssignee.order_num.asc(),
BizProductionTaskStepAssignee.create_time.asc(),
)
.all()
)
by_step: Dict[str, List] = {}
for assignee, user in rows:
by_step.setdefault(assignee.step_id, []).append((assignee, user))
for step in steps:
pairs = by_step.get(step["id"], [])
step["assigneeUserIds"] = [a.user_id for a, _ in pairs]
step["assigneeNames"] = [
f"{(u.nick_name or u.user_name or '')}({u.user_no or ''})".strip()
for _, u in pairs
if u
]
step["assignees"] = [
{
"userId": a.user_id,
"nickName": (u.nick_name or u.user_name or "") if u else "",
"userNo": (u.user_no or "") if u else "",
}
for a, u in pairs
]
def enrich_step_completed_by_names(db: Session, steps: List[Dict]) -> None:
uids = list({(s.get("completedBy") or "").strip() for s in steps if (s.get("completedBy") or "").strip()})
if not uids:
return
users = {
u.id: u
for u in db.query(SysUser).filter(SysUser.id.in_(uids)).all()
}
for step in steps:
uid = (step.get("completedBy") or "").strip()
if not uid:
step["completedByName"] = ""
continue
u = users.get(uid)
step["completedByName"] = (
f"{(u.nick_name or u.user_name or '')}({u.user_no or ''})".strip() if u else uid
)
def user_is_step_assignee(
db: Session, step_id: str, user_id: str, tenant_no: str
) -> bool:
uid = (user_id or "").strip()
if not uid:
return False
row = (
db.query(BizProductionTaskStepAssignee.id)
.filter(
BizProductionTaskStepAssignee.step_id == step_id,
BizProductionTaskStepAssignee.user_id == uid,
BizProductionTaskStepAssignee.tenant_no == tenant_no,
BizProductionTaskStepAssignee.del_flag == 0,
)
.first()
)
return row is not None
def user_is_any_step_assignee_of_task(
db: Session, task_id: str, user_id: str, tenant_no: str
) -> bool:
uid = (user_id or "").strip()
if not uid:
return False
row = (
db.query(BizProductionTaskStepAssignee.id)
.join(
BizProductionTaskStep,
BizProductionTaskStep.id == BizProductionTaskStepAssignee.step_id,
)
.filter(
BizProductionTaskStep.task_id == task_id,
BizProductionTaskStep.del_flag == 0,
BizProductionTaskStepAssignee.user_id == uid,
BizProductionTaskStepAssignee.tenant_no == tenant_no,
BizProductionTaskStepAssignee.del_flag == 0,
)
.first()
)
return row is not None