From 7aa685840ffea6ebddec395a27b1cdd6cde41630 Mon Sep 17 00:00:00 2001 From: zlwu <704541015@qq.com> Date: Wed, 21 Oct 2026 18:45:43 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=94=B6=E6=AC=BE=E8=B4=A6=E6=88=B7?= =?UTF-8?q?=E9=80=89=E9=A1=B9=E5=8F=AA=E8=BF=94=E5=9B=9E=E5=BD=93=E5=89=8D?= =?UTF-8?q?=E7=99=BB=E8=AE=B0=E4=BA=BA=E5=B9=B6=E5=B8=A6=E4=B8=8A=E6=89=80?= =?UTF-8?q?=E5=B1=9E=E4=BA=BA=E5=A7=93=E5=90=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 发起校验能识别已删除、他租户和他人账户,编辑账户不会改掉 create_by。 Co-authored-by: Cursor --- backend/app/api/oa_approval.py | 10 ++++- backend/app/crud/oa_receive_account.py | 40 +++++++++++++++---- backend/app/services/oa_receive_account.py | 46 ++++++++++++++++++++++ 3 files changed, 87 insertions(+), 9 deletions(-) diff --git a/backend/app/api/oa_approval.py b/backend/app/api/oa_approval.py index c67e7533..f7652fe0 100644 --- a/backend/app/api/oa_approval.py +++ b/backend/app/api/oa_approval.py @@ -84,7 +84,15 @@ async def list_receive_account_options( db: Session = Depends(get_db), ): tenant_no = _get_tenant_no(db, user) - return success_response({"list": ora_crud.list_options(db, tenant_no=tenant_no)}) + return success_response( + { + "list": ora_crud.list_options( + db, + tenant_no=tenant_no, + user_id=user.get("user_id") or "", + ) + } + ) @router.post("/receive-accounts/batch-delete") diff --git a/backend/app/crud/oa_receive_account.py b/backend/app/crud/oa_receive_account.py index b7acb02c..b181209f 100644 --- a/backend/app/crud/oa_receive_account.py +++ b/backend/app/crud/oa_receive_account.py @@ -8,7 +8,7 @@ from sqlalchemy import func, or_ from sqlalchemy.orm import Session from app.core.utils import generate_uuid -from app.models.models import OaReceiveAccount +from app.models.models import OaReceiveAccount, SysUser def row_to_dict(r: OaReceiveAccount) -> Dict[str, Any]: @@ -26,9 +26,26 @@ def row_to_dict(r: OaReceiveAccount) -> Dict[str, Any]: "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 "", + "ownerUserId": (r.create_by or "").strip(), } +def _owner_name_map(db: Session, user_ids: List[str]) -> Dict[str, str]: + ids = [item for item in {uid.strip() for uid in user_ids if uid and uid.strip()}] + if not ids: + return {} + rows = db.query(SysUser.id, SysUser.nick_name).filter(SysUser.id.in_(ids)).all() + return {row.id: (row.nick_name or "").strip() for row in rows} + + +def _attach_owner_names(db: Session, items: List[Dict[str, Any]]) -> List[Dict[str, Any]]: + names = _owner_name_map(db, [item.get("ownerUserId") or "" for item in items]) + for item in items: + uid = item.get("ownerUserId") or "" + item["ownerName"] = names.get(uid) or "—" + return items + + def _filtered_query( db: Session, *, @@ -74,17 +91,24 @@ def list_accounts( .limit(page_size) .all() ) - return [row_to_dict(x) for x in rows], int(total) + return _attach_owner_names(db, [row_to_dict(x) for x in rows]), int(total) -def list_options(db: Session, *, tenant_no: str) -> List[Dict[str, Any]]: +def list_options(db: Session, *, tenant_no: str, user_id: str) -> List[Dict[str, Any]]: + owner_id = (user_id or "").strip() + if not owner_id: + return [] rows = ( db.query(OaReceiveAccount) - .filter(OaReceiveAccount.tenant_no == tenant_no, OaReceiveAccount.del_flag == 0) + .filter( + OaReceiveAccount.tenant_no == tenant_no, + OaReceiveAccount.del_flag == 0, + func.trim(OaReceiveAccount.create_by) == owner_id, + ) .order_by(OaReceiveAccount.create_time.desc()) .all() ) - return [row_to_dict(x) for x in rows] + return _attach_owner_names(db, [row_to_dict(x) for x in rows]) def get_account(db: Session, account_id: str, tenant_no: str) -> Optional[Dict[str, Any]]: @@ -97,7 +121,7 @@ def get_account(db: Session, account_id: str, tenant_no: str) -> Optional[Dict[s ) .first() ) - return row_to_dict(row) if row else None + return _attach_owner_names(db, [row_to_dict(row)])[0] if row else None def create_account( @@ -131,7 +155,7 @@ def create_account( db.add(obj) db.commit() db.refresh(obj) - return row_to_dict(obj) + return _attach_owner_names(db, [row_to_dict(obj)])[0] def update_account( @@ -168,7 +192,7 @@ def update_account( db.add(obj) db.commit() db.refresh(obj) - return row_to_dict(obj) + return _attach_owner_names(db, [row_to_dict(obj)])[0] def soft_delete_account( diff --git a/backend/app/services/oa_receive_account.py b/backend/app/services/oa_receive_account.py index b0a26426..57fd8a87 100644 --- a/backend/app/services/oa_receive_account.py +++ b/backend/app/services/oa_receive_account.py @@ -3,6 +3,10 @@ from __future__ import annotations from typing import Any, Dict, List, Optional, Tuple +from sqlalchemy.orm import Session + +from app.models.models import OaReceiveAccount + ACCOUNT_TYPES = frozenset({"alipay", "personal", "corporate"}) _BANK_FIELD_ALIASES = { @@ -199,3 +203,45 @@ def validate_receive_account_form_values( continue errors.extend(_validate_receive_account_rule(rule, data)) return errors + + +def validate_receive_account_ownership( + db: Session, + schema: Any, + form_data: Optional[Dict[str, Any]], + *, + tenant_no: str, + initiator_user_id: str, +) -> List[str]: + """校验收款账户快照 id 属于发起人。调用前须已通过格式校验。""" + data = form_data or {} + owner_id = (initiator_user_id or "").strip() + for rule in _schema_rules(schema): + if str(rule.get("type") or "") != "receiveAccount": + continue + field = str(rule.get("field") or "").strip() + if not field: + continue + value = data.get(field) + if not isinstance(value, list): + continue + label = _field_label(rule) + for item in value: + if not isinstance(item, dict): + continue + item_id = str(item.get("id") or "").strip() + if not item_id: + continue + row = ( + db.query(OaReceiveAccount) + .filter( + OaReceiveAccount.id == item_id, + OaReceiveAccount.tenant_no == tenant_no, + ) + .first() + ) + if row is None or int(row.del_flag or 0) == 1: + return [f"「{label}」不存在或已删除"] + if (row.create_by or "").strip() != owner_id: + return [f"「{label}」不属于当前用户"] + return []