Kaynağa Gözat

Merge branch 'master' of http://1.14.104.71:10880/root/ylrz_his_scrm_java

caoliqin 1 hafta önce
ebeveyn
işleme
5973d2a25d
41 değiştirilmiş dosya ile 1044 ekleme ve 24 silme
  1. 56 0
      fs-admin/src/main/java/com/fs/third/controller/WeizouController.java
  2. 15 0
      fs-common/src/main/java/com/fs/common/constant/Constants.java
  3. 8 0
      fs-company/src/main/java/com/fs/company/controller/company/CompanyVoiceRoboticController.java
  4. 29 0
      fs-company/src/main/java/com/fs/company/controller/qw/QwExternalContactController.java
  5. 16 0
      fs-company/src/main/java/com/fs/company/controller/qw/QwUserController.java
  6. 1 0
      fs-framework/src/main/java/com/fs/framework/config/SecurityConfig.java
  7. 5 1
      fs-service/src/main/java/com/fs/company/domain/CompanyVoiceRobotic.java
  8. 3 0
      fs-service/src/main/java/com/fs/company/domain/CompanyVoiceRoboticCallLogAddwx.java
  9. 3 1
      fs-service/src/main/java/com/fs/company/domain/CompanyVoiceRoboticWx.java
  10. 4 2
      fs-service/src/main/java/com/fs/company/domain/CompanyWxClient.java
  11. 1 0
      fs-service/src/main/java/com/fs/company/mapper/CompanyVoiceRoboticWxMapper.java
  12. 3 1
      fs-service/src/main/java/com/fs/company/mapper/CompanyWxClientMapper.java
  13. 2 0
      fs-service/src/main/java/com/fs/company/service/ICompanyVoiceRoboticWxService.java
  14. 3 1
      fs-service/src/main/java/com/fs/company/service/ICompanyWxClientService.java
  15. 27 7
      fs-service/src/main/java/com/fs/company/service/impl/CompanyVoiceRoboticServiceImpl.java
  16. 5 0
      fs-service/src/main/java/com/fs/company/service/impl/CompanyVoiceRoboticWxServiceImpl.java
  17. 7 2
      fs-service/src/main/java/com/fs/company/service/impl/CompanyWxClientServiceImpl.java
  18. 232 0
      fs-service/src/main/java/com/fs/company/service/impl/call/node/AiQwAddWxTaskNode.java
  19. 3 0
      fs-service/src/main/java/com/fs/company/service/impl/call/node/WorkflowNodeFactory.java
  20. 5 1
      fs-service/src/main/java/com/fs/enums/NodeTypeEnum.java
  21. 1 1
      fs-service/src/main/java/com/fs/erp/domain/WeizouApiPushOrderParam.java
  22. 15 4
      fs-service/src/main/java/com/fs/erp/utils/WeizouApiClient.java
  23. 1 0
      fs-service/src/main/java/com/fs/his/domain/FsStoreOrder.java
  24. 1 0
      fs-service/src/main/java/com/fs/his/service/impl/FsStoreOrderServiceImpl.java
  25. 4 0
      fs-service/src/main/java/com/fs/hisStore/domain/FsStoreOrderScrm.java
  26. 5 0
      fs-service/src/main/java/com/fs/hisStore/param/FsStoreOrderCreateParam.java
  27. 7 0
      fs-service/src/main/java/com/fs/hisStore/service/impl/FsStoreOrderScrmServiceImpl.java
  28. 11 0
      fs-service/src/main/java/com/fs/qw/mapper/QwExternalContactMapper.java
  29. 5 0
      fs-service/src/main/java/com/fs/qw/vo/QwExternalContactVO.java
  30. 26 0
      fs-service/src/main/java/com/fs/wxwork/dto/WxAddSearchDTO.java
  31. 21 0
      fs-service/src/main/java/com/fs/wxwork/dto/WxSearchContactDTO.java
  32. 35 0
      fs-service/src/main/java/com/fs/wxwork/dto/WxSearchContactResp.java
  33. 16 0
      fs-service/src/main/java/com/fs/wxwork/service/WxWorkService.java
  34. 14 0
      fs-service/src/main/java/com/fs/wxwork/service/WxWorkServiceImpl.java
  35. 1 0
      fs-service/src/main/resources/application-config-myhk.yml
  36. 8 1
      fs-service/src/main/resources/mapper/company/CompanyVoiceRoboticCallLogAddwxMapper.xml
  37. 18 0
      fs-service/src/main/resources/mapper/company/CompanyVoiceRoboticWxMapper.xml
  38. 13 0
      fs-service/src/main/resources/mapper/company/CompanyWxClientMapper.xml
  39. 9 0
      fs-service/src/main/resources/mapper/hisStore/FsStoreOrderScrmMapper.xml
  40. 391 1
      fs-wx-task/src/main/java/com/fs/app/service/WxTaskService.java
  41. 14 1
      fs-wx-task/src/main/java/com/fs/app/task/WxTask.java

+ 56 - 0
fs-admin/src/main/java/com/fs/third/controller/WeizouController.java

@@ -0,0 +1,56 @@
+package com.fs.third.controller;
+
+import com.fs.common.core.controller.BaseController;
+import com.fs.common.core.domain.AjaxResult;
+import com.fs.his.domain.FsStoreOrder;
+import com.fs.his.service.IFsStoreOrderService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.Map;
+
+import static com.fs.his.utils.PhoneUtil.decryptPhone;
+
+@RestController
+@RequestMapping("/third/weizou")
+public class WeizouController extends BaseController {
+    @Autowired
+    private IFsStoreOrderService fsStoreOrderService;
+    /**
+     * 微走发货同步接口
+     */
+    @PostMapping("/sendGoodsThirdParty")
+    public AjaxResult sendGoodsThirdParty(@RequestBody Map map) {
+        String thirdPartySecret = (String) map.get("thirdPartySecret");
+        String s = decryptPhone(thirdPartySecret);
+        if (!s.equals("weizou_send_goods_call_back")) {
+            return error("认证失败,请核验参数");
+        }
+        FsStoreOrder fsStoreOrder = new FsStoreOrder();
+        if (map.get("orderCode") == null) {
+            return error("参数异常请核验参数");
+        }
+        fsStoreOrder.setOrderId(Long.valueOf((String) map.get("orderCode")));
+        fsStoreOrder.setOperator("微走发货同步");
+// 校验物流单号
+        if (map.get("deliverySn") == null) {
+            return error("参数异常请核验参数:deliverySn不能为空");
+        }
+        fsStoreOrder.setDeliverySn((String) map.get("deliverySn"));
+// 校验物流代码
+        if (map.get("deliveryCode") == null) {
+            return error("参数异常请核验参数:deliveryCode不能为空");
+        }
+        fsStoreOrder.setDeliveryCode((String) map.get("deliveryCode"));
+// 校验物流名称
+        if (map.get("deliveryName") == null) {
+            return error("参数异常请核验参数:deliveryName不能为空");
+        }
+        fsStoreOrder.setDeliveryName((String) map.get("deliveryName"));
+
+        return toAjax(fsStoreOrderService.sendGoods(fsStoreOrder, "微走发货同步"));
+    }
+}

+ 15 - 0
fs-common/src/main/java/com/fs/common/constant/Constants.java

@@ -184,6 +184,10 @@ public class Constants
      * 添加微信
      */
     public static final String ADD_WX = "addWx";
+    /**
+     * 企微添加个微
+     */
+    public static final String QW_ADD_WX = "qwAddWx";
     /**
      * 发送短信
      */
@@ -205,4 +209,15 @@ public class Constants
      * 用于实现回调成功和超时互斥,只执行一个
      */
     public static final String WORKFLOW_ADD_WX_EXECUTED = "workflow:addwx:executed:";
+    /**
+     * 工作流企微加个微执行状态标记 Key: workflow:addwx:executed:{workflowInstanceId}:{wxClientId}
+     * Value: 1-已执行
+     * 用于实现回调成功和超时互斥,只执行一个
+     */
+    public static final String WORKFLOW_QW_ADD_WX_EXECUTED = "workflow:qwaddwx:executed:";
+    /**
+     * 工作流企微加个微超时检测 Key: workflow:addwx:timeout:{workflowInstanceId}:{wxClientId}
+     * Value: 超时时间戳
+     */
+    public static final String WORKFLOW_QW_ADD_WX_TIMEOUT = "workflow:qwaddwx:timeout:";
 }

+ 8 - 0
fs-company/src/main/java/com/fs/company/controller/company/CompanyVoiceRoboticController.java

@@ -94,6 +94,14 @@ public class CompanyVoiceRoboticController extends BaseController
         return getDataTable(list);
     }
 
+//    @PreAuthorize("@ss.hasPermi('system:companyVoiceRobotic:wxListQw')")
+    @GetMapping("/wxListQw")
+    public TableDataInfo wxListQw(Long id){
+        startPage();
+        List<CompanyVoiceRoboticWx> list = companyVoiceRoboticWxService.selectWxListQw(id);
+        return getDataTable(list);
+    }
+
     /**
      * 导出机器人外呼任务列表
      */

+ 29 - 0
fs-company/src/main/java/com/fs/company/controller/qw/QwExternalContactController.java

@@ -139,6 +139,17 @@ public class QwExternalContactController extends BaseController
                 item.setState(item.getState()+"-"+getContactWayNameStream(item.getState(), wayList));
             }
 
+            if (item.getFsUserId() != null) {
+                FsUser fsUser = fsUserService.selectFsUserByUserId(item.getFsUserId());
+                if (fsUser != null && (fsUser.getSource() != null || fsUser.getLoginDevice() != null)) {
+                    item.setIsDownloadApp(1);
+                } else {
+                    item.setIsDownloadApp(0);
+                }
+            } else {
+                item.setIsDownloadApp(0);
+            }
+
         });
 
         return getDataTable(list);
@@ -248,6 +259,17 @@ public class QwExternalContactController extends BaseController
                 item.setState(item.getState()+"-"+getContactWayNameStream(item.getState(), wayList));
             }
 
+            if (item.getFsUserId() != null) {
+                FsUser fsUser = fsUserService.selectFsUserByUserId(item.getFsUserId());
+                if (fsUser != null && (fsUser.getSource() != null || fsUser.getLoginDevice() != null)) {
+                    item.setIsDownloadApp(1);
+                } else {
+                    item.setIsDownloadApp(0);
+                }
+            } else {
+                item.setIsDownloadApp(0);
+            }
+
         });
 
         return getDataTable(list);
@@ -307,15 +329,22 @@ public class QwExternalContactController extends BaseController
         Long fsUserId = item.getFsUserId();
         if (fsUserId==null){
             item.setOrderCount(0L);
+            item.setIsDownloadApp(0);
             return;
         }
         FsUser fsUser = fsUserService.selectFsUserById(fsUserId);
         if (fsUser == null) {
             item.setOrderCount(0L);
+            item.setIsDownloadApp(0);
             return;
         }
         Long orderCount = fsUser.getOrderCount();
         item.setOrderCount(orderCount != null ? orderCount : 0);
+        if (fsUser.getSource() != null || fsUser.getLoginDevice() != null) {
+            item.setIsDownloadApp(1);
+        } else {
+            item.setIsDownloadApp(0);
+        }
     }
 
 

+ 16 - 0
fs-company/src/main/java/com/fs/company/controller/qw/QwUserController.java

@@ -545,6 +545,22 @@ public class QwUserController extends BaseController
         List<QwUser> list = qwUserService.selectQwUserList(qwUser);
         return getDataTable(list);
     }
+
+    /**
+     * 查询企微用户列表-不固定销售公司查询
+     */
+    @GetMapping("/queryQwList")
+    public TableDataInfo queryQwList(QwUser qwUser)
+    {
+        startPage();
+        if(qwUser.getCompanyId() == null){
+            LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+            qwUser.setCompanyId(loginUser.getCompany().getCompanyId());
+        }
+        List<QwUser> list = qwUserService.selectQwUserList(qwUser);
+        return getDataTable(list);
+    }
+
     /**
     * 查询企微员工列表-用于员工管理绑定
     */

+ 1 - 0
fs-framework/src/main/java/com/fs/framework/config/SecurityConfig.java

@@ -145,6 +145,7 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter
                 .antMatchers("/druid/**").anonymous()
                 .antMatchers("/course/userVideo/videoTranscode").anonymous()
                 .antMatchers("/erp/call/**").anonymous()
+                .antMatchers("/third/weizou/**").anonymous()
                 // 除上面外的所有请求全部需要鉴权认证
                 .anyRequest().authenticated()
                 .and()

+ 5 - 1
fs-service/src/main/java/com/fs/company/domain/CompanyVoiceRobotic.java

@@ -124,5 +124,9 @@ public class CompanyVoiceRobotic {
     private LocalTime runtimeRangeStart;
     private LocalTime runtimeRangeEnd;
     //分组服务no
-    private Integer cidGroupNo;
+    private Integer cidGroupNo;    //是否企微 1个微 2企微
+    private Integer isWeCom;
+    //企微用户id
+    @TableField(exist = false)
+    private String qwUserId;
 }

+ 3 - 0
fs-service/src/main/java/com/fs/company/domain/CompanyVoiceRoboticCallLogAddwx.java

@@ -61,6 +61,9 @@ public class CompanyVoiceRoboticCallLogAddwx extends BaseEntity{
     @Excel(name = "个微账号id")
     private Long wxAccountId;
 
+    /** 执加微类型1个微2企微(防止add_type被占用) */
+    private Integer isWeCom;
+
     @TableField(exist = false)
     private Long customerId;
 

+ 3 - 1
fs-service/src/main/java/com/fs/company/domain/CompanyVoiceRoboticWx.java

@@ -14,7 +14,9 @@ import lombok.Data;
 @Data
 public class CompanyVoiceRoboticWx extends BaseEntityTow {
     private static final long serialVersionUID = 1L;
-
+    /** 是否微信 */
+    @Excel(name = "是否微信")
+    private Integer isWeCom;
     /** 意向 */
     @Excel(name = "意向")
     private String intention;

+ 4 - 2
fs-service/src/main/java/com/fs/company/domain/CompanyWxClient.java

@@ -56,7 +56,7 @@ public class CompanyWxClient extends BaseEntityTow
     @Excel(name = "客户意向")
     private String intention;
 
-    /** 是否添加;0否1是 */
+    /** 是否添加;0否1是2待添加3作废 */
     @Excel(name = "是否添加;0否1是2待添加3作废")
     private Integer isAdd;
     private Long accountId;
@@ -85,5 +85,7 @@ public class CompanyWxClient extends BaseEntityTow
     private String roboticName;
     @TableField(exist = false)
     private String memo;
-
+    /** 是否微信 */
+    @Excel(name = "加微类型1个微2企微(防止add_type被占用)")
+    private Integer isWeCom;
 }

+ 1 - 0
fs-service/src/main/java/com/fs/company/mapper/CompanyVoiceRoboticWxMapper.java

@@ -67,4 +67,5 @@ public interface CompanyVoiceRoboticWxMapper extends BaseMapper<CompanyVoiceRobo
 
     List<CompanyVoiceRoboticWx> selectByRoboticIdWithGroupBy(@Param("id") Long id);
 
+    List<CompanyVoiceRoboticWx> selectByRoboticIdQw(@Param("id") Long id, @Param("intention") String intention);
 }

+ 3 - 1
fs-service/src/main/java/com/fs/company/mapper/CompanyWxClientMapper.java

@@ -71,7 +71,7 @@ public interface CompanyWxClientMapper extends BaseMapper<CompanyWxClient> {
 
     List<CompanyWxClient> listCompanyIds(@Param("ids") String[] ids);
 
-    List<CompanyWxClient> getAddWxList(@Param("accountIdList") List<Long> accountIdList);
+    List<CompanyWxClient> getAddWxList(@Param("accountIdList") List<Long> accountIdList, @Param("isWeCom") Integer isWeCom);
 
     List<CompanyWxClient4WorkFlowVO> getAddWxList4Workflow(@Param("accountIdList") List<Long> accountIdList, @Param("execStatus") Integer execStatus, @Param("execNodeType") Integer execNodeType);
 
@@ -82,4 +82,6 @@ public interface CompanyWxClientMapper extends BaseMapper<CompanyWxClient> {
 
     List<CompanyWxClient> getWxClientInfoByCustomerId(@Param("customerId") Long customerId);
 
+    List<CompanyWxClient> getQwAddWxList(@Param("accountIdList") List<Long> accountIdList, @Param("isWeCom") Integer isWeCom);
+
 }

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

@@ -61,4 +61,6 @@ public interface ICompanyVoiceRoboticWxService
     public int deleteCompanyVoiceRoboticWxById(Long id);
 
     List<CompanyVoiceRoboticWx> selectWxList(Long id);
+
+    List<CompanyVoiceRoboticWx> selectWxListQw(Long id);
 }

+ 3 - 1
fs-service/src/main/java/com/fs/company/service/ICompanyWxClientService.java

@@ -69,7 +69,9 @@ public interface ICompanyWxClientService extends IService<CompanyWxClient> {
 
     void addWxTrueResult(AddWxResultVo vo);
 
-    List<CompanyWxClient> getAddWxList(List<Long> accountIdList);
+    List<CompanyWxClient> getAddWxList(List<Long> accountIdList,Integer isWeCom);
 
     List<CompanyWxClient4WorkFlowVO> getAddWxList4Workflow(List<Long> accountIdList);
+
+    List<CompanyWxClient> getQwAddWxList(List<Long> accountIdList,Integer isWeCom);
 }

+ 27 - 7
fs-service/src/main/java/com/fs/company/service/impl/CompanyVoiceRoboticServiceImpl.java

@@ -27,6 +27,8 @@ import com.fs.crm.domain.CrmCustomer;
 import com.fs.crm.mapper.CrmCustomerMapper;
 import com.fs.crm.param.SmsSendBatchParam;
 import com.fs.crm.service.impl.CrmCustomerServiceImpl;
+import com.fs.qw.domain.QwUser;
+import com.fs.qw.mapper.QwUserMapper;
 import com.fs.system.mapper.SysDictDataMapper;
 import com.fs.system.service.ISysConfigService;
 import lombok.RequiredArgsConstructor;
@@ -99,6 +101,7 @@ public class CompanyVoiceRoboticServiceImpl extends ServiceImpl<CompanyVoiceRobo
     private final CompanyAiWorkflowExecLogMapper companyAiWorkflowExecLogMapper;
     private final RedisCache redisCache2;
     private final CompanyAiWorkflowServerMapper companyAiWorkflowServerMapper;
+    private final QwUserMapper qwUserMapper;
     /**
      * 查询机器人外呼任务
      *
@@ -147,12 +150,14 @@ public class CompanyVoiceRoboticServiceImpl extends ServiceImpl<CompanyVoiceRobo
             }
         }
         int i = companyVoiceRoboticMapper.insert(companyVoiceRobotic);
+        int isWeCom = companyVoiceRobotic.getIsWeCom() == null ? 1 : companyVoiceRobotic.getIsWeCom();
         // 设置加微微信列表
         List<RoboticWxVo> qwUserList = companyVoiceRobotic.getQwUserList();
         List<CompanyVoiceRoboticWx> collect = qwUserList.stream().map(e -> {
             CompanyVoiceRoboticWx entity = new CompanyVoiceRoboticWx();
             entity.setIntention(e.getIntention());
             entity.setRoboticId(companyVoiceRobotic.getId());
+            entity.setIsWeCom(isWeCom);
             entity.setAccountId(e.getCompanyUserId());
             entity.setWxDialogId(e.getWxDialogId());
             entity.setSmsTempId(e.getSmsTempId());
@@ -163,6 +168,7 @@ public class CompanyVoiceRoboticServiceImpl extends ServiceImpl<CompanyVoiceRobo
             CompanyWxClient client = new CompanyWxClient();
             client.setRoboticId(companyVoiceRobotic.getId());
             client.setCustomerId(Long.parseLong(e));
+            client.setIsWeCom(isWeCom);
             return client;
         }).collect(Collectors.toList());
         companyWxClientServiceImpl.saveBatch(clients);
@@ -1004,34 +1010,47 @@ public class CompanyVoiceRoboticServiceImpl extends ServiceImpl<CompanyVoiceRobo
         List<CompanyWxClient> resArr = new ArrayList<>();
         //找到任务指定的微信用户
         List<CompanyVoiceRoboticWx> companyVoiceRoboticWxes = companyVoiceRoboticWxMapper.selectByRoboticIdWithGroupBy(robotic.getId());
-        Integer totalSize = 0;
+        int totalSize = 0;
         if (null != companyVoiceRoboticWxes && !companyVoiceRoboticWxes.isEmpty()) {
             totalSize = companyVoiceRoboticWxes.size();
         } else {
             log.error("分配对象空,数据异常");
             throw new RuntimeException("没有找到任务指定的微信用户");
         }
-        List<CompanyWxAccount> accountIds = companyWxAccountMapper.selectBatchIds(PubFun.listToNewList(companyVoiceRoboticWxes, CompanyVoiceRoboticWx::getAccountId));
-        Map<Long, CompanyWxAccount> accountMap = PubFun.listToMapByGroupObject(accountIds, CompanyWxAccount::getId);
+        List<QwUser> qwIds;
+        Map<Long, QwUser> qwMap = Collections.emptyMap();
+        List<CompanyWxAccount> accountIds;
+        Map<Long, CompanyWxAccount> accountMap = Collections.emptyMap();
+        if(robotic.getIsWeCom() == 2){
+            qwIds = qwUserMapper.selectBatchIds(PubFun.listToNewList(companyVoiceRoboticWxes, CompanyVoiceRoboticWx::getAccountId));
+            qwMap = PubFun.listToMapByGroupObject(qwIds, QwUser::getId);
+        }else{
+            accountIds = companyWxAccountMapper.selectBatchIds(PubFun.listToNewList(companyVoiceRoboticWxes, CompanyVoiceRoboticWx::getAccountId));
+            accountMap = PubFun.listToMapByGroupObject(accountIds, CompanyWxAccount::getId);
+        }
+
         List<CompanyWxClient> companyWxClients = companyWxClientMapper.selectListByRoboticId(robotic.getId());
         List<Long> ids = PubFun.listToNewList(companyWxClients, CompanyWxClient::getCustomerId);
         List<CrmCustomer> crmCustomerList = crmCustomerService.selectCrmCustomerListByIds(ids.stream().map(e -> e + "").collect(Collectors.joining(",")));
         Map<Long, CrmCustomer> customerMap = PubFun.listToMapByGroupObject(crmCustomerList, CrmCustomer::getCustomerId);
-        if (null == companyWxClients || companyWxClients.isEmpty()) {
+        if (companyWxClients.isEmpty()) {
             log.error("分配个微空,数据异常");
             throw new RuntimeException("没有找到需要分配微信用户");
         }
-        Integer allocateIndex = 0;
+        int allocateIndex = 0;
         List<CompanyVoiceRoboticWx> updateCompanyVoiceRoboticWxList = new ArrayList<>();
         //分配客户
         for (CompanyWxClient companyWxClient : companyWxClients) {
             CompanyVoiceRoboticWx wx = companyVoiceRoboticWxes.get(allocateIndex++ % totalSize);
-            CompanyWxAccount account = accountMap.get(wx.getAccountId());
             CrmCustomer crmCustomer = customerMap.get(companyWxClient.getCustomerId());
             companyWxClient.setRoboticWxId(wx.getId());
             companyWxClient.setAccountId(wx.getAccountId());
             companyWxClient.setDialogId(wx.getWxDialogId());
-            companyWxClient.setCompanyUserId(account.getCompanyUserId());
+            if(robotic.getIsWeCom() == 2){
+                companyWxClient.setCompanyUserId(qwMap.get(wx.getAccountId()).getCompanyUserId());
+            }else{
+                companyWxClient.setCompanyUserId(accountMap.get(wx.getAccountId()).getCompanyUserId());
+            }
             companyWxClient.setNickName(crmCustomer.getCustomerName());
             companyWxClient.setPhone(crmCustomer.getMobile());
             resArr.add(companyWxClient);
@@ -1158,6 +1177,7 @@ public class CompanyVoiceRoboticServiceImpl extends ServiceImpl<CompanyVoiceRobo
             case 6: return "AI外呼电话";
             case 7: return "AI发送短信";
             case 8: return "AI添加微信";
+            case 9: return "AI企微添加个微";
             default: return "未知";
         }
     }

+ 5 - 0
fs-service/src/main/java/com/fs/company/service/impl/CompanyVoiceRoboticWxServiceImpl.java

@@ -100,4 +100,9 @@ public class CompanyVoiceRoboticWxServiceImpl extends ServiceImpl<CompanyVoiceRo
         List<CompanyVoiceRoboticWx> list = companyVoiceRoboticWxMapper.selectByRoboticId(id, null);
         return list;
     }
+
+    @Override
+    public List<CompanyVoiceRoboticWx> selectWxListQw(Long id) {
+        return companyVoiceRoboticWxMapper.selectByRoboticIdQw(id, null);
+    }
 }

+ 7 - 2
fs-service/src/main/java/com/fs/company/service/impl/CompanyWxClientServiceImpl.java

@@ -228,8 +228,8 @@ public class CompanyWxClientServiceImpl extends ServiceImpl<CompanyWxClientMappe
     }
 
     @Override
-    public List<CompanyWxClient> getAddWxList(List<Long> accountIdList) {
-        return baseMapper.getAddWxList(accountIdList);
+    public List<CompanyWxClient> getAddWxList(List<Long> accountIdList,Integer isWeCom) {
+        return baseMapper.getAddWxList(accountIdList,isWeCom);
     }
 
     /**
@@ -241,4 +241,9 @@ public class CompanyWxClientServiceImpl extends ServiceImpl<CompanyWxClientMappe
     public  List<CompanyWxClient4WorkFlowVO> getAddWxList4Workflow(List<Long> accountIdList){
         return baseMapper.getAddWxList4Workflow(accountIdList, ExecutionStatusEnum.PAUSED.getValue(), NodeTypeEnum.AI_ADD_WX_TASK.getValue());
     }
+
+    @Override
+    public List<CompanyWxClient> getQwAddWxList(List<Long> accountIdList, Integer isWeCom) {
+        return baseMapper.getQwAddWxList(accountIdList,isWeCom);
+    }
 }

+ 232 - 0
fs-service/src/main/java/com/fs/company/service/impl/call/node/AiQwAddWxTaskNode.java

@@ -0,0 +1,232 @@
+package com.fs.company.service.impl.call.node;
+
+import com.alibaba.fastjson.JSONObject;
+import com.fs.common.constant.Constants;
+import com.fs.common.core.redis.RedisCacheT;
+import com.fs.common.utils.StringUtils;
+import com.fs.common.utils.spring.SpringUtils;
+import com.fs.company.domain.*;
+import com.fs.company.mapper.CompanyWxClientMapper;
+import com.fs.company.param.ExecutionContext;
+import com.fs.company.vo.AiCallWorkflowConditionVo;
+import com.fs.company.vo.ExecutionResult;
+import com.fs.enums.ExecutionStatusEnum;
+import com.fs.enums.NodeTypeEnum;
+import lombok.extern.slf4j.Slf4j;
+
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+/**
+ * @Author:peicj
+ * @Description: AI企微添加个微节点
+ * @Date:2026/2/27 11:05
+ */
+@Slf4j
+public class AiQwAddWxTaskNode extends AbstractWorkflowNode {
+
+    private static final CompanyWxClientMapper companyWxClientMapper = SpringUtils.getBean(CompanyWxClientMapper.class);
+    @SuppressWarnings("unchecked")
+    private static final RedisCacheT<String> redisCache = SpringUtils.getBean(RedisCacheT.class);
+    public static final String DELAY_QW_ADD_WX_KEY = "qwAddWxTask:delay:%s:%s:";
+    /**
+     * 默认加微超时时间(分钟)
+     */
+    private static final int DEFAULT_ADD_WX_TIMEOUT_MINUTES = 30;
+
+    public AiQwAddWxTaskNode(String nodeKey, String nodeName, Map<String, Object> properties) {
+        super(nodeKey, nodeName, properties);
+    }
+
+    /**
+     * 收到加微回调后,继续判定和执行下一步动作
+     * 加微成功后不直接流转,而是改为等待状态,等待下一次回调
+     *
+     * @param context 执行上下文
+     * @return 执行结果
+     */
+    @Override
+    protected ExecutionResult doContinue(ExecutionContext context) {
+        //收到回调代表加微通过了
+        CompanyAiWorkflowExec exec = companyAiWorkflowExecMapper.selectByWorkflowInstanceId(context.getWorkflowInstanceId());
+        List<CompanyWorkflowEdge> edges = companyWorkflowEdgeMapper.selectListByWorkflowIdAndNodeKey(exec.getWorkflowId(), nodeKey);
+
+        // 获取业务数据
+        CompanyVoiceRoboticBusiness business = super.getRoboticBusiness(context.getWorkflowInstanceId());
+        // 获取加微记录
+        CompanyWxClient wxClient = companyWxClientMapper.selectById(business.getWxClientId());
+
+        log.info("收到加微回调 - workflowInstanceId: {}, wxClientId: {}, isAdd: {}",
+                context.getWorkflowInstanceId(), business.getWxClientId(), wxClient != null ? wxClient.getIsAdd() : null);
+
+        // 判断加微是否成功 (isAdd: 0否 1是 2待添加 3作废)
+        boolean addSuccess = wxClient != null && Integer.valueOf(1).equals(wxClient.getIsAdd());
+        //回调加微成功
+        if (addSuccess) {
+            List<CompanyWorkflowEdge> cList = edges.stream().filter(a ->
+                            StringUtils.isNotBlank(a.getConditionExpr()) && JSONObject.parseArray(a.getConditionExpr(), AiCallWorkflowConditionVo.class).get(0).isAdd())
+                    .collect(Collectors.toList());
+            if(!cList.isEmpty() && nodeKey.equals(exec.getCurrentNodeKey())){
+                super.runNextNode(context, cList.get(0));
+            }
+        }
+        else {
+            List<CompanyWorkflowEdge> cList = edges.stream().filter(a ->
+                            StringUtils.isNotBlank(a.getConditionExpr()) && !JSONObject.parseArray(a.getConditionExpr(), AiCallWorkflowConditionVo.class).get(0).isAdd())
+                    .collect(Collectors.toList());
+            // 加微失败,根据条件判断走哪条边
+            CompanyWorkflowEdge edge = cList.get(0);
+                AiCallWorkflowConditionVo condition = JSONObject.parseObject(edge.getConditionExpr(), AiCallWorkflowConditionVo.class);
+                // 匹配失败条件
+                if (!condition.isAdd()) {
+                    log.info("加微失败,执行失败分支 - workflowInstanceId: {}", context.getWorkflowInstanceId());
+                    super.runNextNode(context, edge);
+                    return null;
+                }
+
+            log.error("加微失败但未找到失败分支 - workflowInstanceId: {}", context.getWorkflowInstanceId());
+            return null;
+        }
+        return null;
+    }
+
+    /**
+     * 执行加微节点逻辑(只准备数据,实际加微由定时任务执行)
+     *
+     * @param context 执行上下文
+     * @return 执行结果
+     */
+    @Override
+    protected ExecutionResult doExecute(ExecutionContext context) {
+        if (!isAsync()) {
+            return ExecutionResult.failure().nextNodeKey(null).build();
+        }
+        try {
+            super.asyncWorkflowForBlockingNode(context.getWorkflowInstanceId(), context.getCurrentNodeKey(), context, ExecutionStatusEnum.PAUSED);
+            return ExecutionResult.paused()
+                    .outputData(context.getVariables())
+                    .nextNodeKey("").build();
+        } catch (Exception e) {
+            log.error("准备加微任务数据异常 流程:{}:节点:{}执行失败,", context.getWorkflowInstanceId(), nodeKey, e);
+            super.updateWorkflowStatus(context.getWorkflowInstanceId(), ExecutionStatusEnum.INTERRUPT);
+            return ExecutionResult.failure().errorMessage("准备加微任务数据异常: " + e.getMessage()).build();
+        }
+    }
+
+    @Override
+    public NodeTypeEnum getType() {
+        return NodeTypeEnum.AI_QW_ADD_WX_TASK;
+    }
+
+    @Override
+    public Boolean isAsync() {
+        return true;
+    }
+
+
+    /**
+     * 从节点配置获取超时时间(分钟)
+     */
+    private int getTimeoutFromProperties() {
+        if (properties != null && properties.containsKey("timeout")) {
+            Object timeout = properties.get("timeout");
+            if (timeout instanceof Number) {
+                return ((Number) timeout).intValue();
+            }
+            if (timeout instanceof String) {
+                try {
+                    return Integer.parseInt((String) timeout);
+                } catch (NumberFormatException e) {
+                    log.warn("解析超时时间失败: {}, 使用默认值: {}", timeout, DEFAULT_ADD_WX_TIMEOUT_MINUTES);
+                }
+            }
+        }
+        return DEFAULT_ADD_WX_TIMEOUT_MINUTES;
+    }
+
+    /**
+     * 检查并标记已执行(用于互斥逻辑)
+     * 如果返回 true 表示当前是第一个执行的,可以继续
+     * 如果返回 false 表示已经被其他路径执行过了,不再执行
+     *
+     * @param workflowInstanceId 工作流实例ID
+     * @param wxClientId         加微客户ID
+     * @return 是否可以执行
+     */
+    public static boolean tryMarkAsExecuted(String workflowInstanceId, Long wxClientId) {
+        String executedKey = Constants.WORKFLOW_QW_ADD_WX_EXECUTED + workflowInstanceId + ":" + wxClientId;
+        String existingValue = redisCache.getCacheObject(executedKey);
+        if (existingValue != null) {
+            // 已经被执行过了
+            return false;
+        }
+        // 标记为已执行,设置1小时过期
+        redisCache.setCacheObject(executedKey, "1");
+        redisCache.expire(executedKey, 3600);
+        return true;
+    }
+
+    /**
+     * 清除超时检测 Key
+     *
+     * @param workflowInstanceId 工作流实例ID
+     * @param wxClientId         加微客户ID
+     */
+    public static void clearTimeoutKey(String workflowInstanceId, Long wxClientId) {
+        String timeoutKey = Constants.WORKFLOW_QW_ADD_WX_TIMEOUT + workflowInstanceId + ":" + wxClientId;
+        redisCache.deleteObject(timeoutKey);
+        log.info("清除加微超时检测 Key: {}", timeoutKey);
+    }
+
+    /**
+     * getRedisCacheKey
+     *
+     */
+    public static String getDelayAddWxKeyPrefix(Long time) {
+        Date nowDay;
+        if (null != time) {
+            nowDay = new Date(time);
+        }else{
+            nowDay = new Date();
+        }
+        return String.format(DELAY_QW_ADD_WX_KEY, nowDay.getHours(), nowDay.getMinutes());
+    }
+
+    /**
+     * 完成加微动作
+     *
+     */
+//    public void doneQwAddWx(String workflowInstanceId) {
+//        ExecutionContext context = createExecutionContext(workflowInstanceId, nodeKey);
+//        context.setVariable("lastNodeKey", nodeKey);
+//        //启动定时节点倒计时
+//        CompanyAiWorkflowExec exec = companyAiWorkflowExecMapper.selectByWorkflowInstanceId(context.getWorkflowInstanceId());
+//        if (!exec.getCurrentNodeKey().equals(nodeKey)) {
+//            //当前节点已流转
+//            log.error("当前节点已流转 ,目标:{},实际:{}", nodeKey, exec.getCurrentNodeKey());
+//            return;
+//        }
+//        //更新加微日志执行状态
+//        super.updateLogStatusIfExist(context, ExecutionStatusEnum.PAUSED, ExecutionStatusEnum.WAITING);
+//        super.asyncWorkflowForBlockingNode(context.getWorkflowInstanceId(), nodeKey, context, ExecutionStatusEnum.WAITING);
+//        List<CompanyWorkflowEdge> edges = companyWorkflowEdgeMapper.selectListByWorkflowIdAndNodeKey(exec.getWorkflowId(), nodeKey);
+//        edges.forEach(edge -> {
+//            List<AiCallWorkflowConditionVo> conditions = JSONObject.parseArray(edge.getConditionExpr(), AiCallWorkflowConditionVo.class);
+//            if (null == conditions || conditions.isEmpty()) {
+//                super.runNextNode(context, edge);
+//            } else {
+//                AiCallWorkflowConditionVo condition = conditions.get(0);
+//                //节点包含延时条件
+//                if (null != condition.getAddTime() && !condition.isAdd()) {
+//                    long l = System.currentTimeMillis() + condition.getAddTime() * 60 * 1000;
+//                    String redisKey = getDelayAddWxKeyPrefix(l) + workflowInstanceId;
+//                    ExecutionContext nextContext = context.clone();
+//                    nextContext.setCurrentNodeKey(edge.getTargetNodeKey());
+//                    super.redisCache.setCacheObject(redisKey, nextContext);
+//                }
+//            }
+//        });
+//    }
+}

+ 3 - 0
fs-service/src/main/java/com/fs/company/service/impl/call/node/WorkflowNodeFactory.java

@@ -39,6 +39,9 @@ public class WorkflowNodeFactory implements IWorkflowNodeFactory {
             case AI_ADD_WX_TASK:
                 node = new AiAddWxTaskNode(nodeKey, nodeName, properties);
                 break;
+            case AI_QW_ADD_WX_TASK:
+                node = new AiQwAddWxTaskNode(nodeKey, nodeName, properties);
+                break;
             case OUTBOUND_TASK:
                 node = new OutBoundTaskNode(nodeKey, nodeName, properties);
                 break;

+ 5 - 1
fs-service/src/main/java/com/fs/enums/NodeTypeEnum.java

@@ -44,7 +44,11 @@ public enum NodeTypeEnum {
     /**
      * AI添加微信
      */
-    AI_ADD_WX_TASK("AI_ADD_WX_TASK", "AI添加微信",8);
+    AI_ADD_WX_TASK("AI_ADD_WX_TASK", "AI添加微信",8),
+    /**
+     * AI企微添加个微
+     */
+    AI_QW_ADD_WX_TASK("AI_QW_ADD_WX_TASK", "AI企微添加个微",9);
 
     private final String code;
     private final String description;

+ 1 - 1
fs-service/src/main/java/com/fs/erp/domain/WeizouApiPushOrderParam.java

@@ -134,7 +134,7 @@ public class WeizouApiPushOrderParam {
     private String orderMedia;
 
     /**
-     * 额外订单号(对应平台的订单号)
+     * 额外订单号 填写orderId!!!
      */
     @NotNull(message = "额外订单号不能为空")
     private String extendOrderId;

+ 15 - 4
fs-service/src/main/java/com/fs/erp/utils/WeizouApiClient.java

@@ -40,18 +40,29 @@ public class WeizouApiClient {
     @Qualifier("redisTemplate")
     private RedisTemplate redisTemplateInstance;
 
+    @Value("${weizou.baseUrl:https://test-api.weizou.com:9702}")
+    private String baseUrlInstance;
+
+    @Value("${weizou.appId:005fcddb9b664eeca80a13b62e823b63}")
+    private String appIdInstance;
+
+    @Value("${weizou.appSecret:a6cd645383a3405d842ce00da106ded0}")
+    private String appSecretInstance;
     // 3. @PostConstruct 将实例赋值给静态变量
     @PostConstruct
     public void initStatic() {
         redisTemplate = this.redisTemplateInstance;
         log.info("RedisTemplate 静态初始化完成");
+        BASE_URL = this.baseUrlInstance;
+        APP_ID = this.appIdInstance;
+        APP_SECRET = this.appSecretInstance;
+        log.info("配置加载完成:BASE_URL={}, APP_ID={}", BASE_URL, APP_ID);
     }
 
-    private static final String BASE_URL = "https://test-api.weizou.com:9702"; // TODO: 替换为文档中的正式环境地址
-
+    private static String BASE_URL; // TODO: 替换为文档中的正式环境地址
     // TODO: 替换为平台提供的实际应用ID和密钥
-    private static final String APP_ID = "005fcddb9b664eeca80a13b62e823b63";
-    private static final String APP_SECRET = "a6cd645383a3405d842ce00da106ded0";
+    private static  String APP_ID ;
+    private static  String APP_SECRET;
 
     private static final String WEIZOU_API_CLIENT_KEY = "weizou:api:client:key";
 

+ 1 - 0
fs-service/src/main/java/com/fs/his/domain/FsStoreOrder.java

@@ -266,5 +266,6 @@ public class FsStoreOrder extends BaseEntity
     private BigDecimal billPrice;
     private String erpPhone;
     private Integer doctorType2Confirm;
+    private String operator;
 
 }

+ 1 - 0
fs-service/src/main/java/com/fs/his/service/impl/FsStoreOrderServiceImpl.java

@@ -630,6 +630,7 @@ public class FsStoreOrderServiceImpl implements IFsStoreOrderService {
         Logs.setOrderId(fsStoreOrder.getOrderId());
         Logs.setChangeTime(new DateTime());
         Logs.setChangeType("delivery_goods");
+        Optional.ofNullable(fsStoreOrder.getOperator()).ifPresent(Logs::setOperator);
         fsStoreOrderLogsMapper.insertFsStoreOrderLogs(Logs);
         String lastFourNumber = "";
         if (order.getDeliveryCode().equals(ShipperCodeEnum.SF.getValue())) {

+ 4 - 0
fs-service/src/main/java/com/fs/hisStore/domain/FsStoreOrderScrm.java

@@ -366,4 +366,8 @@ public class FsStoreOrderScrm extends BaseEntity
 
     // 线下支付金额
     private BigDecimal offlinePayAmount;
+    //视频ID
+    private Integer videoId;
+    //课程ID
+    private Integer courseId;
 }

+ 5 - 0
fs-service/src/main/java/com/fs/hisStore/param/FsStoreOrderCreateParam.java

@@ -56,4 +56,9 @@ public class FsStoreOrderCreateParam implements Serializable
     private Integer orderMedium; //媒体来源
 
     private Boolean isUserApp = true;
+
+    //视频ID
+    private Integer videoId;
+    //课程ID
+    private Integer courseId;
 }

+ 7 - 0
fs-service/src/main/java/com/fs/hisStore/service/impl/FsStoreOrderScrmServiceImpl.java

@@ -134,6 +134,7 @@ import org.springframework.aop.framework.AopContext;
 import org.springframework.beans.BeanUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.beans.factory.annotation.Value;
 import org.springframework.context.ApplicationEventPublisher;
 import org.springframework.context.annotation.EnableAspectJAutoProxy;
 import org.springframework.context.annotation.Lazy;
@@ -408,6 +409,8 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
     @Autowired
     private FsUserCompanyPackageScrmMapper fsUserCompanyPackageScrmMapper;
 
+    @Value("${cloud_host.company_name}")
+    private String companyName;
     @PostConstruct
     public void initErpServiceMap() {
         erpServiceMap = new HashMap<>();
@@ -897,6 +900,10 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
             storeOrder.setCompanyId(param.getCompanyId());
             storeOrder.setCompanyUserId(param.getCompanyUserId());
 
+            if ("北京卓美".equals(companyName) && param.getVideoId()!=null){
+                storeOrder.setVideoId(param.getVideoId());
+                storeOrder.setCourseId(param.getCourseId());
+            }
             String json = configService.selectConfigByKey("store.config");
             StoreConfig config= JSONUtil.toBean(json, StoreConfig.class);
             //绑定销售

+ 11 - 0
fs-service/src/main/java/com/fs/qw/mapper/QwExternalContactMapper.java

@@ -648,4 +648,15 @@ public interface QwExternalContactMapper extends BaseMapper<QwExternalContact> {
             "</foreach>" +
             "</script>")
     public int batchUpdateQwExternalContactMandatoryRegistration(@Param("map") List<QwMandatoryRegistrParam> batchList);
+
+    @Select("SELECT " +
+            "id " +
+            "FROM " +
+            "qw_external_contact " +
+            "WHERE " +
+            "qw_user_id = #{qwUserId} " +
+            "and add_way = #{addWay} " +
+            "and remark_mobiles LIKE concat('%',#{phone},'%') " +
+            "limit 1")
+    QwExternalContact queryQwUserIdIsAddContact(@Param("qwUserId") Long qwUserId, @Param("phone") String phone, @Param("addWay") int addWay);
 }

+ 5 - 0
fs-service/src/main/java/com/fs/qw/vo/QwExternalContactVO.java

@@ -148,4 +148,9 @@ public class QwExternalContactVO {
      * 广告链路id
      */
     private String traceId;
+
+    /**
+     * 是否下载APP
+     */
+    private Integer isDownloadApp;
 }

+ 26 - 0
fs-service/src/main/java/com/fs/wxwork/dto/WxAddSearchDTO.java

@@ -0,0 +1,26 @@
+package com.fs.wxwork.dto;
+
+import lombok.Data;
+
+/**
+ * @Author:peicj
+ * @Description: 搜索添加外部联系人
+ * @Date:2026/2/28 16:47
+ */
+@Data
+public class WxAddSearchDTO {
+    /**
+     * 用户UUID
+     */
+    private String uuid;
+
+    private Long vid;
+
+    private String optionid;
+
+    private String phone;
+
+    private String content;
+
+    private String ticket;
+}

+ 21 - 0
fs-service/src/main/java/com/fs/wxwork/dto/WxSearchContactDTO.java

@@ -0,0 +1,21 @@
+package com.fs.wxwork.dto;
+
+import lombok.Data;
+
+/**
+ * @Author:peicj
+ * @Description: 根据手机号搜索联系人
+ * @Date:2026/2/28 16:46
+ */
+@Data
+public class WxSearchContactDTO {
+    /**
+     * 用户UUID
+     */
+    private String uuid;
+    /**
+     * 手机号
+     */
+    private String phoneNumber;
+
+}

+ 35 - 0
fs-service/src/main/java/com/fs/wxwork/dto/WxSearchContactResp.java

@@ -0,0 +1,35 @@
+package com.fs.wxwork.dto;
+
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * @Author:peicj
+ * @Description: 根据手机号搜索联系人
+ * @Date:2026/2/28 16:50
+ */
+@Data
+public class WxSearchContactResp {
+
+    private List<UserList> userList;
+
+    @Data
+    public class UserList {
+        private String headImg;
+
+        private String ticket;
+
+        private Long user_id;
+
+        private Integer sex;
+
+        private String name;
+        //1:企微 2:个微
+        private String state;
+
+        private Long corp_id;
+
+        private String openid;
+    }
+}

+ 16 - 0
fs-service/src/main/java/com/fs/wxwork/service/WxWorkService.java

@@ -241,4 +241,20 @@ public interface WxWorkService {
     WxWorkResponseDTO<WxCdnUploadImgLinkResp> cdnUploadImgLink(WxCdnUploadImgLinkDTO param, Long serverId);
 
     WxwSilkVoceDTO getSilkVoiceDoubao(String content, Long companyUserId, QwUser user);
+
+    /**
+     * 根据手机号搜索联系人
+     * @param param     搜索参数
+     * @param serverId  服务器ID
+     * @return WxWorkResponseDTO
+     */
+    WxWorkResponseDTO<WxSearchContactResp> searchContact(WxSearchContactDTO param, Long serverId);
+
+    /**
+     * 搜索添加外部联系人
+     * @param param     搜索参数
+     * @param serverId  服务器ID
+     * @return WxWorkResponseDTO
+     */
+    WxWorkResponseDTO<String> addSearch(WxAddSearchDTO param, Long serverId);
 }

+ 14 - 0
fs-service/src/main/java/com/fs/wxwork/service/WxWorkServiceImpl.java

@@ -391,4 +391,18 @@ public class WxWorkServiceImpl implements WxWorkService {
         log.info("豆包语音生成成功,url: {}", (audioVO.getUrl()));
         return wxwSilkVoceDTO;
     }
+
+    @Override
+    public WxWorkResponseDTO<WxSearchContactResp> searchContact(WxSearchContactDTO param, Long serverId) {
+        String url = getUrl(serverId) + "/SearchContact";
+        return WxWorkHttpUtil.postWithType(url, param, new TypeReference<WxWorkResponseDTO<WxSearchContactResp>>() {
+        });
+    }
+
+    @Override
+    public WxWorkResponseDTO<String> addSearch(WxAddSearchDTO param, Long serverId) {
+        String url = getUrl(serverId) + "/AddSearch";
+        return WxWorkHttpUtil.postWithType(url, param, new TypeReference<WxWorkResponseDTO<String>>() {
+        });
+    }
 }

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

@@ -96,6 +96,7 @@ ipad:
   ipadUrl: http://qwipad.muyi88.com
 #  aiApi: http://152.136.202.157:3000/api
   aiApi: http://49.232.181.28:3000/api
+  wxIpadUrl:
   voiceApi: http://106.52.21.84:8009
   commonApi: http://106.52.21.84:7771
 wx_miniapp_temp:

+ 8 - 1
fs-service/src/main/resources/mapper/company/CompanyVoiceRoboticCallLogAddwxMapper.xml

@@ -15,10 +15,11 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         <result property="createTime"    column="create_time"    />
         <result property="companyId"    column="company_id"    />
         <result property="wxAccountId"    column="wx_account_id"    />
+        <result property="isWeCom"    column="is_we_com"    />
     </resultMap>
 
     <sql id="selectCompanyVoiceRoboticCallLogAddwxVo">
-        select log_id, robotic_id, wx_client_id, run_time, run_param, result, status, create_time, company_id, wx_account_id from company_voice_robotic_call_log_addwx
+        select log_id, robotic_id, wx_client_id, run_time, run_param, result, status, create_time, company_id, wx_account_id,is_we_com from company_voice_robotic_call_log_addwx
     </sql>
 
     <select id="selectCompanyVoiceRoboticCallLogAddwxList" parameterType="CompanyVoiceRoboticCallLogAddwx" resultMap="CompanyVoiceRoboticCallLogAddwxResult">
@@ -32,6 +33,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="status != null "> and status = #{status}</if>
             <if test="companyId != null "> and company_id = #{companyId}</if>
             <if test="wxAccountId != null "> and wx_account_id = #{wxAccountId}</if>
+            <if test="isWeCom != null "> and is_we_com = #{isWeCom}</if>
         </where>
     </select>
     
@@ -52,6 +54,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="createTime != null">create_time,</if>
             <if test="companyId != null">company_id,</if>
             <if test="wxAccountId != null">wx_account_id,</if>
+            <if test="isWeCom != null">is_we_com,</if>
          </trim>
         <trim prefix="values (" suffix=")" suffixOverrides=",">
             <if test="roboticId != null">#{roboticId},</if>
@@ -63,6 +66,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="createTime != null">#{createTime},</if>
             <if test="companyId != null">#{companyId},</if>
             <if test="wxAccountId != null">#{wxAccountId},</if>
+            <if test="isWeCom != null">#{is_we_com},</if>
          </trim>
     </insert>
 
@@ -78,6 +82,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="createTime != null">create_time = #{createTime},</if>
             <if test="companyId != null">company_id = #{companyId},</if>
             <if test="wxAccountId != null">wx_account_id = #{wxAccountId},</if>
+            <if test="isWeCom != null">is_we_com = #{isWeCom},</if>
         </trim>
         where log_id = #{logId}
     </update>
@@ -108,6 +113,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         where 1=1
         <if test="roboticId != null "> and t1.robotic_id = #{roboticId}</if>
         <if test="wxClientId != null "> and t1.wx_client_id = #{wxClientId}</if>
+        <if test="isWeCom != null "> and t1.is_we_com = #{isWeCom}</if>
     </select>
 
     <select id="listByCustomerId" resultType="com.fs.company.vo.CompanyVoiceRoboticCallLogAddwxVO">
@@ -129,5 +135,6 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
                 #{item}
             </foreach>
         </if>
+        <if test="isWeCom != null "> and t1.is_we_com = #{isWeCom}</if>
     </select>
 </mapper>

+ 18 - 0
fs-service/src/main/resources/mapper/company/CompanyVoiceRoboticWxMapper.xml

@@ -47,6 +47,24 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         </if>
     </select>
 
+    <select id="selectByRoboticIdQw" resultType="com.fs.company.domain.CompanyVoiceRoboticWx">
+        SELECT
+        a.*,
+        account.qw_user_name wxNickName,
+        account.qw_user_id wxNo,
+        b.nick_name AS companyUserName,
+        c.NAME dialogName
+        FROM
+        company_voice_robotic_wx a
+        LEFT JOIN qw_user account ON account.id = a.account_id
+        LEFT JOIN company_user b ON account.company_user_id = b.user_id
+        LEFT JOIN company_wx_dialog c ON a.wx_dialog_id = c.id
+        where a.robotic_id = #{id} and a.is_we_com = 2
+        <if test="intention != null and intention != ''">
+            and a.intention = #{intention}
+        </if>
+    </select>
+
     <insert id="insertCompanyVoiceRoboticWx" parameterType="CompanyVoiceRoboticWx" useGeneratedKeys="true" keyProperty="id">
         insert into company_voice_robotic_wx
         <trim prefix="(" suffix=")" suffixOverrides=",">

+ 13 - 0
fs-service/src/main/resources/mapper/company/CompanyWxClientMapper.xml

@@ -160,6 +160,19 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         <if test="accountIdList != null and !accountIdList.isEmpty()">
             and account_id in <foreach collection="accountIdList" open="(" separator="," close=")" item="item">#{item}</foreach>
         </if>
+        <if test="isWeCom != null">
+            and is_we_com = #{isWeCom}
+        </if>
+        group by account_id
+    </select>
+    <select id="getQwAddWxList" resultType="com.fs.company.domain.CompanyWxClient">
+        SELECT * FROM company_wx_client where is_add = 2 and account_id is not null
+        <if test="accountIdList != null and !accountIdList.isEmpty()">
+            and account_id in <foreach collection="accountIdList" open="(" separator="," close=")" item="item">#{item}</foreach>
+        </if>
+        <if test="isWeCom != null">
+            and is_we_com = #{isWeCom}
+        </if>
         group by account_id
     </select>
     <select id="getAddWxList4Workflow" resultType="com.fs.company.vo.CompanyWxClient4WorkFlowVO">

+ 9 - 0
fs-service/src/main/resources/mapper/hisStore/FsStoreOrderScrmMapper.xml

@@ -155,6 +155,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="deliveryImportTime != null "> and delivery_import_time = #{deliveryImportTime}</if>
             <if test="backendEditProductType != null "> and backend_edit_product_type = #{backendEditProductType}</if>
             <if test="remark != null and remark != ''"> and remark = #{remark}</if>
+            <if test="videoId != null and videoId != ''"> and video_id = #{videoId}</if>
+            <if test="courseId != null and courseId != ''"> and course_id = #{courseId}</if>
         </where>
     </select>
 
@@ -260,6 +262,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="followDoctorId != null">follow_doctor_id,</if>
             <if test="cycle != null">cycle,</if>
             <if test="backendEditProductType != null">backend_edit_product_type,</if>
+            <if test="videoId != null">video_id,</if>
+            <if test="courseId != null" >course_id,</if>
          </trim>
         <trim prefix="values (" suffix=")" suffixOverrides=",">
             <if test="orderCode != null and orderCode != ''">#{orderCode},</if>
@@ -347,6 +351,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="followDoctorId != null">#{followDoctorId},</if>
             <if test="cycle != null">#{cycle},</if>
             <if test="backendEditProductType != null">#{backendEditProductType},</if>
+            <if test="videoId != null">#{videoId},</if>
+            <if test="courseId != null" >#{courseId},</if>
          </trim>
     </insert>
 
@@ -441,6 +447,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="cycle != null">cycle = #{cycle},</if>
             <if test="orderRemark != null">order_remark = #{orderRemark},</if>
             <if test="backendEditProductType != null">backend_edit_product_type = #{backendEditProductType},</if>
+            <if test="videoId != null">video_id = #{videoId},</if>
+            <if test="courseId != null">course_id = #{courseId},</if>
+
         </trim>
         where id = #{id}
     </update>

+ 391 - 1
fs-wx-task/src/main/java/com/fs/app/service/WxTaskService.java

@@ -1,5 +1,6 @@
 package com.fs.app.service;
 
+import cn.hutool.core.collection.CollectionUtil;
 import cn.hutool.core.util.ObjectUtil;
 import cn.hutool.core.util.RandomUtil;
 import cn.hutool.json.JSONUtil;
@@ -29,10 +30,19 @@ import com.fs.company.vo.SendMsgVo;
 import com.fs.course.config.WxConfig;
 import com.fs.crm.domain.CrmCustomer;
 import com.fs.crm.service.ICrmCustomerService;
+import com.fs.qw.domain.QwExternalContact;
+import com.fs.qw.domain.QwUser;
+import com.fs.qw.mapper.QwExternalContactMapper;
+import com.fs.qw.mapper.QwUserMapper;
 import com.fs.system.service.ISysConfigService;
 import com.fs.wxcid.dto.friend.AddContactParam;
 import com.fs.wxcid.service.FriendService;
 import com.fs.wxcid.vo.AddContactVo;
+import com.fs.wxwork.dto.WxAddSearchDTO;
+import com.fs.wxwork.dto.WxSearchContactDTO;
+import com.fs.wxwork.dto.WxSearchContactResp;
+import com.fs.wxwork.dto.WxWorkResponseDTO;
+import com.fs.wxwork.service.WxWorkService;
 import lombok.AllArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.redisson.api.RLock;
@@ -89,13 +99,16 @@ public class WxTaskService {
             new ThreadPoolExecutor.CallerRunsPolicy()
     );
     private final RedisKeyScanner redisKeyScanner;
+    private final QwUserMapper qwUserMapper;
+    private final WxWorkService wxWorkService;
+    private final QwExternalContactMapper qwExternalContactMapper;
 
     public void addWx(List<Long> accountIdList) {
         log.info("==========执行加微信任务开始==========");
         String json = sysConfigService.selectConfigByKey("wx.config");
         WxConfig config = JSONUtil.toBean(json, WxConfig.class);
         // 需要添加微信的列表
-        List<CompanyWxClient> list = companyWxClientService.getAddWxList(accountIdList);
+        List<CompanyWxClient> list = companyWxClientService.getAddWxList(accountIdList,1);
         //排除掉没到达加微步骤的人
         List<CompanyVoiceRoboticCallees> exList = companyVoiceRoboticCalleesMapper.selectExcludeList(list);
         List<CompanyVoiceRoboticCallees> collect =
@@ -839,4 +852,381 @@ public class WxTaskService {
         log.info("===========工作流延时任务扫描结束===========");
     }
 
+    /**
+     * 企微加微信任务
+     *
+     * @param accountIdList 企微成员id
+     */
+    public void qwAddWx(List<Long> accountIdList) {
+        log.info("==========执行企微申请加个微任务开始==========");
+        try {
+            // 获取需要添加微信的企微客户列表
+            List<CompanyWxClient> clientList = getFilteredClientList(accountIdList);
+            if (clientList.isEmpty()) {
+                log.info("没有符合条件的客户需要添加微信");
+                return;
+            }
+            
+            // 获取CompanyWxClient信息
+            Map<Long, CompanyWxClient> clientMap = PubFun.listToMapByGroupObject(clientList, CompanyWxClient::getAccountId);
+            // 获取实际企微用户信息
+            List<QwUser> qwUserList = qwUserMapper.selectBatchIds(clientMap.keySet()).stream()
+                    .filter(this::isValidQwUser)
+                    .collect(Collectors.toList());
+            
+            log.info("需要企微添加的账号数量:{}", qwUserList.size());
+            if (qwUserList.isEmpty()) return;
+            
+            // 处理加微逻辑
+            List<CompanyWxClient> upClientList = processQwAddWx(qwUserList, clientMap);
+            
+            // 批量更新客户状态
+            if (!upClientList.isEmpty()) {
+                companyWxClientService.updateBatchById(upClientList);
+                log.info("成功更新{}个客户的加微状态", upClientList.size());
+            }
+        } catch (Exception e) {
+            log.error("企微加微信任务执行异常", e);
+        }
+        log.info("==========执行企微申请加个微任务结束==========");
+    }
+
+    /**
+     * 企微加微结果处理
+     */
+    public void qwAddWxResult(List<Long> accountIdList) {
+        log.info("==========执行企微申请加个微结果查询任务开始==========");
+        try {
+            //is_add = 2,状态为加微中且是企微类型
+            List<CompanyWxClient> clients = companyWxClientService.getQwAddWxList(accountIdList, 2);
+            log.info("需要查询企微加个微结果的数量:{}", clients.size());
+            
+            if (clients.isEmpty()) return;
+            
+            // 处理每个客户的加微结果
+            List<CompanyWxClient> upClientList = new ArrayList<>();
+            clients.parallelStream().forEach(client -> {
+                try {
+                    processSingleClientResult(client, upClientList);
+                } catch (Exception e) {
+                    log.error("处理客户{}加微结果异常", client.getId(), e);
+                }
+            });
+            
+            // 批量更新和后续处理
+            if (!upClientList.isEmpty()) {
+                batchUpdateClients(upClientList);
+            }
+            
+        } catch (Exception e) {
+            log.error("企微加微结果处理异常", e);
+        }
+        log.info("==========执行企微申请加个微结果查询任务结束==========");
+    }
+
+    
+    /**
+     * 获取过滤后的企微客户列表
+     */
+    private List<CompanyWxClient> getFilteredClientList(List<Long> accountIdList) {
+        List<CompanyWxClient> list = companyWxClientService.getAddWxList(accountIdList, 2);
+        
+        // 排除掉没到达加微步骤的人
+        List<CompanyVoiceRoboticCallees> excludeList = companyVoiceRoboticCalleesMapper.selectExcludeList(list);
+        Set<String> excludeKeys = excludeList.stream()
+                .filter(e -> !Constants.QW_ADD_WX.equals(getNextTaskOptimized(e.getTaskFlow(), e.getRunTaskFlow())))
+                .map(callee -> callee.getRoboticId() + "_" + callee.getUserId())
+                .collect(Collectors.toSet());
+        
+        return list.stream()
+                .filter(client -> !excludeKeys.contains(client.getRoboticId() + "_" + client.getCustomerId()))
+                .collect(Collectors.toList());
+    }
+    
+    /**
+     * 验证企微用户有效性
+     */
+    private boolean isValidQwUser(QwUser qwUser) {
+        if (StringUtils.isBlank(qwUser.getUid()) || qwUser.getServerId() == null) {
+            log.info("企微账号{}的uid或serverId为空,跳过执行", qwUser.getQwUserName());
+            return false;
+        }
+        return true;
+    }
+    
+    /**
+     * 处理企微加微逻辑
+     */
+    private List<CompanyWxClient> processQwAddWx(List<QwUser> qwUserList, Map<Long, CompanyWxClient> clientMap) {
+        List<CompanyWxClient> upClientList = Collections.synchronizedList(new ArrayList<>());
+        
+        qwUserList.parallelStream().forEach(qwUser -> {
+            try {
+                processSingleQwUser(qwUser, clientMap, upClientList);
+            } catch (Exception e) {
+                log.error("处理企微用户{}异常", qwUser.getId(), e);
+            }
+        });
+        
+        return upClientList;
+    }
+    
+    /**
+     * 处理单个企微用户
+     */
+    private void processSingleQwUser(QwUser qwUser, Map<Long, CompanyWxClient> clientMap, 
+                                   List<CompanyWxClient> upClientList) {
+        CompanyWxClient client = clientMap.get(qwUser.getId());
+        if (client == null) {
+            log.error("当前账号暂无需要添加微信:{}-{}", qwUser.getId(), qwUser.getQwUserName());
+            return;
+        }
+        
+        CrmCustomer crmCustomer = crmCustomerService.selectCrmCustomerById(client.getCustomerId());
+        if (crmCustomer == null || StringUtils.isBlank(crmCustomer.getMobile())) {
+            log.info("查询客户{}手机号为空,跳过执行", crmCustomer == null ? "" : crmCustomer.getCustomerName());
+            return;
+        }
+        
+        String task = redisCache.getCacheObject(Constants.TASK_ID + client.getRoboticId());
+        log.info("ROBOTIC-ID:{},CLIENT-ID:{},当前任务执行状态:{}", 
+                client.getRoboticId(), client.getId(), task);
+        
+        if (!StringUtils.isNotEmpty(task) || !Constants.QW_ADD_WX.equals(task)) {
+            log.error("ROBOTIC-ID:{},当前任务没有执行加微任务", client.getRoboticId());
+            return;
+        }
+        
+        // 开始申请加微
+        WxWorkResponseDTO<String> resp = qwAddWxInvokeIpad(crmCustomer.getMobile(), qwUser.getUid(), qwUser.getServerId());
+        //处理申请加微结果
+        handleAddWxResult(resp, client, qwUser, crmCustomer, upClientList);
+    }
+
+    /**
+     * 企微加个微调用ipad端
+     * @param mobile  手机号
+     * @param qwUid   企微uid
+     * @param serverId   服务器id
+     * @return String 结果
+     */
+    private WxWorkResponseDTO<String> qwAddWxInvokeIpad(String mobile, String qwUid, Long serverId) {
+        if (StringUtils.isBlank(mobile) || StringUtils.isBlank(qwUid) || serverId == null) {
+            log.warn("参数校验失败: mobile={}, qwUid={}, serverId={}", mobile, qwUid, serverId);
+            return null;
+        }
+        try {
+            WxAddSearchDTO wxAddSearchDTO = new WxAddSearchDTO();
+            wxAddSearchDTO.setUuid(qwUid);
+            wxAddSearchDTO.setPhone(mobile);
+            wxAddSearchDTO.setVid(null);
+            wxAddSearchDTO.setOptionid(null);
+            wxAddSearchDTO.setContent(null);
+            wxAddSearchDTO.setTicket(null);
+
+            WxWorkResponseDTO<String> response = wxWorkService.addSearch(wxAddSearchDTO, serverId);
+            log.debug("企微加微接口调用结果: errcode={}, errmsg={}",
+                    response != null ? response.getErrcode() : "null",
+                    response != null ? response.getErrmsg() : "null");
+
+            return response;
+        } catch (Exception e) {
+            log.error("企微加个微请求接口异常: mobile={}, qwUid={}, serverId={}", mobile, qwUid, serverId, e);
+            return null;
+        }
+    }
+
+    /**
+     * 处理加微结果
+     */
+    private void handleAddWxResult(WxWorkResponseDTO<String> resp, CompanyWxClient client, 
+                                 QwUser qwUser, CrmCustomer crmCustomer, 
+                                 List<CompanyWxClient> upClientList) {
+        JSONObject runParam = new JSONObject();
+        runParam.put("qwId", qwUser.getId());
+        runParam.put("mobile", crmCustomer.getMobile());
+        runParam.put("qwUid", qwUser.getUid());
+        runParam.put("clientId", client.getId());
+        
+        CompanyVoiceRoboticCallLogAddwx addLog = CompanyVoiceRoboticCallLogAddwx.initCallLog(
+                runParam.toJSONString(), client.getId(), client.getRoboticId(), qwUser.getId(), qwUser.getCompanyId());
+        
+        if (resp != null && resp.getErrcode() == 0) {
+            // 加微消息已发送成功
+            client.setIsAdd(2);
+            client.setAddTime(LocalDateTime.now());
+            upClientList.add(client);
+            addLog.setStatus(1);
+            addLog.setResult(JSON.toJSONString(resp));
+            addLog.setIsWeCom(2);
+            log.info("ROBOTIC-ID:{},加微成功", client.getRoboticId());
+        } else {
+            // 加微消息发送失败,补偿重试
+            handleFailedAddWxWithRetry(client, upClientList);
+        }
+        
+        asyncSaveCompanyVoiceRoboticCallLog(addLog);
+    }
+    
+    /**
+     * 处理单个客户加微结果
+     */
+    private void processSingleClientResult(CompanyWxClient client, List<CompanyWxClient> upClientList) {
+        if (StringUtils.isBlank(client.getPhone())) {
+            handleFailedAddWx(client, upClientList, "无电话号码");
+            return;
+        }
+        
+        // 查询外部联系人表是否有数据
+        QwExternalContact qwExternalContact = qwExternalContactMapper.queryQwUserIdIsAddContact(
+                client.getAccountId(), client.getPhone(), 2);
+        
+        if (qwExternalContact != null && qwExternalContact.getId() > 0) {
+            handleSuccessfulAddWx(client, upClientList);
+        } else {
+            handleFailedAddWxWithRetry(client, upClientList);
+        }
+    }
+    
+    /**
+     * 处理加微成功的情况
+     */
+    private void handleSuccessfulAddWx(CompanyWxClient client, List<CompanyWxClient> upClientList) {
+        // 更新记录状态
+        companyVoiceRoboticCallLogAddwxService.lambdaUpdate()
+                .eq(CompanyVoiceRoboticCallLogAddwx::getRoboticId, client.getRoboticId())
+                .eq(CompanyVoiceRoboticCallLogAddwx::getWxClientId, client.getId())
+                .eq(CompanyVoiceRoboticCallLogAddwx::getWxAccountId, client.getAccountId())
+                .eq(CompanyVoiceRoboticCallLogAddwx::getIsWeCom, 2)
+                .set(CompanyVoiceRoboticCallLogAddwx::getStatus, 2)
+                .update();
+        
+        client.setIsAdd(1);
+        client.setAddTime(LocalDateTime.now());
+        upClientList.add(client);
+        redisCache.deleteObject("qwAddWx_" + client.getId());
+        log.info("ROBOTIC-ID:{},加微成功:{}", client.getRoboticId(), client.getId());
+    }
+    
+    /**
+     * 处理加微失败的情况
+     */
+    private void handleFailedAddWx(CompanyWxClient client, List<CompanyWxClient> upClientList, String reason) {
+        log.error("ROBOTIC-ID:{},{}加微失败:{}", client.getRoboticId(), reason, client.getId());
+        client.setIsAdd(3);
+        client.setUpdateTime(new Date());
+        upClientList.add(client);
+        
+        // 更新记录
+        companyVoiceRoboticCallLogAddwxService.lambdaUpdate()
+                .eq(CompanyVoiceRoboticCallLogAddwx::getRoboticId, client.getRoboticId())
+                .eq(CompanyVoiceRoboticCallLogAddwx::getWxClientId, client.getId())
+                .eq(CompanyVoiceRoboticCallLogAddwx::getWxAccountId, client.getAccountId())
+                .eq(CompanyVoiceRoboticCallLogAddwx::getIsWeCom, 2)
+                .set(CompanyVoiceRoboticCallLogAddwx::getStatus, 3)
+                .set(CompanyVoiceRoboticCallLogAddwx::getResult, reason)
+                .update();
+    }
+    
+    /**
+     * 处理加微失败并重试计数
+     */
+    private void handleFailedAddWxWithRetry(CompanyWxClient client, List<CompanyWxClient> upClientList) {
+        log.error("ROBOTIC-ID:{},加微失败:{}", client.getRoboticId(), client.getId());
+        String failCountStr = redisCache.getCacheObject("qwAddWx_" + client.getId());
+        int failCount = 1;
+        
+        if (StringUtils.isNotBlank(failCountStr)) {
+            failCount += Integer.parseInt(failCountStr);
+            if (failCount >= 60 * 24) { // 超过一天
+                handleFailedAddWx(client, upClientList, "超过最大重试次数");
+            } else {
+                redisCache.setCacheObject("qwAddWx_" + client.getId(), String.valueOf(failCount));
+            }
+        } else {
+            redisCache.setCacheObject("qwAddWx_" + client.getId(), String.valueOf(failCount), 25, TimeUnit.HOURS);
+        }
+    }
+    
+    /**
+     * 批量更新客户和相关数据
+     */
+    private void batchUpdateClients(List<CompanyWxClient> upClientList) {
+        companyWxClientService.updateBatchById(upClientList);
+
+        // 从 upClientList 中筛选出 isAdd=1即加微成功的数据
+        List<CompanyWxClient> successClients = upClientList.stream()
+                .filter(client -> client.getIsAdd() != null && client.getIsAdd() == 1)
+                .collect(Collectors.toList());
+
+        // 根据加微成功的用户,判定是否加入延时执行下一步任务
+        Set<Long> roboticIdSet = successClients.stream()
+                .map(CompanyWxClient::getRoboticId)
+                .collect(Collectors.toSet());
+        Set<Long> userIdSet = successClients.stream()
+                .map(CompanyWxClient::getCustomerId)
+                .collect(Collectors.toSet());
+        
+        // 获取任务和callees数据
+        List<CompanyVoiceRobotic> robotList = companyVoiceRoboticMapper.selectBatchIds(roboticIdSet);
+        Map<Long, CompanyVoiceRobotic> roboticsMap = robotList.stream()
+                .collect(Collectors.toMap(CompanyVoiceRobotic::getId, Function.identity(), (a, b) -> a));
+        
+        List<CompanyVoiceRoboticCallees> calleesList = companyVoiceRoboticCalleesMapper
+                .selectCalleesListByRoboticIdsAndUserIds(userIdSet, roboticIdSet);
+        Map<String, CompanyVoiceRoboticCallees> calleesMap = calleesList.stream()
+                .collect(Collectors.toMap(e -> e.getUserId() + "-" + e.getRoboticId(), Function.identity(), (a, b) -> a));
+        
+        // 设置延时任务
+        setupDelayTasks(successClients, roboticsMap, calleesMap);
+        
+        // 更新任务流程状态
+        updateTaskFlows(calleesList, roboticIdSet);
+    }
+    
+    /**
+     * 设置延时任务
+     */
+    private void setupDelayTasks(List<CompanyWxClient> upClientList, 
+                               Map<Long, CompanyVoiceRobotic> roboticsMap,
+                               Map<String, CompanyVoiceRoboticCallees> calleesMap) {
+        upClientList.forEach(client -> {
+            CompanyVoiceRobotic robotic = roboticsMap.get(client.getRoboticId());
+            if (robotic == null) {
+                log.error("ROBOTIC-ID:{},CLIENT-ID:{},没有找到任务", client.getRoboticId(), client.getId());
+                return;
+            }
+            
+            CompanyVoiceRoboticCallees callee = calleesMap.get(client.getCustomerId() + "-" + client.getRoboticId());
+            if (callee == null) {
+                log.error("ROBOTIC-ID:{},CLIENT-ID:{},没有找到任务", client.getRoboticId(), client.getId());
+                return;
+            }
+            
+            Integer addWxTime = robotic.getAddWxTime();
+            if (addWxTime != null) {
+                long endTime = System.currentTimeMillis() + addWxTime * 60 * 1000;
+                String key = Constants.CID_NEXT_TASK_ID + callee.getRoboticId() + ":" + callee.getId();
+                redisCache.setCacheObject(key, String.valueOf(endTime));
+            } else {
+                log.error("ROBOTIC-ID:{},CLIENT-ID:{},没有设置加微后置等待时间", client.getRoboticId(), client.getId());
+            }
+        });
+    }
+    
+    /**
+     * 更新任务流程状态
+     */
+    private void updateTaskFlows(List<CompanyVoiceRoboticCallees> calleesList, Set<Long> roboticIdSet) {
+        calleesList.forEach(callee -> 
+            callee.setRunTaskFlow(
+                StringUtils.isBlank(callee.getRunTaskFlow()) ?
+                    Constants.QW_ADD_WX : callee.getRunTaskFlow() + "," + Constants.QW_ADD_WX
+            )
+        );
+        
+        companyVoiceRoboticCalleesServiceImpl.updateBatchById(calleesList);
+        companyVoiceRoboticServiceImpl.finishAddWxByCallees(roboticIdSet);
+    }
+
 }

+ 14 - 1
fs-wx-task/src/main/java/com/fs/app/task/WxTask.java

@@ -64,5 +64,18 @@ public class WxTask {
     }
 
 
-
+    /**
+     * 企微加微
+     */
+    @Scheduled(cron = "0 0/1 * * * ?")
+    public void qwAddWx() {
+        taskService.qwAddWx(null);
+    }
+    /**
+     * 企微加微结果查询
+     */
+    @Scheduled(cron = "0 0/1 * * * ?")
+    public void qwAddWxResult() {
+        taskService.qwAddWxResult(null);
+    }
 }