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

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

411 lines
13 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(biz_collection_protocol_no)"""
from __future__ import annotations
import math
from datetime import datetime
from typing import Any, Dict, List, Optional, Tuple
from sqlalchemy import and_, func, or_
from sqlalchemy.orm import Session
from app.core.utils import generate_uuid
from app.models.models import BizCollectionProtocolNo, SysUser
def _resolve_issued_user_display_name(
db: Session,
user_id: Optional[str],
fallback_name: Optional[str] = None,
) -> Optional[str]:
"""issued_user_name 为空时,按 issued_user_id 从用户表补全展示名。"""
name = (fallback_name or "").strip()
if name:
return name
uid = (user_id or "").strip()
if not uid:
return None
u = db.query(SysUser).filter(SysUser.id == uid).first()
if not u:
return None
return (u.nick_name or u.user_no or "").strip() or None
def enrich_issued_user_names(db: Session, rows: List[Dict[str, Any]]) -> None:
"""列表/详情:issuedUserName 为空但 issuedUserId 有值时补全展示名(原地修改)。"""
missing_uids = {
(r.get("issuedUserId") or "").strip()
for r in rows
if (r.get("issuedUserId") or "").strip() and not (r.get("issuedUserName") or "").strip()
}
if not missing_uids:
return
users = (
db.query(SysUser.id, SysUser.nick_name, SysUser.user_no)
.filter(SysUser.id.in_(missing_uids))
.all()
)
name_map = {(u.id or ""): ((u.nick_name or u.user_no or "").strip()) for u in users}
for r in rows:
uid = (r.get("issuedUserId") or "").strip()
if uid and not (r.get("issuedUserName") or "").strip():
r["issuedUserName"] = name_map.get(uid, "")
def _row_to_dict(r: BizCollectionProtocolNo) -> Dict[str, Any]:
return {
"id": r.id,
"protocolNo": r.protocol_no,
"issued": int(r.issued or 0),
"issuedUserId": r.issued_user_id or "",
"issuedUserName": r.issued_user_name or "",
"issuedTime": r.issued_time.strftime("%Y-%m-%d %H:%M:%S") if r.issued_time else "",
"tenantNo": r.tenant_no,
"orderNum": r.order_num if r.order_num is not None else 0,
"remark": r.remark or "",
"createTime": r.create_time.strftime("%Y-%m-%d %H:%M:%S") if r.create_time else "",
"updateTime": r.update_time.strftime("%Y-%m-%d %H:%M:%S") if r.update_time else "",
}
def protocol_no_exists(db: Session, no: str, tenant_no: str, exclude_id: Optional[str] = None) -> bool:
v = (no or "").strip()
if not v:
return False
q = db.query(BizCollectionProtocolNo).filter(
and_(
BizCollectionProtocolNo.protocol_no == v,
BizCollectionProtocolNo.tenant_no == tenant_no,
BizCollectionProtocolNo.del_flag == 0,
)
)
if exclude_id:
q = q.filter(BizCollectionProtocolNo.id != exclude_id)
return q.first() is not None
def list_protocol_nos(
db: Session,
*,
page: int = 1,
page_size: int = 10,
keyword: Optional[str] = None,
tenant_no: Optional[str] = None,
) -> Tuple[List[Dict[str, Any]], int]:
def _filtered():
q0 = db.query(BizCollectionProtocolNo).filter(BizCollectionProtocolNo.del_flag == 0)
if tenant_no:
q0 = q0.filter(BizCollectionProtocolNo.tenant_no == tenant_no)
kw = (keyword or "").strip()
if kw:
like = f"%{kw}%"
q0 = q0.filter(or_(BizCollectionProtocolNo.protocol_no.like(like), BizCollectionProtocolNo.remark.like(like)))
return q0
total = _filtered().order_by(None).with_entities(func.count(BizCollectionProtocolNo.id)).scalar() or 0
page = max(1, page)
page_size = min(max(1, page_size), 100)
rows = (
_filtered()
.order_by(BizCollectionProtocolNo.create_time.desc())
.offset((page - 1) * page_size)
.limit(page_size)
.all()
)
return [_row_to_dict(x) for x in rows], int(total)
def get_protocol_no(db: Session, row_id: str, tenant_no: Optional[str] = None) -> Optional[BizCollectionProtocolNo]:
q = db.query(BizCollectionProtocolNo).filter(and_(BizCollectionProtocolNo.id == row_id, BizCollectionProtocolNo.del_flag == 0))
if tenant_no:
q = q.filter(BizCollectionProtocolNo.tenant_no == tenant_no)
return q.first()
def _parse_issued_time(value: Optional[str]) -> Optional[datetime]:
raw = (value or "").strip()
if not raw:
return None
for fmt in ("%Y-%m-%d %H:%M:%S", "%Y-%m-%d %H:%M"):
try:
return datetime.strptime(raw, fmt)
except ValueError:
continue
return None
def create_protocol_no(
db: Session,
*,
protocol_no: str,
issued: int = 0,
tenant_no: str = "zkxw",
order_num: Optional[int] = None,
remark: Optional[str] = None,
user_id: Optional[str] = None,
issued_user_id: Optional[str] = None,
issued_user_name: Optional[str] = None,
issued_time: Optional[str] = None,
) -> BizCollectionProtocolNo:
pn = (protocol_no or "").strip()
if not pn:
raise ValueError("采集协议号不能为空")
if protocol_no_exists(db, pn, tenant_no):
raise ValueError("采集协议号已存在")
is_issued = 1 if issued else 0
issue_uid: Optional[str] = None
issue_uname: Optional[str] = None
issue_time: Optional[datetime] = None
if is_issued:
issue_uid = (issued_user_id or "").strip() or None
if not issue_uid:
raise ValueError("请选择下发用户")
issue_uname = _resolve_issued_user_display_name(db, issue_uid, issued_user_name)
issue_time = _parse_issued_time(issued_time) or datetime.now()
obj = BizCollectionProtocolNo(
id=generate_uuid(),
protocol_no=pn,
issued=is_issued,
issued_user_id=issue_uid,
issued_user_name=issue_uname,
issued_time=issue_time,
tenant_no=tenant_no,
order_num=order_num if order_num is not None else 0,
remark=(remark or "").strip() or None,
create_by=user_id,
create_dept_id="",
create_time=datetime.now(),
update_by=user_id,
update_time=datetime.now(),
del_flag=0,
)
db.add(obj)
db.commit()
db.refresh(obj)
return obj
def update_protocol_no(
db: Session,
row_id: str,
*,
protocol_no: Optional[str] = None,
issued: Optional[int] = None,
order_num: Optional[int] = None,
remark: Optional[str] = None,
user_id: Optional[str] = None,
tenant_no: Optional[str] = None,
issued_user_id: Optional[str] = None,
issued_user_name: Optional[str] = None,
issued_time: Optional[str] = None,
) -> BizCollectionProtocolNo:
obj = get_protocol_no(db, row_id, tenant_no=tenant_no)
if not obj:
raise ValueError("记录不存在")
if protocol_no is not None:
pn = protocol_no.strip()
if not pn:
raise ValueError("采集协议号不能为空")
if pn != obj.protocol_no and protocol_no_exists(db, pn, obj.tenant_no, exclude_id=row_id):
raise ValueError("采集协议号已存在")
obj.protocol_no = pn
if issued is not None:
obj.issued = 1 if issued else 0
if order_num is not None:
obj.order_num = order_num
if remark is not None:
obj.remark = remark.strip() or None
if issued_user_id is not None:
obj.issued_user_id = issued_user_id or None
if issued_user_name is not None:
obj.issued_user_name = issued_user_name or None
if obj.issued == 1 and obj.issued_user_id and not (obj.issued_user_name or "").strip():
obj.issued_user_name = _resolve_issued_user_display_name(db, obj.issued_user_id)
if issued_time is not None:
obj.issued_time = _parse_issued_time(issued_time) if (issued_time or "").strip() else None
if obj.issued == 1 and obj.issued_user_id and not obj.issued_time:
obj.issued_time = datetime.now()
obj.update_by = user_id
obj.update_time = datetime.now()
db.add(obj)
db.commit()
db.refresh(obj)
return obj
def soft_delete_protocol_no(db: Session, row_id: str, tenant_no: Optional[str] = None) -> None:
obj = get_protocol_no(db, row_id, tenant_no=tenant_no)
if not obj:
raise ValueError("记录不存在")
obj.del_flag = 1
obj.update_time = datetime.now()
db.add(obj)
db.commit()
def batch_generate_protocol_nos(
db: Session,
*,
prefix: str,
middle: str,
seq_start: int,
seq_end: int,
digit_count: int = 6,
tenant_no: str = "zkxw",
user_id: Optional[str] = None,
) -> Dict[str, Any]:
"""批量生成协议号: 前缀 + 中间段 + 补零序号"""
p = (prefix or "").strip()
m = (middle or "").strip()
if not p:
raise ValueError("固定前缀不能为空")
if seq_start < 0 or seq_end < 0:
raise ValueError("序号不能为负数")
if seq_start > seq_end:
raise ValueError("开始序号不能大于结束序号")
if digit_count < 1:
raise ValueError("序号位数不能小于1")
count = seq_end - seq_start + 1
if count > 10000:
raise ValueError("单次批量生成不能超过10000条")
generated_nos = []
for i in range(seq_start, seq_end + 1):
seq_str = str(i).zfill(digit_count)
full_no = f"{p}{m}{seq_str}"
generated_nos.append(full_no)
# Batch check existing
existing = set(
row.protocol_no
for row in db.query(BizCollectionProtocolNo.protocol_no)
.filter(
BizCollectionProtocolNo.tenant_no == tenant_no,
BizCollectionProtocolNo.del_flag == 0,
BizCollectionProtocolNo.protocol_no.in_(generated_nos),
)
.all()
)
now = datetime.now()
created = []
skipped = []
for no in generated_nos:
if no in existing:
skipped.append(no)
continue
obj = BizCollectionProtocolNo(
id=generate_uuid(),
protocol_no=no,
issued=0,
tenant_no=tenant_no,
order_num=0,
remark=None,
create_by=user_id,
create_dept_id="",
create_time=now,
update_by=user_id,
update_time=now,
del_flag=0,
)
created.append(obj)
if created:
db.bulk_save_objects(created)
db.commit()
return {
"created": len(created),
"skipped": len(skipped),
"skippedNos": skipped,
}
def batch_issue_protocol_nos(
db: Session,
*,
ids: List[str],
issued_user_id: str,
issued_user_name: Optional[str] = None,
tenant_no: Optional[str] = None,
user_id: Optional[str] = None,
) -> Dict[str, Any]:
"""批量下发协议号"""
if not ids:
raise ValueError("请选择要下发的协议号")
uid = (issued_user_id or "").strip()
if not uid:
raise ValueError("请选择下发用户")
now = datetime.now()
issued_count = 0
already_issued = []
for row_id in ids:
obj = get_protocol_no(db, row_id, tenant_no=tenant_no)
if not obj:
continue
if obj.issued == 1:
already_issued.append(obj.protocol_no)
continue
obj.issued = 1
obj.issued_user_id = uid
obj.issued_user_name = _resolve_issued_user_display_name(db, uid, issued_user_name)
obj.issued_time = now
obj.update_by = user_id
obj.update_time = now
db.add(obj)
issued_count += 1
if issued_count > 0:
db.commit()
return {
"issuedCount": issued_count,
"alreadyIssued": already_issued,
}
def batch_revoke_protocol_nos(
db: Session,
*,
ids: List[str],
tenant_no: Optional[str] = None,
user_id: Optional[str] = None,
) -> Dict[str, Any]:
if not ids:
raise ValueError("请选择要收回的协议号")
now = datetime.now()
revoked_count = 0
not_issued = []
for row_id in ids:
obj = get_protocol_no(db, row_id, tenant_no=tenant_no)
if not obj:
continue
if obj.issued != 1:
not_issued.append(obj.protocol_no)
continue
obj.issued = 0
obj.issued_user_id = None
obj.issued_user_name = None
obj.issued_time = None
obj.update_by = user_id
obj.update_time = now
db.add(obj)
revoked_count += 1
if revoked_count > 0:
db.commit()
return {
"revokedCount": revoked_count,
"notIssued": not_issued,
}
def total_pages(total: int, page_size: int) -> int:
if total <= 0:
return 0
return int(math.ceil(total / float(page_size)))