ソースを参照

在职转接提示词优化3.0

xw 1 日 前
コミット
a5a8888cf2

+ 10 - 0
fs-admin/src/main/java/com/fs/qw/controller/QwExternalContactController.java

@@ -424,4 +424,14 @@ public class QwExternalContactController extends BaseController
         int count = param.getIds() != null ? param.getIds().size() : 0;
         return R.ok("转接任务已提交,共 " + count + " 人,完成后请留意转接通知");
     }
+
+    /**
+     * 在职/离职转接前置校验(iPad好友关系)
+     */
+    @PreAuthorize("@ss.hasPermi('qw:externalContact:transfer')")
+    @PostMapping("/transferPreCheck")
+    public R transferPreCheck(@RequestBody TransferPreCheckParam param)
+    {
+        return qwExternalContactService.transferPreCheck(param);
+    }
 }

+ 15 - 2
fs-company/src/main/java/com/fs/company/controller/qw/QwExternalContactController.java

@@ -540,8 +540,8 @@ public class QwExternalContactController extends BaseController
     }
 
     /**
-    * 在职转接 分配客户
-    */
+     * 在职转接 分配客户
+     */
     @PreAuthorize("@ss.hasPermi('qw:externalContact:transfer')")
     @Log(title = "企业微信客户", businessType = BusinessType.UPDATE)
     @PutMapping("/transfer")
@@ -559,6 +559,19 @@ public class QwExternalContactController extends BaseController
         qwUserServiceAsyncHelper.transfer(param);
         return R.ok("转接任务已提交,共 " + param.getIds().size() + " 人,完成后请留意转接通知");
     }
+
+    /**
+     * 在职/离职转接前置校验(iPad好友关系)
+     */
+    @PreAuthorize("@ss.hasPermi('qw:externalContact:transfer')")
+    @PostMapping("/transferPreCheck")
+    public R transferPreCheck(@RequestBody TransferPreCheckParam param)
+    {
+        if (param.isFilter()) {
+            param.setIds(getList(param.getAddType(), param.getParam()));
+        }
+        return qwExternalContactService.transferPreCheck(param);
+    }
     /**
      * 修改企业微信客户
      */

+ 18 - 0
fs-service/src/main/java/com/fs/qw/param/TransferPreCheckParam.java

@@ -0,0 +1,18 @@
+package com.fs.qw.param;
+
+import lombok.Data;
+
+import java.util.List;
+
+@Data
+public class TransferPreCheckParam {
+    /** 待转接客户ID列表 */
+    private List<Long> ids;
+    /** 接替员工 qw_user 表主键ID */
+    private Long userId;
+    private String corpId;
+    /** 是否按筛选条件(与转接接口一致) */
+    private boolean filter;
+    private Integer addType;
+    private QwExternalContactParam param;
+}

+ 2 - 0
fs-service/src/main/java/com/fs/qw/service/IQwExternalContactService.java

@@ -120,6 +120,8 @@ public interface IQwExternalContactService extends IService<QwExternalContact> {
 
     R transfer(TransferParam param);
 
+    R transferPreCheck(TransferPreCheckParam param);
+
     R addUserTag(QwExternalContactAddTagParam param) throws JSONException;
     R addTagByWatch(QwExtContactAddTagByWatchParam param) throws JSONException;
 

+ 181 - 0
fs-service/src/main/java/com/fs/qw/service/impl/QwExternalContactServiceImpl.java

@@ -79,6 +79,11 @@ import com.fs.sop.service.ISopUserLogsService;
 import com.fs.system.service.ISysConfigService;
 import com.fs.system.service.ISysDictTypeService;
 import com.fs.voice.utils.StringUtil;
+import com.fs.wxwork.dto.WxwGetExternalContactsDTO;
+import com.fs.wxwork.dto.WxwGetExternalContactsPageDTO;
+import com.fs.wxwork.dto.WxwIpadExternalContactItemDTO;
+import com.fs.wxwork.dto.WxWorkResponseDTO;
+import com.fs.wxwork.service.WxWorkService;
 import com.google.gson.Gson;
 import com.google.gson.reflect.TypeToken;
 import lombok.extern.slf4j.Slf4j;
@@ -170,6 +175,8 @@ public class QwExternalContactServiceImpl extends ServiceImpl<QwExternalContactM
     @Autowired
     private ICrmMsgService crmMsgService;
     @Autowired
+    private WxWorkService wxWorkService;
+    @Autowired
     QwAutoTagsLogsMapper qwAutoTagsLogsMapper;
     @Autowired
     IAdHtmlClickLogService  iAdHtmlClickLogService;
@@ -1311,6 +1318,180 @@ public class QwExternalContactServiceImpl extends ServiceImpl<QwExternalContactM
         return R.ok(buildTransferSummary(stats));
     }
 
+    @Override
+    public R transferPreCheck(TransferPreCheckParam param) {
+        if (param.getIds() == null || param.getIds().isEmpty()) {
+            return R.error("请选择需要分配的客户");
+        }
+        QwUser takeoverUser = qwUserMapper.selectQwUserById(param.getUserId());
+        if (takeoverUser == null) {
+            return R.error("接替员工不存在");
+        }
+        List<TransferPreCheckCustomerVO> transferable = new ArrayList<>();
+        Map<String, PreCheckFailBucket> untransferableMap = new LinkedHashMap<>();
+        Map<String, IpadContactIndex> ipadContactCache = new HashMap<>();
+
+        for (Long id : param.getIds()) {
+            QwExternalContact contact = qwExternalContactMapper.selectQwExternalContactById(id);
+            if (contact == null) {
+                addPreCheckFail(untransferableMap, "客户不存在", "NOT_FOUND", buildPreCheckCustomer(null, id, null, null));
+                continue;
+            }
+            TransferPreCheckCustomerVO customerVO = buildPreCheckCustomer(contact, id,
+                    contact.getUserId(), resolveHandoverUserName(contact.getUserId(), contact.getCorpId()));
+            if (contact.getUserId() != null && contact.getUserId().equals(takeoverUser.getQwUserId())) {
+                addPreCheckFail(untransferableMap, "原跟进人与接手人一样,不可继承", "SAME_USER", customerVO);
+                continue;
+            }
+            QwUser handoverUser = qwUserMapper.selectQwUserByCorpIdAndUserId(contact.getCorpId(), contact.getUserId());
+            if (handoverUser == null || StringUtils.isEmpty(handoverUser.getUid()) || handoverUser.getServerId() == null) {
+                addPreCheckFail(untransferableMap, "所属员工iPad未登录,无法校验好友关系", "IPAD_OFFLINE", customerVO);
+                continue;
+            }
+            String cacheKey = handoverUser.getUid() + ":" + handoverUser.getServerId();
+            IpadContactIndex index = ipadContactCache.computeIfAbsent(cacheKey,
+                    k -> loadIpadContactIndex(handoverUser.getUid(), handoverUser.getServerId()));
+            if (index == null) {
+                addPreCheckFail(untransferableMap, "拉取iPad联系人失败,无法校验好友关系", "IPAD_FETCH_FAIL", customerVO);
+                continue;
+            }
+            WxwIpadExternalContactItemDTO ipadContact = matchIpadContact(contact, index);
+            if (ipadContact == null) {
+                addPreCheckFail(untransferableMap, "没有好友关系", "NO_FRIEND", customerVO);
+                continue;
+            }
+            String statusReason = getIpadFriendStatusReason(ipadContact.getStatus());
+            if (statusReason != null) {
+                addPreCheckFail(untransferableMap, statusReason, "STATUS_" + ipadContact.getStatus(), customerVO);
+                continue;
+            }
+            transferable.add(customerVO);
+        }
+
+        TransferPreCheckVO result = new TransferPreCheckVO();
+        result.setTransferable(transferable);
+        result.setTransferableCount(transferable.size());
+        result.setUntransferable(rebuildPreCheckGroups(untransferableMap));
+        result.setUntransferableCount(result.getUntransferable().stream().mapToInt(g -> g.getCustomers().size()).sum());
+        return R.ok().put("data", result);
+    }
+
+    private List<TransferPreCheckGroupVO> rebuildPreCheckGroups(Map<String, PreCheckFailBucket> bucketMap) {
+        List<TransferPreCheckGroupVO> groups = new ArrayList<>();
+        bucketMap.forEach((key, bucket) -> {
+            TransferPreCheckGroupVO group = new TransferPreCheckGroupVO();
+            group.setReason(bucket.reason);
+            group.setReasonCode(bucket.reasonCode);
+            group.setCustomers(bucket.customers);
+            groups.add(group);
+        });
+        return groups;
+    }
+
+    private static class PreCheckFailBucket {
+        private final String reason;
+        private final String reasonCode;
+        private final List<TransferPreCheckCustomerVO> customers = new ArrayList<>();
+
+        PreCheckFailBucket(String reason, String reasonCode) {
+            this.reason = reason;
+            this.reasonCode = reasonCode;
+        }
+    }
+
+    private void addPreCheckFail(Map<String, PreCheckFailBucket> bucketMap, String reason, String reasonCode,
+                                 TransferPreCheckCustomerVO customerVO) {
+        String key = reasonCode + ":" + reason;
+        bucketMap.computeIfAbsent(key, k -> new PreCheckFailBucket(reason, reasonCode)).customers.add(customerVO);
+    }
+
+    private TransferPreCheckCustomerVO buildPreCheckCustomer(QwExternalContact contact, Long id,
+                                                             String handoverUserId, String handoverUserName) {
+        TransferPreCheckCustomerVO vo = new TransferPreCheckCustomerVO();
+        vo.setId(contact != null ? contact.getId() : id);
+        vo.setName(contact != null ? contact.getName() : "未知客户");
+        vo.setExternalUserId(contact != null ? contact.getExternalUserId() : null);
+        vo.setHandoverUserId(handoverUserId);
+        vo.setHandoverUserName(handoverUserName);
+        return vo;
+    }
+
+    private String resolveHandoverUserName(String handoverUserId, String corpId) {
+        if (StringUtils.isEmpty(handoverUserId)) {
+            return null;
+        }
+        return qwUserMapper.selectQwUserName(handoverUserId, corpId);
+    }
+
+    private static class IpadContactIndex {
+        private final Map<String, WxwIpadExternalContactItemDTO> byUnionid = new HashMap<>();
+        private final Map<String, WxwIpadExternalContactItemDTO> byNickname = new HashMap<>();
+    }
+
+    private IpadContactIndex loadIpadContactIndex(String uuid, Long serverId) {
+        IpadContactIndex index = new IpadContactIndex();
+        Long seq = 0L;
+        int limit = 100;
+        int maxLoop = 500;
+        for (int i = 0; i < maxLoop; i++) {
+            WxwGetExternalContactsDTO req = new WxwGetExternalContactsDTO();
+            req.setUuid(uuid);
+            req.setLimit(limit);
+            req.setSeq(seq);
+            WxWorkResponseDTO<WxwGetExternalContactsPageDTO> resp = wxWorkService.getIpadExternalContactPage(req, serverId);
+            if (resp == null || resp.getErrcode() == null || resp.getErrcode() != 0 || resp.getData() == null) {
+                return index.byUnionid.isEmpty() && index.byNickname.isEmpty() ? null : index;
+            }
+            List<WxwIpadExternalContactItemDTO> list = resp.getData().getList();
+            if (list == null || list.isEmpty()) {
+                break;
+            }
+            for (WxwIpadExternalContactItemDTO item : list) {
+                if (StringUtils.isNotEmpty(item.getUnionid())) {
+                    index.byUnionid.put(item.getUnionid(), item);
+                }
+                if (StringUtils.isNotEmpty(item.getNickname())) {
+                    index.byNickname.putIfAbsent(item.getNickname(), item);
+                }
+            }
+            Long nextSeq = resp.getData().getSeq();
+            if (nextSeq == null || nextSeq.equals(seq) || list.size() < limit) {
+                break;
+            }
+            seq = nextSeq;
+        }
+        return index;
+    }
+
+    private WxwIpadExternalContactItemDTO matchIpadContact(QwExternalContact contact, IpadContactIndex index) {
+        if (StringUtils.isNotEmpty(contact.getUnionid())) {
+            WxwIpadExternalContactItemDTO item = index.byUnionid.get(contact.getUnionid());
+            if (item != null) {
+                return item;
+            }
+        }
+        if (StringUtils.isNotEmpty(contact.getName())) {
+            return index.byNickname.get(contact.getName());
+        }
+        return null;
+    }
+
+    private String getIpadFriendStatusReason(Integer status) {
+        if (status == null) {
+            return "没有好友关系";
+        }
+        switch (status) {
+            case 0:
+                return "互相删除";
+            case 8:
+                return "主动删除拉黑";
+            case 2049:
+                return "被删除";
+            default:
+                return null;
+        }
+    }
+
     private void handleActiveTransferResult(QwTransferCustomerResult transferResult, QwExternalContact qwExternalContact,
                                             QwUser qwUser, Long id, TransferParam param, TransferBatchStats stats) {
         String customerName = getTransferCustomerName(qwExternalContact);

+ 12 - 0
fs-service/src/main/java/com/fs/qw/vo/TransferPreCheckCustomerVO.java

@@ -0,0 +1,12 @@
+package com.fs.qw.vo;
+
+import lombok.Data;
+
+@Data
+public class TransferPreCheckCustomerVO {
+    private Long id;
+    private String name;
+    private String externalUserId;
+    private String handoverUserId;
+    private String handoverUserName;
+}

+ 12 - 0
fs-service/src/main/java/com/fs/qw/vo/TransferPreCheckGroupVO.java

@@ -0,0 +1,12 @@
+package com.fs.qw.vo;
+
+import lombok.Data;
+
+import java.util.List;
+
+@Data
+public class TransferPreCheckGroupVO {
+    private String reason;
+    private String reasonCode;
+    private List<TransferPreCheckCustomerVO> customers;
+}

+ 13 - 0
fs-service/src/main/java/com/fs/qw/vo/TransferPreCheckVO.java

@@ -0,0 +1,13 @@
+package com.fs.qw.vo;
+
+import lombok.Data;
+
+import java.util.List;
+
+@Data
+public class TransferPreCheckVO {
+    private List<TransferPreCheckCustomerVO> transferable;
+    private List<TransferPreCheckGroupVO> untransferable;
+    private Integer transferableCount;
+    private Integer untransferableCount;
+}

+ 8 - 10
fs-service/src/main/java/com/fs/wxwork/dto/WxwGetExternalContactsDTO.java

@@ -1,5 +1,6 @@
 package com.fs.wxwork.dto;
 
+import com.alibaba.fastjson.annotation.JSONField;
 import lombok.Data;
 
 @Data
@@ -10,19 +11,16 @@ public class WxwGetExternalContactsDTO {
     private String uuid;
     /**
      * 每次返回数量
-     * <p>
-     * 限制每次查询返回的数据条数。
-     * </p>
      */
     private Integer limit;
     /**
-     * 查询下标
-     * <p>
-     * 用于指示查询的起始位置,类似于页码。
-     * 数据返回时,会填写返回的seq,客户端在下一次查询时应使用此值。
-     * 如果返回为空,则表示查询已完成,没有更多数据。
-     * </p>
+     * 查询下标,第一次填0,后续填接口返回的seq
      */
+    @JSONField(name = "seq")
+    private Long seq;
+    /**
+     * 兼容旧字段
+     */
+    @JSONField(serialize = false, deserialize = false)
     private String strSeq;
-
 }

+ 11 - 0
fs-service/src/main/java/com/fs/wxwork/dto/WxwGetExternalContactsPageDTO.java

@@ -0,0 +1,11 @@
+package com.fs.wxwork.dto;
+
+import lombok.Data;
+
+import java.util.List;
+
+@Data
+public class WxwGetExternalContactsPageDTO {
+    private List<WxwIpadExternalContactItemDTO> list;
+    private Long seq;
+}

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

@@ -0,0 +1,21 @@
+package com.fs.wxwork.dto;
+
+import com.alibaba.fastjson.annotation.JSONField;
+import lombok.Data;
+
+@Data
+public class WxwIpadExternalContactItemDTO {
+    private String unionid;
+    private String nickname;
+    private String avatar;
+    private Integer sex;
+    private String mobile;
+    private String remarks;
+    @JSONField(name = "user_id")
+    private Long userId;
+    private Long seq;
+    /**
+     * 0互相删除 8主动删除拉黑 2049被删除 其他为好友
+     */
+    private Integer status;
+}

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

@@ -58,6 +58,11 @@ public interface WxWorkService {
      */
     WxWorkResponseDTO<List<WxwGetExternalContactRespDTO>> getExternalContacts(WxwGetExternalContactsDTO param,Long serverId);
 
+    /**
+     * 获取iPad外部联系人列表(分页)
+     */
+    WxWorkResponseDTO<WxwGetExternalContactsPageDTO> getIpadExternalContactPage(WxwGetExternalContactsDTO param, Long serverId);
+
     /**
      * 输入验证码设置
      * @param param 参数

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

@@ -108,6 +108,13 @@ public class WxWorkServiceImpl implements WxWorkService {
         });
     }
 
+    @Override
+    public WxWorkResponseDTO<WxwGetExternalContactsPageDTO> getIpadExternalContactPage(WxwGetExternalContactsDTO param, Long serverId) {
+        String url = getUrl(serverId) + "/GetExternalContacts";
+        return WxWorkHttpUtil.postWithType(url, param, new TypeReference<WxWorkResponseDTO<WxwGetExternalContactsPageDTO>>() {
+        });
+    }
+
     @Override
     public WxWorkResponseDTO CheckCode(WxWorkCheckCodeDTO param, Long serverId) {
         String url = getUrl(serverId) + "/CheckCode";