Files
2026-10-19 17:34:11 +08:00

152 lines
5.0 KiB
Go
Raw Permalink 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.
package controllers
import (
"fmt"
"time"
"server/models"
"server/pkg/jwtutil"
"server/services"
approvalEngine "server/services/approval"
"github.com/beego/beego/v2/client/orm"
)
// reimburse_approval_core.go 报销单「审批人视角」操作共享逻辑。
//
// Web 端(/backend/reimbursements/approve|reject|my-approve-task)与
// 小程序端(/api/oa/expense/approve|reject|my-approve-task)共用本文件;
// 审批中心 TaskAct 也复用其中的 biz_type 判定与时间线回写。
// isReimburseBizType 判断审批任务/实例是否属于报销类单据。
func isReimburseBizType(bizType string) bool {
return approvalEngine.IsReimburseBizType(bizType)
}
// claimsReimburseUIDs 当前登录用户可能出现在 approver_id 上的两种口径:
// 认证中心 identity_id 与本租户 uid(历史上两种写法并存,这里都兼容)。
func claimsReimburseUIDs(claims *jwtutil.Claims) []interface{} {
raw := uint64(claims.UserID)
out := []interface{}{raw}
if uid := services.ResolveTenantUID(uint64(claims.TenantId), raw); uid != raw {
out = append(out, uid)
}
return out
}
// reimburseMyTaskCore 查当前用户在报销单 id 上的待办审批任务;无待办返回 (nil, nil)。
func reimburseMyTaskCore(claims *jwtutil.Claims, id uint64) (*models.ApprovalTask, error) {
tid := uint64(claims.TenantId)
var task models.ApprovalTask
err := models.Orm.QueryTable(new(models.ApprovalTask)).
Filter("scope", models.ApprovalScopeTenant).
Filter("tid", tid).
Filter("biz_type", models.ApprovalBizReimburse).
Filter("biz_id", id).
Filter("status", models.ApprovalTaskPending).
Filter("is_deleted", 0).
Filter("approver_id__in", claimsReimburseUIDs(claims)...).
OrderBy("-id").One(&task)
if err == orm.ErrNoRows {
return nil, nil
}
if err != nil {
return nil, err
}
return &task, nil
}
// reimburseActCore 报销单审批操作共享逻辑(pass=true 通过 / false 驳回)。
//
// 返回 (httpCode, errMsg, hint):errMsg 非空表示失败;成功时 hint 为提示文案。
// 节点流转交由统一审批引擎,本函数负责定位当前用户的待办任务并补记业务时间线。
func reimburseActCore(claims *jwtutil.Claims, id uint64, pass bool, comment string) (int, string, string) {
if _, err := findOwnedReimbursement(claims, id); err != nil {
return 404, "报销单不存在", ""
}
task, err := reimburseMyTaskCore(claims, id)
if err != nil {
return 500, "查询审批任务失败", ""
}
if task == nil {
return 400, "当前没有待您审批的节点", ""
}
result, err := approvalEngine.Approve(task.ID, approvalEngine.ApproveInput{
ApproverID: task.ApproverID,
Pass: pass,
Comment: comment,
})
if err != nil {
return 400, err.Error(), ""
}
applyReimburseApprovalResult(uint64(claims.TenantId), id,
uint64(claims.UserID), reimburseActorName(uint64(claims.TenantId), claims), task.Sort,
pass, comment, result.Finished, result.InstanceStat)
hint := result.Message
if hint == "" {
if pass {
hint = "审批通过"
} else {
hint = "已驳回"
}
}
return 200, "", hint
}
// applyReimburseApprovalResult 把一次审批动作回写到报销业务侧:
// 补记 yz_backend_approval_records 时间线;流程结束时同步单据终态(引擎的
// SyncBizResult 也会做同样的事,这里按终态幂等,避免重复插记录)。
func applyReimburseApprovalResult(tid, bizID, actorID uint64, actorName string, step int,
pass bool, comment string, finished bool, instStatus int8) {
if tid == 0 || bizID == 0 {
return
}
action := int8(models.ApprovalActionApprove)
if !pass {
action = models.ApprovalActionReject
}
var row models.BackendReimbursement
if err := models.Orm.QueryTable(new(models.BackendReimbursement)).
Filter("id", bizID).Filter("tid", tid).Filter("is_deleted", 0).One(&row); err != nil {
return
}
// 终态同步(审批中→通过/驳回),引擎已同步过则跳过
var bizStatus int8
if finished {
switch instStatus {
case models.ApprovalInstanceApproved:
bizStatus = 2
case models.ApprovalInstanceRejected:
bizStatus = 3
}
if bizStatus != 0 && row.Status != bizStatus {
_, _ = models.Orm.QueryTable(new(models.BackendReimbursement)).
Filter("id", bizID).Filter("tid", tid).
Update(orm.Params{"status": bizStatus, "current_approver_id": 0, "update_time": time.Now()})
}
}
// 时间线去重:同一步骤同一动作只记一次
dup := models.Orm.QueryTable(new(models.BackendApprovalRecord)).
Filter("tid", tid).Filter("reimbursement_id", bizID).
Filter("approver_id", actorID).Filter("action", action).
Filter("step", int8(step)).Filter("is_deleted", 0).Exist()
if dup {
return
}
var commentPtr *string
if comment != "" {
commentPtr = &comment
}
_, _ = models.Orm.Insert(&models.BackendApprovalRecord{
Tid: tid, UID: uint64Ptr(actorID), ReimbursementID: bizID,
ApproverID: actorID, ApproverName: fmt.Sprintf("%s", actorName), Action: action,
Comment: commentPtr, Step: int8(step), PrevStep: int8(step),
})
}