Files
zkxw/backend/app/services/oa_bank_receipt.py
T
zlwuandCursor b042266c5b fix(oa): stabilize bank receipt list order via order_num
Preserve PUT item sequence when create_time ties on replace.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-16 16:56:09 +08:00

180 lines
5.7 KiB
Python

"""OA 实例银行回执单(biz_attachment 关联)。"""
from __future__ import annotations
import uuid
from datetime import datetime
from typing import Any, Dict, List, Optional
from sqlalchemy.orm import Session
from app.models.models import BizAttachment, OaInstance
BUSINESS_TYPE = "oa_bank_receipt"
class OaBankReceiptError(Exception):
pass
def _require_instance(db: Session, instance_id: str, tenant_no: str) -> OaInstance:
iid = (instance_id or "").strip()
if not iid:
raise OaBankReceiptError("实例ID无效")
row = (
db.query(OaInstance)
.filter(
OaInstance.id == iid,
OaInstance.tenant_no == tenant_no,
OaInstance.del_flag == 0,
)
.first()
)
if not row:
raise OaBankReceiptError("审批实例不存在")
return row
def _row_to_item(row: BizAttachment) -> Dict[str, Any]:
return {
"id": row.id,
"fileName": row.original_name or "",
"fileUrl": row.file_url or "",
"fileType": row.file_type or "",
"fileSize": row.file_size,
"createTime": row.create_time.strftime("%Y-%m-%d %H:%M:%S") if row.create_time else "",
}
def list_bank_receipts(db: Session, *, instance_id: str, tenant_no: str) -> List[Dict[str, Any]]:
_require_instance(db, instance_id, tenant_no)
rows = (
db.query(BizAttachment)
.filter(
BizAttachment.tenant_no == tenant_no,
BizAttachment.business_type == BUSINESS_TYPE,
BizAttachment.business_id == instance_id,
BizAttachment.del_flag == 0,
)
.order_by(
BizAttachment.order_num.asc(),
BizAttachment.create_time.asc(),
BizAttachment.id.asc(),
)
.all()
)
return [_row_to_item(r) for r in rows]
def replace_bank_receipts(
db: Session,
*,
instance_id: str,
tenant_no: str,
items: List[Dict[str, Any]],
user_id: str,
dept_id: str = "",
) -> List[Dict[str, Any]]:
_require_instance(db, instance_id, tenant_no)
now = datetime.now()
desired: List[Dict[str, Any]] = []
seen_urls = set()
for raw in items or []:
url = str((raw or {}).get("fileUrl") or (raw or {}).get("file_url") or "").strip()
if not url or url in seen_urls:
continue
seen_urls.add(url)
desired.append(
{
"fileUrl": url,
"fileName": str((raw or {}).get("fileName") or (raw or {}).get("file_name") or "").strip()
or url.rsplit("/", 1)[-1],
"fileType": str((raw or {}).get("fileType") or (raw or {}).get("file_type") or "").strip(),
"fileSize": (raw or {}).get("fileSize")
if (raw or {}).get("fileSize") is not None
else (raw or {}).get("file_size"),
"orderNum": len(desired),
}
)
existing = (
db.query(BizAttachment)
.filter(
BizAttachment.tenant_no == tenant_no,
BizAttachment.business_type == BUSINESS_TYPE,
BizAttachment.business_id == instance_id,
BizAttachment.del_flag == 0,
)
.all()
)
existing_by_url = {r.file_url: r for r in existing if r.file_url}
desired_urls = {d["fileUrl"] for d in desired}
for row in existing:
if row.file_url not in desired_urls:
row.del_flag = 1
row.update_by = user_id or None
row.update_time = now
for d in desired:
url = d["fileUrl"]
order_num = int(d["orderNum"])
row: Optional[BizAttachment] = existing_by_url.get(url)
if row is None:
row = (
db.query(BizAttachment)
.filter(
BizAttachment.tenant_no == tenant_no,
BizAttachment.file_url == url,
BizAttachment.del_flag == 0,
)
.order_by(BizAttachment.create_time.desc())
.first()
)
if row is None:
ext = ""
if "." in d["fileName"]:
ext = d["fileName"].rsplit(".", 1)[-1][:20]
size = d["fileSize"]
try:
size_int = int(size) if size is not None else None
except (TypeError, ValueError):
size_int = None
row = BizAttachment(
id=uuid.uuid4().hex,
business_type=BUSINESS_TYPE,
business_id=instance_id,
original_name=d["fileName"][:200],
file_ext=ext or None,
file_url=url,
file_size=size_int,
file_type=(d["fileType"] or None),
tenant_no=tenant_no,
order_num=order_num,
create_by=user_id or None,
create_dept_id=dept_id or "",
create_time=now,
update_by=user_id or None,
update_time=now,
del_flag=0,
)
db.add(row)
else:
row.business_type = BUSINESS_TYPE
row.business_id = instance_id
row.del_flag = 0
row.order_num = order_num
if d["fileName"]:
row.original_name = d["fileName"][:200]
if d["fileType"]:
row.file_type = d["fileType"]
if d["fileSize"] is not None:
try:
row.file_size = int(d["fileSize"])
except (TypeError, ValueError):
pass
row.update_by = user_id or None
row.update_time = now
db.flush()
return list_bank_receipts(db, instance_id=instance_id, tenant_no=tenant_no)