Browse Source

feature(项目):
①增加附件管理页签,报价单可多次上传,不覆盖之前的报价单信息。
②项目跟进超期不提醒助理,不在邮件提醒,直接在系统内弹窗提醒产品线经理和销售。

ZZH-wl 2 năm trước cách đây
mục cha
commit
18dbef39cc

+ 15 - 4
opms_admin/app/handler/user.go

@@ -3,6 +3,8 @@ package handler
 import (
 	"context"
 	"dashoo.cn/opms_libary/myerrors"
+	"dashoo.cn/opms_libary/request"
+	"github.com/gogf/gf/util/gconv"
 	"strings"
 
 	"github.com/gogf/gf/util/gvalid"
@@ -198,17 +200,26 @@ func (h *UserHandler) SetStatus(ctx context.Context, req *model.SysUserStatusReq
 }
 
 // GetDataScope 获取某用户数据集合权限,返回Ids(用户Id列表),返回-1表示无角色,返回-2表示有全部集合权限
-func (h *RoleHandler) GetDataScope(ctx context.Context, nullParams interface{}, rsp *comm_def.CommonMsg) error {
+func (h *UserHandler) GetDataScope(ctx context.Context, req *comm_def.IdReq, rsp *comm_def.CommonMsg) error {
 	userService, err := service.NewUserService(ctx)
 	if err != nil {
 		return err
 	}
-	ids, err := userService.GetDataScope(userService.GetCxtUserId())
+	sysUserInfo, err := userService.GetUserInfoById(int(req.Id))
 	if err != nil {
 		return err
 	}
-	rsp.Data = ids
-	return err
+	userInfo := new(request.UserInfo)
+	if err = gconv.Struct(sysUserInfo, userInfo); err != nil {
+		return err
+	}
+	dataScope, err := userService.GetDataScope(userInfo)
+	if err != nil {
+		return err
+	}
+	rsp.Code = 200
+	rsp.Data = dataScope
+	return nil
 }
 
 // GetUserByDept 获取部门下所属用户

+ 1 - 0
opms_admin/app/model/sys_message.go

@@ -18,6 +18,7 @@ type SysMessage internal.SysMessage
 type SysMessageSearchReq struct {
 	MsgTitle string `json:"msgTitle"`
 	MsgType  string `json:"msgType"`
+	IsRead   string `json:"isRead"` // 是否已读(10否20是)
 	request.PageReq
 }
 

+ 6 - 3
opms_admin/app/service/sys_message.go

@@ -53,10 +53,13 @@ func (s *MessageService) GetListByUser(req *model.SysMessageSearchReq) (total in
 	m := s.Dao.As("msg").LeftJoin(dao.SysMessageLog.Table, "log", "msg.id=log.msg_id").Where("log.user_id", s.GetCxtUserId())
 	if req != nil {
 		if req.MsgTitle != "" {
-			m = m.WhereLike(s.Dao.C.MsgTitle, "%"+req.MsgTitle+"%")
+			m = m.WhereLike("msg."+s.Dao.C.MsgTitle, "%"+req.MsgTitle+"%")
 		}
 		if req.MsgType != "" {
-			m = m.Where(s.Dao.C.MsgType, req.MsgType)
+			m = m.Where("msg."+s.Dao.C.MsgType, req.MsgType)
+		}
+		if req.IsRead != "" {
+			m = m.Where("log.is_read", req.IsRead)
 		}
 	}
 	total, err = m.Count()
@@ -135,7 +138,7 @@ func (s *MessageService) Create(req *model.CreateSysMessageReq) (err error) {
 	return
 }
 
-//SendMail 发送邮件
+// SendMail 发送邮件
 func (s *MessageService) SendMail(req *model.SendMessageReq) (err error) {
 	data := new(model.SysMessage)
 	if err := gconv.Struct(req, data); err != nil {

+ 3 - 9
opms_admin/app/service/sys_user.go

@@ -63,20 +63,14 @@ func (s *UserService) Login(username, password string) (*request.UserInfo, error
 		return nil, err
 	}
 	userInfo.IsFirstLogin = sysUserInfo.IsFirstLogin == "10"
-	// 权限
-	userInfo.Roles, userInfo.Posts, userInfo.Groups, err = s.GetUserPermission(userInfo.Id)
-	if err != nil {
-		g.Log().Error(err)
-		return nil, myerrors.TipsError("获取用户权限失败")
-	}
+
 	// 数据权限
-	userInfo.DataScope, err = s.GetDataScope(userInfo.Id)
+	userInfo.DataScope, err = s.GetDataScope(userInfo)
 	if err != nil {
 		g.Log().Error(err)
 		return nil, myerrors.TipsError("获取用户数据权限失败")
 	}
-	userInfo.DataScope["roles"] = userInfo.Roles
-	userInfo.DataScope["posts"] = userInfo.Posts
+
 	// 更新允许登录错误次数和是否首次登录
 	if sysUserInfo.AllowErrorNum != 5 {
 		data := g.Map{

+ 10 - 7
opms_admin/app/service/sys_user_datascope.go

@@ -4,6 +4,7 @@ import (
 	"dashoo.cn/micro/app/dao"
 	"dashoo.cn/micro/app/model"
 	"dashoo.cn/opms_libary/myerrors"
+	"dashoo.cn/opms_libary/request"
 	"github.com/gogf/gf/container/gset"
 	"github.com/gogf/gf/frame/g"
 	"github.com/gogf/gf/util/gconv"
@@ -120,14 +121,14 @@ func (s *UserService) GetMaxRoleDataScopeByUser(userId int) ([]*model.SysRole, e
 }
 
 // GetDataScope 获取某用户数据集合权限,返回Ids(用户Id列表),返回-1表示无角色,返回-2表示有全部集合权限
-func (s *UserService) GetDataScope(userId int) (where g.Map, err error) {
+func (s *UserService) GetDataScope(userInfo *request.UserInfo) (dataScope g.Map, err error) {
 	userDao := s.Dao
-	userInfo, err := userDao.WherePri(userId).Where(dao.SysUser.C.Status, "10").FindOne()
+	userId := userInfo.Id
+	// 权限
+	userInfo.Roles, userInfo.Posts, userInfo.Groups, err = s.GetUserPermission(userInfo.Id)
 	if err != nil {
-		return nil, err
-	}
-	if userInfo == nil {
-		return nil, myerrors.TipsError("用户不存在")
+		g.Log().Error(err)
+		return nil, myerrors.TipsError("获取用户权限失败")
 	}
 
 	// 产品线数据权限
@@ -205,9 +206,11 @@ func (s *UserService) GetDataScope(userId int) (where g.Map, err error) {
 	if userIdArr.Size() == 0 {
 		userIdArr.Add(userId)
 	}
-	dataScope := g.Map{"userIds": userIdArr.Slice(), "cust_city_id": regionList, "product_line": productLineList}
+	dataScope = g.Map{"userIds": userIdArr.Slice(), "cust_city_id": regionList, "product_line": productLineList}
 	if isBigProject != "" {
 		dataScope["is_big"] = isBigProject
 	}
+	dataScope["roles"] = userInfo.Roles
+	dataScope["posts"] = userInfo.Posts
 	return dataScope, nil
 }

+ 1 - 0
opms_admin/main.go

@@ -62,6 +62,7 @@ var AuthExcludePaths = []string{
 	"/SystemMessage/Create",
 	"/Message/SendMail",
 	"/Dict/GetDictDataByType",
+	"/User/GetDataScope",
 }
 
 // 处理Auth

+ 14 - 1
opms_parent/app/handler/proj/business_file.go

@@ -29,7 +29,6 @@ func (p *BusinessFileHandler) GetList(ctx context.Context, req *projModel.Busine
 	return nil
 }
 
-// Create 添加产品信息
 func (p *BusinessFileHandler) Create(ctx context.Context, req *projModel.ProjBusinessFileReq, rsp *comm_def.CommonMsg) error {
 	// 参数校验
 	if err := gvalid.CheckStruct(ctx, req, nil); err != nil {
@@ -58,3 +57,17 @@ func (p *BusinessFileHandler) DeleteByIds(ctx context.Context, req *comm_def.Ids
 	err = businessService.DeleteByIds(req.Ids)
 	return err
 }
+
+// 下载钉钉附件
+func (h *BusinessFileHandler) DownloadDingTalkFile(ctx context.Context, req *comm_def.IdReq, rsp *comm_def.CommonMsg) error {
+	businessService, err := projSrv.NewBusinessFileService(ctx)
+	if err != nil {
+		return err
+	}
+	data, err := businessService.DownloadDingTalkFile(int(req.Id))
+	if err != nil {
+		return err
+	}
+	rsp.Data = data
+	return nil
+}

+ 17 - 0
opms_parent/app/service/base.go

@@ -300,6 +300,23 @@ func GetUsersByRoleCode(ctx context.Context, roleCode []string, pageSize ...int)
 	return res, nil
 }
 
+// 获取用户权限
+func GetUserDataScope(userId int) (g.Map, error) {
+	srv := micro_srv.InitMicroSrvClient("User", "micro_srv.auth")
+	defer srv.Close()
+	req := &comm_def.IdReq{Id: int64(userId)}
+	resp := &comm_def.CommonMsg{}
+	tenant := g.Config().GetString("micro_srv.tenant")
+	ctx := context.WithValue(context.TODO(), share.ReqMetaDataKey, map[string]string{"tenant": tenant})
+	err := srv.Call(ctx, "GetDataScope", req, resp)
+	if err != nil || resp.Data == nil {
+		g.Log().Error(err)
+		return nil, myerrors.MicroCallError("获取用户权限失败")
+	}
+
+	return resp.Data.(g.Map), nil
+}
+
 // 跟进发送邮件消息
 func GSendMail(msg g.MapStrStr) error {
 	srv := micro_srv.InitMicroSrvClient("Message", "micro_srv.auth")

+ 341 - 214
opms_parent/app/service/proj/business_cron.go

@@ -38,32 +38,41 @@ func businessFollowRun() {
 		return
 	}
 	// 从配置中获取消息提醒设置
-	configs, err := g.DB(tenant).Model("sys_config").Where("config_key IN ('SalesDirector','SalesAssociate')").FindAll()
+	configs, err := g.DB(tenant).Model("sys_config").Where("config_key IN ('SalesDirector','SalesAssociate', 'ProductLineManager')").FindAll()
 	if err != nil && err != sql.ErrNoRows {
 		glog.Error(err)
 		return
 	}
-	// 销售总监用户Id
-	salesDirector := []string{}
-	// 销售助理用户Id
-	salesAssociate := []string{}
+	// 销售总监用户Id,销售助理用户Id,产品线经理用户Id
+	salesDirector, salesAssociate, productLineManager := []string{}, []string{}, []string{}
+
 	for _, config := range configs {
 		if config["config_key"].String() == "SalesDirector" {
 			salesDirector = strings.Split(config["config_value"].String(), ",")
 		} else if config["config_key"].String() == "SalesAssociate" {
 			salesAssociate = strings.Split(config["config_value"].String(), ",")
+		} else if config["config_key"].String() == "ProductLineManager" {
+			productLineManager = strings.Split(config["config_value"].String(), ",")
 		}
 	}
 
-	businessFollowOverdueSalesAssociate(tenant, []string{"13"})
 	if len(salesDirector) > 0 {
 		go businessFollowOverdueSalesDirector(tenant, salesDirector)
 	}
 	if len(salesAssociate) > 0 {
-		go businessFollowOverdueSalesDirector(tenant, salesAssociate)
+		go businessFollowOverdueSalesAssociate(tenant, salesAssociate)
+	}
+	if len(productLineManager) > 0 {
+		businessFollowOverdueProductLineManager(tenant, productLineManager)
 	}
+
 	go businessFollowOverdueSalesEngineer(tenant)
+}
 
+type FollowOverdue struct {
+	SaleId   int64  `json:"saleId"`
+	Count    int64  `json:"count"`
+	BusNames string `json:"busNames"`
 }
 
 // 超期3天提醒销售总监
@@ -72,31 +81,35 @@ func businessFollowOverdueSalesDirector(tenant string, userIds []string) {
 	LastWeekDay := now.AddDate(0, 0, -10)
 	LastTwoWeekDay := now.AddDate(0, 0, -17)
 	LastMonthDay := now.AddDate(0, -1, -3)
+	businessA, businessB, businessC := new(FollowOverdue), new(FollowOverdue), new(FollowOverdue)
 
-	businessACount, err := projDao.NewProjBusinessDao(tenant).Where(projDao.ProjBusiness.C.NboType, StatusA).
-		WhereLTE(projDao.ProjBusiness.C.FinalFollowTime, LastWeekDay).Count()
+	err := projDao.NewProjBusinessDao(tenant).Where(projDao.ProjBusiness.C.NboType, StatusA).
+		WhereLTE(projDao.ProjBusiness.C.FinalFollowTime, LastWeekDay).
+		Fields("count(id) as count, group_concat(nbo_name) as busNames").Scan(&businessA)
 	if err != nil {
 		g.Log().Error("获取A类超期3天未跟进项目", err)
 	}
-	businessBCount, err := projDao.NewProjBusinessDao(tenant).Where(projDao.ProjBusiness.C.NboType, StatusB).
-		WhereLTE(projDao.ProjBusiness.C.FinalFollowTime, LastTwoWeekDay).Count()
+	err = projDao.NewProjBusinessDao(tenant).Where(projDao.ProjBusiness.C.NboType, StatusB).
+		WhereLTE(projDao.ProjBusiness.C.FinalFollowTime, LastTwoWeekDay).
+		Fields("count(id) as count, group_concat(nbo_name) as busNames").Scan(&businessB)
 	if err != nil {
 		g.Log().Error("获取B类超期3天未跟进项目", err)
 	}
-	businessCCount, err := projDao.NewProjBusinessDao(tenant).Where(projDao.ProjBusiness.C.NboType, StatusC).
-		WhereLTE(projDao.ProjBusiness.C.FinalFollowTime, LastMonthDay).Count()
+	err = projDao.NewProjBusinessDao(tenant).Where(projDao.ProjBusiness.C.NboType, StatusC).
+		WhereLTE(projDao.ProjBusiness.C.FinalFollowTime, LastMonthDay).
+		Fields("count(id) as count, group_concat(nbo_name) as busNames").Scan(&businessC)
 	if err != nil {
 		g.Log().Error("获取C类超期3天未跟进项目", err)
 	}
 	var msg string
-	if businessACount > 0 {
-		msg += fmt.Sprintf("您有超期3天未跟进A类项目%v个,", businessACount)
+	if businessA.Count > 0 {
+		msg += fmt.Sprintf("您有超期3天未跟进A类项目%v个,项目名称分别为 %v,", businessA.Count, businessA.BusNames)
 	}
-	if businessBCount > 0 {
-		msg += fmt.Sprintf("您有超期3天未跟进B类项目%v个,", businessBCount)
+	if businessB.Count > 0 {
+		msg += fmt.Sprintf("您有超期3天未跟进B类项目%v个,项目名称分别为 %v,", businessB.Count, businessB.BusNames)
 	}
-	if businessCCount > 0 {
-		msg += fmt.Sprintf("您有超期3天未跟进C类项目%v个,", businessCCount)
+	if businessC.Count > 0 {
+		msg += fmt.Sprintf("您有超期3天未跟进C类项目%v个,项目名称分别为 %v,", businessC.Count, businessC.BusNames)
 	}
 	if msg != "" {
 		businessNotifyMessage(userIds, gstr.TrimRightStr(msg, ","))
@@ -114,188 +127,193 @@ func businessFollowOverdueSalesAssociate(tenant string, userIds []string) {
 	LastTwoWeekTridDay := now.AddDate(0, 0, -17)
 	LastMonthTridDay := now.AddDate(0, -1, -3)
 
-	businessATridCount, err := projDao.NewProjBusinessDao(tenant).Where(projDao.ProjBusiness.C.NboType, StatusA).
-		WhereLTE(projDao.ProjBusiness.C.FinalFollowTime, LastWeekDay).Count()
+	businessATrid, businessBTrid, businessCTrid := new(FollowOverdue), new(FollowOverdue), new(FollowOverdue)
+	businessA, businessB, businessC := new(FollowOverdue), new(FollowOverdue), new(FollowOverdue)
+
+	err := projDao.NewProjBusinessDao(tenant).Where(projDao.ProjBusiness.C.NboType, StatusA).
+		WhereLTE(projDao.ProjBusiness.C.FinalFollowTime, LastWeekDay).
+		Fields("count(id) as count, group_concat(nbo_name) as busNames").Scan(&businessATrid)
 	if err != nil {
 		g.Log().Error("获取A类超期3天未跟进项目", err)
 	}
-	businessBTridCount, err := projDao.NewProjBusinessDao(tenant).Where(projDao.ProjBusiness.C.NboType, StatusB).
-		WhereLTE(projDao.ProjBusiness.C.FinalFollowTime, LastTwoWeekDay).Count()
+	err = projDao.NewProjBusinessDao(tenant).Where(projDao.ProjBusiness.C.NboType, StatusB).
+		WhereLTE(projDao.ProjBusiness.C.FinalFollowTime, LastTwoWeekDay).
+		Fields("count(id) as count, group_concat(nbo_name) as busNames").Scan(businessBTrid)
 	if err != nil {
 		g.Log().Error("获取B类超期3天未跟进项目", err)
 	}
-	businessCTridCount, err := projDao.NewProjBusinessDao(tenant).Where(projDao.ProjBusiness.C.NboType, StatusC).
-		WhereLTE(projDao.ProjBusiness.C.FinalFollowTime, LastMonthDay).Count()
+	err = projDao.NewProjBusinessDao(tenant).Where(projDao.ProjBusiness.C.NboType, StatusC).
+		WhereLTE(projDao.ProjBusiness.C.FinalFollowTime, LastMonthDay).
+		Fields("count(id) as count, group_concat(nbo_name) as busNames").Scan(businessCTrid)
 	if err != nil {
 		g.Log().Error("获取C类超期3天未跟进项目", err)
 	}
 
-	businessACount, err := projDao.NewProjBusinessDao(tenant).Where(projDao.ProjBusiness.C.NboType, StatusA).
+	err = projDao.NewProjBusinessDao(tenant).Where(projDao.ProjBusiness.C.NboType, StatusA).
 		WhereLTE(projDao.ProjBusiness.C.FinalFollowTime, LastWeekDay).WhereGTE(projDao.ProjBusiness.C.FinalFollowTime, LastWeekTridDay).
-		Count()
+		Fields("count(id) as count, group_concat(nbo_name) as busNames").Scan(businessA)
 	if err != nil {
 		g.Log().Error("获取A类超期1天未跟进项目", err)
 	}
-	businessBCount, err := projDao.NewProjBusinessDao(tenant).Where(projDao.ProjBusiness.C.NboType, StatusB).
+	err = projDao.NewProjBusinessDao(tenant).Where(projDao.ProjBusiness.C.NboType, StatusB).
 		WhereLTE(projDao.ProjBusiness.C.FinalFollowTime, LastTwoWeekDay).WhereGTE(projDao.ProjBusiness.C.FinalFollowTime, LastTwoWeekTridDay).
-		Count()
+		Fields("count(id) as count, group_concat(nbo_name) as busNames").Scan(businessB)
 	if err != nil {
 		g.Log().Error("获取B类超期1天未跟进项目", err)
 	}
-	businessCCount, err := projDao.NewProjBusinessDao(tenant).Where(projDao.ProjBusiness.C.NboType, StatusC).
+	err = projDao.NewProjBusinessDao(tenant).Where(projDao.ProjBusiness.C.NboType, StatusC).
 		WhereLTE(projDao.ProjBusiness.C.FinalFollowTime, LastMonthDay).WhereGTE(projDao.ProjBusiness.C.FinalFollowTime, LastMonthTridDay).
-		Count()
+		Fields("count(id) as count, group_concat(nbo_name) as busNames").Scan(businessC)
 	if err != nil {
 		g.Log().Error("获取C类超期1天未跟进项目", err)
 	}
 	var msg string
-	if businessATridCount > 0 {
-		msg += fmt.Sprintf("您有超期3天未跟进A类项目%v个,", businessATridCount)
+	if businessATrid.Count > 0 {
+		msg += fmt.Sprintf("您有超期3天未跟进A类项目%v个,项目名称分别为 %v,", businessATrid.Count, businessATrid.BusNames)
 	}
-	if businessBTridCount > 0 {
-		msg += fmt.Sprintf("您有超期3天未跟进B类项目%v个,", businessBTridCount)
+	if businessBTrid.Count > 0 {
+		msg += fmt.Sprintf("您有超期3天未跟进B类项目%v个,项目名称分别为 %v,", businessBTrid.Count, businessBTrid.BusNames)
 	}
-	if businessCTridCount > 0 {
-		msg += fmt.Sprintf("您有超期3天未跟进C类项目%v个,", businessCTridCount)
+	if businessCTrid.Count > 0 {
+		msg += fmt.Sprintf("您有超期3天未跟进C类项目%v个,项目名称分别为 %v,", businessCTrid.Count, businessCTrid.BusNames)
 	}
-	if businessACount > 0 {
-		msg += fmt.Sprintf("您有超期1天未跟进A类项目%v个,", businessACount)
+	if businessA.Count > 0 {
+		msg += fmt.Sprintf("您有超期1天未跟进A类项目%v个,项目名称分别为 %v,", businessA.Count, businessA.BusNames)
 	}
-	if businessBCount > 0 {
-		msg += fmt.Sprintf("您有超期1天未跟进B类项目%v个,", businessBCount)
+	if businessB.Count > 0 {
+		msg += fmt.Sprintf("您有超期1天未跟进B类项目%v个,项目名称分别为 %v,", businessB.Count, businessB.BusNames)
 	}
-	if businessCCount > 0 {
-		msg += fmt.Sprintf("您有超期1天未跟进C类项目%v个,", businessCCount)
+	if businessC.Count > 0 {
+		msg += fmt.Sprintf("您有超期1天未跟进C类项目%v个,项目名称分别为 %v,", businessC.Count, businessC.BusNames)
 	}
 	if msg != "" {
 		businessNotifyMessage(userIds, gstr.TrimRightStr(msg, ","))
 	}
 }
 
-// 项目每周进行一次超期三天未跟进统计表,每周给销售助理发一次邮件
-func businesspeopleUpReminder() {
+// 在到达超期时间前3天进行提醒、当天进行提醒,这两次提醒只提醒销售工程师, 超期1天后提醒
+func businessFollowOverdueProductLineManager(tenant string, userIds []string) {
+	now := gtime.Now().StartOfDay()
+	// 超期一天
+	LastWeekOneDay := now.AddDate(0, 0, -8)
+	LastTwoWeekOneDay := now.AddDate(0, 0, -15)
+	LastMonthOneDay := now.AddDate(0, -1, -1)
+	// 超期当天
+	LastWeekCurrentDay := now.AddDate(0, 0, -7)
+	LastTwoWeekCurrentDay := now.AddDate(0, 0, -14)
+	LastMonthCurrentDay := now.AddDate(0, -1, 0)
+	//  在到达超期时间前3天进行提醒
+	LastWeekBeforeTridDay := now.AddDate(0, 0, -4)
+	LastTwoWeekBeforeTridDay := now.AddDate(0, 0, -11)
+	LastMonthBeforeTridDay := now.AddDate(0, -1, 3)
 
-	tenant := g.Config().GetString("micro_srv.tenant")
-	if tenant == "" {
-		glog.Error("定时任务租户码未设置,请前往配置")
-		return
-	}
-	// 从配置中获取消息提醒设置
-	configs, err := g.DB(tenant).Model("sys_config").Where("config_key IN ('SalesDirector','SalesAssociate')").FindAll()
-	if err != nil && err != sql.ErrNoRows {
-		glog.Error(err)
-		return
-	}
-	// 销售助理用户Id
-	salesAssociate := []string{}
-	for _, config := range configs {
-		if config["config_key"].String() == "SalesAssociate" {
-			salesAssociate = strings.Split(config["config_value"].String(), ",")
+	for _, userId := range userIds {
+		LastWeekOne, LastTwoWeekOne, LastMonthOne := new(FollowOverdue), new(FollowOverdue), new(FollowOverdue)
+		LastWeekCurrent, LastTwoWeekCurrent, LastMonthCurrent := new(FollowOverdue), new(FollowOverdue), new(FollowOverdue)
+		LastWeekBeforeTrid, LastTwoWeekBeforeTrid, LastMonthBeforeTrid := new(FollowOverdue), new(FollowOverdue), new(FollowOverdue)
+
+		dataScope, err := service.GetUserDataScope(gconv.Int(userId))
+		if err != nil {
+			g.Log().Error(fmt.Sprintf("定时任务项目超期提醒用户数据权限查询失败:userId-%v,", userId), err)
+			continue
 		}
-	}
-	var list []*model.ProjBusinessRes
-	var listA []*model.ProjBusinessRes
-	var listB []*model.ProjBusinessRes
-	var listC []*model.ProjBusinessRes
-	now := gtime.Now().StartOfDay()
-	LastWeekDay := now.AddDate(0, 0, -8)
-	LastTwoWeekDay := now.AddDate(0, 0, -15)
-	LastMonthDay := now.AddDate(0, -1, -1)
-	err = projDao.NewProjBusinessDao(tenant).Where(projDao.ProjBusiness.C.NboType, StatusA).
-		WhereLTE(projDao.ProjBusiness.C.FinalFollowTime, LastWeekDay).OrderDesc("id").Scan(&listA)
-	if err != nil {
-		g.Log().Error("获取A类超期3天未跟进项目", err)
-	}
-	if len(listA) > 0 {
-		list = append(list, listA...)
-	}
-	err = projDao.NewProjBusinessDao(tenant).Where(projDao.ProjBusiness.C.NboType, StatusB).
-		WhereLTE(projDao.ProjBusiness.C.FinalFollowTime, LastTwoWeekDay).OrderDesc("id").Scan(&listB)
-	if err != nil {
-		g.Log().Error("获取B类超期3天未跟进项目", err)
-	}
-	if len(listB) > 0 {
-		list = append(list, listB...)
-	}
-	err = projDao.NewProjBusinessDao(tenant).Where(projDao.ProjBusiness.C.NboType, StatusC).
-		WhereLTE(projDao.ProjBusiness.C.FinalFollowTime, LastMonthDay).OrderDesc("id").Scan(&listC)
-	if err != nil {
-		g.Log().Error("获取C类超期3天未跟进项目", err)
-	}
-	if len(listC) > 0 {
-		list = append(list, listC...)
-	}
-	if err != nil {
-		g.Log().Error("获取未跟进项目", err)
-	}
-	ctx := context.WithValue(context.TODO(), share.ReqMetaDataKey, map[string]string{"tenant": tenant})
-	rsp, err := service.GetDictDataByType(ctx, "proj_nbo_type")
-	if err != nil {
-		g.Log().Error("项目类别数据字典", err)
-	}
-	if err != nil {
-		g.Log().Error("获取未跟进项目", err)
-		return
-	}
-	var msg string
-	if len(list) > 0 {
-		msg += fmt.Sprintf("您有超期3天未跟进项目%v个,请登录系统查看", len(list))
-	}
-	f := excelize.NewFile()
-	f.MergeCell("Sheet1", "A1", "D1")
-	style, _ := f.NewStyle(`{"alignment":{"horizontal":"center"}}`)
-	f.SetCellValue("Sheet1", "A1", "超期三天未跟进项目")
-	f.SetCellStyle("sheet1", "A1", "D1", style)
-	f.SetColWidth("Sheet1", "A", "K", 20)
-	f.SetCellValue("Sheet1", "A2", "序号")
-	f.SetCellValue("Sheet1", "B2", "项目名称")
-	f.SetCellValue("Sheet1", "C2", "项目类别")
-	f.SetCellValue("Sheet1", "D2", "最新跟进时间")
-	line := 2
-	if len(list) > 0 {
-		for _, v := range list {
-			line++
-			f.SetCellValue("Sheet1", fmt.Sprintf("A%d", line), gconv.String(line-2))
-			f.SetCellValue("Sheet1", fmt.Sprintf("B%d", line), v.NboName)
-			if len(rsp) > 0 {
-				f.SetCellValue("Sheet1", fmt.Sprintf("C%d", line), rsp[v.NboType])
-			} else {
-				f.SetCellValue("Sheet1", fmt.Sprintf("C%d", line), v.NboType)
-			}
-			f.SetCellValue("Sheet1", fmt.Sprintf("D%d", line), v.FinalFollowTime)
+		ctx := context.WithValue(context.TODO(), "contextService", g.Map{"dataScope": dataScope})
 
+		err = projDao.NewProjBusinessDao(tenant).Where(projDao.ProjBusiness.C.NboType, StatusA).
+			WhereLTE(projDao.ProjBusiness.C.FinalFollowTime, LastWeekOneDay).DataScope(ctx, "sale_id").
+			Fields("count(id) as count, group_concat(nbo_name) as busNames").Scan(&LastWeekOne)
+		if err != nil {
+			g.Log().Error("获取A类超期一天未跟进项目", err)
 		}
-	}
-	dir := g.Config().GetString("file.cronstatic")
-	filename := "项目跟进" + gconv.String(time.Now().UnixNano()) + ".xlsx"
-	path := dir + filename
-	if err = f.SaveAs(path); err != nil {
-		g.Log().Error("Excel保存失败", err)
-	}
-	if len(salesAssociate) > 0 {
-		for _, val := range salesAssociate {
-			if val == "" {
-				continue
-			}
-			msgs := g.MapStrStr{
-				"msgTitle":    "超期3天未跟进项目提醒",
-				"msgContent":  msg,
-				"recvUserIds": "1",
-				"recvUser":    "系统管理员",
-				"opnUrl":      path,
-			}
-			if err = service.GSendMail(msgs); err != nil {
-				g.Log().Error("SendMail() error = %v", err)
-			}
+		err = projDao.NewProjBusinessDao(tenant).Where(projDao.ProjBusiness.C.NboType, StatusB).
+			WhereLTE(projDao.ProjBusiness.C.FinalFollowTime, LastTwoWeekOneDay).DataScope(ctx, "sale_id").
+			Fields("count(id) as count, group_concat(nbo_name) as busNames").Scan(&LastTwoWeekOne)
+		if err != nil {
+			g.Log().Error("获取B类超期一天未跟进项目", err)
+		}
+		err = projDao.NewProjBusinessDao(tenant).Where(projDao.ProjBusiness.C.NboType, StatusC).
+			WhereLTE(projDao.ProjBusiness.C.FinalFollowTime, LastMonthOneDay).DataScope(ctx, "sale_id").
+			Fields("count(id) as count, group_concat(nbo_name) as busNames").Scan(&LastMonthOne)
+		if err != nil {
+			g.Log().Error("获取C类超期一天未跟进项目", err)
 		}
 
-	}
+		err = projDao.NewProjBusinessDao(tenant).Where(projDao.ProjBusiness.C.NboType, StatusA).
+			WhereLTE(projDao.ProjBusiness.C.FinalFollowTime, LastWeekCurrentDay).
+			WhereGTE(projDao.ProjBusiness.C.FinalFollowTime, LastWeekOneDay).DataScope(ctx, "sale_id").
+			Fields("count(id) as count, group_concat(nbo_name) as busNames").Scan(&LastWeekCurrent)
+		if err != nil {
+			g.Log().Error("获取A类超期当天未跟进项目", err)
+		}
+		err = projDao.NewProjBusinessDao(tenant).Where(projDao.ProjBusiness.C.NboType, StatusB).
+			WhereLTE(projDao.ProjBusiness.C.FinalFollowTime, LastTwoWeekCurrentDay).
+			WhereGTE(projDao.ProjBusiness.C.FinalFollowTime, LastTwoWeekOneDay).DataScope(ctx, "sale_id").
+			Fields("count(id) as count, group_concat(nbo_name) as busNames").Scan(&LastTwoWeekCurrent)
+		if err != nil {
+			g.Log().Error("获取B类超期当天未跟进项目", err)
+		}
+		err = projDao.NewProjBusinessDao(tenant).Where(projDao.ProjBusiness.C.NboType, StatusC).
+			WhereLTE(projDao.ProjBusiness.C.FinalFollowTime, LastMonthCurrentDay).
+			WhereGTE(projDao.ProjBusiness.C.FinalFollowTime, LastMonthOneDay).DataScope(ctx, "sale_id").
+			Fields("count(id) as count, group_concat(nbo_name) as busNames").Scan(&LastMonthCurrent)
+		if err != nil {
+			g.Log().Error("获取C类超期当天未跟进项目", err)
+		}
 
-}
+		err = projDao.NewProjBusinessDao(tenant).Where(projDao.ProjBusiness.C.NboType, StatusA).
+			WhereLTE(projDao.ProjBusiness.C.FinalFollowTime, LastWeekBeforeTridDay).
+			WhereGTE(projDao.ProjBusiness.C.FinalFollowTime, LastWeekCurrentDay).DataScope(ctx, "sale_id").
+			Fields("count(id) as count, group_concat(nbo_name) as busNames").Scan(&LastWeekBeforeTrid)
+		if err != nil {
+			g.Log().Error("获取A类超期当天未跟进项目", err)
+		}
+		err = projDao.NewProjBusinessDao(tenant).Where(projDao.ProjBusiness.C.NboType, StatusB).
+			WhereLTE(projDao.ProjBusiness.C.FinalFollowTime, LastTwoWeekBeforeTridDay).
+			WhereGTE(projDao.ProjBusiness.C.FinalFollowTime, LastTwoWeekCurrentDay).DataScope(ctx, "sale_id").
+			Fields("count(id) as count, group_concat(nbo_name) as busNames").Scan(&LastTwoWeekBeforeTrid)
+		if err != nil {
+			g.Log().Error("获取B类超期当天未跟进项目", err)
+		}
+		err = projDao.NewProjBusinessDao(tenant).Where(projDao.ProjBusiness.C.NboType, StatusC).
+			WhereLTE(projDao.ProjBusiness.C.FinalFollowTime, LastMonthBeforeTridDay).
+			WhereGTE(projDao.ProjBusiness.C.FinalFollowTime, LastMonthCurrentDay).DataScope(ctx, "sale_id").
+			Fields("count(id) as count, group_concat(nbo_name) as busNames").Scan(&LastMonthBeforeTrid)
+		if err != nil {
+			g.Log().Error("获取C类超期当天未跟进项目", err)
+		}
 
-type FollowOverdue struct {
-	SaleId int64 `json:"saleId"`
-	Count  int64 `json:"count"`
+		var msg string
+		if LastWeekOne.Count != 0 {
+			msg += fmt.Sprintf("您有超期1天未跟进A类项目%v个,项目名称分别为 %v,", LastWeekOne.Count, LastWeekOne.BusNames)
+		}
+		if LastTwoWeekOne.Count != 0 {
+			msg += fmt.Sprintf("您有超期1天未跟进B类项目%v个,项目名称分别为 %v,", LastTwoWeekOne.Count, LastTwoWeekOne.BusNames)
+		}
+		if LastMonthOne.Count != 0 {
+			msg += fmt.Sprintf("您有超期1天未跟进C类项目%v个,项目名称分别为 %v,", LastMonthOne.Count, LastMonthOne.BusNames)
+		}
+		if LastWeekCurrent.Count != 0 {
+			msg += fmt.Sprintf("您今天有超期未跟进A类项目%v个,项目名称分别为 %v,", LastWeekCurrent.Count, LastWeekCurrent.BusNames)
+		}
+		if LastTwoWeekCurrent.Count != 0 {
+			msg += fmt.Sprintf("您今天有超期未跟进B类项目%v个,项目名称分别为 %v,", LastTwoWeekCurrent.Count, LastTwoWeekCurrent.BusNames)
+		}
+		if LastMonthCurrent.Count != 0 {
+			msg += fmt.Sprintf("您今天有超期未跟进C类项目%v个,项目名称分别为 %v,", LastMonthCurrent.Count, LastMonthCurrent.BusNames)
+		}
+		if LastWeekBeforeTrid.Count != 0 {
+			msg += fmt.Sprintf("您3天后有超期未跟进A类项目%v个,项目名称分别为 %v,", LastWeekBeforeTrid.Count, LastWeekBeforeTrid.BusNames)
+		}
+		if LastTwoWeekBeforeTrid.Count != 0 {
+			msg += fmt.Sprintf("您3天后有超期未跟进B类项目%v个,项目名称分别为 %v,", LastTwoWeekBeforeTrid.Count, LastTwoWeekBeforeTrid.BusNames)
+		}
+		if LastMonthBeforeTrid.Count != 0 {
+			msg += fmt.Sprintf("您3天后有超期未跟进C类项目%v个,项目名称分别为 %v,", LastMonthBeforeTrid.Count, LastMonthBeforeTrid.BusNames)
+		}
+		if msg != "" {
+			businessNotifyMessage([]string{userId}, gstr.TrimRightStr(msg, ","))
+		}
+	}
 }
 
 // 在到达超期时间前3天进行提醒、当天进行提醒,这两次提醒只提醒销售工程师, 超期1天后提醒
@@ -318,70 +336,70 @@ func businessFollowOverdueSalesEngineer(tenant string) {
 	LastWeekCurrentCount, LastTwoWeekCurrentCount, LastMonthCurrentCount := make([]FollowOverdue, 0), make([]FollowOverdue, 0), make([]FollowOverdue, 0)
 	LastWeekBeforeTridCount, LastTwoWeekBeforeTridCount, LastMonthBeforeTridCount := make([]FollowOverdue, 0), make([]FollowOverdue, 0), make([]FollowOverdue, 0)
 	err := projDao.NewProjBusinessDao(tenant).Where(projDao.ProjBusiness.C.NboType, StatusA).
-		WhereLTE(projDao.ProjBusiness.C.FinalFollowTime, LastWeekOneDay).
-		Fields("sale_id, count(id) as count").Group("sale_id").OrderAsc("sale_id").Scan(&LastWeekOneCount)
+		WhereLTE(projDao.ProjBusiness.C.FinalFollowTime, LastWeekOneDay).Group("sale_id").OrderAsc("sale_id").
+		Fields("sale_id, count(id) as count, group_concat(nbo_name) as busNames").Scan(&LastWeekOneCount)
 	if err != nil {
 		g.Log().Error("获取A类超期一天未跟进项目", err)
 	}
 	err = projDao.NewProjBusinessDao(tenant).Where(projDao.ProjBusiness.C.NboType, StatusB).
-		WhereLTE(projDao.ProjBusiness.C.FinalFollowTime, LastTwoWeekOneDay).
-		Fields("sale_id, count(id) as count").Group("sale_id").OrderAsc("sale_id").Scan(&LastTwoWeekOneCount)
+		WhereLTE(projDao.ProjBusiness.C.FinalFollowTime, LastTwoWeekOneDay).Group("sale_id").OrderAsc("sale_id").
+		Fields("sale_id, count(id) as count, group_concat(nbo_name) as busNames").Scan(&LastTwoWeekOneCount)
 	if err != nil {
 		g.Log().Error("获取B类超期一天未跟进项目", err)
 	}
 	err = projDao.NewProjBusinessDao(tenant).Where(projDao.ProjBusiness.C.NboType, StatusC).
-		WhereLTE(projDao.ProjBusiness.C.FinalFollowTime, LastMonthOneDay).
-		Fields("sale_id, count(id) as count").Group("sale_id").OrderAsc("sale_id").Scan(&LastMonthOneCount)
+		WhereLTE(projDao.ProjBusiness.C.FinalFollowTime, LastMonthOneDay).Group("sale_id").OrderAsc("sale_id").
+		Fields("sale_id, count(id) as count, group_concat(nbo_name) as busNames").Scan(&LastMonthOneCount)
 	if err != nil {
 		g.Log().Error("获取C类超期一天未跟进项目", err)
 	}
 
 	err = projDao.NewProjBusinessDao(tenant).Where(projDao.ProjBusiness.C.NboType, StatusA).
 		WhereLTE(projDao.ProjBusiness.C.FinalFollowTime, LastWeekCurrentDay).
-		WhereGTE(projDao.ProjBusiness.C.FinalFollowTime, LastWeekOneDay).
-		Fields("sale_id, count(id) as count").Group("sale_id").OrderAsc("sale_id").Scan(&LastWeekCurrentCount)
+		WhereGTE(projDao.ProjBusiness.C.FinalFollowTime, LastWeekOneDay).Group("sale_id").OrderAsc("sale_id").
+		Fields("sale_id, count(id) as count, group_concat(nbo_name) as busNames").Scan(&LastWeekCurrentCount)
 	if err != nil {
 		g.Log().Error("获取A类超期当天未跟进项目", err)
 	}
 	err = projDao.NewProjBusinessDao(tenant).Where(projDao.ProjBusiness.C.NboType, StatusB).
 		WhereLTE(projDao.ProjBusiness.C.FinalFollowTime, LastTwoWeekCurrentDay).
-		WhereGTE(projDao.ProjBusiness.C.FinalFollowTime, LastTwoWeekOneDay).
-		Fields("sale_id, count(id) as count").Group("sale_id").OrderAsc("sale_id").Scan(&LastTwoWeekCurrentCount)
+		WhereGTE(projDao.ProjBusiness.C.FinalFollowTime, LastTwoWeekOneDay).Group("sale_id").OrderAsc("sale_id").
+		Fields("sale_id, count(id) as count, group_concat(nbo_name) as busNames").Scan(&LastTwoWeekCurrentCount)
 	if err != nil {
 		g.Log().Error("获取B类超期当天未跟进项目", err)
 	}
 	err = projDao.NewProjBusinessDao(tenant).Where(projDao.ProjBusiness.C.NboType, StatusC).
 		WhereLTE(projDao.ProjBusiness.C.FinalFollowTime, LastMonthCurrentDay).
-		WhereGTE(projDao.ProjBusiness.C.FinalFollowTime, LastMonthOneDay).
-		Fields("sale_id, count(id) as count").Group("sale_id").OrderAsc("sale_id").Scan(&LastMonthCurrentCount)
+		WhereGTE(projDao.ProjBusiness.C.FinalFollowTime, LastMonthOneDay).Group("sale_id").OrderAsc("sale_id").
+		Fields("sale_id, count(id) as count, group_concat(nbo_name) as busNames").Scan(&LastMonthCurrentCount)
 	if err != nil {
 		g.Log().Error("获取C类超期当天未跟进项目", err)
 	}
 
 	err = projDao.NewProjBusinessDao(tenant).Where(projDao.ProjBusiness.C.NboType, StatusA).
 		WhereLTE(projDao.ProjBusiness.C.FinalFollowTime, LastWeekBeforeTridDay).
-		WhereGTE(projDao.ProjBusiness.C.FinalFollowTime, LastWeekCurrentDay).
-		Fields("sale_id, count(id) as count").Group("sale_id").OrderAsc("sale_id").Scan(&LastWeekBeforeTridCount)
+		WhereGTE(projDao.ProjBusiness.C.FinalFollowTime, LastWeekCurrentDay).Group("sale_id").OrderAsc("sale_id").
+		Fields("sale_id, count(id) as count, group_concat(nbo_name) as busNames").Scan(&LastWeekBeforeTridCount)
 	if err != nil {
 		g.Log().Error("获取A类超期当天未跟进项目", err)
 	}
 	err = projDao.NewProjBusinessDao(tenant).Where(projDao.ProjBusiness.C.NboType, StatusB).
 		WhereLTE(projDao.ProjBusiness.C.FinalFollowTime, LastTwoWeekBeforeTridDay).
-		WhereGTE(projDao.ProjBusiness.C.FinalFollowTime, LastTwoWeekCurrentDay).
-		Fields("sale_id, count(id) as count").Group("sale_id").OrderAsc("sale_id").Scan(&LastTwoWeekBeforeTridCount)
+		WhereGTE(projDao.ProjBusiness.C.FinalFollowTime, LastTwoWeekCurrentDay).Group("sale_id").OrderAsc("sale_id").
+		Fields("sale_id, count(id) as count, group_concat(nbo_name) as busNames").Scan(&LastTwoWeekBeforeTridCount)
 	if err != nil {
 		g.Log().Error("获取B类超期当天未跟进项目", err)
 	}
 	err = projDao.NewProjBusinessDao(tenant).Where(projDao.ProjBusiness.C.NboType, StatusC).
 		WhereLTE(projDao.ProjBusiness.C.FinalFollowTime, LastMonthBeforeTridDay).
-		WhereGTE(projDao.ProjBusiness.C.FinalFollowTime, LastMonthCurrentDay).
-		Fields("sale_id, count(id) as count").Group("sale_id").OrderAsc("sale_id").Scan(&LastMonthBeforeTridCount)
+		WhereGTE(projDao.ProjBusiness.C.FinalFollowTime, LastMonthCurrentDay).Group("sale_id").OrderAsc("sale_id").
+		Fields("sale_id, count(id) as count, group_concat(nbo_name) as busNames").Scan(&LastMonthBeforeTridCount)
 	if err != nil {
 		g.Log().Error("获取C类超期当天未跟进项目", err)
 	}
 
-	allSaleIds := gset.NewStrSet(true)
-	saleIds, lastWeekOneCountMap := handleSalesEngineerData(LastWeekOneCount)
+	allSaleIds := gset.NewIntSet(true)
+	saleIds, LastWeekOneCountMap := handleSalesEngineerData(LastWeekOneCount)
 	allSaleIds = allSaleIds.Union(saleIds)
 	saleIds, LastTwoWeekOneCountMap := handleSalesEngineerData(LastTwoWeekOneCount)
 	allSaleIds = allSaleIds.Union(saleIds)
@@ -401,46 +419,46 @@ func businessFollowOverdueSalesEngineer(tenant string) {
 	allSaleIds = allSaleIds.Union(saleIds)
 	for _, saleId := range allSaleIds.Slice() {
 		var msg string
-		if lastWeekOneCountMap[saleId] != "" {
-			msg += fmt.Sprintf("您有超期1天未跟进A类项目%v个,", lastWeekOneCountMap[saleId])
+		if LastWeekOneCountMap[saleId].Count != 0 {
+			msg += fmt.Sprintf("您有超期1天未跟进A类项目%v个,项目名称分别为 %v,", LastWeekOneCountMap[saleId].Count, LastWeekOneCountMap[saleId].BusNames)
 		}
-		if LastTwoWeekOneCountMap[saleId] != "" {
-			msg += fmt.Sprintf("您有超期1天未跟进B类项目%v个,", LastTwoWeekOneCountMap[saleId])
+		if LastTwoWeekOneCountMap[saleId].Count != 0 {
+			msg += fmt.Sprintf("您有超期1天未跟进B类项目%v个,项目名称分别为 %v,", LastTwoWeekOneCountMap[saleId].Count, LastTwoWeekOneCountMap[saleId].BusNames)
 		}
-		if LastMonthOneCountMap[saleId] != "" {
-			msg += fmt.Sprintf("您有超期1天未跟进C类项目%v个,", LastMonthOneCountMap[saleId])
+		if LastMonthOneCountMap[saleId].Count != 0 {
+			msg += fmt.Sprintf("您有超期1天未跟进C类项目%v个,项目名称分别为 %v,", LastMonthOneCountMap[saleId].Count, LastMonthOneCountMap[saleId].BusNames)
 		}
-		if LastWeekCurrentCountMap[saleId] != "" {
-			msg += fmt.Sprintf("您今天有超期未跟进A类项目%v个,", LastWeekCurrentCountMap[saleId])
+		if LastWeekCurrentCountMap[saleId].Count != 0 {
+			msg += fmt.Sprintf("您今天有超期未跟进A类项目%v个,项目名称分别为 %v,", LastWeekCurrentCountMap[saleId].Count, LastWeekCurrentCountMap[saleId].BusNames)
 		}
-		if LastTwoWeekCurrentCountMap[saleId] != "" {
-			msg += fmt.Sprintf("您今天有超期未跟进B类项目%v个,", LastTwoWeekCurrentCountMap[saleId])
+		if LastTwoWeekCurrentCountMap[saleId].Count != 0 {
+			msg += fmt.Sprintf("您今天有超期未跟进B类项目%v个,项目名称分别为 %v,", LastTwoWeekCurrentCountMap[saleId].Count, LastTwoWeekCurrentCountMap[saleId].BusNames)
 		}
-		if LastMonthCurrentCountMap[saleId] != "" {
-			msg += fmt.Sprintf("您今天有超期未跟进C类项目%v个,", LastMonthCurrentCountMap[saleId])
+		if LastMonthCurrentCountMap[saleId].Count != 0 {
+			msg += fmt.Sprintf("您今天有超期未跟进C类项目%v个,项目名称分别为 %v,", LastMonthCurrentCountMap[saleId].Count, LastMonthCurrentCountMap[saleId].BusNames)
 		}
-		if LastWeekBeforeTridCountMap[saleId] != "" {
-			msg += fmt.Sprintf("您3天后有超期未跟进A类项目%v个,", LastWeekBeforeTridCountMap[saleId])
+		if LastWeekBeforeTridCountMap[saleId].Count != 0 {
+			msg += fmt.Sprintf("您3天后有超期未跟进A类项目%v个,项目名称分别为 %v,", LastWeekBeforeTridCountMap[saleId].Count, LastWeekBeforeTridCountMap[saleId].BusNames)
 		}
-		if LastTwoWeekBeforeTridCountMap[saleId] != "" {
-			msg += fmt.Sprintf("您3天后有超期未跟进B类项目%v个,", LastTwoWeekBeforeTridCountMap[saleId])
+		if LastTwoWeekBeforeTridCountMap[saleId].Count != 0 {
+			msg += fmt.Sprintf("您3天后有超期未跟进B类项目%v个,项目名称分别为 %v,", LastTwoWeekBeforeTridCountMap[saleId].Count, LastTwoWeekBeforeTridCountMap[saleId].BusNames)
 		}
-		if LastMonthBeforeTridCountMap[saleId] != "" {
-			msg += fmt.Sprintf("您3天后有超期未跟进C类项目%v个,", LastMonthBeforeTridCountMap[saleId])
+		if LastMonthBeforeTridCountMap[saleId].Count != 0 {
+			msg += fmt.Sprintf("您3天后有超期未跟进C类项目%v个,项目名称分别为 %v,", LastMonthBeforeTridCountMap[saleId].Count, LastMonthBeforeTridCountMap[saleId].BusNames)
 		}
 		if msg != "" {
-			businessNotifyMessage([]string{saleId}, gstr.TrimRightStr(msg, ","))
+			businessNotifyMessage([]string{gconv.String(saleId)}, gstr.TrimRightStr(msg, ","))
 		}
 	}
 }
 
-func handleSalesEngineerData(countData []FollowOverdue) (saleIds *gset.StrSet, data g.MapStrStr) {
-	saleIds = gset.NewStrSet(true)
-	data = make(g.MapStrStr)
+func handleSalesEngineerData(countData []FollowOverdue) (saleIds *gset.IntSet, data map[int]FollowOverdue) {
+	saleIds = gset.NewIntSet(true)
+	data = make(map[int]FollowOverdue)
 	for _, v := range countData {
-		saleId := gconv.String(v.SaleId)
+		saleId := gconv.Int(v.SaleId)
 		saleIds.Add(saleId)
-		data[saleId] = gconv.String(v.Count)
+		data[saleId] = v
 	}
 	return
 }
@@ -460,21 +478,130 @@ func notifyMessage(ids, message string) {
 		"msgType":     "20",
 		"recvUserIds": ids,
 		"msgStatus":   "10",
-		"sendType":    "10,20,30,40",
+		"sendType":    "10,30",
 	}
 	if err := service.CreateSystemMessage(msg); err != nil {
 		glog.Error("消息提醒异常:", err)
 	}
 }
 
-type alignment struct {
-	horizontal      string `json:"horizontal"`
-	indent          int    `json:"indent"`
-	justifylastline bool   `json:"justify_last_line"`
-	readingorder    uint64 `json:"reading_order"`
-	relativeindent  int    `json:"relative_indent"`
-	shrinktofit     bool   `json:"shrink_to_fit"`
-	textrotation    int    `json:"text_rotation"`
-	vertical        string `json:"vertical"`
-	wraptext        bool   `json:"wrap_text"`
+// 项目每周进行一次超期三天未跟进统计表,每周给销售助理发一次邮件
+func businesspeopleUpReminder() {
+
+	tenant := g.Config().GetString("micro_srv.tenant")
+	if tenant == "" {
+		glog.Error("定时任务租户码未设置,请前往配置")
+		return
+	}
+	// 从配置中获取消息提醒设置
+	configs, err := g.DB(tenant).Model("sys_config").Where("config_key IN ('SalesDirector','SalesAssociate')").FindAll()
+	if err != nil && err != sql.ErrNoRows {
+		glog.Error(err)
+		return
+	}
+	// 销售助理用户Id
+	salesAssociate := []string{}
+	for _, config := range configs {
+		if config["config_key"].String() == "SalesAssociate" {
+			salesAssociate = strings.Split(config["config_value"].String(), ",")
+		}
+	}
+	var list []*model.ProjBusinessRes
+	var listA []*model.ProjBusinessRes
+	var listB []*model.ProjBusinessRes
+	var listC []*model.ProjBusinessRes
+	now := gtime.Now().StartOfDay()
+	LastWeekDay := now.AddDate(0, 0, -8)
+	LastTwoWeekDay := now.AddDate(0, 0, -15)
+	LastMonthDay := now.AddDate(0, -1, -1)
+	err = projDao.NewProjBusinessDao(tenant).Where(projDao.ProjBusiness.C.NboType, StatusA).
+		WhereLTE(projDao.ProjBusiness.C.FinalFollowTime, LastWeekDay).OrderDesc("id").Scan(&listA)
+	if err != nil {
+		g.Log().Error("获取A类超期3天未跟进项目", err)
+	}
+	if len(listA) > 0 {
+		list = append(list, listA...)
+	}
+	err = projDao.NewProjBusinessDao(tenant).Where(projDao.ProjBusiness.C.NboType, StatusB).
+		WhereLTE(projDao.ProjBusiness.C.FinalFollowTime, LastTwoWeekDay).OrderDesc("id").Scan(&listB)
+	if err != nil {
+		g.Log().Error("获取B类超期3天未跟进项目", err)
+	}
+	if len(listB) > 0 {
+		list = append(list, listB...)
+	}
+	err = projDao.NewProjBusinessDao(tenant).Where(projDao.ProjBusiness.C.NboType, StatusC).
+		WhereLTE(projDao.ProjBusiness.C.FinalFollowTime, LastMonthDay).OrderDesc("id").Scan(&listC)
+	if err != nil {
+		g.Log().Error("获取C类超期3天未跟进项目", err)
+	}
+	if len(listC) > 0 {
+		list = append(list, listC...)
+	}
+	if err != nil {
+		g.Log().Error("获取未跟进项目", err)
+	}
+	ctx := context.WithValue(context.TODO(), share.ReqMetaDataKey, map[string]string{"tenant": tenant})
+	rsp, err := service.GetDictDataByType(ctx, "proj_nbo_type")
+	if err != nil {
+		g.Log().Error("项目类别数据字典", err)
+	}
+	if err != nil {
+		g.Log().Error("获取未跟进项目", err)
+		return
+	}
+	var msg string
+	if len(list) > 0 {
+		msg += fmt.Sprintf("您有超期3天未跟进项目%v个,请登录系统查看", len(list))
+	}
+	f := excelize.NewFile()
+	f.MergeCell("Sheet1", "A1", "D1")
+	style, _ := f.NewStyle(`{"alignment":{"horizontal":"center"}}`)
+	f.SetCellValue("Sheet1", "A1", "超期三天未跟进项目")
+	f.SetCellStyle("sheet1", "A1", "D1", style)
+	f.SetColWidth("Sheet1", "A", "K", 20)
+	f.SetCellValue("Sheet1", "A2", "序号")
+	f.SetCellValue("Sheet1", "B2", "项目名称")
+	f.SetCellValue("Sheet1", "C2", "项目类别")
+	f.SetCellValue("Sheet1", "D2", "最新跟进时间")
+	line := 2
+	if len(list) > 0 {
+		for _, v := range list {
+			line++
+			f.SetCellValue("Sheet1", fmt.Sprintf("A%d", line), gconv.String(line-2))
+			f.SetCellValue("Sheet1", fmt.Sprintf("B%d", line), v.NboName)
+			if len(rsp) > 0 {
+				f.SetCellValue("Sheet1", fmt.Sprintf("C%d", line), rsp[v.NboType])
+			} else {
+				f.SetCellValue("Sheet1", fmt.Sprintf("C%d", line), v.NboType)
+			}
+			f.SetCellValue("Sheet1", fmt.Sprintf("D%d", line), v.FinalFollowTime)
+
+		}
+	}
+	dir := g.Config().GetString("file.cronstatic")
+	filename := "项目跟进" + gconv.String(time.Now().UnixNano()) + ".xlsx"
+	path := dir + filename
+	if err = f.SaveAs(path); err != nil {
+		g.Log().Error("Excel保存失败", err)
+	}
+	if len(salesAssociate) > 0 {
+		for _, val := range salesAssociate {
+			if val == "" {
+				continue
+			}
+			msgs := g.MapStrStr{
+				"msgTitle":    "超期3天未跟进项目提醒",
+				"msgContent":  msg,
+				"recvUserIds": "1",
+				"recvUser":    "系统管理员",
+				"opnUrl":      path,
+			}
+			if err = service.GSendMail(msgs); err != nil {
+				g.Log().Error("SendMail() error = %v", err)
+			}
+		}
+
+	}
+
 }

+ 57 - 0
opms_parent/app/service/proj/business_file.go

@@ -2,7 +2,14 @@ package proj
 
 import (
 	"context"
+	"dashoo.cn/opms_libary/myerrors"
+	"dashoo.cn/opms_libary/plugin/dingtalk"
+	"encoding/base64"
+	"fmt"
 	"github.com/gogf/gf/util/gconv"
+	"io"
+	"net/http"
+	"strings"
 
 	projDao "dashoo.cn/micro/app/dao/proj"
 	model "dashoo.cn/micro/app/model/proj"
@@ -47,3 +54,53 @@ func (p *businessFileService) DeleteByIds(ids []int64) (err error) {
 	_, err = p.Dao.WhereIn(projDao.ProjBusinessFile.C.Id, ids).Delete()
 	return
 }
+
+func (p businessFileService) DownloadDingTalkFile(id int) (string, error) {
+	ent, err := p.Dao.WherePri(id).One()
+	if err != nil {
+		return "", err
+	}
+	if ent == nil {
+		return "", myerrors.TipsError("附件不存在")
+	}
+	if !strings.HasPrefix(ent.FileUrl, "dingtalk") {
+		return "", myerrors.TipsError("此附件不是钉钉附件")
+	}
+	fileInfo := strings.Split(ent.FileUrl, ":")
+	if len(fileInfo) != 3 {
+		return "", myerrors.TipsError("钉钉附件地址不合法")
+	}
+	spaceId := fileInfo[1]
+	fileId := fileInfo[2]
+
+	// res, err := dingtalk.Client.GetStorage().AddPermission(dingtalk.Client.Context.CorpId, spaceId, s.userInfo.DingtalkId, "EDITOR", "USER")
+	// fmt.Println(res, err)
+	// if err != nil {
+	// 	return "", fmt.Errorf("设置权限异常 %s", err.Error())
+	// }
+
+	resp, err := dingtalk.Client.GetStorage().QueryFileDownloadInfo(spaceId, fileId, p.CxtUser.DingtalkId)
+	if err != nil {
+		return "", myerrors.TipsError("获取文件下载信息异常")
+	}
+	fmt.Println(resp, err)
+	req, err := http.NewRequest("GET", resp.HeaderSignatureInfo.ResourceUrls[0], nil)
+	if err != nil {
+		return "", fmt.Errorf("构建文件下载请求异常 %s", err.Error())
+	}
+
+	for k, v := range resp.HeaderSignatureInfo.Headers {
+		req.Header.Add(k, v)
+	}
+	fileresp, err := http.DefaultClient.Do(req)
+	if err != nil {
+		return "", fmt.Errorf("文件下载异常 %s", err.Error())
+	}
+
+	data, err := io.ReadAll(fileresp.Body)
+	if err != nil {
+		return "", fmt.Errorf("读取下载内容异常 %s", err.Error())
+	}
+
+	return base64.StdEncoding.EncodeToString(data), nil
+}