152 lines
5.0 KiB
Go
152 lines
5.0 KiB
Go
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),
|
||
})
|
||
}
|