优化审批功能
This commit is contained in:
1 parent
184d5b0403
commit
395c7f717e
15 files changed
+2997
-16
No files matched your search
@@ -13,6 +13,7 @@ import (
|
||||
"server/models"
|
||||
"server/pkg/jwtutil"
|
||||
"server/services"
|
||||
approvalEngine "server/services/approval"
|
||||
|
||||
"github.com/beego/beego/v2/client/orm"
|
||||
beego "github.com/beego/beego/v2/server/web"
|
||||
@@ -377,6 +378,8 @@ func reimburseListData(claims *jwtutil.Claims, page, pageSize int, keyword strin
|
||||
}
|
||||
list := make([]map[string]interface{}, 0, len(rows))
|
||||
for _, row := range rows {
|
||||
// 审批已结束但业务状态未及时回写时,读取即自愈
|
||||
row.Status = syncReimburseStatusFromInstance(&row)
|
||||
list = append(list, map[string]interface{}{
|
||||
"id": row.ID,
|
||||
"tid": row.Tid,
|
||||
@@ -430,9 +433,58 @@ func reimburseDetailData(claims *jwtutil.Claims, id uint64, scheme, host string)
|
||||
if err != nil && err != orm.ErrNoRows {
|
||||
return nil, 500, "查询明细失败: " + err.Error()
|
||||
}
|
||||
// 审批已结束但业务状态未及时回写时,读取即自愈
|
||||
row.Status = syncReimburseStatusFromInstance(row)
|
||||
return reimbursementDTO(*row, items, scheme, host), 200, ""
|
||||
}
|
||||
|
||||
// syncReimburseStatusFromInstance 报销单状态自愈:单据处于「审批中」时,
|
||||
// 按审批引擎中对应实例(biz_type=reimburse)的最终态回写业务状态。
|
||||
// 用于兜底引擎回写异常/历史数据,列表与详情读取时调用,无变化时原样返回。
|
||||
func syncReimburseStatusFromInstance(row *models.BackendReimbursement) int8 {
|
||||
if row == nil {
|
||||
return 0
|
||||
}
|
||||
if row.Status != 1 {
|
||||
return row.Status
|
||||
}
|
||||
inst, ok := findReimburseInstance(row.Tid, row.ID)
|
||||
if !ok || inst.Status == models.ApprovalInstanceRunning {
|
||||
return row.Status
|
||||
}
|
||||
approvalEngine.SyncBizResult(inst.ID, inst.Status)
|
||||
switch inst.Status {
|
||||
case models.ApprovalInstanceApproved:
|
||||
return 2
|
||||
case models.ApprovalInstanceRejected:
|
||||
return 3
|
||||
case models.ApprovalInstanceWithdrawn:
|
||||
return 4
|
||||
}
|
||||
return row.Status
|
||||
}
|
||||
|
||||
// findReimburseInstance 查报销单对应的审批实例。
|
||||
// 兼容两种 biz_type 写法(reimburse / 早期的 oa),并按 tid、biz_tid 依次匹配,
|
||||
// 这样租户侧与平台侧产生的实例都能找到。
|
||||
func findReimburseInstance(tid, bizID uint64) (*models.ApprovalInstance, bool) {
|
||||
if tid == 0 || bizID == 0 {
|
||||
return nil, false
|
||||
}
|
||||
for _, key := range []string{"tid", "biz_tid"} {
|
||||
var inst models.ApprovalInstance
|
||||
if err := models.Orm.QueryTable(new(models.ApprovalInstance)).
|
||||
Filter(key, tid).
|
||||
Filter("biz_type__in", models.ApprovalBizReimburse, approvalEngine.BizTypeLegacyOA).
|
||||
Filter("biz_id", bizID).
|
||||
Filter("is_deleted", 0).
|
||||
OrderBy("-id").One(&inst); err == nil {
|
||||
return &inst, true
|
||||
}
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// Create POST /backend/reimbursements
|
||||
func (c *BackendReimburseController) Create() {
|
||||
claims, ok := c.claims()
|
||||
@@ -827,8 +879,12 @@ func (c *BackendReimburseController) Submit() {
|
||||
c.reply(200, "success", nil)
|
||||
}
|
||||
|
||||
// reimburseSubmitCore 提交审批共享逻辑:仅草稿可提交,需租户已配置启用审批流,
|
||||
// 提交后状态置为审批中(1),并写入 action=1 的审批记录。
|
||||
// reimburseSubmitCore 提交审批共享逻辑:仅草稿可提交,需租户已配置启用审批流。
|
||||
//
|
||||
// 提交进入统一审批引擎(biz_type=reimburse、biz_id=报销单ID),引擎负责节点流转;
|
||||
// 审批最终结果由 services/approval.SyncBizResult 回写到报销单状态,保证
|
||||
// 「审批中心」与「报销管理」看到的状态一致。
|
||||
// 提交后报销单置为审批中(1),并写入 action=1 的审批记录供业务详情时间线展示。
|
||||
func reimburseSubmitCore(claims *jwtutil.Claims, id uint64) (int, string) {
|
||||
row, err := findOwnedReimbursement(claims, id)
|
||||
if err != nil {
|
||||
@@ -837,24 +893,57 @@ func reimburseSubmitCore(claims *jwtutil.Claims, id uint64) (int, string) {
|
||||
if row.Status != 0 {
|
||||
return 400, "当前状态不可提交"
|
||||
}
|
||||
var flow models.BackendApprovalFlow
|
||||
err = models.Orm.QueryTable(new(models.BackendApprovalFlow)).
|
||||
Filter("tid", claims.TenantId).Filter("is_active", 1).Filter("is_deleted", 0).
|
||||
OrderBy("step_order").One(&flow)
|
||||
if err != nil {
|
||||
tid := uint64(claims.TenantId)
|
||||
flowID := findReimburseFlowID(tid)
|
||||
if flowID == 0 {
|
||||
return 400, "尚未配置审批流程"
|
||||
}
|
||||
|
||||
instID, err := approvalEngine.Submit(approvalEngine.SubmitInput{
|
||||
Scope: models.ApprovalScopeTenant,
|
||||
Tid: tid,
|
||||
FlowID: flowID,
|
||||
BizType: models.ApprovalBizReimburse,
|
||||
BizID: id,
|
||||
BizTitle: reimburseBizTitle(row),
|
||||
SubmitUID: uint64(claims.UserID),
|
||||
SubmitName: claims.Username,
|
||||
DeptID: reimburseSubmitDeptID(tid, uint64(claims.UserID)),
|
||||
FormData: map[string]interface{}{
|
||||
"id": id,
|
||||
"total_amount": row.TotalAmount,
|
||||
"apply_date": row.ApplyDate.Format("2006-01-02"),
|
||||
"description": reimburseDescription(row),
|
||||
"department_id": row.DepartmentID,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return 400, err.Error()
|
||||
}
|
||||
|
||||
// 流程未配置审批节点时引擎会直接通过,此时报销单同步为已通过(2)
|
||||
status := int8(1)
|
||||
var inst models.ApprovalInstance
|
||||
if e := models.Orm.QueryTable(new(models.ApprovalInstance)).Filter("id", instID).One(&inst); e == nil {
|
||||
switch inst.Status {
|
||||
case models.ApprovalInstanceApproved:
|
||||
status = 2
|
||||
case models.ApprovalInstanceRejected:
|
||||
status = 3
|
||||
}
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
_, err = models.Orm.QueryTable(new(models.BackendReimbursement)).Filter("id", id).Update(orm.Params{
|
||||
"status": 1, "current_step": flow.StepOrder, "current_approver_id": 0, "update_time": now,
|
||||
"status": status, "current_step": 0, "current_approver_id": 0, "update_time": now,
|
||||
})
|
||||
if err != nil {
|
||||
return 500, "提交失败"
|
||||
}
|
||||
_, err = models.Orm.Insert(&models.BackendApprovalRecord{
|
||||
Tid: uint64(claims.TenantId), UID: uint64Ptr(uint64(claims.UserID)), ReimbursementID: id,
|
||||
ApproverID: uint64(claims.UserID), ApproverName: claims.Username, Action: 1,
|
||||
Step: flow.StepOrder, PrevStep: 0,
|
||||
Tid: tid, UID: uint64Ptr(uint64(claims.UserID)), ReimbursementID: id,
|
||||
ApproverID: uint64(claims.UserID), ApproverName: claims.Username, Action: models.ApprovalActionSubmit,
|
||||
Step: 0, PrevStep: 0,
|
||||
})
|
||||
if err != nil {
|
||||
return 500, "提交审批记录失败"
|
||||
@@ -862,6 +951,52 @@ func reimburseSubmitCore(claims *jwtutil.Claims, id uint64) (int, string) {
|
||||
return 200, ""
|
||||
}
|
||||
|
||||
// findReimburseFlowID 查找报销单可用的启用审批流:
|
||||
// 优先精确匹配 biz_type=reimburse;若租户未区分单据类型(只按模块 oa 配置),回退到模块流程。
|
||||
func findReimburseFlowID(tid uint64) uint64 {
|
||||
var flow models.TenantApprovalFlow
|
||||
if err := models.Orm.QueryTable(new(models.TenantApprovalFlow)).
|
||||
Filter("tid", tid).Filter("biz_type", models.ApprovalBizReimburse).
|
||||
Filter("status", 1).Filter("is_deleted", 0).
|
||||
OrderBy("-is_default", "-id").One(&flow); err == nil {
|
||||
return flow.ID
|
||||
}
|
||||
if err := models.Orm.QueryTable(new(models.TenantApprovalFlow)).
|
||||
Filter("tid", tid).Filter("module_code", "oa").
|
||||
Filter("status", 1).Filter("is_deleted", 0).
|
||||
Filter("biz_type__in", "", "oa").
|
||||
OrderBy("-is_default", "-id").One(&flow); err == nil {
|
||||
return flow.ID
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// reimburseSubmitDeptID 取发起人所属组织ID,供审批引擎按「部门主管」规则解析审批人。
|
||||
func reimburseSubmitDeptID(tid, uid uint64) uint64 {
|
||||
var tu models.SystemTenantUser
|
||||
if err := models.Orm.QueryTable(new(models.SystemTenantUser)).
|
||||
Filter("uid", uid).Filter("tid", tid).
|
||||
Filter("delete_time__isnull", true).One(&tu); err != nil {
|
||||
return 0
|
||||
}
|
||||
return tu.OrgID
|
||||
}
|
||||
|
||||
// reimburseBizTitle 审批标题:优先报销说明,其次带单据号。
|
||||
func reimburseBizTitle(row *models.BackendReimbursement) string {
|
||||
if desc := reimburseDescription(row); desc != "" {
|
||||
return desc
|
||||
}
|
||||
return fmt.Sprintf("报销单 #%d", row.ID)
|
||||
}
|
||||
|
||||
func reimburseDescription(row *models.BackendReimbursement) string {
|
||||
if row == nil || row.Description == nil {
|
||||
return ""
|
||||
}
|
||||
return strings.TrimSpace(*row.Description)
|
||||
}
|
||||
|
||||
type reimbursementStatusPayload struct {
|
||||
Status int8 `json:"status"`
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user