Ver Fonte

商机/app邀请码

三七 há 1 semana atrás
pai
commit
50bfdd1340
100 ficheiros alterados com 4472 adições e 429 exclusões
  1. 129 0
      fs-admin/src/main/java/com/fs/crm/controller/CrmBusinessController.java
  2. 14 8
      fs-admin/src/main/java/com/fs/crm/controller/CrmEventController.java
  3. 100 0
      fs-admin/src/main/java/com/fs/crm/controller/CrmExtDetailController.java
  4. 72 0
      fs-admin/src/main/java/com/fs/crm/controller/CrmExtLogController.java
  5. 45 0
      fs-admin/src/main/java/com/fs/crm/task/CrmTask.java
  6. 40 0
      fs-company-app/src/main/java/com/fs/app/controller/app/ImController.java
  7. 117 12
      fs-ipad-task/src/main/java/com/fs/app/service/IpadSendServer.java
  8. 20 2
      fs-qw-task/src/main/java/com/fs/app/taskService/impl/SopLogsTaskServiceImpl.java
  9. 2 0
      fs-service/src/main/java/com/fs/company/mapper/CompanyConfigMapper.java
  10. 2 0
      fs-service/src/main/java/com/fs/company/service/ICompanyConfigService.java
  11. 6 0
      fs-service/src/main/java/com/fs/company/service/impl/CompanyConfigServiceImpl.java
  12. 4 0
      fs-service/src/main/java/com/fs/company/service/impl/CompanyUserServiceImpl.java
  13. 7 0
      fs-service/src/main/java/com/fs/config/cloud/CloudHostProper.java
  14. 1 0
      fs-service/src/main/java/com/fs/course/domain/FsCourseRedPacketLog.java
  15. 1 0
      fs-service/src/main/java/com/fs/course/param/FsCourseSendRewardUParam.java
  16. 3 0
      fs-service/src/main/java/com/fs/course/service/IFsUserCourseVideoService.java
  17. 37 40
      fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseVideoServiceImpl.java
  18. 339 0
      fs-service/src/main/java/com/fs/crm/domain/CrmBusiness.java
  19. 17 0
      fs-service/src/main/java/com/fs/crm/domain/CrmCustomer.java
  20. 8 110
      fs-service/src/main/java/com/fs/crm/domain/CrmCustomerContacts.java
  21. 14 125
      fs-service/src/main/java/com/fs/crm/domain/CrmCustomerVisit.java
  22. 65 0
      fs-service/src/main/java/com/fs/crm/domain/CrmExtDetail.java
  23. 136 0
      fs-service/src/main/java/com/fs/crm/domain/CrmExtLog.java
  24. 95 0
      fs-service/src/main/java/com/fs/crm/domain/CrmFollowUp.java
  25. 6 1
      fs-service/src/main/java/com/fs/crm/enums/CustomerLogEnum.java
  26. 73 0
      fs-service/src/main/java/com/fs/crm/mapper/CrmBusinessMapper.java
  27. 2 0
      fs-service/src/main/java/com/fs/crm/mapper/CrmCustomerContactsMapper.java
  28. 6 0
      fs-service/src/main/java/com/fs/crm/mapper/CrmCustomerMapper.java
  29. 13 0
      fs-service/src/main/java/com/fs/crm/mapper/CrmCustomerVisitMapper.java
  30. 88 0
      fs-service/src/main/java/com/fs/crm/mapper/CrmExtDetailMapper.java
  31. 62 0
      fs-service/src/main/java/com/fs/crm/mapper/CrmExtLogMapper.java
  32. 65 0
      fs-service/src/main/java/com/fs/crm/mapper/CrmFollowUpMapper.java
  33. 96 0
      fs-service/src/main/java/com/fs/crm/param/CrmBusinessAddAndUpdateParam.java
  34. 118 0
      fs-service/src/main/java/com/fs/crm/param/CrmBusinessImportParam.java
  35. 109 0
      fs-service/src/main/java/com/fs/crm/param/CrmBusinessQueryParam.java
  36. 15 0
      fs-service/src/main/java/com/fs/crm/param/CrmCustomerUpdateOrAddParam.java
  37. 93 0
      fs-service/src/main/java/com/fs/crm/param/CrmExtDetailAddOrUpdateParam.java
  38. 80 0
      fs-service/src/main/java/com/fs/crm/service/ICrmBusinessService.java
  39. 9 0
      fs-service/src/main/java/com/fs/crm/service/ICrmCustomerContactsService.java
  40. 6 0
      fs-service/src/main/java/com/fs/crm/service/ICrmCustomerService.java
  41. 10 0
      fs-service/src/main/java/com/fs/crm/service/ICrmCustomerVisitService.java
  42. 97 0
      fs-service/src/main/java/com/fs/crm/service/ICrmExtDetailService.java
  43. 62 0
      fs-service/src/main/java/com/fs/crm/service/ICrmExtLogService.java
  44. 65 0
      fs-service/src/main/java/com/fs/crm/service/ICrmFollowUpService.java
  45. 682 0
      fs-service/src/main/java/com/fs/crm/service/impl/CrmBusinessServiceImpl.java
  46. 44 0
      fs-service/src/main/java/com/fs/crm/service/impl/CrmCustomerContactsServiceImpl.java
  47. 242 13
      fs-service/src/main/java/com/fs/crm/service/impl/CrmCustomerServiceImpl.java
  48. 83 6
      fs-service/src/main/java/com/fs/crm/service/impl/CrmCustomerVisitServiceImpl.java
  49. 412 0
      fs-service/src/main/java/com/fs/crm/service/impl/CrmExtDetailServiceImpl.java
  50. 96 0
      fs-service/src/main/java/com/fs/crm/service/impl/CrmExtLogServiceImpl.java
  51. 103 0
      fs-service/src/main/java/com/fs/crm/service/impl/CrmFollowUpServiceImpl.java
  52. 125 0
      fs-service/src/main/java/com/fs/crm/vo/CrmBusinessListVO.java
  53. 93 0
      fs-service/src/main/java/com/fs/crm/vo/CrmExtDetailVo.java
  54. 15 0
      fs-service/src/main/java/com/fs/crm/vo/CrmFollowUpVo.java
  55. 63 0
      fs-service/src/main/java/com/fs/his/config/AppConfig.java
  56. 2 0
      fs-service/src/main/java/com/fs/his/config/IntegralConfig.java
  57. 2 0
      fs-service/src/main/java/com/fs/his/enums/FsUserIntegralLogTypeEnum.java
  58. 77 77
      fs-service/src/main/java/com/fs/his/service/impl/FsStorePaymentServiceImpl.java
  59. 12 0
      fs-service/src/main/java/com/fs/his/service/impl/FsUserIntegralLogsServiceImpl.java
  60. 6 1
      fs-service/src/main/java/com/fs/his/service/impl/FsUserInvitedServiceImpl.java
  61. 1 1
      fs-service/src/main/java/com/fs/hisStore/mapper/FsStoreOrderScrmMapper.java
  62. 1 1
      fs-service/src/main/java/com/fs/hisStore/mapper/FsStoreProductScrmMapper.java
  63. 2 0
      fs-service/src/main/java/com/fs/im/service/OpenIMService.java
  64. 19 0
      fs-service/src/main/java/com/fs/im/service/impl/OpenIMServiceImpl.java
  65. 6 32
      fs-service/src/main/java/com/fs/system/config/SystemConfig.java
  66. 1 0
      fs-service/src/main/resources/application-config-bly.yml
  67. 1 0
      fs-service/src/main/resources/application-config-dev-yjb.yml
  68. 1 0
      fs-service/src/main/resources/application-config-dev.yml
  69. 1 0
      fs-service/src/main/resources/application-config-druid-bnkc.yml
  70. 1 0
      fs-service/src/main/resources/application-config-druid-cfryt.yml
  71. 1 0
      fs-service/src/main/resources/application-config-druid-ddgy.yml
  72. 1 0
      fs-service/src/main/resources/application-config-druid-drk.yml
  73. 1 0
      fs-service/src/main/resources/application-config-druid-fby.yml
  74. 1 0
      fs-service/src/main/resources/application-config-druid-hat.yml
  75. 1 0
      fs-service/src/main/resources/application-config-druid-hcl.yml
  76. 1 0
      fs-service/src/main/resources/application-config-druid-hdt.yml
  77. 1 0
      fs-service/src/main/resources/application-config-druid-hsyy.yml
  78. 2 0
      fs-service/src/main/resources/application-config-druid-hzyy.yml
  79. 1 0
      fs-service/src/main/resources/application-config-druid-jkj.yml
  80. 1 0
      fs-service/src/main/resources/application-config-druid-jnmy.yml
  81. 1 0
      fs-service/src/main/resources/application-config-druid-jnsyj.yml
  82. 2 0
      fs-service/src/main/resources/application-config-druid-jsbk.yml
  83. 1 0
      fs-service/src/main/resources/application-config-druid-jzzx.yml
  84. 1 0
      fs-service/src/main/resources/application-config-druid-kyt.yml
  85. 1 0
      fs-service/src/main/resources/application-config-druid-lmjy.yml
  86. 1 0
      fs-service/src/main/resources/application-config-druid-mengniu.yml
  87. 1 0
      fs-service/src/main/resources/application-config-druid-qdtst.yml
  88. 1 0
      fs-service/src/main/resources/application-config-druid-sczy.yml
  89. 1 0
      fs-service/src/main/resources/application-config-druid-sft.yml
  90. 1 0
      fs-service/src/main/resources/application-config-druid-whhm.yml
  91. 1 0
      fs-service/src/main/resources/application-config-druid-xfk.yml
  92. 1 0
      fs-service/src/main/resources/application-config-druid-xzt.yml
  93. 1 0
      fs-service/src/main/resources/application-config-druid-yjb.yml
  94. 1 0
      fs-service/src/main/resources/application-config-druid-yxj.yml
  95. 1 0
      fs-service/src/main/resources/application-config-druid-yzt.yml
  96. 1 0
      fs-service/src/main/resources/application-config-druid-zsjk.yml
  97. 1 0
      fs-service/src/main/resources/application-config-druid.yml
  98. 1 0
      fs-service/src/main/resources/application-config-fzbt.yml
  99. 1 0
      fs-service/src/main/resources/application-config-myhk.yml
  100. 1 0
      fs-service/src/main/resources/application-config-shdn.yml

+ 129 - 0
fs-admin/src/main/java/com/fs/crm/controller/CrmBusinessController.java

@@ -0,0 +1,129 @@
+package com.fs.crm.controller;
+
+
+import com.fs.common.annotation.Log;
+import com.fs.common.core.controller.BaseController;
+import com.fs.common.core.domain.AjaxResult;
+import com.fs.common.core.domain.model.LoginUser;
+import com.fs.common.core.page.TableDataInfo;
+import com.fs.common.enums.BusinessType;
+import com.fs.common.utils.ServletUtils;
+import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.crm.param.CrmBusinessAddAndUpdateParam;
+import com.fs.crm.param.CrmBusinessQueryParam;
+import com.fs.crm.service.ICrmBusinessService;
+import com.fs.crm.vo.CrmBusinessListVO;
+import com.hc.openapi.tool.util.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+import com.fs.framework.web.service.TokenService;
+
+import javax.servlet.http.HttpServletRequest;
+import java.util.List;
+
+/**
+ * 商机Controller
+ *
+ * @author fs
+ * @date 2025-01-16
+ */
+@RestController
+@RequestMapping("/crm/business")
+public class CrmBusinessController extends BaseController
+{
+    @Autowired
+    private ICrmBusinessService crmBusinessService;
+    @Autowired
+    private TokenService tokenService;
+
+    /**
+     * 查询商机列表
+     */
+    @PreAuthorize("@ss.hasPermi('crm:business:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(CrmBusinessQueryParam param)
+    {
+        startPage();
+        if(!StringUtils.isEmpty(param.getCreateTimeRange())){
+            param.setCreateTimeArr(param.getCreateTimeRange().split("--"));
+        }
+        if(!StringUtils.isEmpty(param.getNextTimeRange())){
+            param.setNextTimeArr(param.getNextTimeRange().split("--"));
+        }
+        List<CrmBusinessListVO> list = crmBusinessService.selectCrmBusinessList(param);
+
+        return getDataTable(list);
+    }
+//    /**
+//     * 查询商机列表
+//     */
+//    @PreAuthorize("@ss.hasPermi('crm:business:list')")
+//    @GetMapping("/list")
+//    public TableDataInfo list(CrmBusiness crmBusiness)
+//    {
+//        startPage();
+//        List<CrmBusiness> list = crmBusinessService.selectCrmBusinessList(crmBusiness);
+//        return getDataTable(list);
+//    }
+
+    /**
+     * 导出商机列表
+     */
+    @PreAuthorize("@ss.hasPermi('crm:business:export')")
+    @Log(title = "商机", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(CrmBusinessQueryParam param)
+    {
+        List<CrmBusinessListVO> list = crmBusinessService.selectCrmBusinessList(param);
+        ExcelUtil<CrmBusinessListVO> util = new ExcelUtil<CrmBusinessListVO>(CrmBusinessListVO.class);
+        return util.exportExcel(list, "business");
+    }
+
+    /**
+     * 获取商机详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('crm:business:query')")
+    @GetMapping(value = "/{businessId}")
+    public AjaxResult getInfo(@PathVariable("businessId") Long businessId)
+    {
+        return AjaxResult.success(crmBusinessService.selectCrmBusinessById(businessId));
+    }
+
+    /**
+     * 新增商机
+     */
+    @PreAuthorize("@ss.hasPermi('crm:business:add')")
+    @Log(title = "商机", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody CrmBusinessAddAndUpdateParam param, HttpServletRequest request)
+    {
+        //创建人
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        param.setCreateBy(loginUser.getUsername());
+        return toAjax(crmBusinessService.insertCrmBusiness(param));
+    }
+
+    /**
+     * 修改商机
+     */
+    @PreAuthorize("@ss.hasPermi('crm:business:edit')")
+    @Log(title = "商机", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody CrmBusinessAddAndUpdateParam crmBusiness)
+    {
+        return toAjax(crmBusinessService.updateCrmBusiness(crmBusiness));
+    }
+
+    /**
+     * 删除商机
+     */
+    @PreAuthorize("@ss.hasPermi('crm:business:remove')")
+    @Log(title = "商机", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{businessIds}")
+    public AjaxResult remove(@PathVariable Long[] businessIds)
+    {
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        return toAjax(crmBusinessService.deleteCrmBusinessByIds(businessIds,loginUser.getUser().getNickName()));
+    }
+}

+ 14 - 8
fs-admin/src/main/java/com/fs/crm/controller/CrmEventController.java

@@ -1,22 +1,28 @@
 package com.fs.crm.controller;
 
+import java.util.List;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
 import com.fs.common.annotation.Log;
 import com.fs.common.core.controller.BaseController;
 import com.fs.common.core.domain.AjaxResult;
-import com.fs.common.core.page.TableDataInfo;
 import com.fs.common.enums.BusinessType;
-import com.fs.common.utils.poi.ExcelUtil;
 import com.fs.crm.domain.CrmEvent;
 import com.fs.crm.service.ICrmEventService;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.security.access.prepost.PreAuthorize;
-import org.springframework.web.bind.annotation.*;
-
-import java.util.List;
+import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.common.core.page.TableDataInfo;
 
 /**
  * 代办事项Controller
- *
+ * 
  * @author fs
  * @date 2022-12-21
  */

+ 100 - 0
fs-admin/src/main/java/com/fs/crm/controller/CrmExtDetailController.java

@@ -0,0 +1,100 @@
+package com.fs.crm.controller;
+
+
+import com.fs.common.annotation.Log;
+import com.fs.common.core.controller.BaseController;
+import com.fs.common.core.domain.AjaxResult;
+import com.fs.common.core.domain.R;
+import com.fs.common.core.page.TableDataInfo;
+import com.fs.common.enums.BusinessType;
+import com.fs.common.utils.ServletUtils;
+import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.crm.param.CrmExtDetailAddOrUpdateParam;
+import com.fs.crm.service.ICrmExtDetailService;
+import com.fs.crm.vo.CrmExtDetailVo;
+import com.fs.watch.param.BaseQueryParam;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+import com.fs.framework.web.service.TokenService;
+
+import java.util.List;
+
+/**
+ * 字段扩展详情Controller
+ *
+ * @author fs
+ * @date 2025-02-17
+ */
+@RestController
+@RequestMapping("/crm/detail")
+public class CrmExtDetailController extends BaseController
+{
+    @Autowired
+    private ICrmExtDetailService crmExtDetailService;
+    @Autowired
+    private TokenService tokenService;
+
+    /**
+     * 查询字段扩展列
+     */
+//    @PreAuthorize("@ss.hasPermi('crm:detail:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(BaseQueryParam param)
+    {
+        startPage();
+        List<CrmExtDetailVo> list = crmExtDetailService.getTableColumnsMetadata(param);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出字段扩展详情列表
+     */
+//    @PreAuthorize("@ss.hasPermi('crm:detail:export')")
+    @Log(title = "字段扩展详情", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(BaseQueryParam param)
+    {
+        List<CrmExtDetailVo> list = crmExtDetailService.getTableColumnsMetadata(param);
+        ExcelUtil<CrmExtDetailVo> util = new ExcelUtil<CrmExtDetailVo>(CrmExtDetailVo.class);
+        return util.exportExcel(list, "detail");
+    }
+
+
+
+    /**
+     * 新增字段扩展详情
+     */
+    @PreAuthorize("@ss.hasPermi('crm:detail:add')")
+    @Log(title = "字段扩展详情", businessType = BusinessType.INSERT)
+    @PostMapping
+    public R add(@RequestBody CrmExtDetailAddOrUpdateParam param)
+    {
+        String nickName = tokenService.getLoginUser(ServletUtils.getRequest()).getUser().getNickName();
+        return crmExtDetailService.insertColumn(param,nickName);
+    }
+
+    /**
+     * 修改字段扩展详情
+     */
+    @PreAuthorize("@ss.hasPermi('crm:detail:edit')")
+    @Log(title = "字段扩展详情", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public R edit(@RequestBody CrmExtDetailAddOrUpdateParam param)
+    {
+        String nickName = tokenService.getLoginUser(ServletUtils.getRequest()).getUser().getNickName();
+        return crmExtDetailService.updateColumn(param,nickName);
+    }
+
+    /**
+     * 删除字段扩展详情
+     */
+    @PreAuthorize("@ss.hasPermi('crm:detail:remove')")
+    @Log(title = "字段扩展详情", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{columnNames}")
+    public R remove(@PathVariable String[] columnNames)
+    {
+        String nickName = tokenService.getLoginUser(ServletUtils.getRequest()).getUser().getNickName();
+        return crmExtDetailService.deleteColumns(columnNames,nickName);
+    }
+}

+ 72 - 0
fs-admin/src/main/java/com/fs/crm/controller/CrmExtLogController.java

@@ -0,0 +1,72 @@
+package com.fs.crm.controller;
+
+import java.util.List;
+
+import com.fs.crm.domain.CrmExtLog;
+import com.fs.crm.service.ICrmExtLogService;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import com.fs.common.annotation.Log;
+import com.fs.common.core.controller.BaseController;
+import com.fs.common.core.domain.AjaxResult;
+import com.fs.common.enums.BusinessType;
+import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.common.core.page.TableDataInfo;
+
+/**
+ * 修改字段扩展日志Controller
+ *
+ * @author fs
+ * @date 2025-02-17
+ */
+@RestController
+@RequestMapping("/crm/log")
+public class CrmExtLogController extends BaseController
+{
+    @Autowired
+    private ICrmExtLogService crmExtLogService;
+
+    /**
+     * 查询修改字段扩展日志列表
+     */
+    @PreAuthorize("@ss.hasPermi('crm:log:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(CrmExtLog crmExtLog)
+    {
+        startPage();
+        List<CrmExtLog> list = crmExtLogService.selectCrmExtLogList(crmExtLog);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出修改字段扩展日志列表
+     */
+    @PreAuthorize("@ss.hasPermi('crm:log:export')")
+    @Log(title = "修改字段扩展日志", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(CrmExtLog crmExtLog)
+    {
+        List<CrmExtLog> list = crmExtLogService.selectCrmExtLogList(crmExtLog);
+        ExcelUtil<CrmExtLog> util = new ExcelUtil<CrmExtLog>(CrmExtLog.class);
+        return util.exportExcel(list, "log");
+    }
+
+    /**
+     * 获取修改字段扩展日志详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('crm:log:query')")
+    @GetMapping(value = "/{logId}")
+    public AjaxResult getInfo(@PathVariable("logId") Long logId)
+    {
+        return AjaxResult.success(crmExtLogService.selectCrmExtLogById(logId));
+    }
+
+}

+ 45 - 0
fs-admin/src/main/java/com/fs/crm/task/CrmTask.java

@@ -0,0 +1,45 @@
+package com.fs.crm.task;
+
+import com.fs.crm.service.ICrmCustomerService;
+import com.fs.crm.service.ICrmCustomerVisitService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+@Component("crmTask")
+public class CrmTask {
+
+    @Autowired
+    private ICrmCustomerService crmCustomerService;
+    @Autowired
+    private ICrmCustomerVisitService customerVisitService;
+
+    /**
+     * 回收线索
+     * 生产环境 凌晨12点执行
+     * 测试环境 每10秒执行一次 0/10 * * * * ?
+     */
+    public void recoveryClue() {
+        crmCustomerService.recoveryClue();
+    }
+
+    /**
+     * 跟进提醒
+     * 生产环境 每天凌晨查询当天需要跟进的记录
+     * 测试环境 每10秒执行一次 0/10 * * * * ?
+     */
+    public void followupNotice() {
+        customerVisitService.followupNotice();
+    }
+
+    /**
+     * 已存在商机未跟进回收公海
+     * 生产环境 每天凌晨未跟进商机客户
+     * 测试环境 每10秒执行一次 0/10 * * * * ?
+     */
+    public void recoveryBusiness() {
+        customerVisitService.recoveryBusiness();
+    }
+
+
+
+}

+ 40 - 0
fs-company-app/src/main/java/com/fs/app/controller/app/ImController.java

@@ -0,0 +1,40 @@
+package com.fs.app.controller.app;
+
+import com.fs.common.core.domain.R;
+import com.fs.im.dto.OpenImResponseDTO;
+import com.fs.im.service.OpenIMService;
+import io.swagger.annotations.Api;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * @description: im 相关接口
+ * @author: Xgb
+ * @createDate: 2026/4/3
+ * @version: 1.0
+ */
+@Api("im 相关接口")
+@RestController
+@RequestMapping(value = "/app/im")
+@Slf4j
+public class ImController {
+
+    @Autowired
+    private OpenIMService openIMService;
+    /**
+     * @Description: 查询用户信息
+     * @Param:
+     * @Return:
+     * @Author xgb
+     * @Date 2026/4/3 15:45
+     */
+    @GetMapping("/getUserInfo")
+    public R getUserInfo(String id) {
+        OpenImResponseDTO responseDTO = openIMService.getUserInfo(id);
+        return R.ok().put("data",responseDTO);
+    }
+
+}

+ 117 - 12
fs-ipad-task/src/main/java/com/fs/app/service/IpadSendServer.java

@@ -43,6 +43,7 @@ import com.fs.qwApi.param.QwExternalContactHParam;
 import com.fs.sop.domain.QwSopLogs;
 import com.fs.sop.service.IQwSopLogsService;
 import com.fs.sop.service.impl.QwSopLogsServiceImpl;
+import com.fs.system.domain.SysConfig;
 import com.fs.system.service.ISysConfigService;
 import com.fs.wxwork.dto.*;
 import lombok.AllArgsConstructor;
@@ -55,6 +56,8 @@ import java.time.LocalDateTime;
 import java.time.ZoneId;
 import java.util.*;
 import java.util.concurrent.TimeUnit;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 import java.util.stream.Collectors;
 
 @Slf4j
@@ -87,6 +90,8 @@ public class IpadSendServer {
     private static final List<String> PROJECT_NAMES = Arrays.asList("济南联志健康", "北京存在文化","宽益堂");
     private final LiveWatchLogMapper liveWatchLogMapper;
 
+    private static final Pattern LINK_PATTERN = Pattern.compile("link=([^&]+)");
+
     private void sendMiniProgram(BaseVo vo, QwSopCourseFinishTempSetting.Setting content, Map<String, FsCoursePlaySourceConfig> miniMap, Long companyId) {
         // 发送参数原本的appid
         String appid = content.getMiniprogramAppid();
@@ -704,12 +709,28 @@ public class IpadSendServer {
                     }
                     sendMiniProgram(vo, content, miniMap, qwUser.getCompanyId());
                 case "14":
+                    log.error("进入到福袋》》》》》》》");
                     // 记录福袋发送记录
-                    Long businessId = addLuckyBagCollectRecord(qwUser, content,qwSopLogs);
-                    if (ObjectUtil.isEmpty(businessId)) {
+                    LuckyBag luckyBag = luckyBagMapper.selectLuckyBagById(content.getLuckyBagId());
+                    if(ObjectUtil.isNotEmpty(luckyBag)&&luckyBag.getDataStatus().equals("0")){
+                        qwSopLogsService.updateQwSopLogsByWatchLogType(qwSopLogs.getId(), "福袋配置被禁用");
+                    }
+                    else if (ObjectUtil.isNotEmpty(qwSopLogs.getFsUserId())){
+                        //获取配置并校验
+                        SysConfig sysConfig = configService.selectConfigByConfigKey("luckyBag.config");
+                        if (ObjectUtil.isEmpty(sysConfig)) {
+                            qwSopLogsService.updateQwSopLogsByWatchLogType(qwSopLogs.getId(), "福袋配置不存在");
+                        }
+                    }
+                    try{
+
+                        Long businessId = addLuckyBagCollectRecord(luckyBag,qwUser, content,qwSopLogs);
+                        content.setMiniprogramPage(quickProcess(content.getMiniprogramPage(),businessId));
+                        // 福袋
+                        sendMiniProgram(vo, content, miniMap);
+                    }catch (Exception e){
                         qwSopLogsService.updateQwSopLogsByWatchLogType(qwSopLogs.getId(), "福袋发放失败,请重新发送!");
                     }
-                    sendMiniProgram(vo, content, miniMap, qwUser.getCompanyId());
                 case "17":
                     // 小程序H5看课
                     sendMiniProgram(vo, content, miniMap);
@@ -865,20 +886,13 @@ public class IpadSendServer {
         ipadSendUtils.loginOut(user.getUid(), user.getServerId());
     }
 
-    /**
-     * @Description: 生成福袋记录
-     * @Param:
-     * @Return:
-     * @Author xgb
-     * @Date 2026/2/4 11:39
-     */
-    private Long addLuckyBagCollectRecord(QwUser qwUser, QwSopCourseFinishTempSetting.Setting content, QwSopLogs qwSopLogs) {
+
+    public Long addLuckyBagCollectRecord(LuckyBag luckyBag, QwUser qwUser, QwSopCourseFinishTempSetting.Setting content, QwSopLogs qwSopLogs) {
 
         try{
             String json = configService.selectConfigByKey("course.config");
             CourseConfig config = JSON.parseObject(json, CourseConfig.class);
             Date updateTime = createUpdateTime(content, new Date(), config);
-            LuckyBag luckyBag = luckyBagMapper.selectLuckyBagById(content.getLuckyBagId());
             String companyUserId = String.valueOf(qwUser.getCompanyUserId()).trim();
             String companyId = String.valueOf(qwUser.getCompanyId()).trim();
             Long businessId = addLuckyBagCollectRecord(qwUser,luckyBag,content,qwSopLogs,updateTime,companyUserId,companyId,content.getChatId());
@@ -889,6 +903,97 @@ public class IpadSendServer {
         }
     }
 
+
+
+    /**
+     * 优化版快速处理方法
+     */
+    public static String quickProcess(String miniprogramPage, Long businessId) {
+        if (miniprogramPage == null || businessId == null) {
+            return miniprogramPage;
+        }
+
+        try {
+            Matcher matcher = LINK_PATTERN.matcher(miniprogramPage);
+            if (matcher.find()) {
+                String encodedLink = matcher.group(1);
+
+                // 第一次URL解码
+                String decodedLink = java.net.URLDecoder.decode(encodedLink, "UTF-8");
+
+                // 处理多次转义的情况:如果解码后仍有转义字符,继续解码
+                String jsonStr = decodedLink;
+                while (jsonStr.contains("\\\"") || jsonStr.contains("\\\\")) {
+                    // 检查是否是合法的JSON格式
+                    if (jsonStr.trim().startsWith("{") && jsonStr.trim().endsWith("}")) {
+                        break; // 已经是JSON对象字符串,不需要再解码
+                    }
+                    // 尝试再次解码
+                    try {
+                        jsonStr = java.net.URLDecoder.decode(jsonStr, "UTF-8");
+                    } catch (Exception e) {
+                        // 如果解码失败,说明不是URL编码,跳出循环
+                        break;
+                    }
+                }
+
+                // 清理JSON字符串中的转义字符
+                jsonStr = cleanJsonString(jsonStr);
+
+                // 解析JSON
+                JSONObject linkJson = JSON.parseObject(jsonStr);
+
+                // 检查并添加businessId
+                if (!linkJson.containsKey("businessId") ||
+                        linkJson.getString("businessId") == null ||
+                        linkJson.getString("businessId").isEmpty()) {
+
+                    linkJson.put("businessId", businessId);
+
+                    // 重新编码(只编码一次)
+                    String updatedJson = linkJson.toJSONString();
+                    String updatedEncoded = java.net.URLEncoder.encode(updatedJson, "UTF-8");
+
+                    // 替换原link参数
+                    return miniprogramPage.replace(
+                            "link=" + encodedLink,
+                            "link=" + updatedEncoded
+                    );
+                }
+            }
+            return miniprogramPage;
+        } catch (Exception e) {
+            // 记录错误日志,这里可以替换为你的日志框架
+            System.err.println("处理小程序页面链接失败: " + e.getMessage());
+            e.printStackTrace();
+            // 根据业务需求,可以选择抛出异常或返回原字符串
+            // throw new RuntimeException("处理小程序页面链接失败", e);
+            return miniprogramPage; // 失败时返回原字符串,避免影响主流程
+        }
+    }
+
+    /**
+     * 清理JSON字符串中的多余转义
+     */
+    private static String cleanJsonString(String jsonStr) {
+        if (jsonStr == null || jsonStr.isEmpty()) {
+            return jsonStr;
+        }
+
+        String result = jsonStr;
+
+        // 移除多余的转义
+        result = result.replace("\\\"", "\"");
+        result = result.replace("\\\\", "\\");
+
+        // 处理开头和结尾的转义引号
+        if (result.startsWith("\"") && result.endsWith("\"")) {
+            result = result.substring(1, result.length() - 1);
+        }
+
+        return result;
+    }
+
     /**
      * 过期时间
      *

+ 20 - 2
fs-qw-task/src/main/java/com/fs/app/taskService/impl/SopLogsTaskServiceImpl.java

@@ -1891,8 +1891,26 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
 
                     break;
                 case "14":
-                    String linkBy = createActivityLinkByMiniApp(setting, sopLogs, sopLogs.getCorpId(), new Date(), courseId, videoId,
-                            qwUserId, companyUserId, companyId, cachedCourseConfig, logVo.getChatId());
+                    String linkBy = createActivityLinkByMiniApp(setting,sopLogs,sopLogs.getCorpId(),new Date(), courseId, videoId,
+                            qwUserId, companyUserId, companyId,cachedCourseConfig,logVo.getChatId());
+
+                    if(sopLogs.getSendType()==1){
+                        setting.setMiniprogramAppid(miniAppId);
+                    }
+                    else {
+                        //算主备小程序
+                        String finalAppId = getAppIdFromMiniMap(miniMap, companyId, sendMsgType, grade);
+
+                        if (StringUtil.strIsNullOrEmpty(finalAppId)) {
+                            finalAppId = miniAppId;
+                        }
+
+                        if (!StringUtil.strIsNullOrEmpty(finalAppId)) {
+                            setting.setMiniprogramAppid(finalAppId);
+                        } else {
+                            log.error("公司的小程序id为空:采用了前端传的固定值" + sopLogs.getSopId());
+                        }
+                    }
                     setting.setMiniprogramTitle("福袋发放");
                     setting.setMiniprogramPage(linkBy);
                     setting.setContentType("14");

+ 2 - 0
fs-service/src/main/java/com/fs/company/mapper/CompanyConfigMapper.java

@@ -86,4 +86,6 @@ public interface CompanyConfigMapper
             "is_del = 0\n" +
             "and FIND_IN_SET(#{companyId},set_company_ids)")
     List<CompanyMiniAppVO> getCompanyMiniAppList(@Param("companyId") Long companyId);
+
+    List<CompanyConfig> selectListByKey(String configKey);
 }

+ 2 - 0
fs-service/src/main/java/com/fs/company/service/ICompanyConfigService.java

@@ -87,4 +87,6 @@ public interface ICompanyConfigService
      * @return
      */
     R saveCompanyMiniApp(SaveCompanyMiniAppParam param);
+
+    List<CompanyConfig> selectListByKey(String configKey);
 }

+ 6 - 0
fs-service/src/main/java/com/fs/company/service/impl/CompanyConfigServiceImpl.java

@@ -20,6 +20,7 @@ import org.springframework.data.redis.core.RedisTemplate;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
+import java.util.Collections;
 import java.util.Date;
 import java.util.List;
 import java.util.concurrent.ConcurrentHashMap;
@@ -261,4 +262,9 @@ public class CompanyConfigServiceImpl implements ICompanyConfigService
         }
         return R.ok();
     }
+
+    @Override
+    public List<CompanyConfig> selectListByKey(String configKey) {
+        return companyConfigMapper.selectListByKey(configKey);
+    }
 }

+ 4 - 0
fs-service/src/main/java/com/fs/company/service/impl/CompanyUserServiceImpl.java

@@ -22,6 +22,7 @@ import com.fs.company.param.CompanyUserCodeParam;
 import com.fs.company.param.CompanyUserQwParam;
 import com.fs.company.service.*;
 import com.fs.company.vo.*;
+import com.fs.config.cloud.CloudHostProper;
 import com.fs.course.service.IFsUserCompanyUserService;
 import com.fs.his.domain.FsUser;
 import com.fs.his.mapper.FsUserMapper;
@@ -136,6 +137,8 @@ public class CompanyUserServiceImpl implements ICompanyUserService
     @Autowired
     private  CompanyFsUserMapper companyFsUserMapper;
 
+//    @Autowired
+//    private CloudHostProper cloudHostProper;
 
     /**
      * 查询物业公司管理员信息
@@ -1045,6 +1048,7 @@ public class CompanyUserServiceImpl implements ICompanyUserService
     public R getBindInfo(Long companyUserId) {
         //链接
         String url = bindBaseUrl + companyUserId;
+//        String url = cloudHostProper.getBindBaseUrl() + companyUserId;
         if (CloudHostUtils.hasCloudHostName("木易华康")) {
             url = "https://h5api.muyikp.com/bindcompanyuser?companyUserId=" + companyUserId;
         }

+ 7 - 0
fs-service/src/main/java/com/fs/config/cloud/CloudHostProper.java

@@ -13,6 +13,13 @@ public class CloudHostProper {
     @Value("${headerImg.imgUrl}")
     private String headerImg;
 
+
+    @Value("${headerImg.download_poster_url}")
+    private String downloadPosterUrl;
+
+//    @Value("${headerImg.bindBaseUrl}")
+//    private String bindBaseUrl;
+
     @Value("${cloud_host.projectCode}")
     private String projectCode;
 

+ 1 - 0
fs-service/src/main/java/com/fs/course/domain/FsCourseRedPacketLog.java

@@ -77,4 +77,5 @@ public class FsCourseRedPacketLog extends BaseEntity
     private String mchId;
 
     private Integer watchType;
+
 }

+ 1 - 0
fs-service/src/main/java/com/fs/course/param/FsCourseSendRewardUParam.java

@@ -37,6 +37,7 @@ public class FsCourseSendRewardUParam implements Serializable
     @NotBlank(message = "小程序参数不能为空")
     private String appId; //前端传来的小程序的appid
     private Integer rewardType; //奖励类型 1红包 2积分 3随机转盘 4保底转盘
+
     private String code;
 
 }

+ 3 - 0
fs-service/src/main/java/com/fs/course/service/IFsUserCourseVideoService.java

@@ -284,5 +284,8 @@ public interface IFsUserCourseVideoService extends IService<FsUserCourseVideo> {
      */
     R createAppFd(LuckyBagCollectRecord param);
 
+    /**
+    * app发红包
+    */
     R withdrawal(FsCourseSendRewardUParam param);
 }

+ 37 - 40
fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseVideoServiceImpl.java

@@ -179,7 +179,6 @@ public class FsUserCourseVideoServiceImpl extends ServiceImpl<FsUserCourseVideoM
     @Autowired
     private FsStoreProductScrmMapper fsStoreProductScrmMapper;
 
-
     @Autowired
     private RedissonClient redissonClient;
     @Autowired
@@ -4815,6 +4814,43 @@ public class FsUserCourseVideoServiceImpl extends ServiceImpl<FsUserCourseVideoM
         }
     }
 
+    /**
+     * 用户提现
+     * @param param
+     */
+    @Override
+    @Transactional
+    public R withdrawal(FsCourseSendRewardUParam param) {
+        Long userId = param.getUserId();
+        // 生成锁的key,基于用户ID和视频ID确保同一用户同一视频的请求被锁定
+        String lockKey = "reward_red_lock:user:" + userId;
+        RLock lock = redissonClient.getLock(lockKey);
+
+        try {
+            // 尝试获取锁,等待时间5秒,锁过期时间30秒
+            boolean isLocked = lock.tryLock(5, 300, TimeUnit.SECONDS);
+            if (!isLocked) {
+                logger.warn("获取锁失败,用户ID:{}", userId);
+                return R.error("操作频繁,请稍后再试!");
+            }
+
+            logger.info("成功获取锁,开始处理奖励发放,用户ID:{}", userId);
+            return executeWithdrawal(param);
+
+        } catch (InterruptedException e) {
+            Thread.currentThread().interrupt();
+            logger.error("获取锁被中断,用户ID:{}", userId, e);
+            return R.error("系统繁忙,请重试!");
+        } finally {
+            // 释放锁
+            if (lock.isHeldByCurrentThread()) {
+                lock.unlock();
+                logger.info("释放锁成功,用户ID:{}", userId);
+            }
+        }
+
+
+    }
 
     private Map<String,Object> addLuckyBagCollectRecord(Long luckyBagId,
                                                         Long userId,
@@ -4921,45 +4957,6 @@ public class FsUserCourseVideoServiceImpl extends ServiceImpl<FsUserCourseVideoM
     }
 
 
-
-
-    /**
-     * 用户提现
-     * @param param
-     * @return
-     */
-    @Override
-    @Transactional
-    public R withdrawal(FsCourseSendRewardUParam param) {
-        Long userId = param.getUserId();
-        // 生成锁的key,基于用户ID和视频ID确保同一用户同一视频的请求被锁定
-        String lockKey = "reward_red_lock:user:" + userId;
-        RLock lock = redissonClient.getLock(lockKey);
-
-        try {
-            // 尝试获取锁,等待时间5秒,锁过期时间30秒
-            boolean isLocked = lock.tryLock(5, 300, TimeUnit.SECONDS);
-            if (!isLocked) {
-                logger.warn("获取锁失败,用户ID:{}", userId);
-                return R.error("操作频繁,请稍后再试!");
-            }
-
-            logger.info("成功获取锁,开始处理奖励发放,用户ID:{}", userId);
-            return executeWithdrawal(param);
-
-        } catch (InterruptedException e) {
-            Thread.currentThread().interrupt();
-            logger.error("获取锁被中断,用户ID:{}", userId, e);
-            return R.error("系统繁忙,请重试!");
-        } finally {
-            // 释放锁
-            if (lock.isHeldByCurrentThread()) {
-                lock.unlock();
-                logger.info("释放锁成功,用户ID:{}", userId);
-            }
-        }
-    }
-
     private R executeWithdrawal(FsCourseSendRewardUParam param) {
         log.info("进入用户判断");
         FsUser user = fsUserMapper.selectFsUserByUserId(param.getUserId());

+ 339 - 0
fs-service/src/main/java/com/fs/crm/domain/CrmBusiness.java

@@ -0,0 +1,339 @@
+package com.fs.crm.domain;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fs.common.annotation.Excel;
+import com.fs.common.core.domain.BaseEntity;
+import org.apache.commons.lang3.builder.ToStringBuilder;
+import org.apache.commons.lang3.builder.ToStringStyle;
+
+import java.util.Date;
+
+/**
+ * 商机对象 crm_business
+ *
+ * @author fs
+ * @date 2025-01-16
+ */
+public class CrmBusiness extends BaseEntity
+{
+    private static final long serialVersionUID = 1L;
+
+    /** 商机id */
+    private Long businessId;
+
+    /** 客户id */
+    @Excel(name = "客户id")
+    private Long customerId;
+
+    /** 线索来源
+    0企查询
+    1企查查
+    2领鸟云
+    3其他 */
+    @Excel(name = "线索来源 0企查询 1企查查 2领鸟云 3其他")
+    private Integer source;
+
+    /** 客户经理 */
+    @Excel(name = "客户经理")
+    private String manager;
+
+    /** 公司名称(只填公司全称,个人用户就填名字) */
+    @Excel(name = "公司名称(只填公司全称,个人用户就填名字)")
+    private String companyName;
+
+
+
+    /** 接口人角色 0老板,1采购,2财务,3技术负责人,4一般技术人员,5项目负责人,6其他 */
+    @Excel(name = "接口人角色 0老板,1采购,2财务,3技术负责人,4一般技术人员,5项目负责人,6其他")
+    private Integer contactRole;
+
+    /** 业务场景网站,电商,游戏,小程序,APP,OA,ERP,CRM,物联网,AI人工智能,国产化,数字人,内部办公系统,等保,其他 */
+    @Excel(name = "业务场景")
+    private String businessScenario;
+
+    /** 方案涉及的产品 */
+    @Excel(name = "方案涉及的产品")
+    private String product;
+
+    /** 采购周期(单位:天) */
+    @Excel(name = "采购周期(单位:天)")
+    private Long purchaseCycle;
+
+    /** 跟进状态:0跟进中,1已流失,2已赢单,3待验证 */
+    @Excel(name = "跟进状态:0跟进中,1已流失,2已赢单,3待验证")
+    private Integer businessStatus;
+
+
+    @Excel(name = "备注")
+    private String remark;
+
+    /** 项目阶段
+    A:100%转商,
+    B:90%合同下单&流程,
+    C:80%确认商务阶段,
+    D:70%确认产品技术方案及配置,
+    E:60%POC测试验证,
+    F:50%项目沟通&立项
+    G:40%前期咨询,
+    L:丢单 */
+    @Excel(name = "项目阶段")
+    private String projectPhase;
+
+    /** 意向等级
+    40%~50%:低意向
+    60%~70%:中意向
+    80%~100%:高意向 */
+    @Excel(name = "意向等级")
+    private Integer level;
+
+    /** 是否绑定BP账号:0未注册 1已注册未绑定 2不明确 3已绑定 */
+    @Excel(name = "是否绑定BP账号:0未注册 1已注册未绑定 2不明确 3已绑定")
+    private Integer isBp;
+
+    /** BP账户 */
+    @Excel(name = "BP账户")
+    private String bpAccount;
+
+    /** 预计成单时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd")
+    @Excel(name = "预计成单时间", width = 30, dateFormat = "yyyy-MM-dd")
+    private Date preTime;
+
+    /** 预计付费(元) */
+    @Excel(name = "预计付费(元)")
+    private Float preMoney;
+
+    /** 下次跟进时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd")
+    @Excel(name = "下次跟进时间", width = 30, dateFormat = "yyyy-MM-dd")
+    private Date nextTime;
+
+
+    //创建人Id
+    private Long createId;
+
+    //所属公司id
+    private Long companyId;
+
+
+
+    private Integer isShow;
+
+
+    public void setBusinessId(Long businessId)
+    {
+        this.businessId = businessId;
+    }
+
+    public Long getBusinessId()
+    {
+        return businessId;
+    }
+    public void setCustomerId(Long customerId)
+    {
+        this.customerId = customerId;
+    }
+
+    public Long getCustomerId()
+    {
+        return customerId;
+    }
+    public void setSource(Integer source)
+    {
+        this.source = source;
+    }
+
+    public Integer getSource()
+    {
+        return source;
+    }
+    public void setManager(String manager)
+    {
+        this.manager = manager;
+    }
+
+    public String getManager()
+    {
+        return manager;
+    }
+    public void setCompanyName(String companyName)
+    {
+        this.companyName = companyName;
+    }
+
+    public String getCompanyName()
+    {
+        return companyName;
+    }
+    public void setContactRole(Integer contactRole)
+    {
+        this.contactRole = contactRole;
+    }
+
+    public Integer getContactRole()
+    {
+        return contactRole;
+    }
+    public void setBusinessScenario(String businessScenario)
+    {
+        this.businessScenario = businessScenario;
+    }
+
+    public String getBusinessScenario()
+    {
+        return businessScenario;
+    }
+    public void setProduct(String product)
+    {
+        this.product = product;
+    }
+
+    @Override
+    public String getRemark() {
+        return remark;
+    }
+
+    @Override
+    public void setRemark(String remark) {
+        this.remark = remark;
+    }
+
+    public String getProduct()
+    {
+        return product;
+    }
+    public void setPurchaseCycle(Long purchaseCycle)
+    {
+        this.purchaseCycle = purchaseCycle;
+    }
+
+    public Long getPurchaseCycle()
+    {
+        return purchaseCycle;
+    }
+    public void setBusinessStatus(Integer businessStatus)
+    {
+        this.businessStatus = businessStatus;
+    }
+
+    public Integer getBusinessStatus()
+    {
+        return businessStatus;
+    }
+    public void setProjectPhase(String projectPhase)
+    {
+        this.projectPhase = projectPhase;
+    }
+
+    public String getProjectPhase()
+    {
+        return projectPhase;
+    }
+    public void setLevel(Integer level)
+    {
+        this.level = level;
+    }
+
+    public Integer getLevel()
+    {
+        return level;
+    }
+    public void setIsBp(Integer isBp)
+    {
+        this.isBp = isBp;
+    }
+
+    public Integer getIsBp()
+    {
+        return isBp;
+    }
+    public void setBpAccount(String bpAccount)
+    {
+        this.bpAccount = bpAccount;
+    }
+
+    public String getBpAccount()
+    {
+        return bpAccount;
+    }
+    public void setPreTime(Date preTime)
+    {
+        this.preTime = preTime;
+    }
+
+    public Date getPreTime()
+    {
+        return preTime;
+    }
+    public void setPreMoney(Float preMoney)
+    {
+        this.preMoney = preMoney;
+    }
+
+    public Float getPreMoney()
+    {
+        return preMoney;
+    }
+    public void setNextTime(Date nextTime)
+    {
+        this.nextTime = nextTime;
+    }
+
+    public Date getNextTime()
+    {
+        return nextTime;
+    }
+
+
+    public Long getCreateId() {
+        return createId;
+    }
+
+    public void setCreateId(Long createId) {
+        this.createId = createId;
+    }
+
+    public Long getCompanyId() {
+        return companyId;
+    }
+
+    public void setCompanyId(Long companyId) {
+        this.companyId = companyId;
+    }
+
+    public Integer getIsShow() {
+        return isShow;
+    }
+
+    public void setIsShow(Integer isShow) {
+        this.isShow = isShow;
+    }
+
+    @Override
+    public String toString() {
+        return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE)
+            .append("businessId", getBusinessId())
+            .append("customerId", getCustomerId())
+            .append("source", getSource())
+            .append("manager", getManager())
+            .append("companyName", getCompanyName())
+            .append("contactRole", getContactRole())
+            .append("businessScenario", getBusinessScenario())
+            .append("product", getProduct())
+            .append("purchaseCycle", getPurchaseCycle())
+            .append("businessStatus", getBusinessStatus())
+            .append("remark", getRemark())
+            .append("projectPhase", getProjectPhase())
+            .append("level", getLevel())
+            .append("isBp", getIsBp())
+            .append("bpAccount", getBpAccount())
+            .append("preTime", getPreTime())
+            .append("preMoney", getPreMoney())
+            .append("nextTime", getNextTime())
+            .append("createTime", getCreateTime())
+            .append("updateTime", getUpdateTime())
+            .append("createBy", getCreateBy())
+            .append("createId", getCreateId())
+            .toString();
+    }
+}

+ 17 - 0
fs-service/src/main/java/com/fs/crm/domain/CrmCustomer.java

@@ -195,4 +195,21 @@ public class CrmCustomer extends BaseEntity
      */
     private String traceId;
 
+    private String customerCompanyName;
+
+    private String isWx;
+
+    private String businessScenario;
+
+    private String product;
+
+    private String moneyRemark;
+    @JsonFormat(pattern = "yyyy-MM-dd")
+    private Date nextTime; //下次跟进时间
+
+    private Integer isPoolRule; //是否参加公海回收规则 1:是
+
+    @JsonFormat(pattern = "yyyy-MM-dd")
+    private Date sysVisitTime; //系统自建跟进时间
+
 }

+ 8 - 110
fs-service/src/main/java/com/fs/crm/domain/CrmCustomerContacts.java

@@ -2,15 +2,19 @@ package com.fs.crm.domain;
 
 import com.fs.common.annotation.Excel;
 import com.fs.common.core.domain.BaseEntity;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
 import org.apache.commons.lang3.builder.ToStringBuilder;
 import org.apache.commons.lang3.builder.ToStringStyle;
 
 /**
  * 客户联系人对象 crm_customer_contacts
- * 
+ *
  * @author fs
  * @date 2023-03-14
  */
+@EqualsAndHashCode(callSuper = true)
+@Data
 public class CrmCustomerContacts extends BaseEntity
 {
     private static final long serialVersionUID = 1L;
@@ -22,6 +26,9 @@ public class CrmCustomerContacts extends BaseEntity
     @Excel(name = "客户ID")
     private Long customerId;
 
+    /** 商机ID */
+    private Long businessId;
+
     /** 联系人名称 */
     @Excel(name = "联系人名称")
     private String name;
@@ -54,113 +61,4 @@ public class CrmCustomerContacts extends BaseEntity
     @Excel(name = "是否删除")
     private Long companyId;
 
-    public void setContactsId(Long contactsId) 
-    {
-        this.contactsId = contactsId;
-    }
-
-    public Long getContactsId() 
-    {
-        return contactsId;
-    }
-    public void setCustomerId(Long customerId) 
-    {
-        this.customerId = customerId;
-    }
-
-    public Long getCustomerId() 
-    {
-        return customerId;
-    }
-    public void setName(String name) 
-    {
-        this.name = name;
-    }
-
-    public String getName() 
-    {
-        return name;
-    }
-    public void setMobile(String mobile) 
-    {
-        this.mobile = mobile;
-    }
-
-    public String getMobile() 
-    {
-        return mobile;
-    }
-    public void setEmail(String email) 
-    {
-        this.email = email;
-    }
-
-    public String getEmail() 
-    {
-        return email;
-    }
-    public void setWeixin(String weixin) 
-    {
-        this.weixin = weixin;
-    }
-
-    public String getWeixin() 
-    {
-        return weixin;
-    }
-    public void setAddress(String address) 
-    {
-        this.address = address;
-    }
-
-    public String getAddress() 
-    {
-        return address;
-    }
-    public void setCreateUserId(Long createUserId) 
-    {
-        this.createUserId = createUserId;
-    }
-
-    public Long getCreateUserId() 
-    {
-        return createUserId;
-    }
-    public void setIsDel(Integer isDel) 
-    {
-        this.isDel = isDel;
-    }
-
-    public Integer getIsDel() 
-    {
-        return isDel;
-    }
-    public void setCompanyId(Long companyId) 
-    {
-        this.companyId = companyId;
-    }
-
-    public Long getCompanyId() 
-    {
-        return companyId;
-    }
-
-    @Override
-    public String toString() {
-        return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE)
-            .append("contactsId", getContactsId())
-            .append("customerId", getCustomerId())
-            .append("name", getName())
-            .append("mobile", getMobile())
-            .append("email", getEmail())
-            .append("weixin", getWeixin())
-            .append("address", getAddress())
-            .append("remark", getRemark())
-            .append("createUserId", getCreateUserId())
-            .append("createTime", getCreateTime())
-            .append("updateTime", getUpdateTime())
-            .append("isDel", getIsDel())
-            .append("companyId", getCompanyId())
-            .toString();
-    }
 }

+ 14 - 125
fs-service/src/main/java/com/fs/crm/domain/CrmCustomerVisit.java

@@ -3,6 +3,8 @@ package com.fs.crm.domain;
 import com.fasterxml.jackson.annotation.JsonFormat;
 import com.fs.common.annotation.Excel;
 import com.fs.common.core.domain.BaseEntity;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
 import org.apache.commons.lang3.builder.ToStringBuilder;
 import org.apache.commons.lang3.builder.ToStringStyle;
 
@@ -14,6 +16,8 @@ import java.util.Date;
  * @author fs
  * @date 2022-12-21
  */
+@EqualsAndHashCode(callSuper = true)
+@Data
 public class CrmCustomerVisit extends BaseEntity
 {
     private static final long serialVersionUID = 1L;
@@ -25,6 +29,11 @@ public class CrmCustomerVisit extends BaseEntity
     @Excel(name = "客户ID")
     private Long customerId;
 
+    /** 商机ID */
+    @Excel(name = "商机ID")
+    private Long businessId;
+
+
     /** 类型 1 电话 2 微信 */
     @Excel(name = "类型 1 电话 2 微信")
     private Long visitType;
@@ -65,129 +74,9 @@ public class CrmCustomerVisit extends BaseEntity
     @Excel(name = "客户跟进阶段")
     private Integer customerUserStatus;
 
-    public Integer getCustomerUserStatus() {
-        return customerUserStatus;
-    }
-
-    public void setCustomerUserStatus(Integer customerUserStatus) {
-        this.customerUserStatus = customerUserStatus;
-    }
-
-    public void setVisitId(Long visitId)
-    {
-        this.visitId = visitId;
-    }
-
-    public Long getVisitId()
-    {
-        return visitId;
-    }
-    public void setCustomerId(Long customerId)
-    {
-        this.customerId = customerId;
-    }
-
-    public Long getCustomerId()
-    {
-        return customerId;
-    }
-    public void setVisitType(Long visitType)
-    {
-        this.visitType = visitType;
-    }
-
-    public Long getVisitType()
-    {
-        return visitType;
-    }
-    public void setContent(String content)
-    {
-        this.content = content;
-    }
-
-    public String getContent()
-    {
-        return content;
-    }
-    public void setPhotos(String photos)
-    {
-        this.photos = photos;
-    }
-
-    public String getPhotos()
-    {
-        return photos;
-    }
-    public void setNextTime(Date nextTime)
-    {
-        this.nextTime = nextTime;
-    }
-
-    public Date getNextTime()
-    {
-        return nextTime;
-    }
-    public void setLng(String lng)
-    {
-        this.lng = lng;
-    }
-
-    public String getLng()
-    {
-        return lng;
-    }
-    public void setLat(String lat)
-    {
-        this.lat = lat;
-    }
-
-    public String getLat()
-    {
-        return lat;
-    }
-    public void setAddress(String address)
-    {
-        this.address = address;
-    }
-
-    public String getAddress()
-    {
-        return address;
-    }
-    public void setCompanyUserId(Long companyUserId)
-    {
-        this.companyUserId = companyUserId;
-    }
-
-    public Long getCompanyUserId()
-    {
-        return companyUserId;
-    }
-    public void setCompanyId(Long companyId)
-    {
-        this.companyId = companyId;
-    }
-
-    public Long getCompanyId()
-    {
-        return companyId;
-    }
-
-    @Override
-    public String toString() {
-        return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE)
-            .append("visitId", getVisitId())
-            .append("customerId", getCustomerId())
-            .append("visitType", getVisitType())
-            .append("content", getContent())
-            .append("photos", getPhotos())
-            .append("nextTime", getNextTime())
-            .append("lng", getLng())
-            .append("lat", getLat())
-            .append("address", getAddress())
-            .append("companyUserId", getCompanyUserId())
-            .append("createTime", getCreateTime())
-            .append("companyId", getCompanyId())
-            .toString();
-    }
+    /** 跟进类型 0:线索跟进 1:客户跟进 */
+    @Excel(name = "跟进类型 0:线索跟进 1:客户跟进 2:商机跟进")
+    private Integer type;
+
+    private Integer isShow;
 }

+ 65 - 0
fs-service/src/main/java/com/fs/crm/domain/CrmExtDetail.java

@@ -0,0 +1,65 @@
+package com.fs.crm.domain;
+
+import com.fs.common.annotation.Excel;
+import com.fs.common.core.domain.BaseEntity;
+import org.apache.commons.lang3.builder.ToStringBuilder;
+import org.apache.commons.lang3.builder.ToStringStyle;
+
+/**
+ * 字段扩展详情对象 crm_ext_detail
+ *
+ * @author fs
+ * @date 2025-02-17
+ */
+public class CrmExtDetail extends BaseEntity
+{
+    private static final long serialVersionUID = 1L;
+
+    /** 主键id */
+    private Long id;
+
+    /** 关联id */
+    @Excel(name = "关联id")
+    private Long correlateId;
+
+    /** 关联类型 */
+    @Excel(name = "关联类型")
+    private String correlateType;
+
+    public void setId(Long id)
+    {
+        this.id = id;
+    }
+
+    public Long getId()
+    {
+        return id;
+    }
+    public void setCorrelateId(Long correlateId)
+    {
+        this.correlateId = correlateId;
+    }
+
+    public Long getCorrelateId()
+    {
+        return correlateId;
+    }
+    public void setCorrelateType(String correlateType)
+    {
+        this.correlateType = correlateType;
+    }
+
+    public String getCorrelateType()
+    {
+        return correlateType;
+    }
+
+    @Override
+    public String toString() {
+        return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE)
+            .append("id", getId())
+            .append("correlateId", getCorrelateId())
+            .append("correlateType", getCorrelateType())
+            .toString();
+    }
+}

+ 136 - 0
fs-service/src/main/java/com/fs/crm/domain/CrmExtLog.java

@@ -0,0 +1,136 @@
+package com.fs.crm.domain;
+
+import com.fs.common.annotation.Excel;
+import com.fs.common.core.domain.BaseEntity;
+import org.apache.commons.lang3.builder.ToStringBuilder;
+import org.apache.commons.lang3.builder.ToStringStyle;
+
+/**
+ * 修改字段扩展日志对象 crm_ext_log
+ *
+ * @author fs
+ * @date 2025-02-17
+ */
+public class CrmExtLog extends BaseEntity
+{
+    private static final long serialVersionUID = 1L;
+
+    /** ID */
+    private Long logId;
+
+    /** 执行语句 */
+    @Excel(name = "执行语句")
+    private String executeSql;
+
+    /** 修改字段名 */
+    @Excel(name = "修改字段名")
+    private String columnName;
+//
+    /** 关联表字段Id */
+    @Excel(name = "关联表字段Id")
+    private String correlateTypeId;
+
+
+
+    /** 执行类型 */
+    @Excel(name = "执行类型")
+    private String type;
+
+    /** 备份表地址 */
+    @Excel(name = "备份表地址")
+    private String backupUrl;
+
+
+    @Excel(name = "执行结果")
+    private String result;
+
+    @Excel(name = "报错信息")
+    private String errorMsg;
+
+    public void setLogId(Long logId)
+    {
+        this.logId = logId;
+    }
+
+    public Long getLogId()
+    {
+        return logId;
+    }
+    public void setExecuteSql(String executeSql)
+    {
+        this.executeSql = executeSql;
+    }
+
+    public String getExecuteSql()
+    {
+        return executeSql;
+    }
+    public void setColumnName(String columnName)
+    {
+        this.columnName = columnName;
+    }
+
+    public String getColumnName()
+    {
+        return columnName;
+    }
+    public void setCorrelateTypeId(String correlateTypeId)
+    {
+        this.correlateTypeId = correlateTypeId;
+    }
+
+    public String getCorrelateTypeId()
+    {
+        return correlateTypeId;
+    }
+    public void setBackupUrl(String backupUrl)
+    {
+        this.backupUrl = backupUrl;
+    }
+
+    public String getBackupUrl()
+    {
+        return backupUrl;
+    }
+
+    public String getType() {
+        return type;
+    }
+
+    public void setType(String type) {
+        this.type = type;
+    }
+
+    public String getResult() {
+        return result;
+    }
+
+    public void setResult(String result) {
+        this.result = result;
+    }
+
+    public String getErrorMsg() {
+        return errorMsg;
+    }
+
+    public void setErrorMsg(String errorMsg) {
+        this.errorMsg = errorMsg;
+    }
+
+
+    @Override
+    public String toString() {
+        return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE)
+            .append("logId", getLogId())
+            .append("executeSql", getExecuteSql())
+            .append("columnName", getColumnName())
+            .append("correlateTypeId", getCorrelateTypeId())
+            .append("type", getType())
+            .append("backupUrl", getBackupUrl())
+            .append("createBy", getCreateBy())
+            .append("createTime", getCreateTime())
+            .append("result", getResult())
+            .append("errorMsg", getErrorMsg())
+            .toString();
+    }
+}

+ 95 - 0
fs-service/src/main/java/com/fs/crm/domain/CrmFollowUp.java

@@ -0,0 +1,95 @@
+package com.fs.crm.domain;
+
+import com.fs.common.annotation.Excel;
+import com.fs.common.core.domain.BaseEntity;
+import org.apache.commons.lang3.builder.ToStringBuilder;
+import org.apache.commons.lang3.builder.ToStringStyle;
+
+/**
+ * 跟进提醒对象 crm_follow_up
+ *
+ * @author fs
+ * @date 2025-04-15
+ */
+public class CrmFollowUp extends BaseEntity
+{
+    private static final long serialVersionUID = 1L;
+
+    /** $column.columnComment */
+    private Long id;
+
+    /** 客户ID */
+    @Excel(name = "客户ID")
+    private Long customerId;
+
+    /** 跟进记录id */
+    @Excel(name = "跟进记录id")
+    private Long visitId;
+
+    /** 销售ID */
+    @Excel(name = "销售ID")
+    private Long companyUserId;
+
+    /** 是否处理 0:未读 1:已处理 */
+    @Excel(name = "是否处理 0:未读 1:已处理")
+    private Integer isHandle;
+
+    public void setId(Long id)
+    {
+        this.id = id;
+    }
+
+    public Long getId()
+    {
+        return id;
+    }
+    public void setCustomerId(Long customerId)
+    {
+        this.customerId = customerId;
+    }
+
+    public Long getCustomerId()
+    {
+        return customerId;
+    }
+    public void setVisitId(Long visitId)
+    {
+        this.visitId = visitId;
+    }
+
+    public Long getVisitId()
+    {
+        return visitId;
+    }
+    public void setCompanyUserId(Long companyUserId)
+    {
+        this.companyUserId = companyUserId;
+    }
+
+    public Long getCompanyUserId()
+    {
+        return companyUserId;
+    }
+    public void setIsHandle(Integer isHandle)
+    {
+        this.isHandle = isHandle;
+    }
+
+    public Integer getIsHandle()
+    {
+        return isHandle;
+    }
+
+    @Override
+    public String toString() {
+        return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE)
+            .append("id", getId())
+            .append("customerId", getCustomerId())
+            .append("visitId", getVisitId())
+            .append("companyUserId", getCompanyUserId())
+            .append("isHandle", getIsHandle())
+            .append("createTime", getCreateTime())
+            .append("updateTime", getUpdateTime())
+            .toString();
+    }
+}

+ 6 - 1
fs-service/src/main/java/com/fs/crm/enums/CustomerLogEnum.java

@@ -13,7 +13,12 @@ public enum CustomerLogEnum {
     RECEIVE(3,"认领"),
     RECOVER(4,"回收"),
     ASSIGN_USER(5,"分配员工"),
-    CANCEL_ASSIGN(6,"撤销分配");
+    CANCEL_ASSIGN(6,"撤销分配"),
+    GAIN(7,"捞取未分配线索"),
+    CREATE(8,"创建客户"),
+    BUSINESS(9,"创建商机"),
+    DEL_BUSINESS(10,"删除商机"),
+    UPDATE_BUSINESS(11,"修改商机");
 
     private Integer value;
     private String desc;

+ 73 - 0
fs-service/src/main/java/com/fs/crm/mapper/CrmBusinessMapper.java

@@ -0,0 +1,73 @@
+package com.fs.crm.mapper;
+
+
+
+import com.fs.crm.domain.CrmBusiness;
+import com.fs.crm.vo.CrmBusinessListVO;
+import com.fs.crm.param.CrmBusinessQueryParam;
+import io.lettuce.core.dynamic.annotation.Param;
+
+import java.util.List;
+
+/**
+ * 商机Mapper接口
+ *
+ * @author fs
+ * @date 2025-01-16
+ */
+public interface CrmBusinessMapper
+{
+    /**
+     * 查询商机
+     *
+     * @param businessId 商机ID
+     * @return 商机
+     */
+    public CrmBusiness selectCrmBusinessById(Long businessId);
+
+    /**
+     * 查询商机列表
+     *
+     * @param param 商机
+     * @return 商机集合
+     */
+    public List<CrmBusinessListVO> selectCrmBusinessList(CrmBusinessQueryParam param);
+
+    /**
+     * 新增商机
+     *
+     * @param crmBusiness 商机
+     * @return 结果
+     */
+    public int insertCrmBusiness(CrmBusiness crmBusiness);
+
+    /**
+     * 修改商机
+     *
+     * @param crmBusiness 商机
+     * @return 结果
+     */
+    public int updateCrmBusiness(CrmBusiness crmBusiness);
+
+    /**
+     * 删除商机
+     *
+     * @param businessId 商机ID
+     * @return 结果
+     */
+    public int deleteCrmBusinessById(Long businessId);
+
+    /**
+     * 批量删除商机
+     *
+     * @param businessIds 需要删除的数据ID
+     * @return 结果
+     */
+    public int deleteCrmBusinessByIds(Long[] businessIds);
+
+    int updateCrmBusinessByCustomerId(CrmBusiness crmBusiness);
+
+    void updateBatchCrmBusiness(@Param("businessIds") List<Long> businessIds, @Param("maps") CrmBusiness crmBusiness);
+
+    CrmBusiness getLastByCustomerId(@Param("customerId")Long customerId);
+}

+ 2 - 0
fs-service/src/main/java/com/fs/crm/mapper/CrmCustomerContactsMapper.java

@@ -75,4 +75,6 @@ public interface CrmCustomerContactsMapper
             " order by contacts_id desc "+
             "</script>"})
     List<CrmCustomerContactsListQueryVO> selectCrmCustomerContactsListQuery(@Param("maps") CrmCustomerContactsListQueryParam param);
+
+    CrmCustomerContacts selectCrmCustomerContactsByMobile(String mobile);
 }

+ 6 - 0
fs-service/src/main/java/com/fs/crm/mapper/CrmCustomerMapper.java

@@ -997,4 +997,10 @@ public interface CrmCustomerMapper extends BaseMapper<CrmCustomer> {
     void insertCrmCustomerInfo(CrmCustomerInfo crmCustomerInfo);
 
     int updateCrmCustomerInfo(CrmCustomerInfo crmCustomerInfo);
+
+    Long countReceiveCustomer(@Param("userId")Long userId);
+
+    List<CrmCustomer> selectRecoveryClue(@Param("companyId") Long companyId,@Param("limitDay") Integer limitDay,@Param("businessLimit") Integer businessLimit);
+
+    List<CrmCustomer> selectRecoveryBusiness(@Param("companyId") Long companyId,@Param("limitDay")Integer limitDay);
 }

+ 13 - 0
fs-service/src/main/java/com/fs/crm/mapper/CrmCustomerVisitMapper.java

@@ -1,6 +1,7 @@
 package com.fs.crm.mapper;
 
 import com.alibaba.fastjson.JSONObject;
+import com.fs.crm.domain.CrmCustomer;
 import com.fs.crm.domain.CrmCustomerVisit;
 import com.fs.crm.param.CrmCustomerStatisticsParam;
 import com.fs.crm.param.CrmCustomerVisitListParam;
@@ -198,4 +199,16 @@ public interface CrmCustomerVisitMapper
             " order by v.visit_id desc "+
             "</script>"})
     List<CrmCustomerVisitExportVO> selectCrmCustomerVisitExportListVO(@Param("maps") CrmCustomerVisitListParam param);
+
+    CrmCustomerVisit selectNewByBusinessId(@Param("businessId")Long businessId);
+
+    CrmCustomerVisit getLastByCustomerId(@Param("customerId")Long customerId);
+
+    List<CrmCustomer> selectRecoveryClue(@Param("companyId") Long companyId, @Param("limitDay") Integer limitDay);
+
+    List<CrmCustomerVisit> selectNextTimeList();
+
+
+    List<CrmCustomer> selectVisitTimeout();
+
 }

+ 88 - 0
fs-service/src/main/java/com/fs/crm/mapper/CrmExtDetailMapper.java

@@ -0,0 +1,88 @@
+package com.fs.crm.mapper;
+
+import com.fs.crm.domain.CrmExtDetail;
+import com.fs.crm.param.CrmExtDetailAddOrUpdateParam;
+import com.fs.crm.vo.CrmExtDetailVo;
+import com.fs.watch.param.BaseQueryParam;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 字段扩展详情Mapper接口
+ *
+ * @author fs
+ * @date 2025-02-17
+ */
+public interface CrmExtDetailMapper
+{
+    /**
+     * 查询字段扩展详情
+     *
+     * @param id 字段扩展详情ID
+     * @return 字段扩展详情
+     */
+    public CrmExtDetail selectCrmExtDetailById(Long id);
+
+    /**
+     * 查询字段扩展详情列表
+     *
+     * @param crmExtDetail 字段扩展详情
+     * @return 字段扩展详情集合
+     */
+    public List<CrmExtDetail> selectCrmExtDetailList(CrmExtDetail crmExtDetail);
+
+    /**
+     * 新增字段扩展详情
+     *
+     * @param columns 字段扩展详情
+     * @return 结果
+     */
+    public int insertCrmExtDetail(@Param("columns") List<String> columns,@Param("values")List<Object> values);
+
+    /**
+     * 修改字段扩展详情
+     *
+     * @param crmExtDetail 字段扩展详情
+     * @return 结果
+     */
+    public int updateCrmExtDetail(@Param("crmExtDetail") Map<String,Object> crmExtDetail,@Param("id") int id);
+
+    /**
+     * 删除字段扩展详情
+     *
+     * @param id 字段扩展详情ID
+     * @return 结果
+     */
+    public int deleteCrmExtDetailById(Long id);
+
+    /**
+     * 批量删除字段扩展详情
+     *
+     * @param ids 需要删除的数据ID
+     * @return 结果
+     */
+    public int deleteCrmExtDetailByIds(Long[] ids);
+
+    List<CrmExtDetailVo> getTableColumnsMetadata(BaseQueryParam param);
+
+    /**
+     * 查询是否存在该字段
+     * @param fieldName
+     * @return
+     */
+    Integer checkIfFieldExists(String fieldName);
+
+    void insertColumn(CrmExtDetailAddOrUpdateParam param);
+
+    void deleteColumns(@Param("columnNames") String[] columnNames);
+
+    void updateColumn(CrmExtDetailAddOrUpdateParam param);
+
+    List<CrmExtDetailVo> getTableColumnsMetaList(BaseQueryParam param);
+
+    Map<String,Object> selectCrmExtDetailByCondition(Map<String, Object> map);
+
+    void deleteByCorrelateIdAndType(@Param("customerIds") Long[] customerIds, @Param("type")String type);
+}

+ 62 - 0
fs-service/src/main/java/com/fs/crm/mapper/CrmExtLogMapper.java

@@ -0,0 +1,62 @@
+package com.fs.crm.mapper;
+
+import com.fs.crm.domain.CrmExtLog;
+
+import java.util.List;
+
+/**
+ * 修改字段扩展日志Mapper接口
+ *
+ * @author fs
+ * @date 2025-02-17
+ */
+public interface CrmExtLogMapper
+{
+    /**
+     * 查询修改字段扩展日志
+     *
+     * @param logId 修改字段扩展日志ID
+     * @return 修改字段扩展日志
+     */
+    public CrmExtLog selectCrmExtLogById(Long logId);
+
+    /**
+     * 查询修改字段扩展日志列表
+     *
+     * @param crmExtLog 修改字段扩展日志
+     * @return 修改字段扩展日志集合
+     */
+    public List<CrmExtLog> selectCrmExtLogList(CrmExtLog crmExtLog);
+
+    /**
+     * 新增修改字段扩展日志
+     *
+     * @param crmExtLog 修改字段扩展日志
+     * @return 结果
+     */
+    public int insertCrmExtLog(CrmExtLog crmExtLog);
+
+    /**
+     * 修改修改字段扩展日志
+     *
+     * @param crmExtLog 修改字段扩展日志
+     * @return 结果
+     */
+    public int updateCrmExtLog(CrmExtLog crmExtLog);
+
+    /**
+     * 删除修改字段扩展日志
+     *
+     * @param logId 修改字段扩展日志ID
+     * @return 结果
+     */
+    public int deleteCrmExtLogById(Long logId);
+
+    /**
+     * 批量删除修改字段扩展日志
+     *
+     * @param logIds 需要删除的数据ID
+     * @return 结果
+     */
+    public int deleteCrmExtLogByIds(Long[] logIds);
+}

+ 65 - 0
fs-service/src/main/java/com/fs/crm/mapper/CrmFollowUpMapper.java

@@ -0,0 +1,65 @@
+package com.fs.crm.mapper;
+
+import com.fs.crm.domain.CrmFollowUp;
+import com.fs.crm.vo.CrmFollowUpVo;
+
+import java.util.List;
+
+/**
+ * 跟进提醒Mapper接口
+ *
+ * @author fs
+ * @date 2025-04-15
+ */
+public interface CrmFollowUpMapper
+{
+    /**
+     * 查询跟进提醒
+     *
+     * @param id 跟进提醒ID
+     * @return 跟进提醒
+     */
+    public CrmFollowUp selectCrmFollowUpById(Long id);
+
+    /**
+     * 查询跟进提醒列表
+     *
+     * @param crmFollowUp 跟进提醒
+     * @return 跟进提醒集合
+     */
+    public List<CrmFollowUp> selectCrmFollowUpList(CrmFollowUp crmFollowUp);
+
+    /**
+     * 新增跟进提醒
+     *
+     * @param crmFollowUp 跟进提醒
+     * @return 结果
+     */
+    public int insertCrmFollowUp(CrmFollowUp crmFollowUp);
+
+    /**
+     * 修改跟进提醒
+     *
+     * @param crmFollowUp 跟进提醒
+     * @return 结果
+     */
+    public int updateCrmFollowUp(CrmFollowUp crmFollowUp);
+
+    /**
+     * 删除跟进提醒
+     *
+     * @param id 跟进提醒ID
+     * @return 结果
+     */
+    public int deleteCrmFollowUpById(Long id);
+
+    /**
+     * 批量删除跟进提醒
+     *
+     * @param ids 需要删除的数据ID
+     * @return 结果
+     */
+    public int deleteCrmFollowUpByIds(Long[] ids);
+
+    List<CrmFollowUpVo> selectCrmFollowUpAndCustomerList(CrmFollowUp crmFollowUp);
+}

+ 96 - 0
fs-service/src/main/java/com/fs/crm/param/CrmBusinessAddAndUpdateParam.java

@@ -0,0 +1,96 @@
+package com.fs.crm.param;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fs.common.core.domain.BaseEntity;
+import com.fs.crm.domain.CrmCustomerContacts;
+import lombok.Data;
+
+import java.util.Date;
+import java.util.List;
+
+@Data
+public class CrmBusinessAddAndUpdateParam extends BaseEntity {
+    /** 商机id */
+    private Long businessId;
+
+    /** 客户id */
+    private Long customerId;
+
+    /** 线索来源
+     0企查询
+     1企查查
+     2领鸟云
+     3其他 */
+    private Integer source;
+
+    /** 客户经理 */
+    private String manager;
+
+    /** 公司名称(只填公司全称,个人用户就填名字) */
+    private String companyName;
+
+
+    /** 接口人角色 0老板,1采购,2财务,3技术负责人,4一般技术人员,5项目负责人,6其他 */
+    private Integer contactRole;
+
+    /** 业务场景网站,电商,游戏,小程序,APP,OA,ERP,CRM,物联网,AI人工智能,国产化,数字人,内部办公系统,等保,其他 */
+    private String businessScenario;
+
+    /** 方案涉及的产品 */
+    private String product;
+
+    /** 采购周期(单位:天) */
+    private Long purchaseCycle;
+
+    /** 跟进状态:0跟进中,1已流失,2已赢单,3待验证 */
+    private Integer businessStatus;
+
+    /** 项目阶段
+     A:100%转商,
+     B:90%合同下单&流程,
+     C:80%确认商务阶段,
+     D:70%确认产品技术方案及配置,
+     E:60%POC测试验证,
+     F:50%项目沟通&立项
+     G:40%前期咨询,
+     L:丢单 */
+    private String projectPhase;
+
+    /** 意向等级
+     40%~50%:低意向
+     60%~70%:中意向
+     80%~100%:高意向 */
+    private Integer level;
+
+    /** 是否绑定BP账号:0未注册 1已注册未绑定 2不明确 3已绑定 */
+    private Integer isBp;
+
+    /** BP账户 */
+    private String bpAccount;
+
+    /** 预计成单时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd")
+    private Date preTime;
+
+    /** 预计付费(元) */
+    private Float preMoney;
+
+    /** 下次跟进时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd")
+    private Date nextTime;
+
+    private List<CrmCustomerContacts> mobileList;
+
+    private String mobile;
+
+    private Long createId;
+
+    private Long updateId;
+
+    private Long companyId;
+
+    private String opeName;
+
+    //跟进内容
+    private String content;
+}

+ 118 - 0
fs-service/src/main/java/com/fs/crm/param/CrmBusinessImportParam.java

@@ -0,0 +1,118 @@
+package com.fs.crm.param;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fs.common.annotation.Excel;
+import lombok.Data;
+
+import java.util.Date;
+
+@Data
+public class CrmBusinessImportParam {
+    /** 商机id */
+    private Long businessId;
+
+    /** 客户id */
+    @Excel(name = "客户id")
+    private Long customerId;
+
+
+    @Excel(name = "客户电话")
+    private String mobile;
+
+    @Excel(name = "客户来源",dictType = "crm_customer_source")
+    private String source;
+
+    /** 线索来源
+     0企查询
+     1企查查
+     2领鸟云
+     3其他 */
+//    @Excel(name = "线索来源",dictType = "crm_customer_source",required = true)
+//    private String source;
+
+    /** 客户经理 */
+    @Excel(name = "客户经理")
+    private String manager;
+
+    /** 公司名称(只填公司全称,个人用户就填名字) */
+    @Excel(name = "公司名称")
+    private String companyName;
+
+
+    /** 接口人角色 0老板,1采购,2财务,3技术负责人,4一般技术人员,5项目负责人,6其他 */
+    @Excel(name = "接口人角色",dictType = "crm_contact_role")
+    private String contactRole;
+
+    /** 业务场景网站,电商,游戏,小程序,APP,OA,ERP,CRM,物联网,AI人工智能,国产化,数字人,内部办公系统,等保,其他 */
+    @Excel(name = "业务场景",dictType = "business_scenario_type")
+    private String businessScenario;
+
+    /** 方案涉及的产品 */
+    @Excel(name = "意向产品")
+    private String product;
+
+    /** 采购周期(单位:天) */
+    @Excel(name = "采购周期")
+    private Long purchaseCycle;
+
+    /** 跟进状态:0跟进中,1已流失,2已赢单,3待验证 */
+    @Excel(name = "跟进状态",dictType = "crm_business_status")
+    private String businessStatus;
+
+    /** 项目阶段
+     A:100%转商,
+     B:90%合同下单&流程,
+     C:80%确认商务阶段,
+     D:70%确认产品技术方案及配置,
+     E:60%POC测试验证,
+     F:50%项目沟通&立项
+     G:40%前期咨询,
+     L:丢单 */
+    @Excel(name = "项目阶段",dictType = "crm_project_phase")
+    private String projectPhase;
+
+    /** 意向等级
+     40%~50%:低意向
+     60%~70%:中意向
+     80%~100%:高意向 */
+    @Excel(name = "意向等级",dictType = "crm_level")
+    private String level;
+
+//    /** 是否绑定BP账号:0未注册 1已注册未绑定 2不明确 3已绑定 */
+//    @Excel(name = "是否绑定BP账号")
+//    private Integer isBp;
+
+//    /** BP账户 */
+//    @Excel(name = "公司名称")
+//    private String bpAccount;
+
+    /** 预计成单时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd")
+    @Excel(name = "预计成单时间",dateFormat = "yyyy-MM-dd")
+    private Date preTime;
+
+    /** 预计付费(元) */
+    @Excel(name = "预计付费(元)")
+    private Float preMoney;
+
+    /** 下次跟进时间 */
+    @Excel(name = "下次跟进时间",dateFormat = "yyyy-MM-dd")
+    @JsonFormat(pattern = "yyyy-MM-dd")
+    private Date nextTime;
+
+
+//    @Excel(name = "公司名称")
+//    private String mobile;
+
+    private Long createId;
+
+    private Long updateId;
+
+    private Long companyId;
+
+    private String opeName;
+
+    //跟进内容
+    @Excel(name = "跟进内容")
+    private String content;
+}

+ 109 - 0
fs-service/src/main/java/com/fs/crm/param/CrmBusinessQueryParam.java

@@ -0,0 +1,109 @@
+package com.fs.crm.param;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fs.common.param.BaseQueryParam;
+import lombok.Data;
+
+import java.util.Date;
+
+@Data
+public class CrmBusinessQueryParam extends BaseQueryParam {
+    /** 商机id */
+    private Long businessId;
+
+    /** 客户id */
+    private Long customerId;
+
+    /** 客户名字 */
+    private String customerName;
+
+    /** 线索来源
+     0企查询
+     1企查查
+     2领鸟云
+     3其他 */
+    private Integer source;
+
+    /** 客户经理 */
+    private String manager;
+
+    /** 公司名称(只填公司全称,个人用户就填名字) */
+    private String companyName;
+
+    /** 电话号码 */
+    private String mobile;
+
+    /** 接口人角色 0老板,1采购,2财务,3技术负责人,4一般技术人员,5项目负责人,6其他 */
+    private Integer contactRole;
+
+    /** 业务场景网站,电商,游戏,小程序,APP,OA,ERP,CRM,物联网,AI人工智能,国产化,数字人,内部办公系统,等保,其他 */
+    private String businessScenario;
+
+    /** 方案涉及的产品 */
+    private String product;
+
+    /** 采购周期(单位:天) */
+    private Long purchaseCycle;
+
+    /** 跟进状态:0跟进中,1已流失,2已赢单,3待验证 */
+    private Integer businessStatus;
+
+
+    /** 跟进状态:0跟进中,1已流失,2已赢单,3待验证 */
+    private String remark;
+
+    /** 项目阶段
+     A:100%转商,
+     B:90%合同下单&流程,
+     C:80%确认商务阶段,
+     D:70%确认产品技术方案及配置,
+     E:60%POC测试验证,
+     F:50%项目沟通&立项
+     G:40%前期咨询,
+     L:丢单 */
+    private String projectPhase;
+
+    /** 意向等级
+     40%~50%:低意向
+     60%~70%:中意向
+     80%~100%:高意向 */
+    private Integer level;
+
+    /** 是否绑定BP账号:0未注册 1已注册未绑定 2不明确 3已绑定 */
+    private Integer isBp;
+
+    /** BP账户 */
+    private String bpAccount;
+
+    /** 预计成单时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd")
+    private Date preTime;
+
+    /** 预计付费(元) */
+    private Float preMoney;
+
+    /** 最后一次跟进时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+//    @Excel(name = "挖掘时间",dateFormat = "yyyy-MM-dd HH:mm:ss")
+    private Date createTime;
+
+    /** 下次跟进时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd")
+    private Date nextTime;
+
+
+    private String createTimeRange;
+    private String[] createTimeArr;
+
+    private String nextTimeRange;
+    private String[] nextTimeArr;
+
+    private String orderBy;//下次跟进时间排序规则
+
+    //创建人Id
+    private Long createId;
+
+    //所属公司id
+    private Long companyId;
+
+}

+ 15 - 0
fs-service/src/main/java/com/fs/crm/param/CrmCustomerUpdateOrAddParam.java

@@ -0,0 +1,15 @@
+package com.fs.crm.param;
+
+import com.fs.crm.domain.CrmCustomer;
+import lombok.Data;
+
+import java.util.Map;
+
+@Data
+public class CrmCustomerUpdateOrAddParam extends CrmCustomer {
+    //扩展字段
+    private Map<String,Object> ext;
+
+    //创建人名字
+    private String opeName;
+}

+ 93 - 0
fs-service/src/main/java/com/fs/crm/param/CrmExtDetailAddOrUpdateParam.java

@@ -0,0 +1,93 @@
+package com.fs.crm.param;
+
+import lombok.Data;
+
+@Data
+public class CrmExtDetailAddOrUpdateParam {
+
+    /**
+     * 原字段名
+     */
+    private String oldColumnName;
+    /**
+     * 字段名
+     */
+    private String columnName;
+
+    /**
+     * 数据类型及其额外的属性
+     */
+    private String columnType;
+
+    /**
+     * 默认值
+     */
+    private String columnDefault;
+
+
+    /**
+     * 是否可以为 NULL YES:允许为null NO:不允许
+     */
+    private String isNullable;
+
+    /**
+     * 数据类型。例如:VARCHAR, INT, TEXT, DATE 等。
+     */
+    private String dataType;
+
+    /**
+     * 字符类型的列(如 VARCHAR 或 CHAR),该列表示字符的最大长度。如果该列不是字符类型,则该字段为 NULL
+     */
+    private Integer characterMaximumLength;
+
+    /**
+     * 字符类型的列(如 VARCHAR 或 CHAR),该列表示字符的最大长度。如果该列不是字符类型,则该字段为 NULL
+     */
+//    private String characterMaximumLength;
+
+    /**
+     * 数字类型的列,表示小数点后的位数(即精度的小数部分)
+     */
+//    private Integer numericScale;
+
+
+    /**
+     * 对于日期/时间类型的列(如 DATETIME 或 TIMESTAMP),该列表示日期时间值的精度(通常是秒或毫秒)。
+     */
+    private String datetimePrecision;
+
+
+    /**
+     * 是否是索引的一部分
+     * PRI: 主键列
+     * UNI: 唯一索引列
+     * MUL: 非唯一索引列
+     * NULL: 该列没有索引
+     */
+//    private String columnKey;
+
+
+    /**
+     * 列的附加信息
+     * auto_increment: 表示该列为自增列(通常用于主键)
+     * unsigned: 表示该列的无符号属性(对于整数类型)
+     * on update CURRENT_TIMESTAMP: 表示该列的默认值在每次更新时会变为当前时间。
+     */
+//    private String extra;
+
+
+    /**
+     * 该列表示对该列的权限,例如 SELECT, INSERT, UPDATE 等。通常,该字段为 SELECT,INSERT,UPDATE,REFERENCES 等
+     */
+//    private String privileges;
+
+    /**
+     * 字段类型
+     */
+//    private Integer nullable;
+
+    /**
+     * 该列包含列的注释,如果没有注释,则为 NULL
+     */
+    private String columnComment;
+}

+ 80 - 0
fs-service/src/main/java/com/fs/crm/service/ICrmBusinessService.java

@@ -0,0 +1,80 @@
+package com.fs.crm.service;
+
+import com.fs.company.domain.CompanyUser;
+import com.fs.crm.domain.CrmBusiness;
+import com.fs.crm.param.CrmBusinessAddAndUpdateParam;
+import com.fs.crm.param.CrmBusinessImportParam;
+import com.fs.crm.param.CrmBusinessQueryParam;
+import com.fs.crm.vo.CrmBusinessListVO;
+
+import java.util.List;
+
+/**
+ * 商机Service接口
+ *
+ * @author fs
+ * @date 2025-01-16
+ */
+public interface ICrmBusinessService
+{
+    /**
+     * 查询商机
+     *
+     * @param businessId 商机ID
+     * @return 商机
+     */
+    public CrmBusiness selectCrmBusinessById(Long businessId);
+
+    /**
+     * 查询商机列表
+     *
+     * @param param 商机
+     * @return 商机集合
+     */
+    public List<CrmBusinessListVO> selectCrmBusinessList(CrmBusinessQueryParam param);
+
+    /**
+     * 新增商机
+     *
+     * @param param 商机
+     * @return 结果
+     */
+    public int insertCrmBusiness(CrmBusinessAddAndUpdateParam param);
+
+    /**
+     * 新建商机(仅适用系统自建)
+     * @param crmBusiness
+     * @return
+     */
+    int insert(CrmBusiness crmBusiness);
+    /**
+     * 修改商机
+     * param
+     * @return 结果
+     */
+    public int updateCrmBusiness(CrmBusinessAddAndUpdateParam param);
+
+    /**
+     * 批量删除商机
+     *
+     * @param businessIds 需要删除的商机ID
+     * @return 结果
+     */
+    public int deleteCrmBusinessByIds(Long[] businessIds,String opeName);
+
+    /**
+     * 删除商机信息
+     *
+     * @param businessId 商机ID
+     * @return 结果
+     */
+    public int deleteCrmBusinessById(Long businessId,String opeName);
+
+    int updateCrmBusinessByCustomerId(CrmBusiness crmBusiness);
+
+    CrmBusiness getLastByCustomerId(Long customerId);
+
+    String importBusinessData(List<CrmBusinessImportParam> list, CompanyUser user);
+
+//    String gain(AssignmentBusinessVo vo, Long userId, String userName);
+}

+ 9 - 0
fs-service/src/main/java/com/fs/crm/service/ICrmCustomerContactsService.java

@@ -63,4 +63,13 @@ public interface ICrmCustomerContactsService
     public int deleteCrmCustomerContactsById(Long contactsId);
 
     List<CrmCustomerContactsListQueryVO> selectCrmCustomerContactsListQuery(CrmCustomerContactsListQueryParam param);
+
+    void addByBusiness(Long customerId, Long businessId, List<CrmCustomerContacts> mobileList);
+
+    /**
+     * 根据手机号查询
+     * @param mobile
+     * @return
+     */
+    CrmCustomerContacts selectCrmCustomerContactsByMobile(String mobile);
 }

+ 6 - 0
fs-service/src/main/java/com/fs/crm/service/ICrmCustomerService.java

@@ -165,4 +165,10 @@ public interface ICrmCustomerService
     void computePayMoney();
 
     List<CrmMyCustomerListQueryVO> selectCrmMyAssistListQuery(CrmMyCustomerListQueryParam param);
+
+    /**
+     * 回收线索
+     */
+    void recoveryClue();
+
 }

+ 10 - 0
fs-service/src/main/java/com/fs/crm/service/ICrmCustomerVisitService.java

@@ -82,4 +82,14 @@ public interface ICrmCustomerVisitService
     List<JSONObject> selectCrmCustomerVisitCounts(Map<String, Object> toMap);
 
     List<CrmCustomerVisitExportVO> selectCrmCustomerVisitExportListVO(CrmCustomerVisitListParam param);
+
+    /**
+     * 跟进提醒
+     */
+    void followupNotice();
+
+    /**
+     * 回收未跟进的商机客户
+     */
+    void recoveryBusiness();
 }

+ 97 - 0
fs-service/src/main/java/com/fs/crm/service/ICrmExtDetailService.java

@@ -0,0 +1,97 @@
+package com.fs.crm.service;
+
+import com.fs.common.core.domain.R;
+import com.fs.crm.domain.CrmExtDetail;
+import com.fs.crm.param.CrmExtDetailAddOrUpdateParam;
+import com.fs.crm.vo.CrmExtDetailVo;
+import com.fs.watch.param.BaseQueryParam;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 字段扩展详情Service接口
+ *
+ * @author fs
+ * @date 2025-02-17
+ */
+public interface ICrmExtDetailService
+{
+    /**
+     * 查询字段扩展详情
+     *
+     * @param id 字段扩展详情ID
+     * @return 字段扩展详情
+     */
+    public CrmExtDetail selectCrmExtDetailById(Long id);
+
+    public Map<String,Object> selectCrmExtDetailByCondition(Map<String,Object> map);
+
+    /**
+     * 查询字段扩展详情列表
+     *
+     * @param crmExtDetail 字段扩展详情
+     * @return 字段扩展详情集合
+     */
+    public List<CrmExtDetail> selectCrmExtDetailList(CrmExtDetail crmExtDetail);
+
+    /**
+     * 新增字段扩展详情
+     *
+     * @param crmExtDetail 字段扩展详情
+     * @return 结果
+     */
+    public int insertCrmExtDetail(Map<String,Object> crmExtDetail);
+
+    /**
+     * 修改字段扩展详情
+     *
+     * @param crmExtDetail 字段扩展详情
+     * @return 结果
+     */
+    public int updateCrmExtDetail(Map<String,Object> crmExtDetail);
+
+    /**
+     * 批量删除字段扩展详情
+     *
+     * @param ids 需要删除的字段扩展详情ID
+     * @return 结果
+     */
+    public int deleteCrmExtDetailByIds(Long[] ids);
+
+    /**
+     * 删除字段扩展详情信息
+     *
+     * @param id 字段扩展详情ID
+     * @return 结果
+     */
+    public int deleteCrmExtDetailById(Long id);
+
+    /**
+     * 查询扩展表列
+     * @param param
+     * @return
+     */
+    List<CrmExtDetailVo> getTableColumnsMetadata(BaseQueryParam param);
+
+    List<CrmExtDetailVo> getTableColumnsMetaList(BaseQueryParam param);
+
+    /**
+     * 新增列
+     * @param param
+     * @param createBy
+     * @return
+     */
+    R insertColumn(CrmExtDetailAddOrUpdateParam param, String createBy);
+
+    /**
+     * 批量删除列
+     * @param columnNames
+     * @return
+     */
+    R deleteColumns(String[] columnNames,String createBy);
+
+    R updateColumn(CrmExtDetailAddOrUpdateParam param, String createBy);
+
+    void deleteByCorrelateIdAndType(Long[] customerIds, String customerId);
+}

+ 62 - 0
fs-service/src/main/java/com/fs/crm/service/ICrmExtLogService.java

@@ -0,0 +1,62 @@
+package com.fs.crm.service;
+
+import com.fs.crm.domain.CrmExtLog;
+
+import java.util.List;
+
+/**
+ * 修改字段扩展日志Service接口
+ *
+ * @author fs
+ * @date 2025-02-17
+ */
+public interface ICrmExtLogService
+{
+    /**
+     * 查询修改字段扩展日志
+     *
+     * @param logId 修改字段扩展日志ID
+     * @return 修改字段扩展日志
+     */
+    public CrmExtLog selectCrmExtLogById(Long logId);
+
+    /**
+     * 查询修改字段扩展日志列表
+     *
+     * @param crmExtLog 修改字段扩展日志
+     * @return 修改字段扩展日志集合
+     */
+    public List<CrmExtLog> selectCrmExtLogList(CrmExtLog crmExtLog);
+
+    /**
+     * 新增修改字段扩展日志
+     *
+     * @param crmExtLog 修改字段扩展日志
+     * @return 结果
+     */
+    public int insertCrmExtLog(CrmExtLog crmExtLog);
+
+    /**
+     * 修改修改字段扩展日志
+     *
+     * @param crmExtLog 修改字段扩展日志
+     * @return 结果
+     */
+    public int updateCrmExtLog(CrmExtLog crmExtLog);
+
+    /**
+     * 批量删除修改字段扩展日志
+     *
+     * @param logIds 需要删除的修改字段扩展日志ID
+     * @return 结果
+     */
+    public int deleteCrmExtLogByIds(Long[] logIds);
+
+    /**
+     * 删除修改字段扩展日志信息
+     *
+     * @param logId 修改字段扩展日志ID
+     * @return 结果
+     */
+    public int deleteCrmExtLogById(Long logId);
+}

+ 65 - 0
fs-service/src/main/java/com/fs/crm/service/ICrmFollowUpService.java

@@ -0,0 +1,65 @@
+package com.fs.crm.service;
+
+import com.fs.crm.domain.CrmFollowUp;
+import com.fs.crm.vo.CrmFollowUpVo;
+
+import java.util.List;
+
+/**
+ * 跟进提醒Service接口
+ *
+ * @author fs
+ * @date 2025-04-15
+ */
+public interface ICrmFollowUpService
+{
+    /**
+     * 查询跟进提醒
+     *
+     * @param id 跟进提醒ID
+     * @return 跟进提醒
+     */
+    public CrmFollowUp selectCrmFollowUpById(Long id);
+
+    /**
+     * 查询跟进提醒列表
+     *
+     * @param crmFollowUp 跟进提醒
+     * @return 跟进提醒集合
+     */
+    public List<CrmFollowUp> selectCrmFollowUpList(CrmFollowUp crmFollowUp);
+
+    /**
+     * 新增跟进提醒
+     *
+     * @param crmFollowUp 跟进提醒
+     * @return 结果
+     */
+    public int insertCrmFollowUp(CrmFollowUp crmFollowUp);
+
+    /**
+     * 修改跟进提醒
+     *
+     * @param crmFollowUp 跟进提醒
+     * @return 结果
+     */
+    public int updateCrmFollowUp(CrmFollowUp crmFollowUp);
+
+    /**
+     * 批量删除跟进提醒
+     *
+     * @param ids 需要删除的跟进提醒ID
+     * @return 结果
+     */
+    public int deleteCrmFollowUpByIds(Long[] ids);
+
+    /**
+     * 删除跟进提醒信息
+     *
+     * @param id 跟进提醒ID
+     * @return 结果
+     */
+    public int deleteCrmFollowUpById(Long id);
+
+    List<CrmFollowUpVo> selectCrmFollowUpAndCustomerList(CrmFollowUp crmFollowUp);
+}

+ 682 - 0
fs-service/src/main/java/com/fs/crm/service/impl/CrmBusinessServiceImpl.java

@@ -0,0 +1,682 @@
+package com.fs.crm.service.impl;
+
+import cn.jiguang.common.resp.APIConnectionException;
+import cn.jiguang.common.resp.APIRequestException;
+import com.alibaba.fastjson.JSON;
+import com.fs.aiSipCall.utils.DateUtils;
+import com.fs.common.OrderUtils;
+import com.fs.common.core.domain.R;
+import com.fs.common.core.domain.entity.SysDictData;
+import com.fs.common.exception.CustomException;
+import com.fs.common.utils.*;
+import com.fs.company.domain.Company;
+import com.fs.company.domain.CompanyUser;
+import com.fs.company.mapper.CompanyMapper;
+import com.fs.company.mapper.CompanyUserMapper;
+import com.fs.company.service.impl.CompanyServiceImpl;
+import com.fs.crm.domain.*;
+import com.fs.crm.enums.CustomerLogEnum;
+import com.fs.crm.mapper.*;
+import com.fs.crm.param.*;
+import com.fs.crm.service.ICrmBusinessService;
+import com.fs.crm.service.ICrmCustomerContactsService;
+import com.fs.crm.service.ICrmCustomerService;
+import com.fs.crm.vo.CrmBusinessListVO;
+import org.springframework.beans.BeanUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.*;
+
+/**
+ * 商机Service业务层处理
+ *
+ * @author fs
+ * @date 2025-01-16
+ */
+@Service
+public class CrmBusinessServiceImpl implements ICrmBusinessService {
+    @Autowired
+    private CrmBusinessMapper crmBusinessMapper;
+    @Autowired
+    private CrmCustomerLogsMapper logsMapper;
+    @Autowired
+    private CrmCustomerMapper customerMapper;
+    @Autowired
+    private ICrmCustomerContactsService contactsService;
+    @Autowired
+    private CrmCustomerMapper crmCustomerMapper;
+    @Autowired
+    private CrmCustomerLogsMapper customerLogsMapper;
+    @Autowired
+    private CrmCustomerVisitMapper customerVisitMapper;
+    @Autowired
+    private ICrmCustomerService crmCustomerService;
+    @Autowired
+    private CrmCustomerUserMapper crmCustomerUserMapper;
+    @Autowired
+    private CompanyMapper companyMapper;
+    @Autowired
+    private CompanyUserMapper companyUserMapper;
+
+    /**
+     * 查询商机
+     *
+     * @param businessId 商机ID
+     * @return 商机
+     */
+    @Override
+    public CrmBusiness selectCrmBusinessById(Long businessId) {
+        return crmBusinessMapper.selectCrmBusinessById(businessId);
+    }
+
+    /**
+     * 查询商机列表
+     *
+     * @param param 商机
+     * @return 商机
+     */
+    @Override
+    public List<CrmBusinessListVO> selectCrmBusinessList(CrmBusinessQueryParam param) {
+        List<CrmBusinessListVO> list = crmBusinessMapper.selectCrmBusinessList(param);
+        if (list != null) {
+            for (CrmBusinessListVO vo : list) {
+                Long customerId = vo.getCustomerId();
+                CrmCustomerContacts contacts = new CrmCustomerContacts();
+                if (customerId != null) {
+                    contacts.setCustomerId(customerId);
+                    //客户联系电话
+                    CrmCustomer crmCustomer = customerMapper.selectCrmCustomerById(customerId);
+                    if (crmCustomer != null) {
+                        vo.setMobile(crmCustomer.getMobile());
+                    }
+                } else {
+                    contacts.setBusinessId(vo.getBusinessId());
+                }
+                if (vo.getCustomerId() == null){
+                    //查询最新跟进内容
+                    CrmCustomerVisit visit = customerVisitMapper.selectNewByBusinessId(vo.getBusinessId());
+                    if (visit != null) {
+                        vo.setNextTime(visit.getNextTime());
+                        vo.setContent(visit.getContent());
+                    }
+                }
+                List<CrmCustomerContacts> mobileList = contactsService.selectCrmCustomerContactsList(contacts);
+                vo.setMobileList(mobileList);
+
+
+            }
+        }
+        return list;
+    }
+
+    /**
+     * 新增商机
+     *
+     * @param param 商机
+     * @return 结果
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public int insertCrmBusiness(CrmBusinessAddAndUpdateParam param) {
+        Date nowDate = DateUtils.getNowDate();
+        //新增商机
+        CrmBusiness business = new CrmBusiness();
+        BeanUtils.copyProperties(param, business);
+        business.setCreateTime(nowDate);
+        if (business.getNextTime() == null){
+            business.setNextTime(DateUtils.addDays(nowDate,20));
+        }
+        int i = crmBusinessMapper.insertCrmBusiness(business);
+        Long businessId = business.getBusinessId();
+        if (i > 0) {
+            param.setBusinessId(businessId);
+            Long customerId = param.getCustomerId();
+            //其他联系人
+            List<CrmCustomerContacts> mobileList = param.getMobileList();
+            //客户新建商机 不用添加mobile
+            if (customerId == null) {
+                //直接新建商机
+                if (mobileList != null) {
+                    if (StringUtils.isNotBlank(param.getMobile())) {
+                        CrmCustomerContacts contact = new CrmCustomerContacts();
+                        contact.setName(param.getCompanyName());
+                        contact.setMobile(param.getMobile());
+                        mobileList.add(contact);
+                    }
+                }
+            }
+            if (mobileList != null && !mobileList.isEmpty()) {
+                //添加联系人
+                contactsService.addByBusiness(customerId, businessId, mobileList);
+            }
+            checkPoolRule(param,customerId);
+            checkInsertLog(param,business);
+            //2025.9.2 规则 有商机的客户归属管理员
+            CrmCustomer customer = crmCustomerMapper.selectCrmCustomerById(param.getCustomerId());
+            if (customer != null) {
+                //查询管理员
+                assignAdmin(param, customer, customerId,nowDate);
+            }
+
+        }
+
+        return i;
+    }
+
+    private void assignAdmin(CrmBusinessAddAndUpdateParam param, CrmCustomer customer, Long customerId,Date nowDate) {
+        Company company = companyMapper.selectCompanyById(customer.getCompanyId());
+        Long userId = company.getUserId();
+        CompanyUser user = companyUserMapper.selectCompanyUserById(userId);
+        //写入认领记录
+        CrmCustomerUser crmCustomerUser = new CrmCustomerUser();
+
+        crmCustomerUser.setCustomerId(customerId);
+        crmCustomerUser.setCompanyId(user.getCompanyId());
+        crmCustomerUser.setCompanyUserId(user.getUserId());
+        crmCustomerUser.setStatus(1);
+        crmCustomerUser.setCreateTime(nowDate);
+        crmCustomerUser.setStartTime(nowDate);
+        crmCustomerUser.setIsPool(0);
+        //结束时间按规则配置
+        crmCustomerUserMapper.insertCrmCustomerUser(crmCustomerUser);
+        customer.setReceiveUserId(user.getUserId());
+        customer.setIsReceive(1);
+        customer.setReceiveTime(nowDate);
+        customer.setCustomerUserId(crmCustomerUser.getCustomerUserId());
+        customer.setDeptId(user.getDeptId());
+        customer.setIsPool(0);
+        customer.setPoolTime(null);
+        customer.setIsPoolRule(0); // 2025.9.2 所有商机客户 不流转公海
+        //2025.04.09 如果没有下次跟进时间 建立20天后的跟进时间
+        if (customer.getNextTime() == null){
+            CrmCustomerVisit crmCustomerVisit = new CrmCustomerVisit();
+            crmCustomerVisit.setCustomerId(param.getCustomerId());
+            crmCustomerVisit.setCompanyId(crmCustomerUser.getCompanyId());
+            crmCustomerVisit.setCompanyUserId(crmCustomerUser.getCompanyUserId());
+            crmCustomerVisit.setVisitType(-1L);
+            crmCustomerVisit.setContent("创建商机客户,自动添加下次联系时间");
+            crmCustomerVisit.setType(1);
+            crmCustomerVisit.setIsShow(1);
+            crmCustomerVisit.setCreateTime(nowDate);
+            crmCustomerVisit.setNextTime(DateUtils.addDays(nowDate, 20));
+            customer.setNextTime(crmCustomerVisit.getNextTime());
+            customerVisitMapper.insertCrmCustomerVisit(crmCustomerVisit);
+        }
+        crmCustomerMapper.updateCrmCustomer(customer);
+        //写日志
+        CrmCustomerLogs logs = new CrmCustomerLogs();
+        logs.setCustomerId(customer.getCustomerId());
+        logs.setCreateTime(new Date());
+        logs.setLogsType(CustomerLogEnum.RECEIVE.getValue());
+        logs.setTitle(CustomerLogEnum.RECEIVE.getDesc());
+        logs.setRemark(param.getOpeName() + "创建商机,系统自动分配该客户给管理员,客户名称为:" + customer.getCustomerName());
+        logs.setCompanyUserId(param.getCreateId());
+        logsMapper.insertCrmCustomerLogs(logs);
+    }
+
+    private void checkInsertLog(CrmBusinessAddAndUpdateParam param, CrmBusiness business) {
+        //添加客户日志
+        if (param.getCustomerId() != null) {
+            CrmCustomerLogs log=new CrmCustomerLogs();
+            log.setCustomerId(param.getCustomerId());
+            log.setCreateTime(business.getCreateTime());
+            log.setLogsType(CustomerLogEnum.BUSINESS.getValue());
+            log.setTitle(CustomerLogEnum.BUSINESS.getDesc());
+            log.setRemark(param.getOpeName()+"创建商机,商机id:" + business.getBusinessId());
+            logsMapper.insertCrmCustomerLogs(log);
+        }
+    }
+
+    /**
+     * 新增商机
+     *
+     * @param crmBusiness 商机
+     * @return 结果
+     */
+    @Override
+    public int insert(CrmBusiness crmBusiness) {
+        //新增商机
+        crmBusiness.setCreateTime(DateUtils.getNowDate());
+        return crmBusinessMapper.insertCrmBusiness(crmBusiness);
+    }
+
+    /**
+     * 修改商机
+     *
+     * @param param 商机
+     * @return 结果
+     */
+    @Override
+    @Transactional
+    public int updateCrmBusiness(CrmBusinessAddAndUpdateParam param) {
+        CrmBusiness crmBusiness = new CrmBusiness();
+        BeanUtils.copyProperties(param, crmBusiness);
+        crmBusiness.setUpdateTime(DateUtils.getNowDate());
+        Long customerId = param.getCustomerId();
+        //其他联系人
+        List<CrmCustomerContacts> mobileList = param.getMobileList();
+        if (mobileList != null && !mobileList.isEmpty()) {
+            //添加联系人
+            contactsService.addByBusiness(customerId, param.getBusinessId(), mobileList);
+        }
+        checkPoolRule(param, customerId); //处理商机规则
+        return crmBusinessMapper.updateCrmBusiness(crmBusiness);
+    }
+
+    private void checkPoolRule(CrmBusinessAddAndUpdateParam param, Long customerId) {
+        //判断是否100商机 是 相关客户取消公海回收规则
+        String projectPhase = param.getProjectPhase();
+        Integer businessStatus = param.getBusinessStatus();
+        if (StringUtils.isNotBlank(projectPhase) || businessStatus != null) {
+            //同步跟进状态和项目阶段
+            if ("A".equals(projectPhase)){
+                businessStatus = 2;
+            } else if ("L".equals(projectPhase)){
+                businessStatus = 1;
+            }
+            if (businessStatus == 1) {
+                projectPhase = "A";
+            } else if (businessStatus == 2) {
+                projectPhase = "L";
+            }
+            param.setProjectPhase(projectPhase);
+            param.setBusinessStatus(businessStatus);
+        }
+
+        CrmCustomer crmCustomer = null;
+        if (customerId != null) {
+            crmCustomer = customerMapper.selectCrmCustomerById(customerId);
+            if (crmCustomer != null) {
+                if (StringUtils.isNotBlank(projectPhase)) {
+                    //100%转商机
+                    if ("A".equals(projectPhase)) {
+                        //相关客户取消公海回收规则
+                        crmCustomer.setIsPoolRule(0);
+                        customerMapper.updateCrmCustomer(crmCustomer);
+                    } else {
+                        //查询是否有成交商机
+                        CrmBusinessQueryParam queryParam = new CrmBusinessQueryParam();
+                        queryParam.setCustomerId(customerId);
+                        queryParam.setProjectPhase("A");
+                        List<CrmBusinessListVO> list = crmBusinessMapper.selectCrmBusinessList(queryParam);
+                        if (list == null || list.isEmpty()) {
+                            crmCustomer.setIsPoolRule(1);
+                            customerMapper.updateCrmCustomer(crmCustomer);
+                        } else if (list.size() == 1) {
+                            if (Objects.equals(list.get(0).getBusinessId(), param.getBusinessId())) {
+                                crmCustomer.setIsPoolRule(1);
+                                customerMapper.updateCrmCustomer(crmCustomer);
+                            }
+                        }
+                    }
+
+                }
+            }
+        }
+        createLogByUpdateBusiness(param, crmCustomer);
+
+    }
+
+    /**
+     * 添加客户日志 (修改商机)
+     * @param param
+     * @param crmCustomer
+     */
+    private void createLogByUpdateBusiness(CrmBusinessAddAndUpdateParam param,CrmCustomer crmCustomer) {
+        if (crmCustomer != null) {
+            CrmCustomerLogs logTemp = new CrmCustomerLogs();
+            logTemp.setCustomerId(crmCustomer.getCustomerId());
+            logTemp.setCompanyUserId(param.getUpdateId());
+            logTemp.setTitle(CustomerLogEnum.UPDATE_BUSINESS.getDesc());
+            logTemp.setLogsType(CustomerLogEnum.UPDATE_BUSINESS.getValue());
+            //查询原记录
+            StringBuilder remark = getBusinessChange(param);
+            logTemp.setRemark("跟进商机:" +"\n" + remark);
+            logTemp.setCreateTime(new Date());
+            customerLogsMapper.insertCrmCustomerLogs(logTemp);
+        }
+        //判断是否早于最新跟进
+//        if (param.getNextTime() != null && param.getNextTime().after(new Date())) {
+        if (param.getNextTime() != null) {
+            CrmCustomerVisit visitTemp = new CrmCustomerVisit();
+            visitTemp.setCustomerId(crmCustomer == null ?null:crmCustomer.getCustomerId());
+            visitTemp.setBusinessId(param.getBusinessId());
+            visitTemp.setType(2);
+            visitTemp.setVisitType(-1L); //系统自建 修改商机创建的跟进
+            visitTemp.setContent("商机跟进:" + (StringUtils.isNotBlank(param.getContent())?param.getContent():""));
+            visitTemp.setNextTime(param.getNextTime());
+            visitTemp.setCompanyUserId(param.getUpdateId()==null?param.getCreateId():param.getUpdateId());
+            visitTemp.setCompanyId(param.getCompanyId());
+            visitTemp.setIsShow(1);
+            visitTemp.setCreateTime(new Date());
+            customerVisitMapper.insertCrmCustomerVisit(visitTemp);
+            boolean isUpdate = false;
+            if (crmCustomer != null) {
+                if (crmCustomer.getNextTime() != null){
+                    if (param.getNextTime().before(crmCustomer.getNextTime())){
+                        isUpdate = true;
+                    }
+                } else {
+                    isUpdate = true;
+                }
+                //是,则需要更新crm_customer表
+                if (isUpdate) {
+                    crmCustomer.setNextTime(param.getNextTime());
+                    crmCustomerMapper.updateCrmCustomer(crmCustomer);
+                }
+            }
+
+
+        }
+
+    }
+
+    /**
+     * 查询修改了商机的哪些东西
+     * @param param
+     * @return
+     */
+    private StringBuilder getBusinessChange(CrmBusinessAddAndUpdateParam param) {
+        StringBuilder remark = new StringBuilder();
+        CrmBusiness crmBusiness = crmBusinessMapper.selectCrmBusinessById(param.getBusinessId());
+        if (crmBusiness != null) {
+            Integer source = crmBusiness.getSource();
+            if (param.getSource() != null && !param.getSource().equals(source)) {
+                remark.append("将 ").append("线索来源 ").append(source).append(" 改为 ").append(param.getSource()).append("\n");
+            }
+            String manager = crmBusiness.getManager();
+            if (param.getManager() != null && !param.getManager().equals(manager)) {
+                remark.append("将 ").append("客户经理 ").append(manager).append(" 改为 ").append(param.getManager()).append("\n");
+            }
+            String companyName = crmBusiness.getCompanyName();
+            if (param.getCompanyName() != null && !param.getCompanyName().equals(companyName)) {
+                remark.append("将 ").append("公司名称 ").append(companyName).append(" 改为 ").append(param.getCompanyName()).append("\n");
+            }
+            Integer contactRole = crmBusiness.getContactRole();
+            if (param.getContactRole() != null && !param.getContactRole().equals(contactRole)) {
+                remark.append("将 ").append("接口人角色 ").append(contactRole).append(" 改为 ").append(param.getContactRole()).append("\n");
+            }
+            String businessScenario = crmBusiness.getBusinessScenario();
+            if (param.getBusinessScenario() != null && !param.getBusinessScenario().equals(businessScenario)) {
+                remark.append("将 ").append("业务场景 ").append(businessScenario).append(" 改为 ").append(param.getBusinessScenario()).append("\n");
+            }
+            String product = crmBusiness.getProduct();
+            if (param.getProduct() != null && !param.getProduct().equals(product)) {
+                remark.append("将 ").append("意向产品 ").append(product).append(" 改为 ").append(param.getProduct()).append("\n");
+            }
+            Long purchaseCycle = crmBusiness.getPurchaseCycle();
+            if (param.getPurchaseCycle() != null && !param.getPurchaseCycle().equals(purchaseCycle)) {
+                remark.append("将 ").append("采购周期 ").append(purchaseCycle).append(" 改为 ").append(param.getPurchaseCycle()).append("\n");
+            }
+            Integer businessStatus = crmBusiness.getBusinessStatus();
+            if (param.getBusinessStatus() != null && !param.getBusinessStatus().equals(businessStatus)) {
+                remark.append("将 ").append("跟进状态 ").append(businessStatus).append(" 改为 ").append(param.getBusinessStatus()).append("\n");
+            }
+            String businessRemark = crmBusiness.getRemark();
+            if (param.getRemark() != null && !param.getRemark().equals(businessRemark)) {
+                remark.append("将 ").append("备注 ").append(businessRemark).append(" 改为 ").append(param.getRemark()).append("\n");
+            }
+            String projectPhase = crmBusiness.getProjectPhase();
+            if (param.getProjectPhase() != null && !param.getProjectPhase().equals(projectPhase)) {
+                remark.append("将 ").append("项目阶段 ").append(projectPhase).append(" 改为 ").append(param.getProjectPhase()).append("\n");
+            }
+            Integer level = crmBusiness.getLevel();
+            if (param.getLevel() != null && !param.getLevel().equals(level)) {
+                remark.append("将 ").append("意向等级 ").append(level).append(" 改为 ").append(param.getLevel()).append("\n");
+            }
+            Integer isBp = crmBusiness.getIsBp();
+            if (param.getIsBp() != null && !param.getIsBp().equals(isBp)) {
+                remark.append("将 ").append("是否绑定BP ").append(isBp).append(" 改为 ").append(param.getIsBp()).append("\n");
+            }
+            String bpAccount = crmBusiness.getBpAccount();
+            if (param.getBpAccount() != null && !param.getBpAccount().equals(bpAccount)) {
+                remark.append("将 ").append("bp账号 ").append(bpAccount).append(" 改为 ").append(param.getBpAccount()).append("\n");
+            }
+            Date preTime = crmBusiness.getPreTime();
+            if (param.getPreTime() != null && !param.getPreTime().equals(preTime)) {
+                remark.append("将 ").append("预计成单时间 ").append(preTime).append(" 改为 ").append(param.getPreTime()).append("\n");
+            }
+            Float preMoney = crmBusiness.getPreMoney();
+            if (param.getPreMoney() != null && !param.getPreMoney().equals(preMoney)) {
+                remark.append("将 ").append("预计成交金额 ").append(preMoney).append(" 改为 ").append(param.getPreMoney()).append("\n");
+            }
+        }
+        return remark;
+    }
+
+    /**
+     * 批量删除商机
+     *
+     * @param businessIds 需要删除的商机ID
+     * @return 结果
+     */
+    @Override
+    @Transactional
+    public int deleteCrmBusinessByIds(Long[] businessIds,String opeName) {
+        for (Long businessId : businessIds) {
+            delCheckPoolRule(businessId,opeName);
+        }
+        return crmBusinessMapper.deleteCrmBusinessByIds(businessIds);
+    }
+
+    private void delCheckPoolRule(Long businessId,String opeName) {
+        //查询 商机
+        CrmBusiness crmBusiness = selectCrmBusinessById(businessId);
+        if (crmBusiness != null) {
+            Long customerId = crmBusiness.getCustomerId();
+            if (customerId != null) {
+                CrmCustomer crmCustomer = crmCustomerMapper.selectCrmCustomerById(customerId);
+                if (crmCustomer != null) {
+                    //查询是否有成交商机
+                    CrmBusinessQueryParam queryParam = new CrmBusinessQueryParam();
+                    queryParam.setCustomerId(customerId);
+                    queryParam.setProjectPhase("A");
+                    List<CrmBusinessListVO> list = crmBusinessMapper.selectCrmBusinessList(queryParam);
+                    if (list!=null && list.size() == 1) {
+                        if (Objects.equals(list.get(0).getBusinessId(), businessId)) {
+                            crmCustomer.setIsPoolRule(1);
+                            customerMapper.updateCrmCustomer(crmCustomer);
+                        }
+                    }
+                    CrmCustomerLogs log=new CrmCustomerLogs();
+                    log.setCustomerId(customerId);
+                    log.setCreateTime(new Date());
+                    log.setLogsType(CustomerLogEnum.DEL_BUSINESS.getValue());
+                    log.setTitle(CustomerLogEnum.DEL_BUSINESS.getDesc());
+                    log.setRemark(opeName+"删除商机,商机id:" + businessId);
+                    logsMapper.insertCrmCustomerLogs(log);
+                }
+
+            }
+        }
+    }
+
+    /**
+     * 删除商机信息
+     *
+     * @param businessId 商机ID
+     * @return 结果
+     */
+    @Override
+    @Transactional
+    public int deleteCrmBusinessById(Long businessId,String opeName) {
+        delCheckPoolRule(businessId,opeName);
+        return crmBusinessMapper.deleteCrmBusinessById(businessId);
+    }
+
+    @Override
+    public int updateCrmBusinessByCustomerId(CrmBusiness crmBusiness) {
+        crmBusiness.setUpdateTime(DateUtils.getNowDate());
+        return crmBusinessMapper.updateCrmBusinessByCustomerId(crmBusiness);
+    }
+
+    @Override
+    public CrmBusiness getLastByCustomerId(Long customerId) {
+        return crmBusinessMapper.getLastByCustomerId(customerId);
+    }
+
+    @Override
+    public String importBusinessData(List<CrmBusinessImportParam> list, CompanyUser user) {
+        if (StringUtils.isNull(list) || list.size() == 0) {
+            throw new CustomException("导入数据不能为空!");
+        }
+        int successNum = 0;
+        int failureNum = 0;
+        StringBuilder successMsg = new StringBuilder();
+        StringBuilder failureMsg = new StringBuilder();
+        StringBuilder importMsg = new StringBuilder();
+        for (CrmBusinessImportParam param : list) {
+            try {
+                //1.1校验必填字段
+                ExcelUtils.validateRequiredFields(param, list.indexOf(param) + 1); // 传入行号
+                CrmBusinessAddAndUpdateParam addParam = new CrmBusinessAddAndUpdateParam();
+                BeanUtils.copyProperties(param, addParam);
+                //没有相关客户,则新建客户
+                if (param.getCustomerId() == null || param.getCustomerId() <= 0) {
+                    if (StringUtils.isBlank(param.getMobile())){
+                        failureNum++;
+                        String msg = "<br/>" + failureNum + "、第 " + list.indexOf(param) + 1 + "行 客户id为空的情况 客户电话不能为空!";
+                        failureMsg.append(msg);
+                        continue;
+                    }
+                    if (StringUtils.isBlank(param.getCompanyName())){
+                        failureNum++;
+                        String msg = "<br/>" + failureNum + "、第 " + list.indexOf(param) + 1 + "行 客户id为空的情况 公司名称不能为空!";
+                        failureMsg.append(msg);
+                        continue;
+                    }
+                    if (StringUtils.isBlank(param.getSource())){
+                        failureNum++;
+                        String msg = "<br/>" + failureNum + "、第 " + list.indexOf(param) + 1 + "行 客户id为空的情况 客户来源不能为空!";
+                        failureMsg.append(msg);
+                        continue;
+                    }
+                    CrmCustomerUpdateOrAddParam crmCustomer = new CrmCustomerUpdateOrAddParam();
+                    crmCustomer.setCustomerCode(OrderUtils.getOrderNo());
+                    crmCustomer.setMobile(param.getMobile());
+                    crmCustomer.setCustomerCompanyName(param.getCompanyName());
+                    crmCustomer.setCustomerName(param.getCompanyName());
+                    crmCustomer.setSource(param.getSource());
+                    crmCustomer.setIsLine(0);
+                    crmCustomer.setIsDel(0);
+                    crmCustomer.setIsReceive(0);
+                    crmCustomer.setStatus(1);
+                    crmCustomer.setDeptId(user.getDeptId());
+                    crmCustomer.setCreateUserId(user.getUserId());
+                    crmCustomer.setCompanyId(user.getCompanyId());
+                    crmCustomer.setOpeName(user.getNickName());
+                    if(crmCustomerService.insertCrmCustomer(crmCustomer)>0){
+                        CrmCustomeReceiveParam receiveParam=new CrmCustomeReceiveParam();
+                        receiveParam.setCompanyId(user.getCompanyId());
+                        receiveParam.setCompanyUserId(user.getUserId());
+                        receiveParam.setCustomerId(crmCustomer.getCustomerId());
+                        R receiveResult = crmCustomerService.receive(receiveParam, user.getNickName());
+                        if (!"200".equals(receiveResult.get("code").toString())){
+                            failureNum++;
+                            String msg = "<br/>" + failureNum + "、第 " + list.indexOf(param) + 1 + "行 客户创建成功,但认领失败,商机导入失败:" + receiveResult.get("msg");
+                            failureMsg.append(msg);
+                            continue;
+                        }
+                    }else{
+                        failureNum++;
+                        String msg = "<br/>" + failureNum + "、第 " + list.indexOf(param) + 1 + "行 客户创建失败:电话或者公司名字已存在 请查找相关客户导入商机时填写相关客户id";
+                        failureMsg.append(msg);
+                        continue;
+                    }
+                    param.setCustomerId(crmCustomer.getCustomerId());
+                    addParam.setCustomerId(crmCustomer.getCustomerId());
+                    addParam.setMobile(null);
+                }else {
+                    addParam.setMobile(null);
+                    if (StringUtils.isNotBlank((param.getMobile()))){
+                        CrmCustomerContacts crmCustomerContact = new CrmCustomerContacts();
+                        crmCustomerContact.setCustomerId(param.getCustomerId());
+                        crmCustomerContact.setMobile(param.getMobile());
+                        List<CrmCustomerContacts> contacts = new ArrayList<>();
+                        contacts.add(crmCustomerContact);
+                        addParam.setMobileList(contacts);
+                    }
+                }
+                CrmCustomer crmCustomer = customerMapper.selectCrmCustomerById(param.getCustomerId());
+                if (crmCustomer == null) {
+                    failureNum++;
+                    String msg = "<br/>" + failureNum + "、客户ID " + param.getCustomerId() + "不存在该客户 导入失败:";
+                    failureMsg.append(msg);
+                    continue;
+                }
+                addParam.setSource(Integer.valueOf(crmCustomer.getSource()));
+                addParam.setContactRole(StringUtils.isNotBlank(param.getContactRole())?Integer.valueOf(param.getContactRole()):null);
+                addParam.setBusinessStatus(StringUtils.isNotBlank(param.getBusinessStatus())?Integer.valueOf(param.getBusinessStatus()):null);
+                addParam.setLevel(StringUtils.isNotBlank(param.getLevel())?Integer.valueOf(param.getLevel()):null);
+                addParam.setCreateId(user.getUserId());
+                addParam.setOpeName(user.getNickName());
+                addParam.setCompanyId(user.getCompanyId());
+                if (insertCrmBusiness(addParam) > 0) {
+                    successNum++;
+                    successMsg.append("<br/>").append(successNum).append("、第"+list.indexOf(param) + 1 +"行 客户ID ").append(param.getCustomerId()).append(" 导入成功");
+                } else {
+                    failureNum++;
+                    failureMsg.append("<br/>").append(successNum).append("、客户ID ").append(param.getCustomerId()).append(" 导入失败");
+                }
+
+            } catch (Exception e) {
+                failureNum++;
+                String msg = "<br/>" + failureNum + "、客户ID " +param.getCustomerId() + " 导入失败:";
+                failureMsg.append(msg).append(e.getMessage());
+
+            }
+        }
+        if (failureNum > 0) {
+            failureMsg.insert(0, "很抱歉,导入失败!共 " + failureNum + " 条数据格式不正确,错误如下:");
+            //throw new CustomException(failureMsg.toString());
+        } else {
+            successMsg.insert(0, "恭喜您,数据已全部导入成功!共 " + successNum + " 条,数据如下:");
+        }
+        importMsg.append(failureMsg.toString());
+        importMsg.append(successMsg.toString());
+        return importMsg.toString();
+    }
+
+//    @Override
+//    @Transactional
+//    public String gain(AssignmentBusinessVo vo, Long userId, String userName) {
+//        List<Long> businessIds = vo.getBusinessIds();
+//        List<Long> customerIds = vo.getCustomerIds();
+//        if (businessIds.size() != customerIds.size()) {
+//            return "参数错误!";
+//        }
+//        if (businessIds.size() == 0) {
+//            return "无商机捞取!";
+//        }
+//        //修改商机表
+//        CrmBusiness crmBusiness = new CrmBusiness();
+////            crmBusiness.setBusinessId(businessIds[i]);
+//        crmBusiness.setStatus(2); //或1
+//        crmBusiness.setUpdateTime(DateUtils.getNowDate());
+//        crmBusinessMapper.updateBatchCrmBusiness(businessIds, crmBusiness);
+//        //修改客户表
+//        CrmCustomer crmCustomer = new CrmCustomer();
+//        crmCustomer.setIsPool(0);
+////            customer.setReceiveUserId(0L);
+////            customer.setIsReceive(0);
+//        crmCustomer.setPoolTime(new Date());
+////            customer.setCustomerUserId(0L);
+//        crmCustomer.setUpdateTime(DateUtils.getNowDate());
+//        customerMapper.updateBatchCrmCustomer(customerIds, crmCustomer);
+//        //写日志
+//        customerIds.forEach(customerId -> {
+//            CrmCustomerLogs logs = new CrmCustomerLogs();
+//            logs.setCustomerId(customerId);
+//            logs.setCreateTime(new Date());
+//            logs.setLogsType(CustomerLogEnum.GAIN_BUSINESS.getValue());
+//            logs.setTitle(CustomerLogEnum.GAIN_BUSINESS.getDesc());
+//            logs.setRemark(userName + "捞取客户客户id为" +customerId + "的客户");
+//            logs.setCompanyUserId(userId);
+//            logsMapper.insertCrmCustomerLogs(logs);
+//        });
+//
+//        return "捞取成功";
+//    }
+}

+ 44 - 0
fs-service/src/main/java/com/fs/crm/service/impl/CrmCustomerContactsServiceImpl.java

@@ -103,4 +103,48 @@ public class CrmCustomerContactsServiceImpl implements ICrmCustomerContactsServi
     public List<CrmCustomerContactsListQueryVO> selectCrmCustomerContactsListQuery(CrmCustomerContactsListQueryParam param) {
         return crmCustomerContactsMapper.selectCrmCustomerContactsListQuery(param);
     }
+
+    /**
+     * 商机那边新建的联系人
+     * @param customerId
+     * @param businessId
+     * @param mobileList
+     */
+    @Override
+    public void addByBusiness(Long customerId, Long businessId, List<CrmCustomerContacts> mobileList) {
+        for (CrmCustomerContacts item : mobileList) {
+            String mobile = item.getMobile();
+            Boolean flag = false;
+            //判断电话是否存在
+            if (mobile.contains("****")){
+                CrmCustomerContacts query = new CrmCustomerContacts();
+                query.setCustomerId(customerId);
+                query.setMobile(mobile.substring(mobile.length()-4));
+                List<CrmCustomerContacts> crmCustomerContacts = selectCrmCustomerContactsList(query);
+                flag = crmCustomerContacts.isEmpty();
+            } else {
+                CrmCustomerContacts contacts = selectCrmCustomerContactsByMobile(mobile);
+                flag = contacts==null;
+            }
+            if (flag) {
+                if (customerId!=null){
+                    item.setCustomerId(customerId);
+                } else {
+                    item.setBusinessId(businessId);
+                }
+                insertCrmCustomerContacts(item);
+            }
+        }
+
+    }
+
+    /**
+     * 根据手机号查询
+     * @param mobile
+     * @return
+     */
+    @Override
+    public CrmCustomerContacts selectCrmCustomerContactsByMobile(String mobile) {
+        return crmCustomerContactsMapper.selectCrmCustomerContactsByMobile(mobile);
+    }
 }

+ 242 - 13
fs-service/src/main/java/com/fs/crm/service/impl/CrmCustomerServiceImpl.java

@@ -13,16 +13,19 @@ import com.fs.common.exception.CustomException;
 import com.fs.common.utils.DateUtils;
 import com.fs.common.utils.StringUtils;
 import com.fs.company.domain.Company;
+import com.fs.company.domain.CompanyConfig;
 import com.fs.company.domain.CompanyUser;
 import com.fs.company.mapper.CompanyDeptMapper;
 import com.fs.company.mapper.CompanyMapper;
 import com.fs.company.mapper.CompanyUserMapper;
+import com.fs.company.service.ICompanyConfigService;
 import com.fs.crm.domain.*;
 import com.fs.crm.dto.CrmCustomerAssignCompanyDTO;
 import com.fs.crm.dto.CrmCustomerAssignUserDTO;
 import com.fs.crm.enums.CustomerLogEnum;
 import com.fs.crm.mapper.*;
 import com.fs.crm.param.*;
+import com.fs.crm.service.ICrmBusinessService;
 import com.fs.crm.service.ICrmCustomerService;
 import com.fs.crm.service.ICrmMsgService;
 import com.fs.crm.utils.CidUserOrderExtractor;
@@ -31,6 +34,7 @@ import com.fs.hisStore.domain.FsStoreOrderScrm;
 import com.fs.hisStore.mapper.FsStoreOrderScrmMapper;
 import com.fs.jpush.service.JpushService;
 import com.fs.qwApi.param.QwCustomerDetailParam;
+import com.fs.system.config.SystemConfig;
 import com.fs.system.service.ISysDictDataService;
 import com.fs.system.service.ISysDictTypeService;
 import com.fs.wx.sop.service.IWxSopExecuteService;
@@ -41,7 +45,7 @@ import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
-
+import com.alibaba.fastjson.JSON;
 import java.math.BigDecimal;
 import java.util.*;
 import java.util.concurrent.CompletableFuture;
@@ -87,9 +91,16 @@ public class CrmCustomerServiceImpl extends ServiceImpl<CrmCustomerMapper, CrmCu
     private CrmIndexDataMapper crmIndexDataMapper;
     @Autowired
     private ISysDictTypeService dictTypeService;
+    @Autowired
+    private CrmCustomerVisitMapper visitMapper;
+    @Autowired
+    private ICompanyConfigService companyConfigService;
 
     @Autowired
     private FsStoreOrderScrmMapper fsStoreOrderMapper;
+
+    @Autowired
+    private ICrmBusinessService crmBusinessService;
     /**
      * 查询客户
      *
@@ -351,20 +362,79 @@ public class CrmCustomerServiceImpl extends ServiceImpl<CrmCustomerMapper, CrmCu
 //        logs.setRemark(operName+"分配客户"+customer.getCustomerName()+"给部门"+companyDept.getDeptName());
 //        logsMapper.insertCrmCustomerLogs(logs);
 //        return R.ok();
+//    }
+
+//    @Override
+//    @Transactional
+//    public R receive(CrmCustomeReceiveParam param, String operName) {
+//        CrmCustomer customer=crmCustomerMapper.selectCrmCustomerById(param.getCustomerId());
+////        if(customer!=null&&customer.getStatus()==0){
+////            return R.error("已锁定");
+////        }
+//        if(customer!=null&&customer.getIsReceive()==1){
+//            return R.error("客户已认领");
+//        }
+//        //写入认领记录
+//        CrmCustomerUser crmCustomerUser=new CrmCustomerUser();
+//        crmCustomerUser.setCustomerId(customer.getCustomerId());
+//        crmCustomerUser.setCompanyId(param.getCompanyId());
+//        crmCustomerUser.setCompanyUserId(param.getCompanyUserId());
+//        crmCustomerUser.setStatus(1);
+//        crmCustomerUser.setCreateTime(new Date());
+//        crmCustomerUser.setStartTime(new Date());
+//        crmCustomerUser.setIsPool(0);
+//        //结束时间按规则配置 //配置入公海规则 TODO
+//        crmCustomerUserMapper.insertCrmCustomerUser(crmCustomerUser);
+//        customer.setReceiveUserId(param.getCompanyUserId());
+//        customer.setIsReceive(1);
+//        customer.setReceiveTime(new Date());
+//        customer.setCustomerUserId(crmCustomerUser.getCustomerUserId());
+//        customer.setDeptId(param.getDeptId());
+//        crmCustomerMapper.updateCrmCustomer(customer);
+//        //写日志
+//        CrmCustomerLogs logs=new CrmCustomerLogs();
+//        logs.setCustomerId(customer.getCustomerId());
+//        logs.setCreateTime(new Date());
+//        logs.setLogsType(CustomerLogEnum.RECEIVE.getValue());
+//        logs.setTitle(CustomerLogEnum.RECEIVE.getDesc());
+//        logs.setRemark(operName+"认领客户"+customer.getCustomerName());
+//        logs.setCompanyUserId(param.getCompanyUserId());
+//        logsMapper.insertCrmCustomerLogs(logs);
+//        msgService.insertCrmMsg(new CrmMsg(1,"认领客户","您认领了客户:"+customer.getCustomerName(),customer.getCompanyId(),param.getCompanyUserId(),customer.getCustomerId()));
+//
+//        CompanyUser companyUser=companyUserMapper.selectCompanyUserById(param.getCompanyUserId());
+//        if(StringUtils.isNotEmpty(companyUser.getJpushId())){
+//            Map<String, String> extrasMap=new HashMap<>();
+//            extrasMap.put("customerId",param.getCustomerId().toString());
+//            extrasMap.put("type","1");
+//            try {
+//                jpushService.sendRegisterIdPush("客户消息","您认领了一个客户",extrasMap,companyUser.getJpushId());
+//            } catch (APIConnectionException e) {
+//            } catch (APIRequestException e) {
+//            }
+//        }
+//        return R.ok();
 //    }
 
     @Override
     @Transactional
     public R receive(CrmCustomeReceiveParam param, String operName) {
-        CrmCustomer customer=crmCustomerMapper.selectCrmCustomerById(param.getCustomerId());
+        //3.12 要求每个销售认领用户不大于1500
+//        Long customerCount = crmCustomerMapper.countReceiveCustomer(param.getCompanyUserId());
+        // 3.20 暂时去掉
+//        if (customerCount > 1500) {
+//            return R.error("该销售认领数已满");
+//        }
+        CrmCustomer customer = crmCustomerMapper.selectCrmCustomerById(param.getCustomerId());
 //        if(customer!=null&&customer.getStatus()==0){
 //            return R.error("已锁定");
 //        }
-        if(customer!=null&&customer.getIsReceive()==1){
+        if (customer != null && customer.getIsReceive() == 1) {
             return R.error("客户已认领");
         }
+
         //写入认领记录
-        CrmCustomerUser crmCustomerUser=new CrmCustomerUser();
+        CrmCustomerUser crmCustomerUser = new CrmCustomerUser();
         crmCustomerUser.setCustomerId(customer.getCustomerId());
         crmCustomerUser.setCompanyId(param.getCompanyId());
         crmCustomerUser.setCompanyUserId(param.getCompanyUserId());
@@ -379,25 +449,32 @@ public class CrmCustomerServiceImpl extends ServiceImpl<CrmCustomerMapper, CrmCu
         customer.setReceiveTime(new Date());
         customer.setCustomerUserId(crmCustomerUser.getCustomerUserId());
         customer.setDeptId(param.getDeptId());
+
+        //2025.2.7
+        customer.setIsPool(0);
+        customer.setPoolTime(null);
+        //2025.04.09 未跟进而入的公海 需要新建公海
+        checkVisitAndBusiness(param, crmCustomerUser, customer);
+        customer.setIsPoolRule(1);
         crmCustomerMapper.updateCrmCustomer(customer);
         //写日志
-        CrmCustomerLogs logs=new CrmCustomerLogs();
+        CrmCustomerLogs logs = new CrmCustomerLogs();
         logs.setCustomerId(customer.getCustomerId());
         logs.setCreateTime(new Date());
         logs.setLogsType(CustomerLogEnum.RECEIVE.getValue());
         logs.setTitle(CustomerLogEnum.RECEIVE.getDesc());
-        logs.setRemark(operName+"认领客户"+customer.getCustomerName());
+        logs.setRemark(operName + "认领客户" + customer.getCustomerName());
         logs.setCompanyUserId(param.getCompanyUserId());
         logsMapper.insertCrmCustomerLogs(logs);
-        msgService.insertCrmMsg(new CrmMsg(1,"认领客户","您认领了客户:"+customer.getCustomerName(),customer.getCompanyId(),param.getCompanyUserId(),customer.getCustomerId()));
+        msgService.insertCrmMsg(new CrmMsg(1, "认领客户", "您认领了客户:" + customer.getCustomerName(), customer.getCompanyId(), param.getCompanyUserId(), customer.getCustomerId()));
 
-        CompanyUser companyUser=companyUserMapper.selectCompanyUserById(param.getCompanyUserId());
-        if(StringUtils.isNotEmpty(companyUser.getJpushId())){
-            Map<String, String> extrasMap=new HashMap<>();
-            extrasMap.put("customerId",param.getCustomerId().toString());
-            extrasMap.put("type","1");
+        CompanyUser companyUser = companyUserMapper.selectCompanyUserById(param.getCompanyUserId());
+        if (StringUtils.isNotEmpty(companyUser.getJpushId())) {
+            Map<String, String> extrasMap = new HashMap<>();
+            extrasMap.put("customerId", param.getCustomerId().toString());
+            extrasMap.put("type", "1");
             try {
-                jpushService.sendRegisterIdPush("客户消息","您认领了一个客户",extrasMap,companyUser.getJpushId());
+                jpushService.sendRegisterIdPush("客户消息", "您认领了一个客户", extrasMap, companyUser.getJpushId());
             } catch (APIConnectionException e) {
             } catch (APIRequestException e) {
             }
@@ -405,6 +482,62 @@ public class CrmCustomerServiceImpl extends ServiceImpl<CrmCustomerMapper, CrmCu
         return R.ok();
     }
 
+    private void checkVisitAndBusiness(CrmCustomeReceiveParam param, CrmCustomerUser crmCustomerUser, CrmCustomer customer) {
+        CompanyConfig companyConfig = companyConfigService.selectCompanyConfigByKey(param.getCompanyId(), "sys:config");
+        if (companyConfig != null) {
+            String configValue = companyConfig.getConfigValue();
+            SystemConfig config = JSON.parseObject(configValue, SystemConfig.class);
+            Integer visitLimit1 = config.getVisitLimt1();
+            Integer businessLimit = config.getVisitLimt2();
+            if (visitLimit1 != null && visitLimit1 > 0) {
+                CrmCustomerVisit visit = visitMapper.getLastByCustomerId(param.getCustomerId());
+                if (visit != null) {
+                    if (DateUtils.addDays(visit.getCreateTime(), visitLimit1).before(new Date())) {
+                        //新建跟进
+                        checkInsertVisit(param, crmCustomerUser,customer);
+                    }
+                }
+            }
+            if (businessLimit != null && businessLimit > 0) {
+                CrmBusiness business = crmBusinessService.getLastByCustomerId(param.getCustomerId());
+                if (business != null) {
+                    if (DateUtils.addDays(business.getCreateTime(), businessLimit).before(new Date())) {
+                        //新建商机
+                        checkInsertBusiness(param, crmCustomerUser);
+                    }
+                } else {
+                    if (DateUtils.addDays(customer.getCreateTime(), businessLimit).before(new Date())) {
+                        //新建商机
+                        checkInsertBusiness(param, crmCustomerUser);
+                    }
+                }
+            }
+        }
+    }
+
+    private void checkInsertBusiness(CrmCustomeReceiveParam param, CrmCustomerUser crmCustomerUser) {
+        CrmBusiness crmBusiness = new CrmBusiness();
+        crmBusiness.setCustomerId(param.getCustomerId());
+        crmBusiness.setCompanyId(crmCustomerUser.getCompanyId());
+        crmBusiness.setRemark("该线索因规定时间未建商机 进入公海,领取出系统自建商机");
+        crmBusinessService.insert(crmBusiness);
+    }
+
+    private void checkInsertVisit(CrmCustomeReceiveParam param, CrmCustomerUser crmCustomerUser,CrmCustomer customer) {
+        CrmCustomerVisit crmCustomerVisit = new CrmCustomerVisit();
+        crmCustomerVisit.setCustomerId(param.getCustomerId());
+        crmCustomerVisit.setCompanyId(crmCustomerUser.getCompanyId());
+        crmCustomerVisit.setCompanyUserId(crmCustomerUser.getCompanyUserId());
+        crmCustomerVisit.setVisitType(-1L);
+        crmCustomerVisit.setContent("该线索未按时建跟进入公海,领取出系统自建跟进");
+        crmCustomerVisit.setType(1);
+        crmCustomerVisit.setIsShow(0);
+        Date visitTime = new Date();
+        crmCustomerVisit.setCreateTime(visitTime);
+        customer.setSysVisitTime(visitTime);
+        visitMapper.insertCrmCustomerVisit(crmCustomerVisit);
+    }
+
     @Override
     @Transactional
     public R recover(CrmCustomeRecoverParam param, String operName) {
@@ -1086,4 +1219,100 @@ public class CrmCustomerServiceImpl extends ServiceImpl<CrmCustomerMapper, CrmCu
         return vos;
     }
 
+    /**
+     * 回收线索
+     */
+    @Override
+    @Transactional
+    public void recoveryClue() {
+        ArrayList<CrmCustomer> exeCustomers = new ArrayList<>();
+        //区分公司 查询配置文件
+        List<CompanyConfig> companyConfigs = companyConfigService.selectListByKey("sys:config");
+        if (companyConfigs != null && !companyConfigs.isEmpty()) {
+            companyConfigs.forEach(companyConfig -> {
+                String configValue = companyConfig.getConfigValue();
+                SystemConfig config = JSON.parseObject(configValue, SystemConfig.class);
+                Long companyId = companyConfig.getCompanyId();
+                if (config != null && companyId != null) {
+                    //1.查询 领取 n天内没有跟进的线索
+                    Integer visitLimit = config.getVisitLimt();
+                    Integer visitLimit2 = config.getVisitLimt2(); //商机
+                    if (visitLimit != null && visitLimit > 0) {
+                        List<CrmCustomer> customers = crmCustomerMapper.selectRecoveryClue(companyConfig.getCompanyId(),visitLimit, visitLimit2);
+                        if (customers != null && !customers.isEmpty()) {
+                            exeCustomers.addAll(customers);
+                        }
+                    }
+                    //2.查询 有跟进的线索n天内没有跟进的线索
+                    Integer visitLimit1 = config.getVisitLimt1();
+                    if (visitLimit1 != null && visitLimit1 > 0) {
+                        List<CrmCustomer> visCustomers = visitMapper.selectRecoveryClue(companyConfig.getCompanyId(),visitLimit1);
+                        if (visCustomers != null && !visCustomers.isEmpty()) {
+//                            visCustomers.forEach(visCustomer->{
+//                                if(DateUtils.addDays(visCustomer.getReceiveTime(),5).before(DateUtils.getNowDate())){
+//                                    exeCustomers.add(visCustomer);
+//                                }
+//                            });
+                            exeCustomers.addAll(visCustomers);
+                        }
+                    }
+                    //3.查询 n天无商机
+                    if (visitLimit2 != null && visitLimit2 > 0) {
+                        List<CrmCustomer> busCustomers = crmCustomerMapper.selectRecoveryBusiness(companyConfig.getCompanyId(),visitLimit2);
+                        if (busCustomers != null && !busCustomers.isEmpty()) {
+                            exeCustomers.addAll(busCustomers);
+                        }
+                    }
+                }
+            });
+        }
+
+
+        //回收到公海池
+        if (!exeCustomers.isEmpty()) {
+            //去重
+            List<CrmCustomer> distinctCustomers = new ArrayList<>(
+                    exeCustomers.stream()
+                            .collect(Collectors.toMap(CrmCustomer::getCustomerId, customer -> customer,
+                                    (existing, replacement) -> existing))
+                            .values()
+            );
+
+            distinctCustomers.forEach(customer -> {
+                threadPoolTaskExecutor.execute(() -> {
+                    long startTime = System.currentTimeMillis();
+                    if (customer.getIsPool() != null && customer.getIsPool() == 1) {
+                        return; // 已经是公海池客户,不重复处理
+                    }
+                    Long customerUserId = customer.getCustomerUserId();
+                    if (customerUserId != null) {
+                        CrmCustomerUser crmCustomerUser = crmCustomerUserMapper.selectCrmCustomerUserById(customerUserId);
+                        if (crmCustomerUser != null) {
+                            crmCustomerUser.setIsPool(1);
+                            crmCustomerUser.setPoolTime(new Date());
+                            crmCustomerUserMapper.updateCrmCustomerUser(crmCustomerUser);
+
+                        }
+                    }
+                    customer.setIsPool(1);
+                    customer.setIsReceive(0);
+                    customer.setPoolTime(new Date());
+                    customer.setCustomerUserId(null);
+                    crmCustomerMapper.updateCrmCustomer(customer);
+                    //写日志
+                    CrmCustomerLogs logs = new CrmCustomerLogs();
+                    logs.setCustomerId(customer.getCustomerId());
+                    logs.setCreateTime(new Date());
+                    logs.setLogsType(CustomerLogEnum.RECOVER.getValue());
+                    logs.setTitle(CustomerLogEnum.RECOVER.getDesc());
+                    logs.setRemark("系统" + "自动回收客户" + customer.getCustomerName());
+                    logs.setCompanyUserId(customer.getReceiveUserId());
+                    logsMapper.insertCrmCustomerLogs(logs);
+                    log.info("RecoveryTask for completed in {} ms", System.currentTimeMillis() - startTime);
+
+                });
+            });
+        }
+    }
+
 }

+ 83 - 6
fs-service/src/main/java/com/fs/crm/service/impl/CrmCustomerVisitServiceImpl.java

@@ -4,12 +4,9 @@ import com.alibaba.fastjson.JSONObject;
 import com.fs.common.annotation.DataScope;
 import com.fs.common.core.domain.R;
 import com.fs.common.utils.DateUtils;
-import com.fs.crm.domain.CrmCustomer;
-import com.fs.crm.domain.CrmCustomerUser;
-import com.fs.crm.domain.CrmCustomerVisit;
-import com.fs.crm.mapper.CrmCustomerMapper;
-import com.fs.crm.mapper.CrmCustomerUserMapper;
-import com.fs.crm.mapper.CrmCustomerVisitMapper;
+import com.fs.crm.domain.*;
+import com.fs.crm.enums.CustomerLogEnum;
+import com.fs.crm.mapper.*;
 import com.fs.crm.param.CrmCustomerStatisticsParam;
 import com.fs.crm.param.CrmCustomerVisitAddParam;
 import com.fs.crm.param.CrmCustomerVisitListParam;
@@ -18,8 +15,10 @@ import com.fs.crm.service.ICrmCustomerVisitService;
 import com.fs.crm.vo.CrmCustomerVisitExportVO;
 import com.fs.crm.vo.CrmCustomerVisitListVO;
 import com.fs.crm.vo.CrmCustomerVisitStatisticsVO;
+import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.BeanUtils;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
 import org.springframework.stereotype.Service;
 
 import java.util.Date;
@@ -33,6 +32,7 @@ import java.util.Map;
  * @date 2022-12-21
  */
 @Service
+@Slf4j
 public class CrmCustomerVisitServiceImpl implements ICrmCustomerVisitService
 {
     @Autowired
@@ -41,6 +41,16 @@ public class CrmCustomerVisitServiceImpl implements ICrmCustomerVisitService
     private CrmCustomerUserMapper crmCustomerUserMapper;
     @Autowired
     private CrmCustomerMapper crmCustomerMapper;
+
+    @Autowired
+    private CrmFollowUpMapper crmFollowUpMapper;
+
+    @Autowired
+    private CrmCustomerLogsMapper logsMapper;
+
+    @Autowired
+    private ThreadPoolTaskExecutor threadPoolTaskExecutor;
+
     /**
      * 查询跟进
      *
@@ -184,4 +194,71 @@ public class CrmCustomerVisitServiceImpl implements ICrmCustomerVisitService
     public List<CrmCustomerVisitExportVO> selectCrmCustomerVisitExportListVO(CrmCustomerVisitListParam param) {
         return crmCustomerVisitMapper.selectCrmCustomerVisitExportListVO(param);
     }
+
+    /**
+     * 跟进提醒
+     */
+    @Override
+    public void followupNotice() {
+        //查询有下次跟进的记录
+        List<CrmCustomerVisit> list = crmCustomerVisitMapper.selectNextTimeList();
+        if (list != null && !list.isEmpty()) {
+            //添加到提醒表
+            list.forEach(visit->{
+                //查看是否已存在
+                CrmFollowUp crmFollowUp = new CrmFollowUp();
+                crmFollowUp.setCustomerId(visit.getCustomerId());
+                crmFollowUp.setVisitId(visit.getVisitId());
+                crmFollowUp.setCompanyUserId(visit.getCompanyUserId());
+                crmFollowUp.setIsHandle(0);
+                crmFollowUp.setCreateTime(new Date());
+                crmFollowUpMapper.insertCrmFollowUp(crmFollowUp);
+            });
+        }
+    }
+
+    /**
+     * 回收未跟进的商机客户 9.2 所有含有商机的客户不入公海
+     */
+    @Override
+    public void recoveryBusiness() {
+        //查询有下次跟进的记录的customerId
+        List<CrmCustomer> list = crmCustomerVisitMapper.selectVisitTimeout();
+        if (list != null && !list.isEmpty()) {
+            list.forEach(customer -> {
+                threadPoolTaskExecutor.execute(() -> {
+                    long startTime = System.currentTimeMillis();
+                    if (customer.getIsPool() != null && customer.getIsPool() == 1) {
+                        return; // 已经是公海池客户,不重复处理
+                    }
+                    Long customerUserId = customer.getCustomerUserId();
+                    if (customerUserId != null) {
+                        CrmCustomerUser crmCustomerUser = crmCustomerUserMapper.selectCrmCustomerUserById(customerUserId);
+                        if (crmCustomerUser != null) {
+                            crmCustomerUser.setIsPool(1);
+                            crmCustomerUser.setPoolTime(new Date());
+                            crmCustomerUserMapper.updateCrmCustomerUser(crmCustomerUser);
+
+                        }
+                    }
+                    customer.setIsPool(1);
+                    customer.setIsReceive(0);
+                    customer.setPoolTime(new Date());
+                    customer.setCustomerUserId(null);
+                    crmCustomerMapper.updateCrmCustomer(customer);
+                    //写日志
+                    CrmCustomerLogs logs = new CrmCustomerLogs();
+                    logs.setCustomerId(customer.getCustomerId());
+                    logs.setCreateTime(new Date());
+                    logs.setLogsType(CustomerLogEnum.RECOVER.getValue());
+                    logs.setTitle(CustomerLogEnum.RECOVER.getDesc());
+                    logs.setRemark("因未在下次跟进时间跟进 系统自动回收客户" + customer.getCustomerName());
+                    logs.setCompanyUserId(customer.getReceiveUserId());
+                    logsMapper.insertCrmCustomerLogs(logs);
+                    log.info("RecoveryBusinessTask for completed in {} ms", System.currentTimeMillis() - startTime);
+
+                });
+            });
+        }
+    }
 }

+ 412 - 0
fs-service/src/main/java/com/fs/crm/service/impl/CrmExtDetailServiceImpl.java

@@ -0,0 +1,412 @@
+package com.fs.crm.service.impl;
+
+import com.fs.common.core.domain.R;
+import com.fs.common.utils.StringUtils;
+import com.fs.crm.domain.CrmExtDetail;
+import com.fs.crm.domain.CrmExtLog;
+import com.fs.crm.mapper.CrmExtDetailMapper;
+import com.fs.crm.param.CrmExtDetailAddOrUpdateParam;
+import com.fs.crm.service.ICrmExtDetailService;
+import com.fs.crm.service.ICrmExtLogService;
+import com.fs.crm.vo.CrmExtDetailVo;
+import com.fs.watch.param.BaseQueryParam;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.*;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * 字段扩展详情Service业务层处理
+ *
+ * @author fs
+ * @date 2025-02-17
+ */
+@Service
+@Slf4j
+public class CrmExtDetailServiceImpl implements ICrmExtDetailService {
+    @Autowired
+    private CrmExtDetailMapper crmExtDetailMapper;
+
+    @Autowired
+    private ICrmExtLogService crmExtLogService;
+
+    // 正则表达式:只能包含字母和下划线
+    private static final String COLUMN_NAME_PATTERN = "^[a-zA-Z_]+$";
+    // 定义允许的字段类型
+    private static final List<String> allowedTypes = Arrays.asList("INT", "FLOAT", "DATE", "DATETIME", "VARCHAR", "int", "float", "date", "datetime", "varchar");
+
+    /**
+     * 查询字段扩展详情
+     *
+     * @param id 字段扩展详情ID
+     * @return 字段扩展详情
+     */
+    @Override
+    public CrmExtDetail selectCrmExtDetailById(Long id) {
+        return crmExtDetailMapper.selectCrmExtDetailById(id);
+    }
+
+    @Override
+    public Map<String,Object> selectCrmExtDetailByCondition(Map<String, Object> map) {
+        return crmExtDetailMapper.selectCrmExtDetailByCondition(map);
+    }
+
+    /**
+     * 查询字段扩展详情列表
+     *
+     * @param crmExtDetail 字段扩展详情
+     * @return 字段扩展详情
+     */
+    @Override
+    public List<CrmExtDetail> selectCrmExtDetailList(CrmExtDetail crmExtDetail) {
+        return crmExtDetailMapper.selectCrmExtDetailList(crmExtDetail);
+    }
+
+    /**
+     * 新增字段扩展详情
+     *
+     * @param crmExtDetail 字段扩展详情
+     * @return 结果
+     */
+    @Override
+    public int insertCrmExtDetail(Map<String,Object> crmExtDetail) {
+        //获取字段
+        List<CrmExtDetailVo> columns = crmExtDetailMapper.getTableColumnsMetaList(null);
+        List<String> column = new ArrayList<>();
+        List<Object> values = new ArrayList<>();
+        for (CrmExtDetailVo vo : columns) {
+            column.add(vo.getColumnName());
+            values.add(crmExtDetail.get(vo.getColumnName()));
+        }
+        //correlate_id
+        column.add("correlate_id");
+        values.add(crmExtDetail.get("correlate_id"));
+        //correlate_type
+        column.add("correlate_type");
+        values.add(crmExtDetail.get("correlate_type"));
+        return crmExtDetailMapper.insertCrmExtDetail(column,values);
+    }
+
+    /**
+     * 修改字段扩展详情
+     *
+     * @param crmExtDetail 字段扩展详情
+     * @return 结果
+     */
+    @Override
+    public int updateCrmExtDetail(Map<String,Object> crmExtDetail) {
+        Integer id = (Integer) crmExtDetail.get("id");
+        crmExtDetail.remove("id");
+        return crmExtDetailMapper.updateCrmExtDetail(crmExtDetail,id);
+    }
+
+    /**
+     * 批量删除字段扩展详情
+     *
+     * @param ids 需要删除的字段扩展详情ID
+     * @return 结果
+     */
+    @Override
+    public int deleteCrmExtDetailByIds(Long[] ids) {
+        return crmExtDetailMapper.deleteCrmExtDetailByIds(ids);
+    }
+
+    /**
+     * 删除字段扩展详情信息
+     *
+     * @param id 字段扩展详情ID
+     * @return 结果
+     */
+    @Override
+    public int deleteCrmExtDetailById(Long id) {
+        return crmExtDetailMapper.deleteCrmExtDetailById(id);
+    }
+
+    @Override
+    public List<CrmExtDetailVo> getTableColumnsMetadata(BaseQueryParam param) {
+        return crmExtDetailMapper.getTableColumnsMetadata(param);
+    }
+
+    @Override
+    public List<CrmExtDetailVo> getTableColumnsMetaList(BaseQueryParam param) {
+        return crmExtDetailMapper.getTableColumnsMetaList(param);
+    }
+
+    @Override
+    @Transactional
+    public R insertColumn(CrmExtDetailAddOrUpdateParam param, String createBy) {
+        boolean flag = false;
+        param.setColumnType(param.getDataType());
+        Map<String, Object> map = setParam(param);
+        boolean isExecute = (boolean) map.get("flag");
+        StringBuilder sql = (StringBuilder) map.get("sql");
+        StringBuilder msg = (StringBuilder) map.get("msg");
+
+        StringBuilder errorMsg = new StringBuilder();
+        try {
+            //2. 执行 添加到数据库
+            if (isExecute) {
+                crmExtDetailMapper.insertColumn(param);
+                flag = true;
+            }
+        } catch (Exception e) {
+            //3.添加日志
+            msg = new StringBuilder("添加失败,请联系管理员!");
+            errorMsg.append(e.getMessage());
+        } finally {
+            addLog(param, createBy, sql, "添加", flag, errorMsg);
+        }
+
+        return flag ? R.ok() : R.error(String.valueOf(msg));
+
+    }
+
+    /**
+     * 批量删除列
+     *
+     * @param columnNames
+     * @return
+     */
+    @Override
+    public R deleteColumns(String[] columnNames, String createBy) {
+        boolean flag = false;
+        String msg = "";
+        if (columnNames.length < 0) {
+            msg = "未选择删除列";
+        }
+
+        StringBuilder errorMsg = new StringBuilder();
+        try {
+            crmExtDetailMapper.deleteColumns(columnNames);
+            flag = true;
+        } catch (Exception e) {
+            msg = "删除失败,请联系管理员!";
+            errorMsg.append(e.getMessage());
+        } finally {
+            CrmExtDetailAddOrUpdateParam param = new CrmExtDetailAddOrUpdateParam();
+            param.setColumnName(Arrays.toString(columnNames));
+            StringBuilder sql = new StringBuilder("ALTER TABLE crm_ext_detail ");
+            for (int i = 0; i < columnNames.length; i++) {
+                if (i == columnNames.length - 1) {
+                    sql.append("DROP COLUMN ").append(columnNames[i]).append(";");
+                } else {
+                    sql.append("DROP COLUMN ").append(columnNames[i]).append(", ");
+                }
+            }
+            addLog(param, createBy, sql, "删除",flag,errorMsg);
+
+        }
+        return flag ? R.ok() : R.error(msg);
+    }
+
+    @Override
+    public R updateColumn(CrmExtDetailAddOrUpdateParam param, String createBy) {
+        Boolean flag = false;
+        StringBuilder msg = new StringBuilder();
+        Boolean isExecute = true;
+        param.setColumnType(param.getDataType());
+        StringBuilder sql = new StringBuilder();
+        //1参数校验
+        //旧字段是否存在
+        if (!checkIfFieldExists(param.getOldColumnName())) {
+            msg.append("旧字段不存在");
+            isExecute = false;
+        }
+        if (isExecute){
+            Map<String, Object> map = setParam(param);
+            isExecute = (boolean) map.get("flag");
+            sql = (StringBuilder) map.get("sql");
+            msg = (StringBuilder) map.get("msg");
+        }else {
+            addLog(param, createBy, sql, "修改",isExecute,msg);
+        }
+
+        StringBuilder errorMsg = new StringBuilder();
+        try {
+            //2. 执行 添加到数据库
+            if (isExecute) {
+                crmExtDetailMapper.updateColumn(param);
+                flag = true;
+            }
+        } catch (Exception e) {
+            msg.append("修改失败,请联系管理员!");
+            errorMsg.append(e.getMessage());
+        } finally {
+            addLog(param, createBy, sql, "修改",flag,errorMsg);
+        }
+        return flag ? R.ok() : R.error(String.valueOf(msg));
+    }
+
+    @Override
+    public void deleteByCorrelateIdAndType(Long[] customerIds, String customerId) {
+        crmExtDetailMapper.deleteByCorrelateIdAndType(customerIds,customerId);
+    }
+
+    private Map<String, Object> setParam(CrmExtDetailAddOrUpdateParam param) {
+        StringBuilder msg = new StringBuilder();
+        StringBuilder sql = new StringBuilder();
+        Boolean flag = Boolean.FALSE;
+        HashMap<String, Object> res = new HashMap<>();
+        res.put("msg", msg);
+        res.put("sql", sql);
+        res.put("flag", flag);
+        while (true) {
+            String columnName = param.getColumnName(); //新字段
+            if (StringUtils.isBlank(columnName)) {
+                msg.append("字段名不能为空");
+                return res;
+            }
+
+
+            String oldColumnName = param.getOldColumnName();
+            if (StringUtils.isNotBlank(oldColumnName)) {
+                //修改
+                if (oldColumnName.equals(param.getColumnName())) {
+                    //不修改列名
+                    sql.append("ALTER TABLE crm_ext_detail MODIFY COLUMN ").append(columnName).append(" ");
+                } else {
+                    //修改列名
+                    //修改 只有 新旧字段不一样的时候 需要判断
+                    if (checkIfFieldExists(columnName)) {
+                        msg.append("字段名已经存在");
+                        return res;
+                    }
+                    // 使用正则表达式校验字段名
+                    Pattern pattern = Pattern.compile(COLUMN_NAME_PATTERN);
+                    Matcher matcher = pattern.matcher(columnName);
+                    if (!matcher.matches()) {
+                        msg.append("字段名只能包含字母和下划线");
+                        return res;
+                    }
+                    sql.append("ALTER TABLE crm_ext_detail CHANGE COLUMN ").append(oldColumnName).append(" ").append(columnName).append(" ");
+                }
+            } else {
+                //新增
+                if (checkIfFieldExists(columnName)) {
+                    msg.append("字段名已经存在");
+                    return res;
+                }
+                // 使用正则表达式校验字段名
+                Pattern pattern = Pattern.compile(COLUMN_NAME_PATTERN);
+                Matcher matcher = pattern.matcher(columnName);
+                if (!matcher.matches()) {
+                    msg.append("字段名只能包含字母和下划线");
+                    return res;
+                }
+                sql.append("ALTER TABLE crm_ext_detail ADD COLUMN ").append(columnName).append(" ");
+            }
+
+            //字段类型 目前仅支持 int float date dateTime 四种类型
+            String columnType = param.getColumnType();
+            if (!allowedTypes.contains(columnType)) {
+                msg.append("字段类型暂时不支持添加,请联系管理员");
+                return res;
+            }
+            String datetimePrecision = param.getDatetimePrecision();
+            if (StringUtils.isNotBlank(datetimePrecision)) {
+                if ("DATE".equals(datetimePrecision) || "DATETIME".equals(datetimePrecision)) {
+                    param.setColumnType(datetimePrecision);
+                }
+            }
+            //字符串长度
+            if ("VARCHAR".equals(columnType) || "varchar".equals(columnType)) {
+                Integer characterMaximumLength = param.getCharacterMaximumLength();
+                if (characterMaximumLength != null) {
+                    if (characterMaximumLength > 100) {
+                        msg.append("字符串长度超过100,联系管理人员");
+                        return res;
+                    }
+                    columnType = columnType + "(" + characterMaximumLength + ")";
+                    param.setColumnType(columnType);
+                } else {
+                    //初始长度为10
+                    columnType = "VARCHAR(10)";
+                    param.setColumnType(columnType);
+                }
+            }
+            sql.append(columnType).append(" ");
+            //默认值
+            String columnDefault = param.getColumnDefault();
+            if (columnDefault != null) {
+                //字符串类型
+                if (columnType.contains("VARCHAR") || columnType.contains("varchar")) {
+                    if (columnDefault.length() > param.getCharacterMaximumLength()) {
+                        msg.append("默认值长度错误");
+                        return res;
+                    }
+                }
+                //整数类型
+                if ("INT".equals(columnType)) {
+                    try {
+                        Integer.valueOf(columnDefault);
+                    } catch (NumberFormatException e) {
+                        msg.append("默认值格式不正确");
+                        return res;
+                    }
+                }
+                //小数类型
+                if ("FLOAT".equals(columnType)) {
+                    try {
+                        Float.valueOf(columnDefault);
+                    } catch (NumberFormatException e) {
+                        msg.append("默认值格式不正确");
+                        return res;
+                    }
+                }
+
+                sql.append("DEFAULT '").append(columnDefault).append("'").append(" ");
+            }
+
+            //是否为null
+            String isNullable = param.getIsNullable();
+            if (StringUtils.isNotBlank(isNullable)) {
+                if ("NO".equals(isNullable)) {
+                    param.setIsNullable("NOT NULL");
+                } else {
+                    param.setIsNullable(null);
+                }
+            }
+            break;
+        }
+        res.put("flag",true);
+        return res;
+    }
+
+    private void addLog(CrmExtDetailAddOrUpdateParam param, String createBy, StringBuilder sql, String type, boolean result, StringBuilder errorMsg) {
+        //组装sql
+        if (StringUtils.isNotBlank(param.getIsNullable())) {
+            sql.append(param.getIsNullable()).append(" ");
+        }
+        if (StringUtils.isNotBlank(param.getColumnComment())) {
+            sql.append("COMMENT '").append(param.getColumnComment()).append("'");
+        }
+        addOrUpdateLog(createBy, sql, param.getColumnName(), type, result, errorMsg);
+    }
+
+    private void addOrUpdateLog(String createBy, StringBuilder sql, String columnName, String executeType, boolean result, StringBuilder errorMsg) {
+        //添加
+        CrmExtLog log = new CrmExtLog();
+
+        log.setExecuteSql(String.valueOf(sql));
+        log.setColumnName(columnName);
+        //目前仅客户表 todo
+        log.setCorrelateTypeId("customer_id");
+        //备份crm_ext_detail数据 todo
+        log.setBackupUrl(null);
+        log.setCreateBy(createBy);
+        log.setType(executeType);
+        log.setResult(result ? "成功" : "失败");
+        log.setErrorMsg(String.valueOf(errorMsg));
+        crmExtLogService.insertCrmExtLog(log);
+
+    }
+
+    private boolean checkIfFieldExists(String fieldName) {
+        return crmExtDetailMapper.checkIfFieldExists(fieldName) > 0;
+    }
+
+}

+ 96 - 0
fs-service/src/main/java/com/fs/crm/service/impl/CrmExtLogServiceImpl.java

@@ -0,0 +1,96 @@
+package com.fs.crm.service.impl;
+
+import com.fs.common.utils.DateUtils;
+import com.fs.crm.domain.CrmExtLog;
+import com.fs.crm.mapper.CrmExtLogMapper;
+import com.fs.crm.service.ICrmExtLogService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+/**
+ * 修改字段扩展日志Service业务层处理
+ *
+ * @author fs
+ * @date 2025-02-17
+ */
+@Service
+public class CrmExtLogServiceImpl implements ICrmExtLogService
+{
+    @Autowired
+    private CrmExtLogMapper crmExtLogMapper;
+
+    /**
+     * 查询修改字段扩展日志
+     *
+     * @param logId 修改字段扩展日志ID
+     * @return 修改字段扩展日志
+     */
+    @Override
+    public CrmExtLog selectCrmExtLogById(Long logId)
+    {
+        return crmExtLogMapper.selectCrmExtLogById(logId);
+    }
+
+    /**
+     * 查询修改字段扩展日志列表
+     *
+     * @param crmExtLog 修改字段扩展日志
+     * @return 修改字段扩展日志
+     */
+    @Override
+    public List<CrmExtLog> selectCrmExtLogList(CrmExtLog crmExtLog)
+    {
+        return crmExtLogMapper.selectCrmExtLogList(crmExtLog);
+    }
+
+    /**
+     * 新增修改字段扩展日志
+     *
+     * @param crmExtLog 修改字段扩展日志
+     * @return 结果
+     */
+    @Override
+    public int insertCrmExtLog(CrmExtLog crmExtLog)
+    {
+        crmExtLog.setCreateTime(DateUtils.getNowDate());
+        return crmExtLogMapper.insertCrmExtLog(crmExtLog);
+    }
+
+    /**
+     * 修改修改字段扩展日志
+     *
+     * @param crmExtLog 修改字段扩展日志
+     * @return 结果
+     */
+    @Override
+    public int updateCrmExtLog(CrmExtLog crmExtLog)
+    {
+        return crmExtLogMapper.updateCrmExtLog(crmExtLog);
+    }
+
+    /**
+     * 批量删除修改字段扩展日志
+     *
+     * @param logIds 需要删除的修改字段扩展日志ID
+     * @return 结果
+     */
+    @Override
+    public int deleteCrmExtLogByIds(Long[] logIds)
+    {
+        return crmExtLogMapper.deleteCrmExtLogByIds(logIds);
+    }
+
+    /**
+     * 删除修改字段扩展日志信息
+     *
+     * @param logId 修改字段扩展日志ID
+     * @return 结果
+     */
+    @Override
+    public int deleteCrmExtLogById(Long logId)
+    {
+        return crmExtLogMapper.deleteCrmExtLogById(logId);
+    }
+}

+ 103 - 0
fs-service/src/main/java/com/fs/crm/service/impl/CrmFollowUpServiceImpl.java

@@ -0,0 +1,103 @@
+package com.fs.crm.service.impl;
+
+import com.fs.common.utils.DateUtils;
+import com.fs.crm.domain.CrmFollowUp;
+import com.fs.crm.mapper.CrmFollowUpMapper;
+import com.fs.crm.service.ICrmFollowUpService;
+import com.fs.crm.vo.CrmFollowUpVo;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+/**
+ * 跟进提醒Service业务层处理
+ *
+ * @author fs
+ * @date 2025-04-15
+ */
+@Service
+public class CrmFollowUpServiceImpl implements ICrmFollowUpService
+{
+    @Autowired
+    private CrmFollowUpMapper crmFollowUpMapper;
+
+    /**
+     * 查询跟进提醒
+     *
+     * @param id 跟进提醒ID
+     * @return 跟进提醒
+     */
+    @Override
+    public CrmFollowUp selectCrmFollowUpById(Long id)
+    {
+        return crmFollowUpMapper.selectCrmFollowUpById(id);
+    }
+
+    /**
+     * 查询跟进提醒列表
+     *
+     * @param crmFollowUp 跟进提醒
+     * @return 跟进提醒
+     */
+    @Override
+    public List<CrmFollowUp> selectCrmFollowUpList(CrmFollowUp crmFollowUp)
+    {
+        return crmFollowUpMapper.selectCrmFollowUpList(crmFollowUp);
+    }
+
+    /**
+     * 新增跟进提醒
+     *
+     * @param crmFollowUp 跟进提醒
+     * @return 结果
+     */
+    @Override
+    public int insertCrmFollowUp(CrmFollowUp crmFollowUp)
+    {
+        crmFollowUp.setCreateTime(DateUtils.getNowDate());
+        return crmFollowUpMapper.insertCrmFollowUp(crmFollowUp);
+    }
+
+    /**
+     * 修改跟进提醒
+     *
+     * @param crmFollowUp 跟进提醒
+     * @return 结果
+     */
+    @Override
+    public int updateCrmFollowUp(CrmFollowUp crmFollowUp)
+    {
+        crmFollowUp.setUpdateTime(DateUtils.getNowDate());
+        return crmFollowUpMapper.updateCrmFollowUp(crmFollowUp);
+    }
+
+    /**
+     * 批量删除跟进提醒
+     *
+     * @param ids 需要删除的跟进提醒ID
+     * @return 结果
+     */
+    @Override
+    public int deleteCrmFollowUpByIds(Long[] ids)
+    {
+        return crmFollowUpMapper.deleteCrmFollowUpByIds(ids);
+    }
+
+    /**
+     * 删除跟进提醒信息
+     *
+     * @param id 跟进提醒ID
+     * @return 结果
+     */
+    @Override
+    public int deleteCrmFollowUpById(Long id)
+    {
+        return crmFollowUpMapper.deleteCrmFollowUpById(id);
+    }
+
+    @Override
+    public List<CrmFollowUpVo> selectCrmFollowUpAndCustomerList(CrmFollowUp crmFollowUp) {
+        return crmFollowUpMapper.selectCrmFollowUpAndCustomerList(crmFollowUp);
+    }
+}

+ 125 - 0
fs-service/src/main/java/com/fs/crm/vo/CrmBusinessListVO.java

@@ -0,0 +1,125 @@
+package com.fs.crm.vo;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fs.common.annotation.Excel;
+import com.fs.crm.domain.CrmCustomerContacts;
+import lombok.Data;
+
+import java.util.Date;
+import java.util.List;
+
+@Data
+public class CrmBusinessListVO {
+    /** 商机id */
+    @Excel(name = "商机id")
+    private Long businessId;
+
+    /** 客户id */
+    @Excel(name = "客户id")
+    private Long customerId;
+
+    /** 客户名称 */
+    @Excel(name = "客户名称")
+    private String customerName;
+
+    /** 线索来源
+     0企查询
+     1企查查
+     2领鸟云
+     3其他 */
+//    @Excel(name = "线索来源")
+    private Integer source;
+
+    /** 客户经理 */
+    @Excel(name = "客户经理")
+    private String manager;
+
+    /** 公司名称(只填公司全称,个人用户就填名字) */
+    @Excel(name = "公司名称")
+    private String companyName;
+
+    /** 电话号码 */
+    @Excel(name = "电话号码")
+    private String mobile;
+
+    /** 电话号码 */
+    private List<CrmCustomerContacts> mobileList;
+
+    /** 接口人角色 0老板,1采购,2财务,3技术负责人,4一般技术人员,5项目负责人,6其他 */
+//    @Excel(name = "接口人角色")
+    private Integer contactRole;
+
+    /** 业务场景网站,电商,游戏,小程序,APP,OA,ERP,CRM,物联网,AI人工智能,国产化,数字人,内部办公系统,等保,其他 */
+    @Excel(name = "业务场景")
+    private String businessScenario;
+
+    /** 方案涉及的产品 */
+    @Excel(name = "方案涉及的产品")
+    private String product;
+
+    /** 采购周期(单位:天) */
+    @Excel(name = "采购周期(单位:天)")
+    private Long purchaseCycle;
+
+    /** 跟进状态:0跟进中,1已流失,2已赢单,3待验证 */
+    @Excel(name = "跟进状态")
+    private Integer businessStatus;
+
+    @Excel(name = "相关客户跟进内容")
+    private String content;
+
+
+    /** 沟通进展 */
+    @Excel(name = "沟通进展")
+    private String remark;
+
+    /** 项目阶段*/
+    @Excel(name = "项目阶段")
+    private String projectPhase;
+
+    /** 意向等级 */
+//    @Excel(name = "意向等级")
+    private Integer level;
+
+    /** 是否绑定BP账号:0未注册 1已注册未绑定 2不明确 3已绑定 */
+//    @Excel(name = "是否绑定BP账号:0未注册 1已注册未绑定 2不明确 3已绑定")
+    private Integer isBp;
+
+    /** BP账户 */
+    @Excel(name = "BP账户")
+    private String bpAccount;
+
+    /** 预计成单时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd")
+    @Excel(name = "预计成单时间", width = 30, dateFormat = "yyyy-MM-dd")
+    private Date preTime;
+
+    /** 预计付费(元) */
+    @Excel(name = "预计付费(元)")
+    private Float preMoney;
+
+    /** 下次跟进时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd")
+    @Excel(name = "下次跟进时间", width = 30, dateFormat = "yyyy-MM-dd")
+    private Date nextTime;
+
+    /** 回收时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd")
+    @Excel(name = "回收时间", width = 30, dateFormat = "yyyy-MM-dd")
+    private Date recoveryTime;
+
+    /** 状态(已分配1  进行中2  回收3) */
+//    @Excel(name = "状态(已分配1  进行中2  回收3)")
+    private Integer status;
+
+    /** 最后一次跟进时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @Excel(name = "挖掘时间",dateFormat = "yyyy-MM-dd HH:mm:ss")
+    private Date createTime;
+
+    private Long customerUserId;
+
+    private Long companyUserId;
+
+    private Long companyId;
+}

+ 93 - 0
fs-service/src/main/java/com/fs/crm/vo/CrmExtDetailVo.java

@@ -0,0 +1,93 @@
+package com.fs.crm.vo;
+
+import com.fs.common.annotation.Excel;
+import lombok.Data;
+
+@Data
+public class CrmExtDetailVo {
+    /**
+     * 字段名
+     */
+    @Excel(name = "字段名")
+    private String columnName;
+
+    /**
+     * 数据类型及其额外的属性
+     */
+//    @Excel(name = "数据类型")
+    private String columnType;
+
+    /**
+     * 默认值
+     */
+    @Excel(name = "默认值")
+    private String columnDefault;
+
+
+    /**
+     * 是否可以为 NULL YES:允许为null NO:不允许
+     */
+    @Excel(name = "是否可以为NULL")
+    private String isNullable;
+
+    /**
+     * 数据类型。例如:VARCHAR, INT, TEXT, DATE 等。
+     */
+    @Excel(name = "数据类型")
+    private String dataType;
+
+    /**
+     * 字符类型的列(如 VARCHAR 或 CHAR),该列表示字符的最大长度。如果该列不是字符类型,则该字段为 NULL
+     */
+    @Excel(name = "字符串长度")
+    private Integer characterMaximumLength;
+
+    /**
+     * 数字类型的列,表示小数点后的位数(即精度的小数部分)
+     */
+//    @Excel(name = "小数点后的位数")
+//    private String numericScale;
+
+
+    /**
+     * 对于日期/时间类型的列(如 DATETIME 或 TIMESTAMP),该列表示日期时间值的精度(通常是秒或毫秒)。
+     */
+//    @Excel(name = "时间类型")
+    private String datetimePrecision;
+
+
+    /**
+     * 是否是索引的一部分
+     * PRI: 主键列
+     * UNI: 唯一索引列
+     * MUL: 非唯一索引列
+     * NULL: 该列没有索引
+     */
+    private String columnKey;
+
+
+    /**
+     * 列的附加信息
+     * auto_increment: 表示该列为自增列(通常用于主键)
+     * unsigned: 表示该列的无符号属性(对于整数类型)
+     * on update CURRENT_TIMESTAMP: 表示该列的默认值在每次更新时会变为当前时间。
+     */
+    private String extra;
+
+
+    /**
+     * 该列表示对该列的权限,例如 SELECT, INSERT, UPDATE 等。通常,该字段为 SELECT,INSERT,UPDATE,REFERENCES 等
+     */
+    private String privileges;
+
+    /**
+     * 字段类型
+     */
+    private Integer nullable;
+
+    /**
+     * 该列包含列的注释,如果没有注释,则为 NULL
+     */
+    @Excel(name = "注释")
+    private String columnComment;
+}

+ 15 - 0
fs-service/src/main/java/com/fs/crm/vo/CrmFollowUpVo.java

@@ -0,0 +1,15 @@
+package com.fs.crm.vo;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fs.crm.domain.CrmFollowUp;
+import lombok.Data;
+
+import java.util.Date;
+
+@Data
+public class CrmFollowUpVo extends CrmFollowUp {
+    private String customerName;
+    private String content;
+    @JsonFormat(pattern = "yyyy-MM-dd")
+    private Date nextTime;
+}

+ 63 - 0
fs-service/src/main/java/com/fs/his/config/AppConfig.java

@@ -5,6 +5,7 @@ import com.fs.his.domain.FsPackage;
 import com.fs.his.vo.GameVo;
 import lombok.Data;
 
+import java.math.BigDecimal;
 import java.util.List;
 
 @Data
@@ -27,4 +28,66 @@ public class AppConfig {
     private String corpUrl; //APP客服配置 企业主体链接
     private Integer addIntegral; //玩一局游戏加多少积分
     private Integer defaultRewardGold; //看视频获取多少金币
+
+    private Integer isOpenWithdraw;//是否开启提现按钮 0关闭 1开启
+
+    private Integer withdrawRatio;//兑换佣金比例 1元=多少积分
+    private List<Integer> integralTypes;//允许兑换积分类型
+
+    private Integer isNew;//0:老商户 商家转账到零钱 1:新商户 商家转账
+
+    /**
+     * 商户号.
+     */
+    private String mchId;
+    /**
+     * 商户密钥.
+     */
+    private String mchKey;
+
+    /**
+     * p12证书文件的绝对路径或者以classpath:开头的类路径.
+     */
+    private String keyPath;
+
+    /**
+     * apiclient_key.pem证书文件的绝对路径或者以classpath:开头的类路径.
+     */
+    private String privateKeyPath;
+
+    /**
+     * apiclient_cert.pem证书文件的绝对路径或者以classpath:开头的类路径.
+     */
+    private String privateCertPath;
+
+    /**
+     * apiV3 秘钥值.
+     */
+    private String apiV3Key;
+    /**
+     * 公钥ID
+     */
+    private String publicKeyId;
+
+    /**
+     * pub_key.pem证书文件的绝对路径或者以classpath:开头的类路径.
+     */
+    private String publicKeyPath;
+
+    private String notifyUrl;
+
+    private String withdrawalNotifyUrl;
+
+
+    //一次允许提现最大金额(元)
+    private BigDecimal maxApplicationAmount;
+
+    //一天允提现次数
+    private Integer withdrawNum;
+
+    //连续提现几天封控
+    private Integer limitDayNum;
+
+    //连续提现几天封控
+    private BigDecimal limitAmount;
 }

+ 2 - 0
fs-service/src/main/java/com/fs/his/config/IntegralConfig.java

@@ -26,4 +26,6 @@ public class IntegralConfig implements Serializable {
     private Integer integralAddPatient;//新用户完善就诊人获得积分
     private Integer integralAddUserAddress;//新用户填写收货地址获取积分
     private Integer integralSubscriptCourse;//付费课程订阅积分比例
+    private Integer downloadAppIntegral; // 首次下载app获取积分
+    private Integer integralPlayGame; //  游戏获取积分
 }

+ 2 - 0
fs-service/src/main/java/com/fs/his/enums/FsUserIntegralLogTypeEnum.java

@@ -35,6 +35,8 @@ public enum FsUserIntegralLogTypeEnum {
     TYPE_25(25, "直播完课积分"),
     TYPE_26(26, "直播红包积分"),
     TYPE_27(27, "积分订单取消退回积分"),
+    TYPE_28(28, "首次下载APP获取积分"),
+    TYPE_29( 29,"玩游戏获取积分"),
     ;
 
 

+ 77 - 77
fs-service/src/main/java/com/fs/his/service/impl/FsStorePaymentServiceImpl.java

@@ -1775,82 +1775,6 @@ public class FsStorePaymentServiceImpl implements IFsStorePaymentService {
         return fsStorePaymentMapper.selectAllPayment();
     }
 
-    @Override
-    @Transactional
-    public R sendAppRedPacket(WxSendRedPacketParam param) {
-        //组合返回参数
-        R result = new R();
-        String json = configService.selectConfigByKey("his.AppRedPacket");
-        AppRedPacketConfig config = JSONUtil.toBean(json, AppRedPacketConfig.class);
-        if (config.getIsNew() != null && config.getIsNew() == 1) {
-            result = sendRedPacketV3(param, config);
-        } else {
-            result= sendRedPacketLegacy(param, config);
-        }
-
-        result.put("mchId", config.getMchId());
-        result.put("isNew",config.getIsNew());
-        logger.info("App提现返回:{}",result);
-        return result;
-    }
-
-    // 内部方法:处理新版本的发红包逻辑
-    private R sendRedPacketV3(WxSendRedPacketParam param,AppRedPacketConfig config) {
-
-        WxPayConfig payConfig = new WxPayConfig();
-        BeanUtils.copyProperties(config, payConfig);
-        WxPayService wxPayService = new WxPayServiceImpl();
-        wxPayService.setConfig(payConfig);
-        TransferService transferService = wxPayService.getTransferService();
-
-        TransferBillsRequest request = new TransferBillsRequest();
-        request.setAppid(param.getAppId());
-        request.setOpenid(param.getOpenId());
-
-        String code =  OrderCodeUtils.getOrderSn();
-        if(StringUtils.isEmpty(code)){
-            return R.error("订单生成失败,请重试");
-        }
-//        String code = String.valueOf(IdUtil.getSnowflake(0, 0).nextId());
-        request.setOutBillNo("fsAppRed" + code);
-        if (param.getAmount() == null) {
-            return R.error();
-        }
-        Integer amount = WxPayUnifiedOrderRequest.yuanToFen(param.getAmount().toString());
-        request.setTransferAmount(amount);
-        request.setTransferRemark("提现红包领取");
-        request.setUserRecvPerception("活动奖励");
-        request.setNotifyUrl(config.getNotifyUrl());
-        request.setTransferSceneId("1000");
-
-        // 设置场景信息
-        List<TransferBillsRequest.TransferSceneReportInfo> transferSceneReportInfos = new ArrayList<>();
-        TransferBillsRequest.TransferSceneReportInfo info1 = new TransferBillsRequest.TransferSceneReportInfo();
-        info1.setInfoType("活动名称");
-        info1.setInfoContent("提现红包领取");
-        transferSceneReportInfos.add(info1);
-
-        TransferBillsRequest.TransferSceneReportInfo info2 = new TransferBillsRequest.TransferSceneReportInfo();
-        info2.setInfoType("奖励说明");
-        info2.setInfoContent("提现红包领取");
-        transferSceneReportInfos.add(info2);
-        request.setTransferSceneReportInfos(transferSceneReportInfos);
-
-
-        try {
-            logger.info("app商家转账开始:[param:{}]", request);
-            TransferBillsResult transferBillsResult = transferService.transferBills(request);
-            logger.info("Method...商家转账支付完成:[msg:{}]", transferBillsResult);
-            return R.ok("发送红包成功").put("data", transferBillsResult).put("mchId", config.getMchId())
-                    .put("package",transferBillsResult.getPackageInfo())
-                    .put("appId",param.getAppId())
-                    .put("orderCode",request.getOutBillNo());
-        } catch (Exception e) {
-            logger.error("app商家转账支付失败:参数: {} :原因: {}", request, e.getMessage(),e);
-            throw new RuntimeException(e);
-        }
-    }
-
     @Override
     public R paymentByWxaCode(FsStorePaymentPayParam param) {
         FsUser user = userMapper.selectFsUserById(param.getUserId());
@@ -2002,6 +1926,82 @@ public class FsStorePaymentServiceImpl implements IFsStorePaymentService {
 
     }
 
+    @Override
+    @Transactional
+    public R sendAppRedPacket(WxSendRedPacketParam param) {
+        //组合返回参数
+        R result = new R();
+        String json = configService.selectConfigByKey("his.AppRedPacket");
+        AppRedPacketConfig config = JSONUtil.toBean(json, AppRedPacketConfig.class);
+        if (config.getIsNew() != null && config.getIsNew() == 1) {
+            result = sendRedPacketV3(param, config);
+        } else {
+            result= sendRedPacketLegacy(param, config);
+        }
+
+        result.put("mchId", config.getMchId());
+        result.put("isNew",config.getIsNew());
+        logger.info("App提现返回:{}",result);
+        return result;
+    }
+
+    // 内部方法:处理新版本的发红包逻辑
+    private R sendRedPacketV3(WxSendRedPacketParam param,AppRedPacketConfig config) {
+
+        WxPayConfig payConfig = new WxPayConfig();
+        BeanUtils.copyProperties(config, payConfig);
+        WxPayService wxPayService = new WxPayServiceImpl();
+        wxPayService.setConfig(payConfig);
+        TransferService transferService = wxPayService.getTransferService();
+
+        TransferBillsRequest request = new TransferBillsRequest();
+        request.setAppid(param.getAppId());
+        request.setOpenid(param.getOpenId());
+
+        String code =  OrderCodeUtils.getOrderSn();
+        if(StringUtils.isEmpty(code)){
+            return R.error("订单生成失败,请重试");
+        }
+//        String code = String.valueOf(IdUtil.getSnowflake(0, 0).nextId());
+        request.setOutBillNo("fsAppRed" + code);
+        if (param.getAmount() == null) {
+            return R.error();
+        }
+        Integer amount = WxPayUnifiedOrderRequest.yuanToFen(param.getAmount().toString());
+        request.setTransferAmount(amount);
+        request.setTransferRemark("提现红包领取");
+        request.setUserRecvPerception("活动奖励");
+        request.setNotifyUrl(config.getNotifyUrl());
+        request.setTransferSceneId("1000");
+
+        // 设置场景信息
+        List<TransferBillsRequest.TransferSceneReportInfo> transferSceneReportInfos = new ArrayList<>();
+        TransferBillsRequest.TransferSceneReportInfo info1 = new TransferBillsRequest.TransferSceneReportInfo();
+        info1.setInfoType("活动名称");
+        info1.setInfoContent("提现红包领取");
+        transferSceneReportInfos.add(info1);
+
+        TransferBillsRequest.TransferSceneReportInfo info2 = new TransferBillsRequest.TransferSceneReportInfo();
+        info2.setInfoType("奖励说明");
+        info2.setInfoContent("提现红包领取");
+        transferSceneReportInfos.add(info2);
+        request.setTransferSceneReportInfos(transferSceneReportInfos);
+
+
+        try {
+            logger.info("app商家转账开始:[param:{}]", request);
+            TransferBillsResult transferBillsResult = transferService.transferBills(request);
+            logger.info("Method...商家转账支付完成:[msg:{}]", transferBillsResult);
+            return R.ok("发送红包成功").put("data", transferBillsResult).put("mchId", config.getMchId())
+                    .put("package",transferBillsResult.getPackageInfo())
+                    .put("appId",param.getAppId())
+                    .put("orderCode",request.getOutBillNo());
+        } catch (Exception e) {
+            logger.error("app商家转账支付失败:参数: {} :原因: {}", request, e.getMessage(),e);
+            throw new RuntimeException(e);
+        }
+    }
+
     private R sendRedPacketLegacy(WxSendRedPacketParam param, AppRedPacketConfig config) {
         //如果服务号的配置存在,小程序红包接口可以使用服务号来发红包,重新赋值
         //仅老商户支持
@@ -2016,7 +2016,7 @@ public class FsStorePaymentServiceImpl implements IFsStorePaymentService {
         TransferService transferService = wxPayService.getTransferService();
 
         TransferBatchesRequest request = new TransferBatchesRequest();
-        request.setAppid(param.getAppId());
+//        request.setAppid(config.getAppId());
 
 
         // todo 如果未配置负载均衡请还原原本的单号方式

+ 12 - 0
fs-service/src/main/java/com/fs/his/service/impl/FsUserIntegralLogsServiceImpl.java

@@ -20,6 +20,7 @@ import com.fs.his.vo.FsUserIntegralLogsListUVO;
 import com.fs.his.vo.FsUserIntegralLogsListVO;
 import com.fs.his.vo.SubIntegralVO;
 import com.fs.system.service.ISysConfigService;
+import org.apache.commons.lang3.StringUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -375,6 +376,17 @@ public class FsUserIntegralLogsServiceImpl implements IFsUserIntegralLogsService
                     integralNum = param.getPoints() * config.getIntegralSubscriptCourse();
                     logsType = FsUserIntegralLogTypeEnum.TYPE_24;
                     break;
+                case 28: // 下载app领取积分
+                    integralLogs = fsUserIntegralLogsMapper.selectFsUserIntegralLogsByUserIdAndLogType(param.getUserId(),param.getLogType(), null);
+                    if (!integralLogs.isEmpty()){
+                        return R.error("已领取过该积分");
+                    }
+                    integralNum = config.getDownloadAppIntegral();
+                    logsType = FsUserIntegralLogTypeEnum.TYPE_28;
+                    if(StringUtils.isBlank(param.getRemark())){
+                        param.setRemark(logsType.getDesc());
+                    }
+                    break;
                 default:
                     return R.error("积分类型错误,联系管理员");
             }

+ 6 - 1
fs-service/src/main/java/com/fs/his/service/impl/FsUserInvitedServiceImpl.java

@@ -9,6 +9,7 @@ import com.fs.common.QRutils;
 import com.fs.common.core.domain.AjaxResult;
 import com.fs.common.core.domain.R;
 import com.fs.common.utils.DateUtils;
+import com.fs.config.cloud.CloudHostProper;
 import com.fs.course.domain.FsUserCourse;
 import com.fs.his.domain.FsAppVersion;
 import com.fs.his.domain.FsUser;
@@ -41,6 +42,9 @@ public class FsUserInvitedServiceImpl extends ServiceImpl<FsUserInvitedMapper, F
     @Autowired
     private IFsUserIntegralLogsService integralLogsService;
 
+    @Autowired
+    private CloudHostProper cloudHostProper;
+
     /**
      * 查询用户填写邀请码记录
      *
@@ -121,7 +125,8 @@ public class FsUserInvitedServiceImpl extends ServiceImpl<FsUserInvitedMapper, F
             //二维码
             InputStream qrCodeStream = QRutils.getQRCodeImageInputStream(link, 250, 250);
             InputStream mainImageStream = null;
-            String imgUrl = "https://cos.his.cdwjyyh.com/fs/20250308/038f33f3cd4b4b7786e5647f2d5ca609.jpg"; //暂时固定
+//            String imgUrl = "https://cos.his.cdwjyyh.com/fs/20250308/038f33f3cd4b4b7786e5647f2d5ca609.jpg"; //暂时固定
+            String imgUrl = cloudHostProper.getDownloadPosterUrl();
             try {
                 mainImageStream = QRutils.downloadAndEncodeImageToInputStream(imgUrl);
             } catch (Exception e) {

+ 1 - 1
fs-service/src/main/java/com/fs/hisStore/mapper/FsStoreOrderScrmMapper.java

@@ -476,7 +476,7 @@ public interface FsStoreOrderScrmMapper
     List<FsPromotionOrderVO> selectFsPromotionOrderListVO(@Param("maps")FsStoreOrderParam param);
 
     @Select({"<script> " +
-            "select o.id,o.order_code,o.item_json,o.pay_price,o.status,o.is_package,o.package_json,o.delivery_id,o.finish_time  from fs_store_order_scrm o  " +
+            "select o.id,o.order_code,o.item_json,o.pay_price,o.status,o.is_package,o.package_json,o.delivery_id,o.finish_time,o.company_id,o.company_user_id  from fs_store_order_scrm o  " +
             "where o.is_del=0 and o.is_sys_del=0 " +
             "<if test = 'maps.status != null and maps.status != \"\"     '> " +
             "and o.status =#{maps.status} " +

+ 1 - 1
fs-service/src/main/java/com/fs/hisStore/mapper/FsStoreProductScrmMapper.java

@@ -438,7 +438,7 @@ public interface FsStoreProductScrmMapper
     @Select({"<script> " +
             "SELECT distinct fsp.* FROM fs_store_product_scrm fsp " +
             " left join fs_store_product_attr_value_scrm fspav on fsp.product_id = fspav.product_id  " +
-            "WHERE fsp.is_show = 1 and (fspav.bar_code is not null)  and " +
+            "WHERE fsp.is_show = 1 and fsp.is_del = 0  and (fspav.bar_code is not null)  and " +
             " fsp.product_id NOT IN (" +
             "   SELECT product_id FROM live_goods " +
             "   WHERE live_id = #{maps.liveId} " +

+ 2 - 0
fs-service/src/main/java/com/fs/im/service/OpenIMService.java

@@ -108,4 +108,6 @@ public interface OpenIMService {
      * @return
      */
     OpenImResponseDTO deleteUserInfo(String ownerUserID,String friendUserID);
+
+    OpenImResponseDTO getUserInfo(String id);
 }

+ 19 - 0
fs-service/src/main/java/com/fs/im/service/impl/OpenIMServiceImpl.java

@@ -1852,4 +1852,23 @@ public class OpenIMServiceImpl implements OpenIMService {
         responseDTO= JSONUtil.toBean(result2,OpenImResponseDTO.class);
         return responseDTO;
     }
+
+    @Override
+    public OpenImResponseDTO getUserInfo(String id) {
+        // 是数字的话默认用户
+        if(id.matches("\\d+")){
+            id = "U"+id;
+        }
+        String adminToken = getAdminToken();
+        JSONObject jsonObject = new JSONObject();
+        jsonObject.put("userIDs", Collections.singletonList(id));
+        String body = HttpRequest.post(IMConfig.URL+"/user/get_users_info")
+                .header("operationID", String.valueOf(System.currentTimeMillis()))
+                .header("token", adminToken)
+                .body(jsonObject.toString())
+                .execute()
+                .body();
+        OpenImResponseDTO responseDTO= JSONUtil.toBean(body,OpenImResponseDTO.class);
+        return responseDTO;
+    }
 }

+ 6 - 32
fs-service/src/main/java/com/fs/system/config/SystemConfig.java

@@ -1,12 +1,15 @@
 package com.fs.system.config;
 
+import lombok.Data;
+
 import java.io.Serializable;
 
 /**
  * 参数配置表 sys_config
- * 
+ *
 
  */
+@Data
 public class SystemConfig implements  Serializable
 {
 
@@ -15,35 +18,6 @@ public class SystemConfig implements  Serializable
     private Integer visitLimt;//跟进通知
     private Integer contractLimt;//合同通知
 
-    public String getTel() {
-        return tel;
-    }
-
-    public void setTel(String tel) {
-        this.tel = tel;
-    }
-
-    public Integer getNoticeType() {
-        return noticeType;
-    }
-
-    public void setNoticeType(Integer noticeType) {
-        this.noticeType = noticeType;
-    }
-
-    public Integer getVisitLimt() {
-        return visitLimt;
-    }
-
-    public void setVisitLimt(Integer visitLimt) {
-        this.visitLimt = visitLimt;
-    }
-
-    public Integer getContractLimt() {
-        return contractLimt;
-    }
-
-    public void setContractLimt(Integer contractLimt) {
-        this.contractLimt = contractLimt;
-    }
+    private Integer visitLimt1;
+    private Integer visitLimt2;
 }

+ 1 - 0
fs-service/src/main/resources/application-config-bly.yml

@@ -110,6 +110,7 @@ cloud_host:
   spaceName:
 headerImg:
   imgUrl: https://beiliyo-2025.obs.cn-north-4.myhuaweicloud.com/fs/20250115/1736944490230.png
+  download_poster_url: https://beiliyo-2025.obs.cn-north-4.myhuaweicloud.com/fs/20250115/1736944490230.png
 
 baidu:
   token: 1231321232

+ 1 - 0
fs-service/src/main/resources/application-config-dev-yjb.yml

@@ -104,6 +104,7 @@ cloud_host:
   spaceName:
 headerImg:
   imgUrl: https://jz-cos-1356808054.cos.ap-chengdu.myqcloud.com/fs/20250515/0877754b59814ea8a428fa3697b20e68.png
+  download_poster_url: https://jz-cos-1356808054.cos.ap-chengdu.myqcloud.com/fs/20250515/0877754b59814ea8a428fa3697b20e68.png
 ipad:
   ipadUrl: http://ipad.cdwjyyh.com
   aiApi: http://152.136.202.157:3000/api

+ 1 - 0
fs-service/src/main/resources/application-config-dev.yml

@@ -117,6 +117,7 @@ doubao:
 
 headerImg:
   imgUrl: https://jz-cos-1356808054.cos.ap-chengdu.myqcloud.com/fs/20250515/0877754b59814ea8a428fa3697b20e68.png
+  download_poster_url: https://jz-cos-1356808054.cos.ap-chengdu.myqcloud.com/fs/20250515/0877754b59814ea8a428fa3697b20e68.png
 ipad:
   url:
   ipadUrl: http://ipad.cdwjyyh.com

+ 1 - 0
fs-service/src/main/resources/application-config-druid-bnkc.yml

@@ -88,6 +88,7 @@ cloud_host:
   spaceName:
 headerImg:
   imgUrl: https://bnkc-1363824368.cos.ap-chongqing.myqcloud.com/fs/logo/bnkc.png
+  download_poster_url: https://bnkc-1363824368.cos.ap-chongqing.myqcloud.com/fs/logo/bnkc.png
 ipad:
   ipadUrl: http://ipad.cdwjyyh.com
   aiApi: 1212121212

+ 1 - 0
fs-service/src/main/resources/application-config-druid-cfryt.yml

@@ -87,6 +87,7 @@ cloud_host:
 #看课授权时显示的头像
 headerImg:
   imgUrl: https://ysy-1329817240.cos.ap-guangzhou.myqcloud.com/ysy/20250820/2c47e4f105b641b4a49df50a77338e32.png
+  download_poster_url: https://ysy-1329817240.cos.ap-guangzhou.myqcloud.com/ysy/20250820/2c47e4f105b641b4a49df50a77338e32.png
 ipad:
   ipadUrl: http://ipad.cfrytjkzx.cn
   aiApi: http://49.232.181.28:3000/api

+ 1 - 0
fs-service/src/main/resources/application-config-druid-ddgy.yml

@@ -108,6 +108,7 @@ doubao:
 #看课授权时显示的头像
 headerImg:
   imgUrl: https://ddgy-1323137866.cos.ap-chongqing.myqcloud.com/fs/20251010/ddgy.jpg
+  download_poster_url: https://ddgy-1323137866.cos.ap-chongqing.myqcloud.com/fs/20251010/ddgy.jpg
 ipad:
   ipadUrl: http://ipad.dingdangtcm.cn
   aiApi: http://49.232.181.28:3000/api

+ 1 - 0
fs-service/src/main/resources/application-config-druid-drk.yml

@@ -82,6 +82,7 @@ cloud_host:
   spaceName:
 headerImg:
   imgUrl: https://drk-1363981074.cos.ap-chongqing.myqcloud.com/fs/logo/30d7a0d1ec31e5ac16c6e96d5ca76ad.png
+  download_poster_url: https://drk-1363981074.cos.ap-chongqing.myqcloud.com/fs/logo/30d7a0d1ec31e5ac16c6e96d5ca76ad.png
 ipad:
   ipadUrl: http://ipad.cdwjyyh.com
   aiApi: 1212121212

+ 1 - 0
fs-service/src/main/resources/application-config-druid-fby.yml

@@ -110,6 +110,7 @@ cloud_host:
   volcengineUrl: https://fbyvolcengine.ylrztop.com
 headerImg:
   imgUrl: https://fbylive.obs.cn-southwest-2.myhuaweicloud.com/fs/20250730/1753840024082.png
+  download_poster_url: https://fbylive.obs.cn-southwest-2.myhuaweicloud.com/fs/20250730/1753840024082.png
 ipad:
   ipadUrl: http://ipad.jiuzhouzaixian.com
   aiApi:

+ 1 - 0
fs-service/src/main/resources/application-config-druid-hat.yml

@@ -92,6 +92,7 @@ cloud_host:
 #看课授权时显示的头像
 headerImg:
   imgUrl: https://hat-1323137866.cos.ap-chongqing.myqcloud.com/fs/20250928/hatlogo.png
+  download_poster_url: https://hat-1323137866.cos.ap-chongqing.myqcloud.com/fs/20250928/hatlogo.png
 ipad:
   ipadUrl: http://hatipad.ylrzcloud.com
   aiApi: http://62:3000/api

+ 1 - 0
fs-service/src/main/resources/application-config-druid-hcl.yml

@@ -95,6 +95,7 @@ cloud_host:
 #看课授权时显示的头像
 headerImg:
   imgUrl: http://hcl-1b2b.obs.cn-south-1.myhuaweicloud.com/fs/20250815/1755228988455.png
+  download_poster_url: http://hcl-1b2b.obs.cn-south-1.myhuaweicloud.com/fs/20250815/1755228988455.png
 ipad:
   ipadUrl: http://ipad.cdwjyyh.com
   aiApi:

+ 1 - 0
fs-service/src/main/resources/application-config-druid-hdt.yml

@@ -90,6 +90,7 @@ cloud_host:
 #看课授权时显示的头像
 headerImg:
   imgUrl: https://jz-cos-1356808054.cos.ap-chengdu.myqcloud.com/fs/20250515/0877754b59814ea8a428fa3697b20e68.png
+  download_poster_url: https://jz-cos-1356808054.cos.ap-chengdu.myqcloud.com/fs/20250515/0877754b59814ea8a428fa3697b20e68.png
 ipad:
   ipadUrl: http://ipad.hebeihdt.com
 #  aiApi: http://152.136.202.157:3000/api

+ 1 - 0
fs-service/src/main/resources/application-config-druid-hsyy.yml

@@ -91,6 +91,7 @@ cloud_host:
 #看课授权时显示的头像
 headerImg:
   imgUrl: https://hsyy-1348049832.cos.ap-chongqing.myqcloud.com/hsyy.jpg
+  download_poster_url: https://hsyy-1348049832.cos.ap-chongqing.myqcloud.com/hsyy/20260413/84f4cc03415c46d0bc3f26d9a4500776.jpg
 ipad:
   ipadUrl: http://ipad.hshsyy.com
   aiApi: http://49.

+ 2 - 0
fs-service/src/main/resources/application-config-druid-hzyy.yml

@@ -95,6 +95,8 @@ cloud_host:
 #看课授权时显示的头像
 headerImg:
   imgUrl: https://hzyy.obs.cn-north-4.myhuaweicloud.com/fs/20250616/1750067609692.png
+  download_poster_url: https://hzyy.obs.cn-north-4.myhuaweicloud.com/fs/20250616/1750067609692.png
+  bindBaseUrl: https://hzyy.obs.cn-north-4.myhuaweicloud.com/fs/20250616/1750067609692.png
 ipad:
   ipadUrl: http://139.159.133.223:8667
   aiApi: http://49.232.181.28:3000/api

+ 1 - 0
fs-service/src/main/resources/application-config-druid-jkj.yml

@@ -82,6 +82,7 @@ cloud_host:
   spaceName:
 headerImg:
   imgUrl: https://jkj-1323137866.cos.ap-chongqing.myqcloud.com/fs/logo/jkj.png
+  download_poster_url: https://jkj-1323137866.cos.ap-chongqing.myqcloud.com/fs/logo/jkj.png
 ipad:
   ipadUrl: http://ipad.cdwjyyh.com
   voiceApi:

+ 1 - 0
fs-service/src/main/resources/application-config-druid-jnmy.yml

@@ -92,6 +92,7 @@ cloud_host:
   volcengineUrl: https://cdjnmyvolcengine.ylrztop.com
 headerImg:
   imgUrl: https
+  download_poster_url: https
 ipad:
   ipadUrl: http://qwipad.jnmyunl.com
   aiApi: http://49.232.181.28:3000/api

+ 1 - 0
fs-service/src/main/resources/application-config-druid-jnsyj.yml

@@ -85,6 +85,7 @@ cloud_host:
 #看课授权时显示的头像
 headerImg:
   imgUrl: https://jz-cos-1356808054.cos.ap-chengdu.myqcloud.com/fs/20250515/0877754b59814ea8a428fa3697b20e68.png
+  download_poster_url: https://jz-cos-1356808054.cos.ap-chengdu.myqcloud.com/fs/20250515/0877754b59814ea8a428fa3697b20e68.png
 ipad:
   ipadUrl: http://ipad.syjcn.top
 #  aiApi: http://152.136.202.157:3000/api

+ 2 - 0
fs-service/src/main/resources/application-config-druid-jsbk.yml

@@ -91,6 +91,8 @@ cloud_host:
 #看课授权时显示的头像
 headerImg:
   imgUrl: https://jsbk-1323137866.cos.ap-chongqing.myqcloud.com/app/jsbk.jpg
+  download_poster_url: https://jsbk-1323137866.cos.ap-chongqing.myqcloud.com/app/jsbk.jpg
+  bindBaseUrl: https://userapp.jnmyunl.com/bindcompanyuser?companyUserId=
 ipad:
   ipadUrl: http://jsbkipad.sywktxd.cn
   aiApi: http://49.232.181.28:3000/api

+ 1 - 0
fs-service/src/main/resources/application-config-druid-jzzx.yml

@@ -98,6 +98,7 @@ cloud_host:
   volcengineUrl: https://jzzxvolcengine.ylrztop.com
 headerImg:
   imgUrl: https://jiuzhouzaixian.obs.cn-southwest-2.myhuaweicloud.com/fs/20250623/1750665141214.png
+  download_poster_url: https://jiuzhouzaixian.obs.cn-southwest-2.myhuaweicloud.com/fs/20250623/1750665141214.png
 ipad:
   ipadUrl: http://ipad.jiuzhouzaixian.com
   aiApi: http://49.232.181.28:3000/api

+ 1 - 0
fs-service/src/main/resources/application-config-druid-kyt.yml

@@ -88,6 +88,7 @@ cloud_host:
 #看课授权时显示的头像
 headerImg:
   imgUrl: https://kuanyitang-1317640934.cos.ap-shanghai.myqcloud.com/kuanyitang/20250813/6b3b62e01672407c98f0561b73e35f6a.jpg
+  download_poster_url: https://kuanyitang-1317640934.cos.ap-shanghai.myqcloud.com/kuanyitang/20250813/6b3b62e01672407c98f0561b73e35f6a.jpg
 ipad:
   ipadUrl: http://kytIpad.ylrzcloud.com
   aiApi:

+ 1 - 0
fs-service/src/main/resources/application-config-druid-lmjy.yml

@@ -71,6 +71,7 @@ cloud_host:
 #看课授权时显示的头像
 headerImg:
   imgUrl: https://liangmiao.obs.cn-southwest-2.myhuaweicloud.com/fs/20250626/1750922536598.png
+  download_poster_url: https://liangmiao.obs.cn-southwest-2.myhuaweicloud.com/fs/20250626/1750922536598.png
 ipad:
   ipadUrl:
 

+ 1 - 0
fs-service/src/main/resources/application-config-druid-mengniu.yml

@@ -92,6 +92,7 @@ cloud_host:
 #看课授权时显示的头像
 headerImg:
   imgUrl: https://mengniu-hw079058881.obs.cn-southwest-2.myhuaweicloud.com/fs/mengniu.png
+  download_poster_url: https://mengniu-hw079058881.obs.cn-southwest-2.myhuaweicloud.com/fs/mengniu.png
 ipad:
   ipadUrl: http://ipad
   aiApi: http://49.

+ 1 - 0
fs-service/src/main/resources/application-config-druid-qdtst.yml

@@ -95,6 +95,7 @@ cloud_host:
 #看课授权时显示的头像
 headerImg:
   imgUrl: https://qdtst-1360717104.cos.ap-nanjing.myqcloud.com/qdtst-1360717104/20250624/937019e4090f46788ef29c4e7df479c3.jpg
+  download_poster_url: https://qdtst-1360717104.cos.ap-nanjing.myqcloud.com/qdtst-1360717104/20250624/937019e4090f46788ef29c4e7df479c3.jpg
 ipad:
   ipadUrl:
   aiApi:

+ 1 - 0
fs-service/src/main/resources/application-config-druid-sczy.yml

@@ -98,6 +98,7 @@ cloud_host:
   volcengineUrl: https://sczytvolcengine.ylrztop.com
 headerImg:
   imgUrl: https://jiuzhouzaixian.obs.cn-southwest-2.myhuaweicloud.com/fs/20250623/1750665141214.png
+  download_poster_url: https://jiuzhouzaixian.obs.cn-southwest-2.myhuaweicloud.com/fs/20250623/1750665141214.png
 ipad:
   ipadUrl: http://ipad.beijingzhuomei.com
   aiApi:

+ 1 - 0
fs-service/src/main/resources/application-config-druid-sft.yml

@@ -88,6 +88,7 @@ cloud_host:
 #看课授权时显示的头像
 headerImg:
   imgUrl: https://sft-1361917636.cos.ap-chongqing.myqcloud.com/sft/20250606/b08b1a6212f44f2998423c8c5d7712ee.png
+  download_poster_url: https://sft-1361917636.cos.ap-chongqing.myqcloud.com/sft/20250606/b08b1a6212f44f2998423c8c5d7712ee.png
 ipad:
   url:
   ipadUrl: http://qwipad.cqsft.vip

+ 1 - 0
fs-service/src/main/resources/application-config-druid-whhm.yml

@@ -82,6 +82,7 @@ cloud_host:
   spaceName:
 headerImg:
   imgUrl: https://whhm-1361716159.cos.ap-chongqing.myqcloud.com/fs/logo/8d71d552783718d726149312bfca24a.png
+  download_poster_url: https://whhm-1361716159.cos.ap-chongqing.myqcloud.com/fs/logo/8d71d552783718d726149312bfca24a.png
 ipad:
   ipadUrl: http://ipad.cdwjyyh.com
   aiApi: 1212121212

+ 1 - 0
fs-service/src/main/resources/application-config-druid-xfk.yml

@@ -87,6 +87,7 @@ cloud_host:
   spaceName:
 headerImg:
   imgUrl: https://xiaofangke-1360933944.cos.ap-nanjing.myqcloud.com/xiaofangke/20250610/9c3fb587d224492e8b61f5dece0b8b7b.png
+  download_poster_url: https://xiaofangke-1360933944.cos.ap-nanjing.myqcloud.com/xiaofangke/20250610/9c3fb587d224492e8b61f5dece0b8b7b.png
 ipad:
   ipadUrl: http://ipad.cdwjyyh.com
   aiApi:

+ 1 - 0
fs-service/src/main/resources/application-config-druid-xzt.yml

@@ -82,6 +82,7 @@ cloud_host:
   spaceName:
 headerImg:
   imgUrl: https://drk-1363981074.cos.ap-chongqing.myqcloud.com/fs/logo/30d7a0d1ec31e5ac16c6e96d5ca76ad.png
+  download_poster_url: https://drk-1363981074.cos.ap-chongqing.myqcloud.com/fs/logo/30d7a0d1ec31e5ac16c6e96d5ca76ad.png
 ipad:
   ipadUrl: http://ipad.cdwjyyh.com
   aiApi: 1212121212

+ 1 - 0
fs-service/src/main/resources/application-config-druid-yjb.yml

@@ -83,6 +83,7 @@ cloud_host:
   spaceName:
 headerImg:
   imgUrl: https://drk-1363981074.cos.ap-chongqing.myqcloud.com/fs/logo/30d7a0d1ec31e5ac16c6e96d5ca76ad.png
+  download_poster_url: https://drk-1363981074.cos.ap-chongqing.myqcloud.com/fs/logo/30d7a0d1ec31e5ac16c6e96d5ca76ad.png
 ipad:
   ipadUrl: http://ipad.bjyjbao.com
   aiApi: http://152.136.202.157:3000/api

+ 1 - 0
fs-service/src/main/resources/application-config-druid-yxj.yml

@@ -87,6 +87,7 @@ cloud_host:
 #看课授权时显示的头像
 headerImg:
   imgUrl: https://yxj-1323137866.cos.ap-chongqing.myqcloud.com/app/yxj.jpg
+  download_poster_url: https://yxj-1323137866.cos.ap-chongqing.myqcloud.com/app/yxj.jpg
 ipad:
   ipadUrl: http://yxjipad.ylrztop.com
   aiApi: http://49/api

+ 1 - 0
fs-service/src/main/resources/application-config-druid-yzt.yml

@@ -80,6 +80,7 @@ cloud_host:
 #看课授权时显示的头像
 headerImg:
   imgUrl: https://yztcourse-1325300895.cos.ap-guangzhou.myqcloud.com/yztcourse/20250523/e04871a98cc84be39a7f60c084698e21.jpg
+  download_poster_url: https://yztcourse-1325300895.cos.ap-guangzhou.myqcloud.com/yztcourse/20250523/e04871a98cc84be39a7f60c084698e21.jpg
 ipad:
   ipadUrl: http://ipad.cdwjyyh.com
   voiceApi:

+ 1 - 0
fs-service/src/main/resources/application-config-druid-zsjk.yml

@@ -69,6 +69,7 @@ cloud_host:
   spaceName:
 headerImg:
   imgUrl: https://zs-1362480099.cos.ap-beijing.myqcloud.com/fs/20250618/4839e2ff3bdb4908b459abea45a04f4b.png
+  download_poster_url: https://zs-1362480099.cos.ap-beijing.myqcloud.com/fs/20250618/4839e2ff3bdb4908b459abea45a04f4b.png
 ipad:
   ipadUrl: http://ipad.cdwjyyh.com
   voiceApi:

+ 1 - 0
fs-service/src/main/resources/application-config-druid.yml

@@ -67,6 +67,7 @@ nuonuo:
   secret: A2EB20764D304D16
 headerImg:
   imgUrl: https://jz-cos-1356808054.cos.ap-chengdu.myqcloud.com/fs/20250515/0877754b59814ea8a428fa3697b20e68.png
+  download_poster_url: https://jz-cos-1356808054.cos.ap-chengdu.myqcloud.com/fs/20250515/0877754b59814ea8a428fa3697b20e68.png
 ipad:
   ipadUrl: http://ipad.cdwjyyh.com
   voiceApi:

+ 1 - 0
fs-service/src/main/resources/application-config-fzbt.yml

@@ -83,6 +83,7 @@ cloud_host:
 #看课授权时显示的头像
 headerImg:
   imgUrl: https://fs-1346741853.cos.ap-chengdu.myqcloud.com/fs/20250323/6189704f2e134b84ad9c9e7c9999f103.jpg
+  download_poster_url: https://fs-1346741853.cos.ap-chengdu.myqcloud.com/fs/20250323/6189704f2e134b84ad9c9e7c9999f103.jpg
 ipad:
   ipadUrl: http://qwipad.fjbaitu.com
   aiApi: http://49.232.181.28:3000/api

+ 1 - 0
fs-service/src/main/resources/application-config-myhk.yml

@@ -92,6 +92,7 @@ cloud_host:
 #看课授权时显示的头像
 headerImg:
   imgUrl: https://fs-1346741853.cos.ap-chengdu.myqcloud.com/fs/20250323/6189704f2e134b84ad9c9e7c9999f103.jpg
+  download_poster_url: https://fs-1346741853.cos.ap-chengdu.myqcloud.com/fs/20250323/6189704f2e134b84ad9c9e7c9999f103.jpg
 ipad:
   ipadUrl: http://qwipad.muyi88.com
 #  aiApi: http://152.136.202.157:3000/api

+ 1 - 0
fs-service/src/main/resources/application-config-shdn.yml

@@ -110,6 +110,7 @@ cloud_host:
   spaceName:
 headerImg:
   imgUrl: https://beiliyo-2025.obs.cn-north-4.myhuaweicloud.com/fs/20250115/1736944490230.png
+  download_poster_url: https://beiliyo-2025.obs.cn-north-4.myhuaweicloud.com/fs/20250115/1736944490230.png
 
 baidu:
   token: 1231321232

Alguns ficheiros não foram mostrados porque muitos ficheiros mudaram neste diff