feat(oa): add permission-controlled shared finance remarks to done center

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.
This commit is contained in:
zlwu committed 2026-10-30 20:42:05 +08:00
1 parent 86df360f35
commit 0e64b413ce
15 files changed
+1188 -78

No files matched your search

+49 -3
View File
@@ -2,7 +2,7 @@
from datetime import datetime
from typing import Any, Dict, List, Optional
from fastapi import APIRouter, Depends, Query
from fastapi import APIRouter, Depends, HTTPException, Query
from pydantic import BaseModel, ConfigDict, Field
from sqlalchemy.orm import Session
@@ -15,6 +15,7 @@ from app.crud import oa_instance_draft as oid_crud
from app.crud import oa_receive_account as ora_crud
from app.models.models import SysUser
from app.services.oa_receive_account import normalize_account_fields
from app.services import oa_finance_remark as finance_remark_svc
router = APIRouter(prefix="/web/admin/oa-approval", tags=["OA审批"])
@@ -1054,6 +1055,44 @@ async def list_oa_instance_form_attachments(
return error_response(400, str(e))
class OaFinanceRemarkBody(BaseModel):
model_config = ConfigDict(populate_by_name=True, extra="forbid")
finance_remark: str = Field(..., alias="financeRemark", max_length=512, strict=True)
@router.get("/instances/{instance_id}/finance-remark")
async def get_oa_finance_remark(
instance_id: str,
user: dict = Depends(require_admin_or_super_admin),
db: Session = Depends(get_db),
):
try:
access = finance_remark_svc.resolve_access(db, user)
return success_response(finance_remark_svc.get_finance_remark(db, access, instance_id))
except finance_remark_svc.OaFinanceRemarkError as exc:
raise HTTPException(status_code=exc.status_code, detail=str(exc)) from exc
@router.put("/instances/{instance_id}/finance-remark")
async def update_oa_finance_remark(
instance_id: str,
body: OaFinanceRemarkBody,
user: dict = Depends(require_admin_or_super_admin),
db: Session = Depends(get_db),
):
try:
access = finance_remark_svc.resolve_access(db, user)
result = finance_remark_svc.update_finance_remark(db, access, instance_id, body.finance_remark)
db.commit()
return success_response(result, message="财务备注已保存")
except finance_remark_svc.OaFinanceRemarkError as exc:
db.rollback()
raise HTTPException(status_code=exc.status_code, detail=str(exc)) from exc
except Exception:
db.rollback()
raise
@router.get("/instances/{instance_id}/bank-receipts")
async def list_oa_bank_receipts(
instance_id: str,
@@ -1365,7 +1404,8 @@ async def list_oa_tasks(
from app.services.oa_engine import OaEngineError, list_tasks
try:
tenant_no = _get_tenant_no(db, user)
access = finance_remark_svc.resolve_access(db, user) if kind == "done" else None
tenant_no = access.tenant_no if access else _get_tenant_no(db, user)
data = list_tasks(
db,
tenant_no=tenant_no,
@@ -1381,7 +1421,13 @@ async def list_oa_tasks(
page=page,
page_size=pageSize,
)
return success_response({"list": data.get("list") or [], "total": int(data.get("total") or 0)})
result = {"list": data.get("list") or [], "total": int(data.get("total") or 0)}
if access is not None:
finance_remark_svc.add_list_remarks(db, access, result["list"])
result["canManageFinanceRemark"] = access.can_manage
return success_response(result)
except finance_remark_svc.OaFinanceRemarkError as exc:
raise HTTPException(status_code=exc.status_code, detail=str(exc)) from exc
except OaEngineError as e:
return error_response(400, str(e))