cust_cron.go 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346
  1. package cust
  2. import (
  3. "dashoo.cn/micro/app/service"
  4. "dashoo.cn/opms_libary/plugin/dingtalk"
  5. "dashoo.cn/opms_libary/plugin/dingtalk/message/corpconversation"
  6. "fmt"
  7. "github.com/360EntSecGroup-Skylar/excelize"
  8. "github.com/gogf/gf/frame/g"
  9. "github.com/gogf/gf/os/glog"
  10. "github.com/gogf/gf/os/gtime"
  11. "github.com/gogf/gf/util/gconv"
  12. "github.com/robfig/cron"
  13. "os"
  14. )
  15. // 初始化,创建每10分钟执行一次的定时任务
  16. func init() {
  17. // 定时任务
  18. c := cron.New()
  19. spec1 := "0 0 20 * * ?" // 每晚八点
  20. spec2 := "0 0 18 * * 0" // 每周日晚6点
  21. spec3 := "0 0 20 * * ?" // 每晚八点
  22. // 新增招标信息每晚8点
  23. if err := c.AddJob(spec1, NewBidCron{}); err != nil {
  24. glog.Error(err)
  25. }
  26. // 未跟进的招标信息,每周日晚上6点
  27. if err := c.AddJob(spec2, NotFollowBidCron{}); err != nil {
  28. glog.Error(err)
  29. }
  30. // 超过2个月没有创建项目以及没有申请闭环的招投标进入已超期
  31. if err := c.AddJob(spec3, OverdueBidCron{}); err != nil {
  32. glog.Error(err)
  33. }
  34. c.Start()
  35. }
  36. // NewBidCron 新增招标信息
  37. type NewBidCron struct {
  38. }
  39. // NotFollowBidCron 未跟进的招标信息
  40. type NotFollowBidCron struct {
  41. }
  42. // OverdueBidCron 招标信息超期
  43. type OverdueBidCron struct {
  44. }
  45. // Run 新增招标信息提醒
  46. func (c NewBidCron) Run() {
  47. tenant := g.Config().GetString("micro_srv.tenant")
  48. if tenant == "" {
  49. glog.Error("租户码未设置,请前往配置")
  50. return
  51. }
  52. // 当前时间
  53. date := gtime.Now()
  54. bidInfos, err := g.DB(tenant).Model("cust_customer_bid_record a").InnerJoin("cust_customer b", "a.cust_id=b.id").Where(fmt.Sprintf("a.created_time LIKE '%v%%'", date.Format("Y-m-d"))).Fields("a.*,b.sales_id").All()
  55. if err != nil {
  56. glog.Error(err)
  57. return
  58. }
  59. data := make(map[int][]map[string]string)
  60. for _, contract := range bidInfos {
  61. id := gconv.Int(contract["sales_id"])
  62. if _, ok := data[id]; !ok {
  63. data[id] = make([]map[string]string, 0)
  64. }
  65. d := map[string]string{
  66. "cuct_name": gconv.String(contract["cuct_name"]),
  67. "product_name": gconv.String(contract["product_name"]),
  68. "published_time": contract["published_time"].GTime().Format("Y-m-d"),
  69. "title": gconv.String(contract["title"]),
  70. "budget": fmt.Sprintf("%.2f", contract["budget"].Float64()),
  71. }
  72. data[id] = append(data[id], d)
  73. }
  74. if len(data) > 0 {
  75. for id, collections := range data {
  76. f := excelize.NewFile()
  77. // Create a new sheet.
  78. sheet := f.NewSheet("Sheet1")
  79. // 表头
  80. f.SetCellValue("Sheet1", Div(1)+"1", "客户名称")
  81. f.SetCellValue("Sheet1", Div(2)+"1", "招标产品名称")
  82. f.SetCellValue("Sheet1", Div(3)+"1", "发布招标日期")
  83. f.SetCellValue("Sheet1", Div(4)+"1", "招标信息标题")
  84. f.SetCellValue("Sheet1", Div(4)+"1", "项目预算")
  85. for index, collection := range collections {
  86. f.SetCellValue("Sheet1", Div(1)+gconv.String(index+2), collection["cuct_name"])
  87. f.SetCellValue("Sheet1", Div(2)+gconv.String(index+2), collection["product_name"])
  88. f.SetCellValue("Sheet1", Div(3)+gconv.String(index+2), collection["published_time"])
  89. f.SetCellValue("Sheet1", Div(4)+gconv.String(index+2), collection["title"])
  90. f.SetCellValue("Sheet1", Div(4)+gconv.String(index+2), collection["budget"])
  91. }
  92. // Set active sheet of the workbook.
  93. f.SetActiveSheet(sheet)
  94. // Save xlsx file by the given path.
  95. file := "./temp/新增招标信息.xlsx"
  96. _, err = createPath("./temp")
  97. if err != nil {
  98. glog.Error(err)
  99. return
  100. }
  101. if err = f.SaveAs(file); err != nil {
  102. glog.Error(err)
  103. return
  104. }
  105. fileId, err := uploadFile(file)
  106. if err != nil {
  107. glog.Error(err)
  108. return
  109. }
  110. notifyMessageFile("新增招标信息提醒", gconv.String(id), fileId)
  111. // 删除已存在的文件
  112. err = os.Remove(file)
  113. if err != nil {
  114. glog.Error(err)
  115. return
  116. }
  117. }
  118. }
  119. }
  120. // Run 未跟进的招标信息提醒
  121. func (c NotFollowBidCron) Run() {
  122. tenant := g.Config().GetString("micro_srv.tenant")
  123. if tenant == "" {
  124. glog.Error("租户码未设置,请前往配置")
  125. return
  126. }
  127. // 当前时间
  128. bidInfos, err := g.DB(tenant).Model("cust_customer_bid_record a").InnerJoin("cust_customer b", "a.cust_id=b.id").LeftJoin("(SELECT * FROM plat_followup WHERE target_type='60') c", "c.target_id=a.id").Where("c.id IS NULL").Group("a.id").Fields("a.*,b.sales_id").All()
  129. if err != nil {
  130. glog.Error(err)
  131. return
  132. }
  133. data := make(map[int][]map[string]string)
  134. for _, contract := range bidInfos {
  135. id := gconv.Int(contract["sales_id"])
  136. if _, ok := data[id]; !ok {
  137. data[id] = make([]map[string]string, 0)
  138. }
  139. d := map[string]string{
  140. "cuct_name": gconv.String(contract["cuct_name"]),
  141. "product_name": gconv.String(contract["product_name"]),
  142. "published_time": contract["published_time"].GTime().Format("Y-m-d"),
  143. "title": gconv.String(contract["title"]),
  144. "budget": fmt.Sprintf("%.2f", contract["budget"].Float64()),
  145. }
  146. data[id] = append(data[id], d)
  147. }
  148. if len(data) > 0 {
  149. for id, collections := range data {
  150. f := excelize.NewFile()
  151. // Create a new sheet.
  152. sheet := f.NewSheet("Sheet1")
  153. // 表头
  154. f.SetCellValue("Sheet1", Div(1)+"1", "客户名称")
  155. f.SetCellValue("Sheet1", Div(2)+"1", "招标产品名称")
  156. f.SetCellValue("Sheet1", Div(3)+"1", "发布招标日期")
  157. f.SetCellValue("Sheet1", Div(4)+"1", "招标信息标题")
  158. f.SetCellValue("Sheet1", Div(4)+"1", "项目预算")
  159. for index, collection := range collections {
  160. f.SetCellValue("Sheet1", Div(1)+gconv.String(index+2), collection["cuct_name"])
  161. f.SetCellValue("Sheet1", Div(2)+gconv.String(index+2), collection["product_name"])
  162. f.SetCellValue("Sheet1", Div(3)+gconv.String(index+2), collection["published_time"])
  163. f.SetCellValue("Sheet1", Div(4)+gconv.String(index+2), collection["title"])
  164. f.SetCellValue("Sheet1", Div(4)+gconv.String(index+2), collection["budget"])
  165. }
  166. // Set active sheet of the workbook.
  167. f.SetActiveSheet(sheet)
  168. // Save xlsx file by the given path.
  169. file := "./temp/未跟进的招标信息.xlsx"
  170. _, err = createPath("./temp")
  171. if err != nil {
  172. glog.Error(err)
  173. return
  174. }
  175. if err = f.SaveAs(file); err != nil {
  176. glog.Error(err)
  177. return
  178. }
  179. fileId, err := uploadFile(file)
  180. if err != nil {
  181. glog.Error(err)
  182. return
  183. }
  184. notifyMessageFile("未跟进的招标信息提醒", gconv.String(id), fileId)
  185. // 删除已存在的文件
  186. err = os.Remove(file)
  187. if err != nil {
  188. glog.Error(err)
  189. return
  190. }
  191. }
  192. }
  193. }
  194. func (c OverdueBidCron) Run() {
  195. tenant := g.Config().GetString("micro_srv.tenant")
  196. if tenant == "" {
  197. glog.Error("租户码未设置,请前往配置")
  198. return
  199. }
  200. date := gtime.Now().AddDate(0, -2, 0).Format("Y-m-d 00:00:00")
  201. // 从项目发布日期起,超过2个月没有创建项目以及没有申请闭环的招投标进入已超期
  202. // 状态:(10跟进中,20待审批,30已闭环、40已超期)
  203. bidInfos, err := g.DB(tenant).Model("cust_customer_bid_record a").LeftJoin("proj_business b", "b.bid_id=a.id").Where(fmt.Sprintf("b.id IS NULL AND a.status='10' AND a.created_time<'%v'", date)).Group("a.id").Fields("a.*").All()
  204. if err != nil {
  205. glog.Error(err)
  206. return
  207. }
  208. if len(bidInfos) > 0 {
  209. ids := ""
  210. for _, bid := range bidInfos {
  211. if ids == "" {
  212. ids = gconv.String(bid["id"])
  213. } else {
  214. ids += "," + gconv.String(bid["id"])
  215. }
  216. }
  217. _, err = g.DB(tenant).Model("cust_customer_bid_record").Update("status='40'", fmt.Sprintf("id IN (%v)", ids))
  218. if err != nil {
  219. glog.Error(err)
  220. return
  221. }
  222. }
  223. }
  224. func uploadFile(file string) (string, error) {
  225. var request corpconversation.UploadConversationFileRequest
  226. request.Type = "file"
  227. request.FileData = "media"
  228. request.FilePath = file
  229. c := dingtalk.Client.GetCorpConversation()
  230. resp, err := c.UploadConversationFile(&request)
  231. if err != nil {
  232. fmt.Println(err)
  233. return "", err
  234. }
  235. return resp.MediaId, nil
  236. }
  237. // notifyMessageFile 发送消息通知
  238. func notifyMessageFile(title, ids, message string) {
  239. msg := g.MapStrStr{
  240. "msgTitle": title,
  241. "msgContent": message,
  242. "msgType": "40", // 文件处理
  243. "recvUserIds": ids,
  244. "msgStatus": "10",
  245. "sendType": "30",
  246. }
  247. if err := service.CreateSystemMessage(msg); err != nil {
  248. glog.Error("消息提醒异常:", err)
  249. }
  250. }
  251. // 判断文件夹是否存在
  252. func pathExists(path string) (bool, error) {
  253. _, err := os.Stat(path)
  254. if err == nil {
  255. return true, nil
  256. }
  257. if os.IsNotExist(err) {
  258. return false, nil
  259. }
  260. return false, err
  261. }
  262. func createPath(path string) (bool, error) {
  263. exist, err := pathExists(path)
  264. if err != nil {
  265. return false, err
  266. }
  267. if exist {
  268. return true, nil
  269. } else {
  270. // 创建文件夹
  271. err := os.MkdirAll(path, os.ModePerm)
  272. if err != nil {
  273. return false, err
  274. } else {
  275. return true, err
  276. }
  277. }
  278. }
  279. // Div 数字转字母
  280. func Div(Num int) string {
  281. var (
  282. Str string = ""
  283. k int
  284. temp []int //保存转化后每一位数据的值,然后通过索引的方式匹配A-Z
  285. )
  286. //用来匹配的字符A-Z
  287. Slice := []string{"", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O",
  288. "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"}
  289. if Num > 26 { //数据大于26需要进行拆分
  290. for {
  291. k = Num % 26 //从个位开始拆分,如果求余为0,说明末尾为26,也就是Z,如果是转化为26进制数,则末尾是可以为0的,这里必须为A-Z中的一个
  292. if k == 0 {
  293. temp = append(temp, 26)
  294. k = 26
  295. } else {
  296. temp = append(temp, k)
  297. }
  298. Num = (Num - k) / 26 //减去Num最后一位数的值,因为已经记录在temp中
  299. if Num <= 26 { //小于等于26直接进行匹配,不需要进行数据拆分
  300. temp = append(temp, Num)
  301. break
  302. }
  303. }
  304. } else {
  305. return Slice[Num]
  306. }
  307. for _, value := range temp {
  308. Str = Slice[value] + Str //因为数据切分后存储顺序是反的,所以Str要放在后面
  309. }
  310. return Str
  311. }