Browse Source

feature(督办): 1.协办人取消星号
2.督办负责人填写进展情况不显示上传附件按钮,且没有提交
3.审批流评价:由督办人审批,选择继续进行或者结束督办。选择结束后由监办人在进行审批。
4.督办负责人、协办人、监办人等没有提醒。接收按钮比较内置,看不到操作。
5.督办内容在详情页面看不到全部信息
6.督办权限,郎总看不到全部督办(抄送功能,且新加左侧分类条目)
7.添加到期提醒规则为手动添加,提醒日期,不限上限

likai 2 years ago
parent
commit
a51320e84d

+ 6 - 0
opms_parent/app/dao/plat/internal/plat_task.go

@@ -43,6 +43,8 @@ type platTaskColumns struct {
 	TaskLabel        string // 任务标签,号拼接
 	SupervisorUserId string // 督办人ID
 	WatchUserId      string // 监办人ID
+	CopyUserId       string // 抄送人ID
+	ReminderRule     string // 提醒规则(参考cron的规则)
 	TargetId         string // 关联对象ID
 	TargetType       string // 关联对象类型(10客户,20项目,30合同,40回款)
 	TargetName       string // 关联对象
@@ -87,6 +89,8 @@ var (
 			TaskLabel:        "task_label",
 			SupervisorUserId: "supervisor_user_id",
 			WatchUserId:      "watch_user_id",
+			CopyUserId:       "copy_user_id",
+			ReminderRule:     "reminder_rule",
 			TargetId:         "target_id",
 			TargetType:       "target_type",
 			TargetName:       "target_name",
@@ -133,6 +137,8 @@ func NewPlatTaskDao(tenant string) PlatTaskDao {
 			TaskLabel:        "task_label",
 			SupervisorUserId: "supervisor_user_id",
 			WatchUserId:      "watch_user_id",
+			CopyUserId:       "copy_user_id",
+			ReminderRule:     "reminder_rule",
 			TargetId:         "target_id",
 			TargetType:       "target_type",
 			TargetName:       "target_name",

+ 2 - 0
opms_parent/app/model/plat/internal/plat_task.go

@@ -24,6 +24,8 @@ type PlatTask struct {
 	TaskLabel        string      `orm:"task_label"         json:"taskLabel"`        // 任务标签,号拼接
 	SupervisorUserId int         `orm:"supervisor_user_id" json:"supervisorUserId"` // 督办人ID
 	WatchUserId      int         `orm:"watch_user_id"      json:"watchUserId"`      // 监办人ID
+	CopyUserId       string      `orm:"copy_user_id"       json:"copyUserId"`       // 抄送人ID
+	ReminderRule     string      `orm:"reminder_rule"      json:"reminderRule"`     // 提醒规则(参考cron的规则)
 	TargetId         int         `orm:"target_id"          json:"targetId"`         // 关联对象ID
 	TargetType       string      `orm:"target_type"        json:"targetType"`       // 关联对象类型(10客户,20项目,30合同,40回款)
 	TargetName       string      `orm:"target_name"        json:"targetName"`       // 关联对象

+ 3 - 0
opms_parent/app/model/plat/plat_task.go

@@ -51,6 +51,8 @@ type AddPlatTaskReq struct {
 	WatchUserId      int         `orm:"watch_user_id"      json:"watchUserId"`                                            // 监办人
 	MainUserId       int         `orm:"main_user_id"       json:"mainUserId"    v:"required|min:1#负责人ID不能为空|负责人ID不能为空"`   // 负责人ID
 	OwnerUserId      string      `orm:"owner_user_id"      json:"ownerUserId"`                                            // 协办人ID
+	CopyUserId       string      `orm:"copy_user_id"       json:"copyUserId"`                                             // 抄送人ID
+	ReminderRule     string      `orm:"reminder_rule"      json:"reminderRule"`                                           // 提醒规则(参考cron的规则)
 	TaskLabel        string      `orm:"task_label"         json:"taskLabel"`                                              // 任务标签,号拼接
 	TargetId         int         `orm:"target_id"          json:"targetId"`                                               // 关联对象ID
 	TargetType       string      `orm:"target_type"        json:"targetType"`                                             // 关联对象类型(10客户,20项目,30合同,40回款)
@@ -91,4 +93,5 @@ type HandleReq struct {
 	HandleStatus string              `json:"handleStatus" `                             // 处理结果(10接收20提交30审批通过40审批退回)
 	HandleDesc   string              `json:"handleDesc"`
 	ProgressList []*PlatTaskProgress `json:"progressList"`
+	IsComplete   string              `json:"isComplete"` // 是否完成 10完成;其他 继续进行
 }

+ 37 - 11
opms_parent/app/service/plat/plat_task.go

@@ -44,6 +44,7 @@ func NewTaskService(ctx context.Context) (svc *taskService, err error) {
 
 // GetList 任务信息列表
 func (s *taskService) GetList(req *model.SearchPlatTaskReq) (total int, TaskList []*model.PlatTaskEx, err error) {
+	orderBy := "plat_task.created_time DESC"
 	TaskModel := s.Dao.LeftJoin("plat_task_handle", "plat_task_handle.task_id=plat_task.id")
 	if req.TaskId != "" {
 		TaskModel = TaskModel.Where("plat_task.id", req.TaskId)
@@ -78,12 +79,15 @@ func (s *taskService) GetList(req *model.SearchPlatTaskReq) (total int, TaskList
 	if req.IsMain == "1" {
 		TaskModel = TaskModel.Where("plat_task.main_user_id", s.GetCxtUserId())
 	}
-	if req.OperateType == "1" {
+	if req.OperateType == "1" { // 我的待办
+		orderBy = "plat_task.task_status ASC, plat_task.created_time DESC"
 		TaskModel = TaskModel.Where(fmt.Sprintf("plat_task_handle.task_status='10' AND (plat_task_handle.main_user_id=%v OR FIND_IN_SET(%v, plat_task_handle.owner_user_id))", s.GetCxtUserId(), s.GetCxtUserId()))
-	} else if req.OperateType == "2" {
+	} else if req.OperateType == "2" { // 我发起的
 		TaskModel = TaskModel.Where("plat_task.created_by", s.GetCxtUserId())
-	} else if req.OperateType == "3" {
+	} else if req.OperateType == "3" { // 我处理的
 		TaskModel = TaskModel.Where(fmt.Sprintf("plat_task_handle.task_status='20' AND plat_task_handle.handle_user_id=%v", s.GetCxtUserId()))
+	} else if req.OperateType == "4" { // 抄送我的
+		TaskModel = TaskModel.Where(fmt.Sprintf("FIND_IN_SET(%v, plat_task.copy_user_id)", s.GetCxtUserId()))
 	}
 	TaskModel = TaskModel.Group("plat_task.id")
 
@@ -94,7 +98,7 @@ func (s *taskService) GetList(req *model.SearchPlatTaskReq) (total int, TaskList
 		return
 	}
 
-	err = TaskModel.Page(req.GetPage()).Order("plat_task.created_time DESC").Fields("plat_task.*,plat_task_handle.step").Scan(&TaskList)
+	err = TaskModel.Page(req.GetPage()).Order(orderBy).Fields("plat_task.*,plat_task_handle.step").Scan(&TaskList)
 	return
 }
 
@@ -294,6 +298,8 @@ func (s *taskService) AddTaskApproval(flow *workflowModel.PlatWorkflow, msg *mes
 			if err != nil {
 				return err
 			}
+			// 发送消息通知
+			go taskNotifyMessage(task.MainUserId, task.OwnerUserId, fmt.Sprintf("%v督办任务需要处理", task.TaskTitle))
 			// 流程日志
 			err = CreateTaskLog(s, nil, task.Id, s.GetCxtUserId(), s.GetCxtUserName(), "督办审批", "通过", "")
 		} else if msg.Result == "refuse" {
@@ -442,7 +448,13 @@ func (s *taskService) Handle(req *model.HandleReq) (err error) {
 			logNodeName = "审批"
 			if req.HandleStatus == "30" {
 				logDesc = s.GetCxtUserName() + "审批通过"
-				nextHandle = createNextTaskHandel(s, task, 40)
+				if req.IsComplete == "10" {
+					// 任务完成
+					nextHandle = createNextTaskHandel(s, task, 40)
+				} else {
+					// 任务未完成,继续执行
+					nextHandle = createNextTaskHandel(s, task, 20)
+				}
 			} else if req.HandleStatus == "40" {
 				logDesc = s.GetCxtUserName() + "审批退回"
 				nextHandle = createNextTaskHandel(s, task, 20)
@@ -457,9 +469,15 @@ func (s *taskService) Handle(req *model.HandleReq) (err error) {
 			if req.HandleStatus == "30" {
 				// 监办人评价,审批通过,任务结束
 				logDesc = s.GetCxtUserName() + "审批通过"
-				nextHandle = nil
-				taskData["actual_close_date"] = now.Format("Y-m-d H:i:s")
-				taskData["task_status"] = "30"
+				if req.IsComplete == "10" {
+					// 任务完成
+					nextHandle = nil
+					taskData["actual_close_date"] = now.Format("Y-m-d H:i:s")
+					taskData["task_status"] = "30"
+				} else {
+					// 任务未完成,继续执行
+					nextHandle = createNextTaskHandel(s, task, 20)
+				}
 			} else if req.HandleStatus == "40" {
 				logDesc = s.GetCxtUserName() + "审批退回"
 				nextHandle = createNextTaskHandel(s, task, 20)
@@ -486,6 +504,12 @@ func (s *taskService) Handle(req *model.HandleReq) (err error) {
 			if err != nil {
 				return err
 			}
+			// 只有督办、监办人需要发送提醒
+			if req.Step == 20 {
+				go taskNotifyMessage(task.SupervisorUserId, "", fmt.Sprintf("%v督办任务需要处理", task.TaskTitle))
+			} else if req.Step == 30 && req.HandleStatus == "30" {
+				go taskNotifyMessage(task.WatchUserId, "", fmt.Sprintf("%v督办任务需要处理", task.TaskTitle))
+			}
 		}
 	}
 
@@ -711,9 +735,11 @@ func (s *taskService) saveProgressList(req *model.HandleReq, now *gtime.Time) (e
 		return err
 	}
 	// 2 保存新的数据
-	_, err = s.Dao.DB.Save("plat_task_progress", req.ProgressList)
-	if err != nil {
-		return err
+	if len(req.ProgressList) > 0 {
+		_, err = s.Dao.DB.Save("plat_task_progress", req.ProgressList)
+		if err != nil {
+			return err
+		}
 	}
 
 	return nil

+ 203 - 0
opms_parent/app/service/plat/plat_task_cron.go

@@ -0,0 +1,203 @@
+package plat
+
+import (
+	model "dashoo.cn/micro/app/model/plat"
+	"dashoo.cn/micro/app/service"
+	"database/sql"
+	"fmt"
+	"github.com/gogf/gf/frame/g"
+	"github.com/gogf/gf/os/glog"
+	"github.com/gogf/gf/os/gtime"
+	"github.com/gogf/gf/util/gconv"
+	"github.com/robfig/cron"
+	"strings"
+	"time"
+)
+
+// 初始化,创建每10分钟执行一次的定时任务
+func init() {
+	// 定时任务
+	c := cron.New()
+	spec := "1 0/10 * * * ?" // 每天10分钟执行一次
+
+	if err := c.AddJob(spec, taskCron{}); err != nil {
+		glog.Error(err)
+	}
+	c.Start()
+}
+
+// 督办任务定时任务定义
+type taskCron struct {
+}
+
+// Run 督办定时任务逻辑
+func (c taskCron) Run() {
+	tenant := g.Config().GetString("setting.task-cron-tenant")
+	if tenant == "" {
+		glog.Error("督办定时任务租户码未设置,请前往配置")
+		return
+	}
+	// 从配置中获取消息提醒设置
+	configs, err := g.DB(tenant).Model("sys_config").Where("config_key IN ('TaskOverdueBefore','TaskOverdueAfter')").FindAll()
+	if err != nil && err != sql.ErrNoRows {
+		glog.Error(err)
+		return
+	}
+	before := -1
+	after := -1
+	// 获取
+	for _, config := range configs {
+		if config["config_key"].String() == "TaskOverdueBefore" {
+			before = gconv.Int(config["config_value"].String())
+		} else if config["config_key"].String() == "TaskOverdueAfter" {
+			after = gconv.Int(config["config_value"].String())
+		}
+	}
+	// 校验
+	if before == -1 || after == -1 {
+		glog.Error("督办定时任务超期提醒参数未配置,请前往配置")
+		return
+	}
+	// 查询数据,启用定时任务
+	var tasks []*model.PlatTask
+	err = g.DB(tenant).Model("plat_task").Where("task_status='10' OR task_status='20'").Scan(&tasks)
+	if err != nil && err != sql.ErrNoRows {
+		glog.Error(err)
+		return
+	}
+	// 当前时间
+	now := gtime.Now()
+	// 生成提醒数据
+	for _, task := range tasks {
+		// 固定日期提醒
+		if task.ReminderRule != "" {
+			rules := strings.Split(task.ReminderRule, " ")
+			if len(rules) != 5 {
+				glog.Error(fmt.Sprintf("%v督办Id为%v提醒规则格式不正确", task.TaskTitle, task.Id))
+			} else {
+				if rules[3] == "*" { // 每天提醒
+					// 校验当前时间
+					remindTime := gtime.NewFromStr(fmt.Sprintf("%v %v:%v:%v", now.Format("Y-m-d"), rules[2], rules[1], rules[0]))
+					// 10分钟一次定时循环,两者相差在10分钟之内(纳秒转换1e9)
+					if (remindTime.Nanosecond()-now.Nanosecond())/(1*60*1e9) <= 10 {
+						taskNotifyMessage(task.MainUserId, task.OwnerUserId, task.TaskTitle+"督办需要处理,请前往执行")
+					}
+				} else if rules[3] == "?" { // 每周提醒
+					// 校验周选项是否匹配
+					weekDays := strings.Split(rules[4], ",")
+					isMatch := false
+					for _, day := range weekDays {
+						if day == transferWeekday(now.Weekday()) {
+							isMatch = true
+							break
+						}
+					}
+					if isMatch {
+						// 校验当前时间
+						remindTime := gtime.NewFromStr(fmt.Sprintf("%v %v:%v:%v", now.Format("Y-m-d"), rules[2], rules[1], rules[0]))
+						// 10分钟一次定时循环,两者相差在10分钟之内(纳秒转换1e9)
+						if (remindTime.Nanosecond()-now.Nanosecond())/(1*60*1e9) <= 10 {
+							taskNotifyMessage(task.MainUserId, task.OwnerUserId, task.TaskTitle+"督办需要处理,请前往执行")
+						}
+					}
+				} else { // 每月提醒
+					monthDays := strings.Split(rules[3], ",")
+					isMatch := false
+					for _, day := range monthDays {
+						if gconv.Int(day) == now.Day() {
+							isMatch = true
+							break
+						}
+					}
+					if isMatch {
+						// 校验当前时间
+						remindTime := gtime.NewFromStr(fmt.Sprintf("%v %v:%v:%v", now.Format("Y-m-d"), rules[2], rules[1], rules[0]))
+						// 10分钟一次定时循环,两者相差在10分钟之内(纳秒转换1e9)
+						if (remindTime.Nanosecond()-now.Nanosecond())/(1*60*1e9) <= 10 {
+							taskNotifyMessage(task.MainUserId, task.OwnerUserId, task.TaskTitle+"督办需要处理,请前往执行")
+						}
+					}
+				}
+			}
+		}
+		// 超期提醒,差10分(定时任务每10分钟执行一次)一天之时进行提醒
+		if task.TaskEndDate != nil {
+			// 超期前提醒
+			beforeDate := task.TaskEndDate.AddDate(0, 0, before)
+			if beforeDate.After(now) {
+				// 10分钟一次定时循环,两者相差在10分钟之内(纳秒转换1e9)
+				if (beforeDate.UnixNano()-now.UnixNano())/(1*60*1e9) <= 10 {
+					taskNotifyMessage(task.MainUserId, task.OwnerUserId, task.TaskTitle+"督办即将超期,请前往执行")
+				}
+			}
+			// 超期后提醒
+			afterDate := task.TaskEndDate.AddDate(0, 0, -after)
+			if now.After(afterDate) {
+				// 10分钟一次定时循环,两者相差在10分钟之内(纳秒转换1e9)
+				if (now.UnixNano()-afterDate.UnixNano())/(1*60*1e9) <= 10 {
+					taskNotifyMessage(task.MainUserId, task.OwnerUserId, task.TaskTitle+"督办已超期,请确认")
+				}
+			}
+		}
+	}
+}
+
+// 督办人任务的消息通知
+func taskNotifyMessage(mainId int, ownerIds, message string) {
+	// 协作人包含负责人的情况
+	ids := ownerIds
+	ownerIdArray := strings.Split(ownerIds, ",")
+	isCon := false
+	for _, id := range ownerIdArray {
+		if id == gconv.String(mainId) {
+			isCon = true
+			break
+		}
+	}
+	if !isCon {
+		if ids == "" {
+			ids = gconv.String(mainId)
+		} else {
+			ids += "," + gconv.String(mainId)
+		}
+	}
+	// 调用统一的消息通知方式
+	notifyMessage(ids, message)
+}
+
+// notifyMessage 发送消息通知
+func notifyMessage(ids, message string) {
+	msg := g.MapStrStr{
+		"msgTitle":    "督办任务提醒",
+		"msgContent":  fmt.Sprintf("<p>%v</p>", message),
+		"msgType":     "20",
+		"recvUserIds": ids,
+		"msgStatus":   "10",
+		"sendType":    "10",
+	}
+	if err := service.CreateSystemMessage(msg); err != nil {
+		glog.Error("消息提醒异常:", err)
+	}
+}
+
+// 英文周转换为中文周
+func transferWeekday(day time.Weekday) string {
+	switch day {
+	case time.Monday:
+		return "1"
+	case time.Tuesday:
+		return "2"
+	case time.Wednesday:
+		return "3"
+	case time.Thursday:
+		return "4"
+	case time.Friday:
+		return "5"
+	case time.Saturday:
+		return "6"
+	case time.Sunday:
+		return "7"
+	default:
+		return "-1"
+	}
+}

+ 1 - 0
opms_parent/config/config.toml

@@ -7,6 +7,7 @@
     srv-name = "dashoo.opms.parent-0.0.1"
     env = "dev"
     swagger = true
+    task-cron-tenant = "default"
 
 # 微服务注册中心配置
 [service_registry]

+ 1 - 0
opms_parent/go.mod

@@ -8,6 +8,7 @@ require (
 	github.com/360EntSecGroup-Skylar/excelize v1.4.1
 	github.com/gogf/gf v1.16.9
 	github.com/mozillazg/go-pinyin v0.19.0
+	github.com/robfig/cron v1.2.0
 	github.com/shopspring/decimal v1.3.1
 	github.com/smallnest/rpcx v1.8.0
 	github.com/stretchr/testify v1.8.0 // indirect

+ 2 - 0
opms_parent/go.sum

@@ -376,6 +376,8 @@ github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 h1:N/ElC8H3+5X
 github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
 github.com/rivo/uniseg v0.1.0 h1:+2KBaVoUmb9XzDsrx/Ct0W/EYOSFf/nWTauy++DprtY=
 github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
+github.com/robfig/cron v1.2.0 h1:ZjScXvvxeQ63Dbyxy76Fj3AT3Ut0aKsyd2/tl3DTMuQ=
+github.com/robfig/cron v1.2.0/go.mod h1:JGuDeoQd7Z6yL4zQhZ3OPEVHB7fL6Ka6skscFHfmt2k=
 github.com/rpcxio/libkv v0.5.1 h1:M0/QqwTcdXz7us0NB+2i8Kq5+wikTm7zZ4Hyb/jNgME=
 github.com/rpcxio/libkv v0.5.1/go.mod h1:zHGgtLr3cFhGtbalum0BrMPOjhFZFJXCKiws/25ewls=
 github.com/rpcxio/rpcx-consul v0.0.0-20220730062257-1ff0472e730f h1:2lAWNSEM9KGGnHlkNjLGjsWPCxHOCQJmRRD1iCFraE4=