788 lines
25 KiB
Go
788 lines
25 KiB
Go
// 工作流模块 HTTP Handler
|
||
package workflow
|
||
|
||
import (
|
||
"encoding/json"
|
||
"fmt"
|
||
"strconv"
|
||
"strings"
|
||
"time"
|
||
|
||
"com.sclktx/m/v2/internal/common/model"
|
||
"com.sclktx/m/v2/internal/common/response"
|
||
"com.sclktx/m/v2/internal/common/utils"
|
||
"com.sclktx/m/v2/internal/pkg/dingtalk"
|
||
"com.sclktx/m/v2/internal/pkg/wechat/templates"
|
||
|
||
"github.com/gin-gonic/gin"
|
||
"github.com/sirupsen/logrus"
|
||
"gorm.io/gorm"
|
||
)
|
||
|
||
// Handler 工作流处理器
|
||
type Handler struct {
|
||
db *gorm.DB
|
||
engine *Engine
|
||
wxSend func(openid, templateID string, data map[string]string, page string) error
|
||
dingTalk *dingtalk.Client
|
||
}
|
||
|
||
// NewHandler 创建工作流处理器
|
||
func NewHandler(db *gorm.DB, wxSend func(openid, templateID string, data map[string]string, page string) error, dingTalk *dingtalk.Client) *Handler {
|
||
return &Handler{
|
||
db: db,
|
||
engine: NewEngine(db),
|
||
wxSend: wxSend,
|
||
dingTalk: dingTalk,
|
||
}
|
||
}
|
||
|
||
// ==================== 请求/响应结构 ====================
|
||
|
||
// CreateDefinitionReq 创建流程定义请求
|
||
type CreateDefinitionReq struct {
|
||
ProcessName string `json:"process_name" binding:"required"`
|
||
ProcessCode string `json:"process_code" binding:"required"`
|
||
Source string `json:"source"`
|
||
Description string `json:"description"`
|
||
RevokeEvents string `json:"revoke_events"`
|
||
NodesJSON string `json:"nodes_json"`
|
||
}
|
||
|
||
// UpdateDefinitionReq 更新流程定义请求
|
||
type UpdateDefinitionReq struct {
|
||
ProcessName string `json:"process_name"`
|
||
Source string `json:"source"`
|
||
Description string `json:"description"`
|
||
IsActive *bool `json:"is_active"`
|
||
RevokeEvents string `json:"revoke_events"`
|
||
NodesJSON string `json:"nodes_json"`
|
||
}
|
||
|
||
// StartInstanceReq 启动流程实例请求
|
||
type StartInstanceReq struct {
|
||
ProcessCode string `json:"process_code" binding:"required"`
|
||
BusinessType string `json:"business_type" binding:"required"`
|
||
BusinessID uint `json:"business_id" binding:"required"`
|
||
Variables map[string]interface{} `json:"variables"`
|
||
}
|
||
|
||
// TaskActionReq 任务操作请求
|
||
type TaskActionReq struct {
|
||
Comment string `json:"comment"`
|
||
}
|
||
|
||
// ==================== 路由注册 ====================
|
||
|
||
// RegisterRoutes 注册工作流路由(需要 JWT 认证)
|
||
func (h *Handler) RegisterRoutes(router *gin.RouterGroup) {
|
||
// 流程定义(管理员操作)
|
||
router.GET("/workflow/definitions", h.ListDefinitions)
|
||
router.GET("/workflow/definitions/:id", h.GetDefinition)
|
||
router.POST("/workflow/definitions", h.CreateDefinition)
|
||
router.PUT("/workflow/definitions/:id", h.UpdateDefinition)
|
||
router.DELETE("/workflow/definitions/:id", h.DeleteDefinition)
|
||
|
||
// 流程实例
|
||
router.POST("/workflow/instances", h.StartInstance)
|
||
router.GET("/workflow/instances", h.ListInstances)
|
||
router.GET("/workflow/instances/:id", h.GetInstanceDetail)
|
||
|
||
// 任务
|
||
router.GET("/workflow/tasks/pending", h.GetPendingTasks)
|
||
router.POST("/workflow/tasks/:id/approve", h.ApproveTask)
|
||
router.POST("/workflow/tasks/:id/reject", h.RejectTask)
|
||
}
|
||
|
||
// ==================== 流程定义管理 ====================
|
||
|
||
// ListDefinitions 获取流程定义列表
|
||
// @Summary 获取流程定义列表
|
||
// @Description 获取所有流程定义列表
|
||
// @Tags 工作流
|
||
// @Accept json
|
||
// @Produce json
|
||
// @Security BearerAuth
|
||
// @Param page query int false "页码" default(1)
|
||
// @Param page_size query int false "每页条数" default(10)
|
||
// @Success 200 {object} response.Response "success"
|
||
// @Router /api/v1/workflow/definitions [get]
|
||
func (h *Handler) ListDefinitions(c *gin.Context) {
|
||
page, pageSize := utils.ParsePagination(c)
|
||
|
||
var defs []model.ProcessDefinition
|
||
var total int64
|
||
|
||
query := h.db.Model(&model.ProcessDefinition{})
|
||
query.Count(&total)
|
||
query.Offset(utils.GetOffset(page, pageSize)).
|
||
Limit(pageSize).
|
||
Order("created_at DESC").
|
||
Find(&defs)
|
||
|
||
response.SuccessPage(c, defs, total, page, pageSize)
|
||
}
|
||
|
||
// GetDefinition 获取流程定义详情
|
||
// @Summary 获取流程定义详情
|
||
// @Description 根据ID获取流程定义详情
|
||
// @Tags 工作流
|
||
// @Accept json
|
||
// @Produce json
|
||
// @Security BearerAuth
|
||
// @Param id path uint true "流程定义ID"
|
||
// @Success 200 {object} response.Response "success"
|
||
// @Router /api/v1/workflow/definitions/{id} [get]
|
||
func (h *Handler) GetDefinition(c *gin.Context) {
|
||
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
|
||
if err != nil {
|
||
response.BadRequest(c, "参数错误")
|
||
return
|
||
}
|
||
|
||
var def model.ProcessDefinition
|
||
if err := h.db.First(&def, id).Error; err != nil {
|
||
response.NotFound(c, "流程定义不存在")
|
||
return
|
||
}
|
||
|
||
response.Success(c, def)
|
||
}
|
||
|
||
// CreateDefinition 创建流程定义
|
||
// @Summary 创建流程定义
|
||
// @Description 创建一个新的流程定义(管理员操作)
|
||
// @Tags 工作流
|
||
// @Accept json
|
||
// @Produce json
|
||
// @Security BearerAuth
|
||
// @Param body body CreateDefinitionReq true "创建流程定义请求"
|
||
// @Success 200 {object} response.Response "success"
|
||
// @Router /api/v1/workflow/definitions [post]
|
||
func (h *Handler) CreateDefinition(c *gin.Context) {
|
||
var req CreateDefinitionReq
|
||
if err := c.ShouldBindJSON(&req); err != nil {
|
||
response.BadRequest(c, "参数错误")
|
||
return
|
||
}
|
||
|
||
// 检查编码唯一性
|
||
var existCount int64
|
||
h.db.Model(&model.ProcessDefinition{}).Where("process_code = ?", req.ProcessCode).Count(&existCount)
|
||
if existCount > 0 {
|
||
response.BadRequest(c, "流程编码已存在")
|
||
return
|
||
}
|
||
|
||
def := model.ProcessDefinition{
|
||
ProcessName: req.ProcessName,
|
||
ProcessCode: req.ProcessCode,
|
||
Source: req.Source,
|
||
Description: req.Description,
|
||
IsActive: true,
|
||
RevokeEvents: req.RevokeEvents,
|
||
NodesJSON: req.NodesJSON,
|
||
}
|
||
|
||
if err := h.db.Create(&def).Error; err != nil {
|
||
response.InternalError(c, "创建失败")
|
||
return
|
||
}
|
||
|
||
response.SuccessWithMessage(c, "创建成功", def)
|
||
}
|
||
|
||
// UpdateDefinition 更新流程定义
|
||
// @Summary 更新流程定义
|
||
// @Description 更新指定流程定义的信息(管理员操作)
|
||
// @Tags 工作流
|
||
// @Accept json
|
||
// @Produce json
|
||
// @Security BearerAuth
|
||
// @Param id path uint true "流程定义ID"
|
||
// @Param body body UpdateDefinitionReq true "更新流程定义请求"
|
||
// @Success 200 {object} response.Response "success"
|
||
// @Router /api/v1/workflow/definitions/{id} [put]
|
||
func (h *Handler) UpdateDefinition(c *gin.Context) {
|
||
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
|
||
if err != nil {
|
||
response.BadRequest(c, "参数错误")
|
||
return
|
||
}
|
||
|
||
var def model.ProcessDefinition
|
||
if err := h.db.First(&def, id).Error; err != nil {
|
||
response.NotFound(c, "流程定义不存在")
|
||
return
|
||
}
|
||
|
||
var req UpdateDefinitionReq
|
||
if err := c.ShouldBindJSON(&req); err != nil {
|
||
response.BadRequest(c, "参数错误")
|
||
return
|
||
}
|
||
|
||
updates := map[string]interface{}{}
|
||
if req.ProcessName != "" {
|
||
updates["process_name"] = req.ProcessName
|
||
}
|
||
if req.Source != "" {
|
||
updates["source"] = req.Source
|
||
}
|
||
if req.Description != "" {
|
||
updates["description"] = req.Description
|
||
}
|
||
if req.IsActive != nil {
|
||
updates["is_active"] = *req.IsActive
|
||
}
|
||
if req.RevokeEvents != "" {
|
||
updates["revoke_events"] = req.RevokeEvents
|
||
}
|
||
if req.NodesJSON != "" {
|
||
updates["nodes_json"] = req.NodesJSON
|
||
}
|
||
|
||
if len(updates) > 0 {
|
||
if err := h.db.Model(&def).Updates(updates).Error; err != nil {
|
||
response.InternalError(c, "更新失败")
|
||
return
|
||
}
|
||
}
|
||
|
||
response.SuccessWithMessage(c, "更新成功", nil)
|
||
}
|
||
|
||
// DeleteDefinition 删除流程定义
|
||
// @Summary 删除流程定义
|
||
// @Description 删除指定的流程定义(管理员操作,存在运行中的实例时无法删除)
|
||
// @Tags 工作流
|
||
// @Accept json
|
||
// @Produce json
|
||
// @Security BearerAuth
|
||
// @Param id path uint true "流程定义ID"
|
||
// @Success 200 {object} response.Response "success"
|
||
// @Router /api/v1/workflow/definitions/{id} [delete]
|
||
func (h *Handler) DeleteDefinition(c *gin.Context) {
|
||
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
|
||
if err != nil {
|
||
response.BadRequest(c, "参数错误")
|
||
return
|
||
}
|
||
|
||
// 检查是否有运行中的实例
|
||
var activeCount int64
|
||
h.db.Model(&model.ProcessInstance{}).Where("process_def_id = ? AND status = 0", id).Count(&activeCount)
|
||
if activeCount > 0 {
|
||
response.BadRequest(c, "存在运行中的流程实例,无法删除")
|
||
return
|
||
}
|
||
|
||
if err := h.db.Delete(&model.ProcessDefinition{}, id).Error; err != nil {
|
||
response.InternalError(c, "删除失败")
|
||
return
|
||
}
|
||
|
||
response.SuccessWithMessage(c, "删除成功", nil)
|
||
}
|
||
|
||
// ==================== 流程实例管理 ====================
|
||
|
||
// StartInstance 启动流程实例
|
||
// @Summary 启动流程实例
|
||
// @Description 根据流程定义启动一个新的流程实例
|
||
// @Tags 工作流
|
||
// @Accept json
|
||
// @Produce json
|
||
// @Security BearerAuth
|
||
// @Param body body StartInstanceReq true "启动流程实例请求"
|
||
// @Success 200 {object} response.Response "success"
|
||
// @Router /api/v1/workflow/instances [post]
|
||
func (h *Handler) StartInstance(c *gin.Context) {
|
||
var req StartInstanceReq
|
||
if err := c.ShouldBindJSON(&req); err != nil {
|
||
response.BadRequest(c, "参数错误")
|
||
return
|
||
}
|
||
|
||
// 查找流程定义
|
||
var def model.ProcessDefinition
|
||
if err := h.db.Where("process_code = ?", req.ProcessCode).First(&def).Error; err != nil {
|
||
response.NotFound(c, "流程定义不存在")
|
||
return
|
||
}
|
||
|
||
userID := utils.GetUserID(c)
|
||
|
||
instance, err := h.engine.StartInstance(def.ID, req.BusinessType, req.BusinessID, userID, req.Variables)
|
||
if err != nil {
|
||
response.InternalError(c, err.Error())
|
||
return
|
||
}
|
||
|
||
response.SuccessWithMessage(c, "启动成功", instance)
|
||
}
|
||
|
||
// ListInstances 获取流程实例列表
|
||
// @Summary 获取流程实例列表
|
||
// @Description 获取所有流程实例列表,可按业务类型和状态筛选
|
||
// @Tags 工作流
|
||
// @Accept json
|
||
// @Produce json
|
||
// @Security BearerAuth
|
||
// @Param business_type query string false "业务类型"
|
||
// @Param status query int false "实例状态"
|
||
// @Param page query int false "页码" default(1)
|
||
// @Param page_size query int false "每页条数" default(10)
|
||
// @Success 200 {object} response.Response "success"
|
||
// @Router /api/v1/workflow/instances [get]
|
||
func (h *Handler) ListInstances(c *gin.Context) {
|
||
page, pageSize := utils.ParsePagination(c)
|
||
businessType := c.Query("business_type")
|
||
businessIDStr := c.Query("business_id")
|
||
statusStr := c.Query("status")
|
||
|
||
var instances []model.ProcessInstance
|
||
var total int64
|
||
|
||
query := h.db.Model(&model.ProcessInstance{}).
|
||
Preload("ProcessDef").
|
||
Preload("Starter")
|
||
if businessType != "" {
|
||
query = query.Where("business_type = ?", businessType)
|
||
}
|
||
if businessIDStr != "" {
|
||
businessID, _ := strconv.ParseUint(businessIDStr, 10, 64)
|
||
if businessID > 0 {
|
||
query = query.Where("business_id = ?", businessID)
|
||
}
|
||
}
|
||
if statusStr != "" {
|
||
status, _ := strconv.Atoi(statusStr)
|
||
query = query.Where("status = ?", status)
|
||
}
|
||
|
||
query.Count(&total)
|
||
query.Offset(utils.GetOffset(page, pageSize)).
|
||
Limit(pageSize).
|
||
Order("created_at DESC").
|
||
Find(&instances)
|
||
|
||
response.SuccessPage(c, instances, total, page, pageSize)
|
||
}
|
||
|
||
// GetInstanceDetail 获取流程实例详情(含任务列表)
|
||
// @Summary 获取流程实例详情
|
||
// @Description 获取流程实例详情,包含关联的任务列表
|
||
// @Tags 工作流
|
||
// @Accept json
|
||
// @Produce json
|
||
// @Security BearerAuth
|
||
// @Param id path uint true "流程实例ID"
|
||
// @Success 200 {object} response.Response "success"
|
||
// @Router /api/v1/workflow/instances/{id} [get]
|
||
func (h *Handler) GetInstanceDetail(c *gin.Context) {
|
||
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
|
||
if err != nil {
|
||
response.BadRequest(c, "参数错误")
|
||
return
|
||
}
|
||
|
||
var instance model.ProcessInstance
|
||
if err := h.db.Preload("ProcessDef").Preload("Starter").First(&instance, id).Error; err != nil {
|
||
response.NotFound(c, "流程实例不存在")
|
||
return
|
||
}
|
||
|
||
var tasks []model.Task
|
||
h.db.Where("instance_id = ?", id).
|
||
Preload("Assignee").
|
||
Order("created_at ASC").
|
||
Find(&tasks)
|
||
|
||
response.Success(c, gin.H{
|
||
"instance": instance,
|
||
"tasks": tasks,
|
||
})
|
||
}
|
||
|
||
// ==================== 任务管理 ====================
|
||
|
||
// GetPendingTasks 获取当前用户的待办任务
|
||
// @Summary 获取待办任务
|
||
// @Description 获取当前用户的待处理流程任务列表
|
||
// @Tags 工作流
|
||
// @Accept json
|
||
// @Produce json
|
||
// @Security BearerAuth
|
||
// @Success 200 {object} response.Response "success"
|
||
// @Router /api/v1/workflow/tasks/pending [get]
|
||
func (h *Handler) GetPendingTasks(c *gin.Context) {
|
||
userID := utils.GetUserID(c)
|
||
|
||
var tasks []model.Task
|
||
h.db.Where("assignee_id = ? AND status = 0", userID).
|
||
Preload("Instance").
|
||
Preload("Instance.ProcessDef").
|
||
Preload("Assignee").
|
||
Order("created_at DESC").
|
||
Find(&tasks)
|
||
|
||
response.Success(c, tasks)
|
||
}
|
||
|
||
// ApproveTask 审批通过任务
|
||
// @Summary 审批通过任务
|
||
// @Description 审批通过指定的流程任务
|
||
// @Tags 工作流
|
||
// @Accept json
|
||
// @Produce json
|
||
// @Security BearerAuth
|
||
// @Param id path uint true "任务ID"
|
||
// @Param body body TaskActionReq true "审批请求"
|
||
// @Success 200 {object} response.Response "success"
|
||
// @Router /api/v1/workflow/tasks/{id}/approve [post]
|
||
func (h *Handler) ApproveTask(c *gin.Context) {
|
||
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
|
||
if err != nil {
|
||
response.BadRequest(c, "参数错误")
|
||
return
|
||
}
|
||
|
||
var req TaskActionReq
|
||
if err := c.ShouldBindJSON(&req); err != nil {
|
||
response.BadRequest(c, "参数错误")
|
||
return
|
||
}
|
||
|
||
userID := utils.GetUserID(c)
|
||
|
||
if err := h.engine.ApproveTask(uint(id), userID, req.Comment); err != nil {
|
||
response.InternalError(c, err.Error())
|
||
return
|
||
}
|
||
|
||
// 审批完成后发送通知
|
||
h.sendAppointmentNotification(uint(id))
|
||
h.sendDingTalkAfterApproval(uint(id), req.Comment)
|
||
|
||
response.SuccessWithMessage(c, "审批通过", nil)
|
||
}
|
||
|
||
// RejectTask 审批拒绝任务
|
||
// @Summary 审批拒绝任务
|
||
// @Description 拒绝指定的流程任务
|
||
// @Tags 工作流
|
||
// @Accept json
|
||
// @Produce json
|
||
// @Security BearerAuth
|
||
// @Param id path uint true "任务ID"
|
||
// @Param body body TaskActionReq true "拒绝请求"
|
||
// @Success 200 {object} response.Response "success"
|
||
// @Router /api/v1/workflow/tasks/{id}/reject [post]
|
||
func (h *Handler) RejectTask(c *gin.Context) {
|
||
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
|
||
if err != nil {
|
||
response.BadRequest(c, "参数错误")
|
||
return
|
||
}
|
||
|
||
var req TaskActionReq
|
||
if err := c.ShouldBindJSON(&req); err != nil {
|
||
response.BadRequest(c, "参数错误")
|
||
return
|
||
}
|
||
|
||
userID := utils.GetUserID(c)
|
||
|
||
if err := h.engine.RejectTask(uint(id), userID, req.Comment); err != nil {
|
||
response.InternalError(c, err.Error())
|
||
return
|
||
}
|
||
|
||
// 拒绝后发送通知
|
||
h.sendAppointmentNotification(uint(id))
|
||
h.sendDingTalkAfterRejection(uint(id), req.Comment)
|
||
|
||
response.SuccessWithMessage(c, "已拒绝", nil)
|
||
}
|
||
|
||
// sendAppointmentNotification 审批结束后发送微信通知给申请人
|
||
func (h *Handler) sendAppointmentNotification(taskID uint) {
|
||
var task model.Task
|
||
if err := h.db.Preload("Instance").First(&task, taskID).Error; err != nil {
|
||
return
|
||
}
|
||
if task.Instance == nil || task.Instance.BusinessType != "appointment" {
|
||
return
|
||
}
|
||
|
||
go func() {
|
||
var appointment model.Appointment
|
||
if err := h.db.First(&appointment, task.Instance.BusinessID).Error; err != nil {
|
||
return
|
||
}
|
||
// 只发送已通过或已拒绝的通知
|
||
if appointment.Status != 1 && appointment.Status != 2 {
|
||
return
|
||
}
|
||
|
||
var creatorUser model.User
|
||
if err := h.db.First(&creatorUser, appointment.CreatorUserID).Error; err != nil || creatorUser.Openid == "" {
|
||
return
|
||
}
|
||
|
||
resultText := "已通过"
|
||
if appointment.Status == 2 {
|
||
resultText = "已拒绝"
|
||
}
|
||
|
||
// 解析 JSON 字段
|
||
var employeeInfo map[string]interface{}
|
||
json.Unmarshal([]byte(appointment.EmployeeInfo), &employeeInfo)
|
||
var visitorInfo map[string]interface{}
|
||
json.Unmarshal([]byte(appointment.VisitorInfo), &visitorInfo)
|
||
company, _ := visitorInfo["company"].(string)
|
||
employeeName, _ := employeeInfo["name"].(string)
|
||
|
||
var areasInfo []string
|
||
json.Unmarshal([]byte(appointment.AreasInfo), &areasInfo)
|
||
areaText := strings.Join(areasInfo, "、")
|
||
|
||
// 格式化时间(微信 thing 类型字段限 20 字)
|
||
timeStr := ""
|
||
if !appointment.VisitStartTime.IsZero() {
|
||
loc, _ := time.LoadLocation("Asia/Shanghai")
|
||
timeStr = appointment.VisitStartTime.In(loc).Format("01-02 15:04") + "-" + appointment.VisitEndTime.In(loc).Format("15:04")
|
||
}
|
||
|
||
// TMPL_RESULT: 访客申请结果通知(thing 字段限 20 字)
|
||
trunc := func(s string) string {
|
||
runes := []rune(s)
|
||
if len(runes) > 20 {
|
||
return string(runes[:20])
|
||
}
|
||
return s
|
||
}
|
||
tmplID := templates.TMPL_RESULT
|
||
if h.wxSend != nil {
|
||
h.wxSend(creatorUser.Openid, tmplID, map[string]string{
|
||
"thing1": trunc(resultText),
|
||
"thing2": trunc(company),
|
||
"thing3": trunc(timeStr),
|
||
"thing8": trunc(employeeName),
|
||
"thing9": trunc(areaText),
|
||
}, fmt.Sprintf("/subpackages/appointment/appointment-detail?id=%d", appointment.ID))
|
||
}
|
||
}()
|
||
}
|
||
|
||
// sendDingTalkAfterApproval 审批通过后发送钉钉消息
|
||
func (h *Handler) sendDingTalkAfterApproval(taskID uint, comment string) {
|
||
if h.dingTalk == nil {
|
||
return
|
||
}
|
||
|
||
var task model.Task
|
||
if err := h.db.Preload("Instance").First(&task, taskID).Error; err != nil {
|
||
return
|
||
}
|
||
if task.Instance == nil || task.Instance.BusinessType != "appointment" || task.Instance.Status != 0 {
|
||
return
|
||
}
|
||
|
||
logrus.WithFields(logrus.Fields{
|
||
"task_id": taskID,
|
||
"instance_id": task.InstanceID,
|
||
}).Info("钉钉:审批通过,开始发送通知")
|
||
|
||
go func() {
|
||
// 获取审批人信息
|
||
var approverUser model.User
|
||
if task.AssigneeID == nil {
|
||
return
|
||
}
|
||
if err := h.db.First(&approverUser, *task.AssigneeID).Error; err != nil {
|
||
logrus.WithError(err).WithField("task_id", taskID).Warn("钉钉:查询审批人信息失败")
|
||
return
|
||
}
|
||
|
||
// 获取预约信息
|
||
var appointment model.Appointment
|
||
if err := h.db.First(&appointment, task.Instance.BusinessID).Error; err != nil {
|
||
return
|
||
}
|
||
|
||
var visitorInfo struct {
|
||
Name string `json:"name"`
|
||
Company string `json:"company"`
|
||
}
|
||
json.Unmarshal([]byte(appointment.VisitorInfo), &visitorInfo)
|
||
visitTime := ""
|
||
if !appointment.VisitStartTime.IsZero() {
|
||
visitTime = dingtalk.FormatCSTTime(appointment.VisitStartTime, appointment.VisitEndTime)
|
||
}
|
||
|
||
// 1. 通知被取消的同节点审批人(同一节点非会签模式,其他人审批后当前节点其他人被取消)
|
||
var cancelledTasks []model.Task
|
||
if err := h.db.Where("instance_id = ? AND node_id = ? AND status = 3 AND id != ?",
|
||
task.InstanceID, task.NodeID, task.ID).Find(&cancelledTasks).Error; err == nil && len(cancelledTasks) > 0 {
|
||
var userIDs []string
|
||
for _, ct := range cancelledTasks {
|
||
if ct.AssigneeID != nil {
|
||
var ud model.UserDepartment
|
||
if err := h.db.Where("user_id = ?", *ct.AssigneeID).First(&ud).Error; err == nil && ud.DingTalkUserID != "" {
|
||
userIDs = append(userIDs, ud.DingTalkUserID)
|
||
} else if err != nil {
|
||
logrus.WithError(err).WithField("user_id", *ct.AssigneeID).Warn("钉钉:查询同节点取消人 UserDepartment 失败")
|
||
} else {
|
||
logrus.WithField("user_id", *ct.AssigneeID).Warn("钉钉:同节点取消人 DingTalkUserID 为空")
|
||
}
|
||
}
|
||
}
|
||
if len(userIDs) > 0 {
|
||
title, markdown := dingtalk.BuildSameNodeCancelledMsg(approverUser.RealName, comment, visitTime)
|
||
logrus.WithFields(logrus.Fields{
|
||
"user_ids": userIDs,
|
||
"title": title,
|
||
}).Info("钉钉:发送同节点取消通知")
|
||
if err := h.dingTalk.BatchSendRobotMessage(userIDs, title, markdown); err != nil {
|
||
logrus.WithError(err).Error("钉钉发送同节点取消通知失败")
|
||
}
|
||
} else {
|
||
logrus.WithField("cancelled_count", len(cancelledTasks)).Info("钉钉:同节点已取消任务无有效钉钉ID")
|
||
}
|
||
} else {
|
||
logrus.WithField("has_cancelled", len(cancelledTasks) > 0).Info("钉钉:无同节点取消任务")
|
||
}
|
||
|
||
// 2. 通知下一个节点审批人
|
||
var pendingTasks []model.Task
|
||
if err := h.db.Where("instance_id = ? AND status = 0", task.InstanceID).Find(&pendingTasks).Error; err == nil && len(pendingTasks) > 0 {
|
||
var userIDs []string
|
||
for _, pt := range pendingTasks {
|
||
if pt.AssigneeID != nil {
|
||
var ud model.UserDepartment
|
||
if err := h.db.Where("user_id = ?", *pt.AssigneeID).First(&ud).Error; err == nil && ud.DingTalkUserID != "" {
|
||
userIDs = append(userIDs, ud.DingTalkUserID)
|
||
} else if err != nil {
|
||
logrus.WithError(err).WithField("user_id", *pt.AssigneeID).Warn("钉钉:查询下一节点审批人 UserDepartment 失败")
|
||
} else {
|
||
logrus.WithField("user_id", *pt.AssigneeID).Warn("钉钉:下一节点审批人 DingTalkUserID 为空")
|
||
}
|
||
}
|
||
}
|
||
if len(userIDs) > 0 {
|
||
msg := &dingtalk.AppointmentResultMessage{
|
||
ApproverName: approverUser.RealName,
|
||
Action: "通过",
|
||
Comment: comment,
|
||
AppointmentID: appointment.ID,
|
||
VisitTime: visitTime,
|
||
VisitorName: visitorInfo.Name,
|
||
}
|
||
title, markdown := dingtalk.BuildApproveNotificationMsg(msg)
|
||
logrus.WithFields(logrus.Fields{
|
||
"user_ids": userIDs,
|
||
"title": title,
|
||
}).Info("钉钉:发送下一节点审批人通知")
|
||
if err := h.dingTalk.BatchSendRobotMessage(userIDs, title, markdown); err != nil {
|
||
logrus.WithError(err).Error("钉钉发送下一节点通知失败")
|
||
}
|
||
} else {
|
||
logrus.WithField("pending_count", len(pendingTasks)).Info("钉钉:下一节点无有效钉钉ID的审批人")
|
||
}
|
||
} else {
|
||
logrus.WithField("has_pending", len(pendingTasks) > 0).Info("钉钉:无下一节点待审批任务")
|
||
}
|
||
}()
|
||
}
|
||
|
||
// sendDingTalkAfterRejection 审批拒绝后发送钉钉消息
|
||
func (h *Handler) sendDingTalkAfterRejection(taskID uint, comment string) {
|
||
if h.dingTalk == nil {
|
||
return
|
||
}
|
||
|
||
var task model.Task
|
||
if err := h.db.Preload("Instance").First(&task, taskID).Error; err != nil {
|
||
return
|
||
}
|
||
if task.Instance == nil || task.Instance.BusinessType != "appointment" {
|
||
return
|
||
}
|
||
|
||
logrus.WithFields(logrus.Fields{
|
||
"task_id": taskID,
|
||
"instance_id": task.InstanceID,
|
||
}).Info("钉钉:审批拒绝,开始发送通知")
|
||
|
||
go func() {
|
||
// 获取拒绝人信息
|
||
var rejector model.User
|
||
if task.AssigneeID == nil {
|
||
return
|
||
}
|
||
if err := h.db.First(&rejector, *task.AssigneeID).Error; err != nil {
|
||
logrus.WithError(err).WithField("task_id", taskID).Warn("钉钉:查询拒绝人信息失败")
|
||
return
|
||
}
|
||
|
||
// 获取预约信息
|
||
var appointment model.Appointment
|
||
if err := h.db.First(&appointment, task.Instance.BusinessID).Error; err != nil {
|
||
return
|
||
}
|
||
|
||
var visitorInfo struct {
|
||
Name string `json:"name"`
|
||
Company string `json:"company"`
|
||
}
|
||
json.Unmarshal([]byte(appointment.VisitorInfo), &visitorInfo)
|
||
visitTime := ""
|
||
if !appointment.VisitStartTime.IsZero() {
|
||
visitTime = dingtalk.FormatCSTTime(appointment.VisitStartTime, appointment.VisitEndTime)
|
||
}
|
||
|
||
// 查找本实例中其他待审批任务,通知其审批人流程已终止
|
||
var otherPendingTasks []model.Task
|
||
if err := h.db.Where("instance_id = ? AND status = 0 AND id != ?", task.InstanceID, task.ID).Find(&otherPendingTasks).Error; err != nil {
|
||
return
|
||
}
|
||
|
||
var userIDs []string
|
||
for _, pt := range otherPendingTasks {
|
||
if pt.AssigneeID != nil {
|
||
var ud model.UserDepartment
|
||
if err := h.db.Where("user_id = ?", *pt.AssigneeID).First(&ud).Error; err == nil && ud.DingTalkUserID != "" {
|
||
userIDs = append(userIDs, ud.DingTalkUserID)
|
||
} else if err != nil {
|
||
logrus.WithError(err).WithField("user_id", *pt.AssigneeID).Warn("钉钉:查询其他待审批人 UserDepartment 失败")
|
||
} else {
|
||
logrus.WithField("user_id", *pt.AssigneeID).Warn("钉钉:其他待审批人 DingTalkUserID 为空")
|
||
}
|
||
}
|
||
}
|
||
|
||
if len(userIDs) > 0 {
|
||
msg := &dingtalk.AppointmentResultMessage{
|
||
ApproverName: rejector.RealName,
|
||
Action: "拒绝",
|
||
Comment: comment,
|
||
AppointmentID: appointment.ID,
|
||
VisitTime: visitTime,
|
||
VisitorName: visitorInfo.Name,
|
||
}
|
||
title, markdown := dingtalk.BuildRejectNotificationMsg(msg)
|
||
logrus.WithFields(logrus.Fields{
|
||
"user_ids": userIDs,
|
||
"title": title,
|
||
}).Info("钉钉:发送拒绝通知给其他待审批人")
|
||
if err := h.dingTalk.BatchSendRobotMessage(userIDs, title, markdown); err != nil {
|
||
logrus.WithError(err).Error("钉钉发送拒绝通知失败")
|
||
}
|
||
} else {
|
||
logrus.WithField("other_pending_count", len(otherPendingTasks)).Info("钉钉:拒绝后无其他待审批人需通知")
|
||
}
|
||
}()
|
||
}
|