Share one finance remark per approval instance across Web and mobile. Require the explicit permission and the current tenant user's own done task for every read and write; keep general instance responses free of financial remarks. Add idempotent MySQL migration without automatic role grants, cross-end editors, isolated backend and frontend tests, and rollout requirements. Refs #7; platform validation and target database migration remain pending.
101 lines
3.8 KiB
Python
101 lines
3.8 KiB
Python
"""已办中心实例级财务备注;权限与本人已办范围同时满足才可访问。"""
|
|
from dataclasses import dataclass
|
|
from datetime import datetime
|
|
from typing import Any, Dict, List
|
|
|
|
from sqlalchemy.orm import Session
|
|
|
|
from app.models.models import OaInstance, OaTask, SysUser
|
|
from app.services.web_auth_roles import iter_user_menu_perm_codes
|
|
|
|
PERMISSION = "oa:done:finance-remark"
|
|
DONE_STATUSES = ("approved", "rejected", "returned", "transferred")
|
|
|
|
|
|
class OaFinanceRemarkError(ValueError):
|
|
def __init__(self, status_code: int, message: str):
|
|
super().__init__(message)
|
|
self.status_code = status_code
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class FinanceRemarkAccess:
|
|
user_id: str
|
|
tenant_no: str
|
|
can_manage: bool
|
|
|
|
|
|
def resolve_access(db: Session, user: dict) -> FinanceRemarkAccess:
|
|
# 不使用缺省租户、客户端租户或角色名称作为敏感字段的授权依据。
|
|
uid = str(user.get("user_id") or "").strip()
|
|
identity = db.query(SysUser.id, SysUser.tenant_no).filter(
|
|
SysUser.id == uid, SysUser.del_flag == 0, SysUser.status == "1"
|
|
).first()
|
|
if not identity or not str(identity.tenant_no or "").strip():
|
|
raise OaFinanceRemarkError(403, "用户不可用")
|
|
tenant = str(identity.tenant_no).strip()
|
|
granted = iter_user_menu_perm_codes(db, uid, tenant_no=tenant)
|
|
return FinanceRemarkAccess(uid, tenant, PERMISSION in granted)
|
|
|
|
|
|
def _accessible_instances(db: Session, access: FinanceRemarkAccess):
|
|
own_done = db.query(OaTask.id).filter(
|
|
OaTask.instance_id == OaInstance.id,
|
|
OaTask.tenant_no == access.tenant_no,
|
|
OaTask.assignee_user_id == access.user_id,
|
|
OaTask.task_type == "approval",
|
|
OaTask.status.in_(DONE_STATUSES),
|
|
OaTask.del_flag == 0,
|
|
).exists()
|
|
return db.query(OaInstance).filter(
|
|
OaInstance.tenant_no == access.tenant_no,
|
|
OaInstance.del_flag == 0,
|
|
own_done,
|
|
)
|
|
|
|
|
|
def _require_instance(db: Session, access: FinanceRemarkAccess, instance_id: str) -> OaInstance:
|
|
if not access.can_manage:
|
|
raise OaFinanceRemarkError(403, "无权查看或修改财务备注")
|
|
instance = _accessible_instances(db, access).filter(OaInstance.id == instance_id).first()
|
|
if instance is None:
|
|
raise OaFinanceRemarkError(404, "审批单不在本人已办范围内")
|
|
return instance
|
|
|
|
|
|
def _remark_data(instance: OaInstance) -> Dict[str, str]:
|
|
return {"instanceId": instance.id, "financeRemark": instance.finance_remark or ""}
|
|
|
|
|
|
def get_finance_remark(db: Session, access: FinanceRemarkAccess, instance_id: str) -> Dict[str, str]:
|
|
return _remark_data(_require_instance(db, access, instance_id))
|
|
|
|
|
|
def update_finance_remark(
|
|
db: Session, access: FinanceRemarkAccess, instance_id: str, finance_remark: str
|
|
) -> Dict[str, str]:
|
|
instance = _require_instance(db, access, instance_id)
|
|
if not isinstance(finance_remark, str) or len(finance_remark) > 512:
|
|
raise OaFinanceRemarkError(422, "财务备注最多 512 字")
|
|
instance.finance_remark = finance_remark
|
|
instance.update_by = access.user_id
|
|
instance.update_time = datetime.now()
|
|
db.flush()
|
|
return _remark_data(instance)
|
|
|
|
|
|
def add_list_remarks(db: Session, access: FinanceRemarkAccess, rows: List[Dict[str, Any]]) -> None:
|
|
# 即使调用方未来调整序列化,也先移除敏感字段,再按权限补充。
|
|
for row in rows:
|
|
row.pop("financeRemark", None)
|
|
if not access.can_manage:
|
|
return
|
|
ids = {row.get("instanceId") for row in rows if row.get("instanceId")}
|
|
if not ids:
|
|
return
|
|
instances = _accessible_instances(db, access).filter(OaInstance.id.in_(ids)).all()
|
|
remarks = {inst.id: inst.finance_remark or "" for inst in instances}
|
|
for row in rows:
|
|
if row.get("instanceId") in remarks:
|
|
row["financeRemark"] = remarks[row["instanceId"]]
|