|
@@ -0,0 +1,353 @@
|
|
|
|
|
+package com.etotem.cfc.service;
|
|
|
|
|
+
|
|
|
|
|
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
|
|
|
|
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
|
|
|
|
+import com.etotem.cfc.common.Result;
|
|
|
|
|
+import com.etotem.cfc.entity.MemberSubscriptionOrder;
|
|
|
|
|
+import com.etotem.cfc.entity.OctopusEffectKeyPerson;
|
|
|
|
|
+import com.etotem.cfc.entity.OctopusEffectRecord;
|
|
|
|
|
+import com.etotem.cfc.entity.OctopusKeyPerson;
|
|
|
|
|
+import com.etotem.cfc.mapper.MemberSubscriptionOrderMapper;
|
|
|
|
|
+import com.etotem.cfc.mapper.OctopusEffectKeyPersonMapper;
|
|
|
|
|
+import com.etotem.cfc.mapper.OctopusEffectRecordMapper;
|
|
|
|
|
+import com.etotem.cfc.mapper.OctopusKeyPersonMapper;
|
|
|
|
|
+import org.springframework.stereotype.Service;
|
|
|
|
|
+
|
|
|
|
|
+import javax.annotation.Resource;
|
|
|
|
|
+import java.math.BigDecimal;
|
|
|
|
|
+import java.util.*;
|
|
|
|
|
+import java.util.stream.Collectors;
|
|
|
|
|
+
|
|
|
|
|
+@Service
|
|
|
|
|
+public class KeyPersonService {
|
|
|
|
|
+
|
|
|
|
|
+ @Resource
|
|
|
|
|
+ private OctopusEffectRecordMapper effectRecordMapper;
|
|
|
|
|
+ @Resource
|
|
|
|
|
+ private OctopusKeyPersonMapper keyPersonMapper;
|
|
|
|
|
+ @Resource
|
|
|
|
|
+ private OctopusEffectKeyPersonMapper ekpMapper;
|
|
|
|
|
+ @Resource
|
|
|
|
|
+ private MemberSubscriptionOrderMapper memberOrderMapper;
|
|
|
|
|
+
|
|
|
|
|
+ // ========== 1. 盘点项目 ==========
|
|
|
|
|
+
|
|
|
|
|
+ public Result<Map<String, Object>> listEffects(Long userId, Integer effectType, Integer page, Integer pageSize) {
|
|
|
|
|
+ Page<OctopusEffectRecord> p = new Page<>(page, pageSize);
|
|
|
|
|
+ LambdaQueryWrapper<OctopusEffectRecord> q = new LambdaQueryWrapper<OctopusEffectRecord>()
|
|
|
|
|
+ .eq(OctopusEffectRecord::getUserId, userId);
|
|
|
|
|
+ if (effectType != null && effectType > 0) {
|
|
|
|
|
+ q.apply("effect_type & {0}", effectType);
|
|
|
|
|
+ }
|
|
|
|
|
+ q.orderByDesc(OctopusEffectRecord::getCreatedAt);
|
|
|
|
|
+ effectRecordMapper.selectPage(p, q);
|
|
|
|
|
+
|
|
|
|
|
+ List<Map<String, Object>> list = p.getRecords().stream().map(this::toEffectVO).collect(Collectors.toList());
|
|
|
|
|
+ Map<String, Object> data = new LinkedHashMap<>();
|
|
|
|
|
+ data.put("list", list);
|
|
|
|
|
+ data.put("total", p.getTotal());
|
|
|
|
|
+ data.put("page", page);
|
|
|
|
|
+ data.put("pageSize", pageSize);
|
|
|
|
|
+ return Result.success(data);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private Map<String, Object> toEffectVO(OctopusEffectRecord r) {
|
|
|
|
|
+ Map<String, Object> m = new LinkedHashMap<>();
|
|
|
|
|
+ m.put("id", r.getId());
|
|
|
|
|
+ m.put("memberOrderId", r.getMemberOrderId());
|
|
|
|
|
+ m.put("effectType", r.getEffectType());
|
|
|
|
|
+ m.put("effectTypeList", parseEffectType(r.getEffectType()));
|
|
|
|
|
+ m.put("effectAmount", r.getEffectAmount());
|
|
|
|
|
+ m.put("effectDesc", r.getEffectDesc());
|
|
|
|
|
+ m.put("status", r.getStatus());
|
|
|
|
|
+ m.put("createdAt", r.getCreatedAt() != null ? r.getCreatedAt().toString() : "");
|
|
|
|
|
+ return m;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private List<String> parseEffectType(Integer type) {
|
|
|
|
|
+ List<String> list = new ArrayList<>();
|
|
|
|
|
+ if (type == null) return list;
|
|
|
|
|
+ if ((type & 1) != 0) list.add("金额大");
|
|
|
|
|
+ if ((type & 2) != 0) list.add("频次高");
|
|
|
|
|
+ if ((type & 4) != 0) list.add("新成交");
|
|
|
|
|
+ return list;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public Result<OctopusEffectRecord> addEffect(Long userId, Long memberOrderId, Integer effectType,
|
|
|
|
|
+ BigDecimal effectAmount, String effectDesc) {
|
|
|
|
|
+ if (memberOrderId != null) {
|
|
|
|
|
+ MemberSubscriptionOrder order = memberOrderMapper.selectById(memberOrderId);
|
|
|
|
|
+ if (order == null || !order.getUserId().equals(userId)) {
|
|
|
|
|
+ return Result.error("会员订单不存在");
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ OctopusEffectRecord r = new OctopusEffectRecord();
|
|
|
|
|
+ r.setUserId(userId);
|
|
|
|
|
+ r.setMemberOrderId(memberOrderId);
|
|
|
|
|
+ r.setEffectType(effectType != null ? effectType : 0);
|
|
|
|
|
+ r.setEffectAmount(effectAmount != null ? effectAmount : BigDecimal.ZERO);
|
|
|
|
|
+ r.setEffectDesc(effectDesc != null ? effectDesc.trim() : null);
|
|
|
|
|
+ r.setStatus(1);
|
|
|
|
|
+ r.setCreatedAt(new Date());
|
|
|
|
|
+ effectRecordMapper.insert(r);
|
|
|
|
|
+ return Result.success(r);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public Result<String> deleteEffect(Long userId, Long effectId) {
|
|
|
|
|
+ OctopusEffectRecord r = effectRecordMapper.selectById(effectId);
|
|
|
|
|
+ if (r == null || !r.getUserId().equals(userId)) {
|
|
|
|
|
+ return Result.error("成效记录不存在");
|
|
|
|
|
+ }
|
|
|
|
|
+ // 级联删除关联关系
|
|
|
|
|
+ ekpMapper.delete(new LambdaQueryWrapper<OctopusEffectKeyPerson>().eq(OctopusEffectKeyPerson::getEffectId, effectId));
|
|
|
|
|
+ effectRecordMapper.deleteById(effectId);
|
|
|
|
|
+ return Result.success(null);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // ========== 2. 找关键人 ==========
|
|
|
|
|
+
|
|
|
|
|
+ public Result<OctopusKeyPerson> addKeyPerson(Long userId, String name, String organization,
|
|
|
|
|
+ String department, String title,
|
|
|
|
|
+ String firstMeetScene, String knowWay,
|
|
|
|
|
+ String contactInfo) {
|
|
|
|
|
+ if (name == null || name.trim().isEmpty()) {
|
|
|
|
|
+ return Result.error("关键人姓名不能为空");
|
|
|
|
|
+ }
|
|
|
|
|
+ OctopusKeyPerson kp = new OctopusKeyPerson();
|
|
|
|
|
+ kp.setUserId(userId);
|
|
|
|
|
+ kp.setName(name.trim());
|
|
|
|
|
+ kp.setOrganization(organization != null ? organization.trim() : null);
|
|
|
|
|
+ kp.setDepartment(department != null ? department.trim() : null);
|
|
|
|
|
+ kp.setTitle(title != null ? title.trim() : null);
|
|
|
|
|
+ kp.setFirstMeetScene(firstMeetScene != null ? firstMeetScene.trim() : null);
|
|
|
|
|
+ kp.setKnowWay(knowWay != null ? knowWay.trim() : null);
|
|
|
|
|
+ kp.setContactInfo(contactInfo != null ? contactInfo.trim() : null);
|
|
|
|
|
+ kp.setCreatedAt(new Date());
|
|
|
|
|
+ keyPersonMapper.insert(kp);
|
|
|
|
|
+ return Result.success(kp);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public Result<OctopusKeyPerson> updateKeyPerson(Long userId, Long id, Map<String, Object> params) {
|
|
|
|
|
+ OctopusKeyPerson kp = keyPersonMapper.selectById(id);
|
|
|
|
|
+ if (kp == null || !kp.getUserId().equals(userId)) {
|
|
|
|
|
+ return Result.error("关键人不存在");
|
|
|
|
|
+ }
|
|
|
|
|
+ if (params.containsKey("name") && params.get("name") != null) kp.setName(params.get("name").toString().trim());
|
|
|
|
|
+ if (params.containsKey("organization")) kp.setOrganization(params.get("organization") != null ? params.get("organization").toString().trim() : null);
|
|
|
|
|
+ if (params.containsKey("department")) kp.setDepartment(params.get("department") != null ? params.get("department").toString().trim() : null);
|
|
|
|
|
+ if (params.containsKey("title")) kp.setTitle(params.get("title") != null ? params.get("title").toString().trim() : null);
|
|
|
|
|
+ if (params.containsKey("firstMeetScene")) kp.setFirstMeetScene(params.get("firstMeetScene") != null ? params.get("firstMeetScene").toString().trim() : null);
|
|
|
|
|
+ if (params.containsKey("knowWay")) kp.setKnowWay(params.get("knowWay") != null ? params.get("knowWay").toString().trim() : null);
|
|
|
|
|
+ if (params.containsKey("contactInfo")) kp.setContactInfo(params.get("contactInfo") != null ? params.get("contactInfo").toString().trim() : null);
|
|
|
|
|
+ keyPersonMapper.updateById(kp);
|
|
|
|
|
+ return Result.success(kp);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public Result<String> deleteKeyPerson(Long userId, Long id) {
|
|
|
|
|
+ OctopusKeyPerson kp = keyPersonMapper.selectById(id);
|
|
|
|
|
+ if (kp == null || !kp.getUserId().equals(userId)) {
|
|
|
|
|
+ return Result.error("关键人不存在");
|
|
|
|
|
+ }
|
|
|
|
|
+ // 级联删除关联关系
|
|
|
|
|
+ ekpMapper.delete(new LambdaQueryWrapper<OctopusEffectKeyPerson>().eq(OctopusEffectKeyPerson::getKeyPersonId, id));
|
|
|
|
|
+ keyPersonMapper.deleteById(id);
|
|
|
|
|
+ return Result.success(null);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public Result<List<Map<String, Object>>> listKeyPersonsByEffect(Long userId, Long effectId) {
|
|
|
|
|
+ OctopusEffectRecord eff = effectRecordMapper.selectById(effectId);
|
|
|
|
|
+ if (eff == null || !eff.getUserId().equals(userId)) {
|
|
|
|
|
+ return Result.error("成效记录不存在");
|
|
|
|
|
+ }
|
|
|
|
|
+ List<OctopusEffectKeyPerson> rels = ekpMapper.selectList(
|
|
|
|
|
+ new LambdaQueryWrapper<OctopusEffectKeyPerson>().eq(OctopusEffectKeyPerson::getEffectId, effectId)
|
|
|
|
|
+ );
|
|
|
|
|
+ if (rels.isEmpty()) return Result.success(new ArrayList<>());
|
|
|
|
|
+
|
|
|
|
|
+ List<Long> kpIds = rels.stream().map(OctopusEffectKeyPerson::getKeyPersonId).collect(Collectors.toList());
|
|
|
|
|
+ Map<Long, OctopusKeyPerson> kpMap = keyPersonMapper.selectBatchIds(kpIds).stream()
|
|
|
|
|
+ .collect(Collectors.toMap(OctopusKeyPerson::getId, k -> k));
|
|
|
|
|
+ Map<Long, Integer> roleMap = rels.stream().collect(Collectors.toMap(OctopusEffectKeyPerson::getKeyPersonId, OctopusEffectKeyPerson::getRole));
|
|
|
|
|
+
|
|
|
|
|
+ List<Map<String, Object>> list = new ArrayList<>();
|
|
|
|
|
+ for (Long kpId : kpIds) {
|
|
|
|
|
+ OctopusKeyPerson kp = kpMap.get(kpId);
|
|
|
|
|
+ if (kp == null) continue;
|
|
|
|
|
+ Map<String, Object> m = new LinkedHashMap<>();
|
|
|
|
|
+ m.put("id", kp.getId());
|
|
|
|
|
+ m.put("name", kp.getName());
|
|
|
|
|
+ m.put("organization", kp.getOrganization());
|
|
|
|
|
+ m.put("department", kp.getDepartment());
|
|
|
|
|
+ m.put("title", kp.getTitle());
|
|
|
|
|
+ m.put("firstMeetScene", kp.getFirstMeetScene());
|
|
|
|
|
+ m.put("knowWay", kp.getKnowWay());
|
|
|
|
|
+ m.put("contactInfo", kp.getContactInfo());
|
|
|
|
|
+ m.put("role", roleMap.get(kpId));
|
|
|
|
|
+ m.put("roleName", roleName(roleMap.get(kpId)));
|
|
|
|
|
+ m.put("createdAt", kp.getCreatedAt() != null ? kp.getCreatedAt().toString() : "");
|
|
|
|
|
+ list.add(m);
|
|
|
|
|
+ }
|
|
|
|
|
+ return Result.success(list);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // ========== 3. 分析关键人 ==========
|
|
|
|
|
+
|
|
|
|
|
+ public Result<Map<String, Object>> analyzeKeywords(Long userId) {
|
|
|
|
|
+ List<OctopusKeyPerson> kps = keyPersonMapper.selectList(
|
|
|
|
|
+ new LambdaQueryWrapper<OctopusKeyPerson>().eq(OctopusKeyPerson::getUserId, userId)
|
|
|
|
|
+ );
|
|
|
|
|
+ Map<String, Integer> orgFreq = new HashMap<>();
|
|
|
|
|
+ Map<String, Integer> deptFreq = new HashMap<>();
|
|
|
|
|
+ Map<String, Integer> titleFreq = new HashMap<>();
|
|
|
|
|
+ Map<String, Integer> sceneFreq = new HashMap<>();
|
|
|
|
|
+ Map<String, Integer> wayFreq = new HashMap<>();
|
|
|
|
|
+
|
|
|
|
|
+ for (OctopusKeyPerson kp : kps) {
|
|
|
|
|
+ inc(orgFreq, kp.getOrganization());
|
|
|
|
|
+ inc(deptFreq, kp.getDepartment());
|
|
|
|
|
+ inc(titleFreq, kp.getTitle());
|
|
|
|
|
+ inc(sceneFreq, kp.getFirstMeetScene());
|
|
|
|
|
+ inc(wayFreq, kp.getKnowWay());
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ Map<String, Object> data = new LinkedHashMap<>();
|
|
|
|
|
+ data.put("organization", topN(orgFreq, 20));
|
|
|
|
|
+ data.put("department", topN(deptFreq, 20));
|
|
|
|
|
+ data.put("title", topN(titleFreq, 20));
|
|
|
|
|
+ data.put("firstMeetScene", topN(sceneFreq, 20));
|
|
|
|
|
+ data.put("knowWay", topN(wayFreq, 20));
|
|
|
|
|
+ return Result.success(data);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private void inc(Map<String, Integer> map, String val) {
|
|
|
|
|
+ if (val == null || val.trim().isEmpty()) return;
|
|
|
|
|
+ map.put(val, map.getOrDefault(val, 0) + 1);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private List<Map<String, Object>> topN(Map<String, Integer> map, int n) {
|
|
|
|
|
+ return map.entrySet().stream()
|
|
|
|
|
+ .sorted(Map.Entry.<String, Integer>comparingByValue().reversed())
|
|
|
|
|
+ .limit(n)
|
|
|
|
|
+ .map(e -> {
|
|
|
|
|
+ Map<String, Object> m = new LinkedHashMap<>();
|
|
|
|
|
+ m.put("keyword", e.getKey());
|
|
|
|
|
+ m.put("count", e.getValue());
|
|
|
|
|
+ return m;
|
|
|
|
|
+ }).collect(Collectors.toList());
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public Result<Map<String, Object>> analysisSummary(Long userId) {
|
|
|
|
|
+ List<OctopusKeyPerson> kps = keyPersonMapper.selectList(
|
|
|
|
|
+ new LambdaQueryWrapper<OctopusKeyPerson>().eq(OctopusKeyPerson::getUserId, userId)
|
|
|
|
|
+ );
|
|
|
|
|
+ List<Map<String, Object>> directions = new ArrayList<>();
|
|
|
|
|
+
|
|
|
|
|
+ addDirections(directions, kps, "organization", "单位", 8);
|
|
|
|
|
+ addDirections(directions, kps, "title", "职务", 8);
|
|
|
|
|
+ addDirections(directions, kps, "department", "部门", 8);
|
|
|
|
|
+ addDirections(directions, kps, "firstMeetScene", "初识场合", 8);
|
|
|
|
|
+ addDirections(directions, kps, "knowWay", "认识路径", 8);
|
|
|
|
|
+
|
|
|
|
|
+ directions.sort((a, b) -> Integer.compare((Integer) b.get("count"), (Integer) a.get("count")));
|
|
|
|
|
+ if (directions.size() > 8) directions = directions.subList(0, 8);
|
|
|
|
|
+
|
|
|
|
|
+ Map<String, Object> data = new LinkedHashMap<>();
|
|
|
|
|
+ data.put("directions", directions);
|
|
|
|
|
+ return Result.success(data);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private void addDirections(List<Map<String, Object>> directions, List<OctopusKeyPerson> kps,
|
|
|
|
|
+ String field, String dimName, int maxPerDim) {
|
|
|
|
|
+ Map<String, Integer> freq = new HashMap<>();
|
|
|
|
|
+ for (OctopusKeyPerson kp : kps) {
|
|
|
|
|
+ String val = getField(kp, field);
|
|
|
|
|
+ if (val != null && !val.trim().isEmpty()) {
|
|
|
|
|
+ freq.put(val, freq.getOrDefault(val, 0) + 1);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ freq.entrySet().stream()
|
|
|
|
|
+ .sorted(Map.Entry.<String, Integer>comparingByValue().reversed())
|
|
|
|
|
+ .limit(maxPerDim)
|
|
|
|
|
+ .forEach(e -> {
|
|
|
|
|
+ Map<String, Object> m = new LinkedHashMap<>();
|
|
|
|
|
+ m.put("dimension", field);
|
|
|
|
|
+ m.put("keyword", e.getKey());
|
|
|
|
|
+ m.put("count", e.getValue());
|
|
|
|
|
+ m.put("reason", genReason(field, e.getKey()));
|
|
|
|
|
+ directions.add(m);
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private String getField(OctopusKeyPerson kp, String field) {
|
|
|
|
|
+ switch (field) {
|
|
|
|
|
+ case "organization": return kp.getOrganization();
|
|
|
|
|
+ case "title": return kp.getTitle();
|
|
|
|
|
+ case "department": return kp.getDepartment();
|
|
|
|
|
+ case "firstMeetScene": return kp.getFirstMeetScene();
|
|
|
|
|
+ case "knowWay": return kp.getKnowWay();
|
|
|
|
|
+ default: return null;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private String genReason(String field, String keyword) {
|
|
|
|
|
+ switch (field) {
|
|
|
|
|
+ case "organization": return "该单位关键人高度聚集";
|
|
|
|
|
+ case "title": return "该职务人脉密集";
|
|
|
|
|
+ case "department": return "该部门关键人多";
|
|
|
|
|
+ case "firstMeetScene": return "该场合易识别关键人";
|
|
|
|
|
+ case "knowWay": return "该认识路径高频";
|
|
|
|
|
+ default: return "";
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // ========== 4. 拓展关键人 ==========
|
|
|
|
|
+
|
|
|
|
|
+ public Result<List<Map<String, Object>>> expansionSuggest(Long userId) {
|
|
|
|
|
+ Result<Map<String, Object>> summaryRes = analysisSummary(userId);
|
|
|
|
|
+ if (summaryRes.getCode() != 200) return Result.success(new ArrayList<>());
|
|
|
|
|
+
|
|
|
|
|
+ @SuppressWarnings("unchecked")
|
|
|
|
|
+ List<Map<String, Object>> dirs = (List<Map<String, Object>>) summaryRes.getData().get("directions");
|
|
|
|
|
+ List<Map<String, Object>> suggests = new ArrayList<>();
|
|
|
|
|
+ for (Map<String, Object> d : dirs) {
|
|
|
|
|
+ Map<String, Object> tpl = new LinkedHashMap<>();
|
|
|
|
|
+ tpl.put("dimension", d.get("dimension"));
|
|
|
|
|
+ tpl.put("keyword", d.get("keyword"));
|
|
|
|
|
+ tpl.put("prefill", buildPrefill((String) d.get("dimension"), (String) d.get("keyword")));
|
|
|
|
|
+ suggests.add(tpl);
|
|
|
|
|
+ }
|
|
|
|
|
+ return Result.success(suggests);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private Map<String, Object> buildPrefill(String dim, String keyword) {
|
|
|
|
|
+ Map<String, Object> p = new LinkedHashMap<>();
|
|
|
|
|
+ switch (dim) {
|
|
|
|
|
+ case "organization":
|
|
|
|
|
+ p.put("organization", keyword);
|
|
|
|
|
+ break;
|
|
|
|
|
+ case "title":
|
|
|
|
|
+ p.put("title", keyword);
|
|
|
|
|
+ break;
|
|
|
|
|
+ case "department":
|
|
|
|
|
+ p.put("department", keyword);
|
|
|
|
|
+ break;
|
|
|
|
|
+ case "firstMeetScene":
|
|
|
|
|
+ p.put("firstMeetScene", keyword);
|
|
|
|
|
+ break;
|
|
|
|
|
+ case "knowWay":
|
|
|
|
|
+ p.put("knowWay", keyword);
|
|
|
|
|
+ break;
|
|
|
|
|
+ }
|
|
|
|
|
+ return p;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private String roleName(Integer role) {
|
|
|
|
|
+ if (role == null) return "";
|
|
|
|
|
+ switch (role) {
|
|
|
|
|
+ case 1: return "引荐";
|
|
|
|
|
+ case 2: return "决策";
|
|
|
|
|
+ case 3: return "其他";
|
|
|
|
|
+ default: return "";
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|