优化报销功能

This commit is contained in:
hero920103 committed 2026-10-16 23:46:49 +08:00
1 parent 395c7f717e
commit 4306cbd74b
19 files changed
+2059 -534

No files matched your search

+176 -43
View File
@@ -41,19 +41,19 @@ import (
// oaApprovalSubmitPayload 提交审批入参
type oaApprovalSubmitPayload struct {
BizType string `json:"biz_type"` // 单据类型(必填)
FlowCode string `json:"flow_code"` // 指定流程(可选;留空取该 biz_type 默认流)
FlowID uint64 `json:"flow_id"` // 指定流程ID(可选)
BizTitle string `json:"biz_title"` // 单据标题(必填)
BizID uint64 `json:"biz_id"` // 关联业务单据ID(可选)
FormData map[string]interface{} `json:"form_data"` // 表单数据
BizType string `json:"biz_type"` // 单据类型(必填)
FlowCode string `json:"flow_code"` // 指定流程(可选;留空取该 biz_type 默认流)
FlowID uint64 `json:"flow_id"` // 指定流程ID(可选)
BizTitle string `json:"biz_title"` // 单据标题(必填)
BizID uint64 `json:"biz_id"` // 关联业务单据ID(可选)
FormData map[string]interface{} `json:"form_data"` // 表单数据
}
// oaApprovalApprovePayload 审批入参
type oaApprovalApprovePayload struct {
TaskID uint64 `json:"task_id"` // 待审批任务ID(必填)
Pass bool `json:"pass"` // true 通过 false 驳回
Comment string `json:"comment"` // 审批意见
TaskID uint64 `json:"task_id"` // 待审批任务ID(必填)
Pass bool `json:"pass"` // true 通过 false 驳回
Comment string `json:"comment"` // 审批意见
}
// ============================ 工具 ============================
@@ -267,8 +267,9 @@ func oaApprovalTaskTag(status int8) string {
}
}
// oaApprovalTaskView 任务视图(移动端时间线用)
func oaApprovalTaskView(t models.ApprovalTask, isCurrent bool) map[string]interface{} {
// oaApprovalTaskView 任务视图(移动端流程时间线用)
// nodeType 来自流程定义(开始/审批/通知/结束),取不到时按审批节点处理。
func oaApprovalTaskView(t models.ApprovalTask, isCurrent bool, nodeType int8) map[string]interface{} {
finishTime := ""
if t.FinishTime != nil {
finishTime = t.FinishTime.Format("2006-01-02 15:04:05")
@@ -277,23 +278,158 @@ func oaApprovalTaskView(t models.ApprovalTask, isCurrent bool) map[string]interf
if t.Deadline != nil {
deadline = t.Deadline.Format("2006-01-02 15:04:05")
}
return map[string]interface{}{
"id": t.ID,
"node_key": t.NodeKey,
"node_name": t.NodeName,
"approver_id": t.ApproverID,
"approver_name": t.ApproverName,
"multi_mode": t.MultiMode,
"status": t.Status,
"status_label": oaApprovalTaskText(t.Status),
"status_tag": oaApprovalTaskTag(t.Status),
"action": t.Action,
"comment": t.Comment,
"is_current": isCurrent,
"deadline": deadline,
"finish_time": finishTime,
"create_time": t.CreateTime.Format("2006-01-02 15:04:05"),
if nodeType == 0 {
nodeType = models.ApprovalNodeApproval
}
return map[string]interface{}{
"id": t.ID,
"node_key": t.NodeKey,
"node_name": t.NodeName,
"node_type": nodeType,
"approver_id": t.ApproverID,
"approver_name": t.ApproverName,
"approver_sub": oaApprovalUserOrgName(t.Tid, t.ApproverID),
"multi_mode": t.MultiMode,
"status": t.Status,
"status_label": oaApprovalTaskText(t.Status),
"status_tag": oaApprovalTaskTag(t.Status),
"action": t.Action,
"comment": t.Comment,
"is_current": isCurrent,
"deadline": deadline,
"finish_time": finishTime,
"create_time": t.CreateTime.Format("2006-01-02 15:04:05"),
}
}
// oaApprovalUserOrgName 审批人所属部门名,用作流程节点副标题(如「财务部」),取不到返回空串。
func oaApprovalUserOrgName(tid, uid uint64) string {
if tid == 0 || uid == 0 {
return ""
}
var tu models.SystemTenantUser
if err := models.Orm.QueryTable(new(models.SystemTenantUser)).
Filter("tid", tid).Filter("uid", uid).
Filter("delete_time__isnull", true).One(&tu); err != nil {
return ""
}
if tu.OrgID == 0 {
return ""
}
var org models.BackendOrganization
if err := models.Orm.QueryTable(new(models.BackendOrganization)).
Filter("id", tu.OrgID).One(&org); err != nil {
return ""
}
return org.OrgName
}
// 时间线虚拟节点的 node_key:用于标识流程的开始 / 结束,前端据此区分渲染。
const (
oaApprovalStartKey = "__start__"
oaApprovalEndKey = "__end__"
)
// oaApprovalFlowTimeline 组装移动端「审批流程」时间线:
// 首尾固定为提交申请(开始)与流程结束(结束),中间是流程产生的节点任务,
// 让发起人一眼看出流程当前走到哪、是否已经走完。
func oaApprovalFlowTimeline(inst models.ApprovalInstance, tasks []models.ApprovalTask) []map[string]interface{} {
nodeTypes := oaApprovalNodeTypes(inst)
timeline := make([]map[string]interface{}, 0, len(tasks)+2)
timeline = append(timeline, oaApprovalStartNode(inst))
for _, t := range tasks {
timeline = append(timeline, oaApprovalTaskView(t, t.NodeKey == inst.CurrentNode, nodeTypes[t.NodeKey]))
}
timeline = append(timeline, oaApprovalEndNode(inst))
return timeline
}
// oaApprovalNodeTypes 读流程定义,取各节点类型(开始/审批/通知/结束)。
// 流程被删除或改版时返回空表,调用方按审批节点兜底。
func oaApprovalNodeTypes(inst models.ApprovalInstance) map[string]int8 {
out := make(map[string]int8)
flow, err := approvalEngine.LoadFlow(inst.Scope, inst.Tid, inst.BizType, inst.FlowID)
if err != nil {
return out
}
for i := range flow.Nodes {
out[flow.Nodes[i].NodeKey] = flow.Nodes[i].NodeType
}
return out
}
// oaApprovalStartNode 虚拟开始节点:即发起人提交动作。
func oaApprovalStartNode(inst models.ApprovalInstance) map[string]interface{} {
timeStr := ""
if inst.SubmitTime != nil {
timeStr = inst.SubmitTime.Format("2006-01-02 15:04:05")
} else if !inst.CreateTime.IsZero() {
timeStr = inst.CreateTime.Format("2006-01-02 15:04:05")
}
return map[string]interface{}{
"id": 0,
"node_key": oaApprovalStartKey,
"node_name": "提交申请",
"node_type": models.ApprovalNodeStart,
"approver_id": inst.SubmitUserID,
"approver_name": inst.SubmitUserName,
"approver_sub": oaApprovalUserOrgName(inst.Tid, inst.SubmitUserID),
"multi_mode": 1,
"status": models.ApprovalTaskApproved,
"status_label": "已提交",
"status_tag": "success",
"action": 0,
"comment": "",
"is_current": false,
"deadline": "",
"finish_time": timeStr,
"create_time": timeStr,
}
}
// oaApprovalEndNode 虚拟结束节点:实例运行中为「待完成」,结束后展示最终结果与完成时间。
func oaApprovalEndNode(inst models.ApprovalInstance) map[string]interface{} {
finishTime := ""
if inst.FinishTime != nil {
finishTime = inst.FinishTime.Format("2006-01-02 15:04:05")
}
end := map[string]interface{}{
"id": 0,
"node_key": oaApprovalEndKey,
"node_name": "流程结束",
"node_type": models.ApprovalNodeEnd,
"approver_id": 0,
"approver_name": "",
"multi_mode": 1,
"status": models.ApprovalTaskPending,
"status_label": "待完成",
"status_tag": "info",
"action": 0,
"comment": "",
"is_current": false,
"deadline": "",
"finish_time": finishTime,
"create_time": finishTime,
}
switch inst.Status {
case models.ApprovalInstanceApproved:
end["status"] = models.ApprovalTaskApproved
end["status_label"] = "已通过"
end["status_tag"] = "success"
case models.ApprovalInstanceRejected:
end["status"] = models.ApprovalTaskRejected
end["status_label"] = "已驳回"
end["status_tag"] = "error"
case models.ApprovalInstanceWithdrawn:
end["status"] = models.ApprovalTaskSkipped
end["status_label"] = "已撤回"
end["status_tag"] = "info"
case models.ApprovalInstanceCanceled:
end["status"] = models.ApprovalTaskSkipped
end["status_label"] = "已取消"
end["status_tag"] = "info"
}
return end
}
// ============================ 路由方法 ============================
@@ -468,7 +604,7 @@ func (c *AppOaController) ApprovalTodo() {
if inst.Status != models.ApprovalInstanceRunning {
continue
}
view := oaApprovalTaskView(t, true)
view := oaApprovalTaskView(t, true, oaApprovalNodeTypes(inst)[t.NodeKey])
view["instance"] = oaApprovalInstanceView(inst)
list = append(list, view)
}
@@ -529,16 +665,13 @@ func (c *AppOaController) ApprovalDetail() {
}
tasks := oaApprovalTasksByInstance(inst.ID)
timeline := make([]map[string]interface{}, 0, len(tasks))
for _, t := range tasks {
timeline = append(timeline, oaApprovalTaskView(t, t.NodeKey == inst.CurrentNode))
}
timeline := oaApprovalFlowTimeline(inst, tasks)
c.oaOk(map[string]interface{}{
"item": oaApprovalInstanceView(inst),
"timeline": timeline,
"can_approve": isApprover,
"can_withdraw": isSubmitter && inst.Status == models.ApprovalInstanceRunning,
"item": oaApprovalInstanceView(inst),
"timeline": timeline,
"can_approve": isApprover,
"can_withdraw": isSubmitter && inst.Status == models.ApprovalInstanceRunning,
"current_task_id": currentTaskID,
})
}
@@ -602,8 +735,8 @@ func (c *AppOaController) ApprovalSubmit() {
Filter("id", instID).One(&inst)
c.oaOk(map[string]interface{}{
"id": instID,
"item": oaApprovalInstanceView(inst),
"id": instID,
"item": oaApprovalInstanceView(inst),
})
}
@@ -662,11 +795,11 @@ func (c *AppOaController) ApprovalApprove() {
Filter("id", res.InstanceID).One(&inst)
c.oaOk(map[string]interface{}{
"finished": res.Finished,
"instance_stat": res.InstanceStat,
"current_node": res.CurrentNode,
"message": res.Message,
"item": oaApprovalInstanceView(inst),
"finished": res.Finished,
"instance_stat": res.InstanceStat,
"current_node": res.CurrentNode,
"message": res.Message,
"item": oaApprovalInstanceView(inst),
})
}