package cn.fw.valhalla.service.bus.follow.strategy; import cn.fw.common.cache.locker.DistributedLocker; import cn.fw.common.exception.BusinessException; import cn.fw.common.web.auth.LoginAuthBean; import cn.fw.valhalla.common.utils.DateUtil; import cn.fw.valhalla.common.utils.StringUtils; import cn.fw.valhalla.domain.db.OriginalData; import cn.fw.valhalla.domain.db.customer.Customer; import cn.fw.valhalla.domain.db.follow.FollowNoticeRecord; import cn.fw.valhalla.domain.db.follow.FollowRecord; import cn.fw.valhalla.domain.db.follow.FollowRecordLog; import cn.fw.valhalla.domain.db.follow.FollowTask; import cn.fw.valhalla.domain.db.pool.CustomerCluePool; import cn.fw.valhalla.domain.db.pool.StammkundePool; import cn.fw.valhalla.domain.dto.FollowAttachmentDTO; import cn.fw.valhalla.domain.enums.*; import cn.fw.valhalla.domain.vo.follow.FollowDetailVO; import cn.fw.valhalla.domain.vo.follow.FollowRecordVO; import cn.fw.valhalla.domain.vo.setting.SettingVO; import cn.fw.valhalla.rpc.angel.InsurerRpcService; import cn.fw.valhalla.rpc.angel.dto.InsuranceDTO; import cn.fw.valhalla.rpc.erp.TodoRpcService; import cn.fw.valhalla.rpc.erp.UserService; import cn.fw.valhalla.rpc.erp.dto.BackLogItemDTO; import cn.fw.valhalla.rpc.erp.dto.UserInfoDTO; import cn.fw.valhalla.rpc.oop.OopService; import cn.fw.valhalla.rpc.oop.dto.ShopDTO; import cn.fw.valhalla.service.bus.cust.CustomerBizService; import cn.fw.valhalla.service.bus.cust.CustomerChangeBizService; import cn.fw.valhalla.service.bus.setting.SettingBizService; import cn.fw.valhalla.service.data.*; import cn.fw.valhalla.service.event.CancelApproveEvent; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import lombok.Getter; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.tuple.Pair; import org.redisson.api.RLock; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.ApplicationEventPublisher; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.CollectionUtils; import java.sql.Timestamp; import java.time.LocalDate; import java.time.LocalDateTime; import java.util.*; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; import static cn.fw.common.businessvalidator.Validator.BV; /** * @author : kurisu * @className : AbstractFollowStrategy * @description : 策略抽象类 FIXME 跟进功能职责可以在拆分得更细 * @date: 2020-08-17 10:42 */ @Slf4j public abstract class AbstractFollowStrategy implements FollowStrategy { @Autowired protected CustomerService customerService; @Autowired protected CustomerBaseInfoService customerBaseInfoService; @Autowired protected CustomerBizService customerBizService; @Autowired protected InsurerRpcService insurerRpcService; @Autowired protected FollowTaskService followTaskService; @Autowired protected FollowRecordService followRecordService; @Autowired protected UserService userService; @Autowired protected FollowRecordLogService followRecordLogService; @Autowired protected FollowNoticeRecordService followNoticeRecordService; @Autowired protected SettingBizService settingBizService; @Autowired protected OopService oopService; @Autowired protected ApplicationEventPublisher eventPublisher; @Autowired protected TodoRpcService todoRpcService; @Autowired protected CustomerLoanInfoService customerLoanInfoService; @Autowired protected CustomerCluePoolService customerCluePoolService; @Autowired protected CustomerChangeBizService customerChangeBizService; @Autowired protected DistributedLocker distributedLocker; @Value("${spring.cache.custom.global-prefix}:follow") @Getter private String keyPrefix; @Value("${follow.todo.FMCode}") @Getter private String FMCode; @Value("${follow.todo.RMCode}") @Getter private String RMCode; @Value("${follow.todo.IRCode}") @Getter private String IRCode; @Value("${follow.todo.ACCode}") @Getter private String ACCode; /** * 单位转换 * * @param unit * @return */ public static int getCalendarType(SettingUnitEnum unit) { switch (unit) { case HOUR: return Calendar.HOUR; case MONTH: return Calendar.MONTH; case MINUTE: return Calendar.MINUTE; default: return Calendar.DATE; } } @Override public List getRecordList(Long taskId) { List followRecordList = followRecordService.getRecordList(taskId); if (CollectionUtils.isEmpty(followRecordList)) { return new ArrayList<>(); } List recordIds = followRecordList.stream().map(FollowRecord::getId).collect(Collectors.toList()); List attachments = followRecordLogService.getListByRecordIds(recordIds); if (CollectionUtils.isEmpty(attachments)) { return new ArrayList<>(); } List list = new ArrayList<>(); for (FollowRecordLog attachment : attachments) { FollowRecordVO vo = new FollowRecordVO(); vo.setAttachments(attachment.getAttachments()); vo.setId(attachment.getId()); vo.setRecordId(attachment.getRecordId()); vo.setTaskId(taskId); vo.setAttType(attachment.getAttType()); vo.setFeedbackType(attachment.getFeedbackType()); vo.setFollowType(followRecordList.get(0).getType()); vo.setUploadTime(attachment.getUploadTime()); vo.setDescribes(attachment.getDescribes()); int i = queryIndexFromRecords(followRecordList, attachment.getRecordId()); vo.setTimes(i + 1); list.add(vo); } return list; } @Override @Transactional(rollbackFor = Exception.class) public void completeRecord(Long id, LoginAuthBean currentUser) { final String lockKey = getLockKey(id); Pair pair = distributedLocker.tryLock(lockKey, TimeUnit.SECONDS, 0, 15); BV.isTrue(Boolean.TRUE.equals(pair.getKey()), () -> "请勿重复提交"); try { BV.isTrue(Boolean.TRUE.equals(pair.getKey()), () -> "请勿重复提交"); FollowRecord record = followRecordService.getById(id); if (Objects.isNull(record)) { return; } Long customerId = record.getCustomerId(); Customer customer = customerService.getById(customerId); FollowTask task = followTaskService.getById(record.getTaskId()); BV.notNull(customer, () -> "档案信息异常"); if (Boolean.FALSE.equals(customer.getYn())) { customer = customerService.queryByFrameNo(customer.getFrameNo(), customer.getGroupId()); BV.notNull(customer, () -> "用户档案不存在"); } BV.isTrue(record.getUserId().equals(currentUser.getUserId()), () -> "无法完成非本人待办任务"); BV.isTrue(Boolean.FALSE.equals(record.getOutTime()), () -> "无法完成已逾期的待办任务"); final Date followTime = DateUtil.localDateTime2Date(LocalDateTime.now()); record.setFollowTime(followTime); List list = followRecordLogService.list(Wrappers.lambdaQuery() .eq(FollowRecordLog::getRecordId, record.getId()) ); BV.isNotEmpty(list, () -> "请上传跟进记录"); boolean match = list.stream().allMatch(log -> Objects.nonNull(log.getFeedbackType())); BV.isTrue(match, () -> "请检查跟进记录是否已完成客户反馈的录入"); followRecordService.updateById(record); if (Objects.nonNull(task)) { task.setCustomerId(customer.getId()); int times = task.getTimes() == null ? 0 : task.getTimes(); task.setTimes(times + 1); } followTaskService.updateById(task); record.setCustomerId(customer.getId()); this.addTaskRecord(record, false); BackLogItemDTO dto = new BackLogItemDTO(record.getUserId(), getItemCode(record.getType()), String.valueOf(record.getId()), followTime, record.getShopId()); todoRpcService.complete(dto); } catch (Exception e) { distributedLocker.unlock(lockKey); throw e; } } @Override @Transactional(rollbackFor = Exception.class) public void completeRecordAndEnd(FollowRecord record) { boolean equals = Boolean.FALSE.equals(record.getOutTime()) && Objects.isNull(record.getFollowTime()); if (!equals) { return; } final Date followTime = DateUtil.localDateTime2Date(LocalDateTime.now()); record.setFollowTime(followTime); followRecordService.updateById(record); this.addTaskRecord(record, false); BackLogItemDTO dto = new BackLogItemDTO(record.getUserId(), getItemCode(record.getType()), String.valueOf(record.getId()), followTime, record.getShopId()); todoRpcService.complete(dto); } /** * 生成跟进记录 * * @param task * @param addDay 二次分配的情况+1天 */ @Override @Transactional(rollbackFor = Exception.class) public void startTask(FollowTask task, boolean addDay) { if (!task.getDeadline().after(task.getBeginTime()) || !task.getDeadline().after(DateUtil.localDateTime2Date(LocalDateTime.now()))) { return; } final FollowRecord record = new FollowRecord(); record.setTaskId(task.getId()); record.setCustomerId(task.getCustomerId()); record.setType(task.getType()); record.setUserId(task.getFollowUser()); record.setUserName(task.getFollowUserName()); Date planTime = DateUtil.startDate(task.getBeginTime()); if (addDay) { planTime = DateUtil.startDate(DateUtil.getExpired(DateUtil.startDate(task.getBeginTime()), 1, Calendar.DATE)); } record.setPlanTime(planTime); settingBizService.querySettingByType(task.getType(), SettingTypeEnum.EFFECTIVE_TIME, task.getGroupId()) .ifPresent(setting -> { Date timestamp = calDate(setting, record.getPlanTime(), false); record.setDeadline(timestamp); }); record.setOutTime(Boolean.FALSE); record.setGroupId(task.getGroupId()); record.setShopId(task.getFollowShop()); record.setCreateTime(DateUtil.localDateTime2Date(LocalDateTime.now())); followRecordService.queryAndSave(record); } @Override @Transactional(rollbackFor = Exception.class) public void overdueProcessing(FollowRecord record) { boolean bool = this.addTaskRecord(record, true); if (bool) { followRecordService.overdue(record.getId()); } } /** * 查询跟进记录 * * @param startIndex * @param pageSize * @param userId * @return */ protected List list(Integer startIndex, Integer pageSize, Long userId, FollowTypeEnum followType) { String sql = String.format("limit %s,%s", startIndex, pageSize); return followRecordService.list(Wrappers.lambdaQuery() .eq(FollowRecord::getUserId, userId) .eq(FollowRecord::getType, followType) .eq(FollowRecord::getOutTime, Boolean.FALSE) .le(FollowRecord::getPlanTime, new Date()) .ge(FollowRecord::getDeadline, new Date()) .isNull(FollowRecord::getFollowTime) .last(sql) ); } abstract public FollowDetailVO assemble(Long customerId); /** * 查询档案正在生效的保险信息 * * @param customerId * @return */ protected Optional queryInsuInfo(Long customerId) { if (Objects.isNull(customerId)) { return Optional.empty(); } InsuranceDTO insuranceDTO = insurerRpcService.queryInsByCustId(customerId); return Optional.ofNullable(insuranceDTO); } /** * 查询下表 * * @param list * @param recordId * @return */ private int queryIndexFromRecords(List list, Long recordId) { for (int i = 0; i < list.size(); i++) { Long id = list.get(i).getId(); if (Objects.equals(id, recordId)) { return i; } } return 0; } /** * 取消生成的跟进待办 * * @param customerId * @param type */ @Transactional(rollbackFor = Exception.class) protected void cancelFollowTodo(Long customerId, FollowTypeEnum type) { List list = Optional.ofNullable(followRecordService.list(Wrappers.lambdaQuery() .eq(FollowRecord::getCustomerId, customerId) .eq(FollowRecord::getType, type) .isNull(FollowRecord::getFollowTime) .ge(FollowRecord::getDeadline, new Date()) )).orElse(new ArrayList<>()); if (CollectionUtils.isEmpty(list)) { return; } List idList = new ArrayList<>(); for (FollowRecord record : list) { if (Boolean.TRUE.equals(record.getAddTodo())) { BackLogItemDTO dto = new BackLogItemDTO(record.getUserId(), getItemCode(record.getType()), String.valueOf(record.getId()), record.getPlanTime(), record.getShopId()); todoRpcService.cancel(dto); } idList.add(record.getId()); } followRecordService.removeByIds(idList); } @Transactional(rollbackFor = Exception.class) protected void completeClue(CustomerCluePool cluePool, OriginalData originalData) { if (Objects.isNull(cluePool)) { return; } if (ClueStatusEnum.COMPLETE.equals(cluePool.getClueStatus()) || ClueStatusEnum.FAILURE.equals(cluePool.getClueStatus())) { return; } if (ClueStatusEnum.WAITING.equals(cluePool.getClueStatus())) { customerCluePoolService.removeById(cluePool.getId()); return; } LocalDateTime deadline = DateUtil.date2LocalDateTime(cluePool.getDeadline()); if (deadline.isAfter(DateUtil.date2LocalDateTime(originalData.getGenerateTime()))) { cluePool.setFinishShopId(originalData.getShopId()); cluePool.setFinishUserId(originalData.getUserId()); UserInfoDTO user = userService.user(originalData.getUserId()); if (Objects.nonNull(user)) { cluePool.setFinishUserName(user.getUserName()); } ShopDTO shop = oopService.shop(originalData.getShopId()); if (Objects.nonNull(shop)) { cluePool.setFinishShopName(shop.getShortName()); } cluePool.setCloseTime(originalData.getGenerateTime()); cluePool.setClueStatus(ClueStatusEnum.COMPLETE); customerCluePoolService.updateById(cluePool); completeTask(cluePool); } } /** * 完成跟进 * * @param pool */ @Transactional(rollbackFor = Exception.class) protected void completeTask(CustomerCluePool pool) { FollowTask followTask = followTaskService.queryOngoingTaskByClueId(pool.getId()); if (Objects.isNull(followTask)) { return; } followTask.setFinishUser(pool.getFinishUserId()); followTask.setFinishUserName(pool.getFinishUserName()); followTask.setFinishShop(pool.getFinishShopId()); followTask.setCloseTime(pool.getCloseTime()); boolean equals = followTask.getFollowUser().equals(pool.getFinishUserId()); followTask.setState(equals ? TaskStateEnum.COMPLETE : TaskStateEnum.DEFEAT); if (!equals) { followTask.setReason(TaskDefeatTypeEnum.F); } boolean succeed = followTaskService.updateById(followTask); if (succeed) { completeTodo(followTask.getId()); followNoticeRecordService.removeByClueId(followTask.getId()); CancelApproveEvent event = new CancelApproveEvent(followTask.getId(), ApproveTypeEnum.FOLLOW_DEFEAT); eventPublisher.publishEvent(event); } } /** * 完成生成的跟进待办 * * @param taskId */ @Transactional(rollbackFor = Exception.class) protected void completeTodo(Long taskId) { followRecordService.remove(Wrappers.lambdaQuery() .eq(FollowRecord::getTaskId, taskId) .eq(FollowRecord::getOutTime, Boolean.FALSE) .eq(FollowRecord::getAddTodo, Boolean.FALSE) .isNull(FollowRecord::getFollowTime) .ge(FollowRecord::getDeadline, new Date()) ); List list = Optional.ofNullable(followRecordService.list(Wrappers.lambdaQuery() .eq(FollowRecord::getTaskId, taskId) .eq(FollowRecord::getOutTime, Boolean.FALSE) .eq(FollowRecord::getAddTodo, Boolean.TRUE) .isNull(FollowRecord::getFollowTime) .ge(FollowRecord::getDeadline, new Date()) )).orElse(new ArrayList<>()); if (CollectionUtils.isEmpty(list)) { return; } for (FollowRecord record : list) { BackLogItemDTO dto = new BackLogItemDTO(record.getUserId(), getItemCode(record.getType()), String.valueOf(record.getId()), record.getPlanTime(), record.getShopId()); todoRpcService.complete(dto); record.setFollowTime(new Date()); record.setOutTime(Boolean.FALSE); } followRecordService.updateBatchById(list); } /** * 更新任务 * * @param list * @param setting */ @Transactional(rollbackFor = Exception.class) protected void updateClue(List list, List setting) { for (SettingVO vo : setting) { final Integer unit = vo.getUnit(); final int value = Optional.ofNullable(vo.getDetailValue()).orElse(0); SettingUnitEnum unitEnum = SettingUnitEnum.ofValue(unit); if (SettingTypeEnum.FIRST_TRIGGER_TIME.getValue().equals(vo.getType())) { if (Objects.nonNull(unitEnum) && value > 0) { final int calendarType = getCalendarType(unitEnum); list.forEach(clue -> { Date originTime = clue.getAddTime(); Timestamp timestamp = DateUtil.getExpired(originTime, value, calendarType); clue.setStartTime(timestamp); }); } } if (SettingTypeEnum.FAIL_TIME.getValue().equals(vo.getType())) { if (Objects.nonNull(unitEnum) && value > 0) { final int calendarType = getCalendarType(unitEnum); list.forEach(clue -> { Date originTime = clue.getAddTime(); Timestamp timestamp = DateUtil.getExpired(originTime, value, calendarType); clue.setDeadline(timestamp); }); } } } customerCluePoolService.updateBatchById(list); } /** * 更新服务号消息记录 * * @param list * @param vo */ @Transactional(rollbackFor = Exception.class) protected void updateNoticeRecord(List list, SettingVO vo) { List idList = list.stream().map(CustomerCluePool::getId).collect(Collectors.toList()); List noticeList = followNoticeRecordService.list(Wrappers.lambdaQuery() .eq(FollowNoticeRecord::getStatus, SendStatusEnum.NOT_SENT) .in(FollowNoticeRecord::getClueId, idList) ); Integer unit = vo.getUnit(); int value = Optional.ofNullable(vo.getDetailValue()).orElse(0); SettingUnitEnum unitEnum = SettingUnitEnum.ofValue(unit); if (Objects.isNull(unitEnum) || value <= 0) { return; } final int calendarType = getCalendarType(unitEnum); for (CustomerCluePool clue : list) { for (FollowNoticeRecord noticeRecord : noticeList) { if (clue.getId().equals(noticeRecord.getClueId())) { Date originTime = clue.getAddTime(); Timestamp timestamp = DateUtil.getExpired(originTime, value, calendarType); noticeRecord.setSendTime(timestamp); } } } followNoticeRecordService.updateBatchById(noticeList); } @Override @Transactional(rollbackFor = Exception.class) public void uploadAtt(FollowAttachmentDTO dto, Long userId) { FollowRecord record = followRecordService.getById(dto.getRecordId()); if (Objects.isNull(record)) { return; } BV.isTrue(record.getUserId().equals(userId), () -> "无法跟进非本人待办任务"); BV.isTrue(Boolean.FALSE.equals(record.getOutTime()), () -> "无法跟进已逾期的待办任务"); FollowRecordLog recordLog = new FollowRecordLog(); recordLog.setId(dto.getId()); recordLog.setAttachments(dto.getAttachments()); recordLog.setFeedbackType(dto.getFeedbackTypeEnum()); recordLog.setAttType(dto.getAttTypeEnum()); recordLog.setDescribes(dto.getDescribes()); recordLog.setRecordId(dto.getRecordId()); if (Objects.isNull(dto.getId())) { recordLog.setUploadTime(new Date()); } followRecordLogService.saveOrUpdate(recordLog); feedbackTask(record.getTaskId(), dto.getFeedbackTypeEnum()); } /** * 生成任务实体 * * @param originalData * @return */ protected CustomerCluePool createClueInfo(final OriginalData originalData, FollowTypeEnum followType, Customer customer) throws Exception { final CustomerCluePool pool = new CustomerCluePool(); pool.setOriginalUserId(customer.getAdviserId()); pool.setOriginalShopId(customer.getShopId()); UserInfoDTO user = userService.user(customer.getAdviserId()); if (Objects.nonNull(user)) { pool.setOriginalUserName(user.getUserName()); } ShopDTO shop = oopService.shop(originalData.getShopId()); if (Objects.nonNull(shop)) { pool.setOriginalShopName(shop.getShortName()); } pool.setClueType(followType); pool.setAddTime(originalData.getGenerateTime()); pool.setClueStatus(ClueStatusEnum.WAITING); pool.setRedistribution(Boolean.FALSE); pool.setGroupId(originalData.getGroupId()); pool.setCreateTime(new Date()); settingBizService.querySettingByType(followType, SettingTypeEnum.FIRST_TRIGGER_TIME, originalData.getGroupId()) .ifPresent(r -> pool.setStartTime(calDate(r, originalData.getGenerateTime(), false))); settingBizService.querySettingByType(followType, SettingTypeEnum.FAIL_TIME, originalData.getGroupId()) .ifPresent(r -> pool.setDeadline(calDate(r, originalData.getGenerateTime(), false))); pool.setRefererId(customer.getId()); pool.setFrameNo(customer.getFrameNo()); pool.setPlateNo(customer.getPlateNo()); return pool; } @Override @Transactional(rollbackFor = Exception.class) public void startClue(CustomerCluePool cluePool) { if (!ClueStatusEnum.WAITING.equals(cluePool.getClueStatus())) { return; } FollowTask task = createTask(cluePool, false); task.setFollowUser(cluePool.getOriginalUserId()); task.setFollowUserName(cluePool.getOriginalUserName()); task.setFollowShop(cluePool.getOriginalShopId()); followTaskService.save(task); startTask(task, false); cluePool.setClueStatus(ClueStatusEnum.ONGOING); } @Override @Transactional(rollbackFor = Exception.class) public void closeTask(FollowTask task) throws BusinessException { final Long clueId = task.getClueId(); final CustomerCluePool cluePool = customerCluePoolService.getById(clueId); task.setState(TaskStateEnum.DEFEAT); task.setCloseTime(new Date()); if (Boolean.TRUE.equals(task.getRedistribution())) { task.setReason(TaskDefeatTypeEnum.C); followTaskService.updateById(task); customerBizService.abandon(task, false); } else if (Objects.nonNull(cluePool)) { if (LocalDateTime.now().isAfter((DateUtil.date2LocalDateTime(cluePool.getDeadline())))) { cluePool.setCloseTime(new Date()); cluePool.setClueStatus(ClueStatusEnum.FAILURE); customerCluePoolService.updateById(cluePool); followTaskService.updateById(task); } else { task.setReason(TaskDefeatTypeEnum.B); if (Boolean.FALSE.equals(cluePool.getRedistribution())) { StammkundePool stammkundePool = customerChangeBizService.changeFollowUser(task); if (Objects.isNull(stammkundePool)) { task.setReason(null); task.setState(TaskStateEnum.ONGOING); task.setCloseTime(null); task.setDeadline(cluePool.getDeadline()); followTaskService.updateById(task); return; } FollowTask followTask = createTask(cluePool, true); followTask.setFollowUser(stammkundePool.getAdviserId()); followTask.setFollowUserName(stammkundePool.getAdviserName()); followTask.setFollowShop(stammkundePool.getShopId()); cluePool.setRedistribution(Boolean.TRUE); followTaskService.updateById(task); customerCluePoolService.updateById(cluePool); followTaskService.save(followTask); startTask(followTask, true); } } } CancelApproveEvent event = new CancelApproveEvent(task.getId(), ApproveTypeEnum.FOLLOW_DEFEAT); eventPublisher.publishEvent(event); } @Override @Transactional(rollbackFor = Exception.class) public void forceStopClue(CustomerCluePool clue) { onStopClue(clue, TaskDefeatTypeEnum.A); } @Override @Transactional(rollbackFor = Exception.class) public void onForbiddenStopClue(CustomerCluePool clue) { onStopClue(clue, TaskDefeatTypeEnum.E); } @Transactional(rollbackFor = Exception.class) protected void renewalTask(Customer customer, CustomerCluePool cluePool) { if (Objects.isNull(cluePool) || Boolean.TRUE.equals(cluePool.getRedistribution())) { return; } UserInfoDTO user = userService.user(customer.getAdviserId()); if (ClueStatusEnum.WAITING.equals(cluePool.getClueStatus())) { cluePool.setOriginalUserId(customer.getAdviserId()); cluePool.setOriginalShopId(customer.getShopId()); if (Objects.nonNull(user)) { cluePool.setOriginalUserName(user.getUserName()); } ShopDTO shop = oopService.shop(customer.getShopId()); if (Objects.nonNull(user)) { cluePool.setOriginalShopName(shop.getShortName()); } customerCluePoolService.updateById(cluePool); return; } FollowTask task = followTaskService.queryOngoingTaskByClueId(cluePool.getId()); if (Objects.isNull(task)) { return; } task.setState(TaskStateEnum.DEFEAT); task.setCloseTime(new Date()); task.setReason(TaskDefeatTypeEnum.B); followTaskService.updateById(task); FollowTask followTask = createTask(cluePool, true); followTask.setFollowUser(customer.getAdviserId()); followTask.setFollowUserName(Optional.ofNullable(user).map(UserInfoDTO::getUserName).orElse(null)); followTask.setFollowShop(customer.getShopId()); followTaskService.save(followTask); startTask(followTask, true); CancelApproveEvent event = new CancelApproveEvent(task.getId(), ApproveTypeEnum.FOLLOW_DEFEAT); eventPublisher.publishEvent(event); } protected FollowTask createTask(final CustomerCluePool cluePool, final boolean redistribution) { final FollowTask task = new FollowTask(); task.setClueId(cluePool.getId()); task.setCustomerId(cluePool.getRefererId()); task.setType(cluePool.getClueType()); task.setState(TaskStateEnum.ONGOING); task.setRedistribution(redistribution); task.setFollowUser(null); task.setFollowUserName(null); task.setFollowShop(null); task.setGroupId(cluePool.getGroupId()); if (redistribution) { task.setBeginTime(new Date()); task.setDeadline(cluePool.getDeadline()); } else { task.setBeginTime(cluePool.getStartTime()); settingBizService.querySettingByType(cluePool.getClueType(), SettingTypeEnum.CHANGE_TIME, cluePool.getGroupId()) .ifPresent(r -> task.setDeadline(calDate(r, cluePool.getAddTime(), FollowTypeEnum.IR.equals(cluePool.getClueType())))); } return task; } /** * 生成消息推送 * * @param cluePool * @param time */ @Transactional(rollbackFor = Exception.class) protected void createFirstNotice(final CustomerCluePool cluePool, final Date time) { if (!cluePool.getDeadline().after(cluePool.getStartTime()) || !cluePool.getDeadline().after(DateUtil.localDateTime2Date(LocalDateTime.now()))) { return; } final FollowNoticeRecord record = new FollowNoticeRecord(); record.setClueId(cluePool.getId()); record.setFollowType(cluePool.getClueType()); record.setCustomerId(cluePool.getRefererId()); record.setStatus(SendStatusEnum.NOT_SENT); settingBizService.querySettingByType(cluePool.getClueType(), SettingTypeEnum.FIRST_NOTICE_TIME, cluePool.getGroupId()) .ifPresent(r -> record.setSendTime(calDate(r, time, false))); record.setCreateTime(new Date()); followNoticeRecordService.save(record); } /** * 计算时间 * * @param vo * @param origin * @return */ protected Date calDate(SettingVO vo, Date origin, boolean reverse) { if (vo.getDetailValue() == null || vo.getDetailValue() < 0) { log.info("关键设置数据异常,请检查数据 : settingType[{}] settingId[{}]", vo.getType(), vo.getId()); return null; } Integer value = vo.getDetailValue(); SettingUnitEnum unit = Objects.requireNonNull(SettingUnitEnum.ofValue(vo.getUnit())); if (vo.getDetailValue() == 0) { if (SettingUnitEnum.DAY.equals(unit)) { return DateUtil.startDate(origin); } return origin; } if (reverse) { value = -1 * value; } Date expired = DateUtil.getExpired(origin, value, getCalendarType(unit)); if (SettingUnitEnum.DAY.equals(unit)) { expired = DateUtil.startDate(expired); } return expired; } protected String getItemCode(FollowTypeEnum type) { switch (type) { case AC: return getACCode(); case FM: return getFMCode(); case IR: return getIRCode(); case RM: return getRMCode(); default: return getFMCode(); } } protected void feedbackTask(Long taskId, FeedbackTypeEnum feedbackTypeEnum) { FollowTask task = followTaskService.getById(taskId); if (Objects.isNull(task) || Objects.isNull(feedbackTypeEnum)) { return; } String feedback = task.getFeedback(); Set feedbackSet = StringUtils.isEmpty(feedback) ? new HashSet<>() : new HashSet<>(Arrays.asList(feedback.split(","))); feedbackSet.add(String.valueOf(feedbackTypeEnum.getValue())); task.setFeedback(String.join(",", feedbackSet)); task.setLastFeedback(feedbackTypeEnum); followTaskService.updateById(task); } private void onStopClue(CustomerCluePool clue, TaskDefeatTypeEnum defeatTypeEnum) { clue.setClueStatus(ClueStatusEnum.FAILURE); clue.setCloseTime(new Date()); FollowTask task = followTaskService.queryOngoingTaskByClueId(clue.getId()); if (Objects.isNull(task)) { return; } task.setState(TaskStateEnum.DEFEAT); task.setCloseTime(new Date()); task.setReason(defeatTypeEnum); followTaskService.updateById(task); List recordList = followRecordService.list(Wrappers.lambdaQuery() .eq(FollowRecord::getTaskId, task.getId()) .eq(FollowRecord::getCustomerId, clue.getRefererId()) .eq(FollowRecord::getOutTime, Boolean.FALSE) .isNull(FollowRecord::getFollowTime) ); if (!CollectionUtils.isEmpty(recordList)) { for (FollowRecord record : recordList) { if (Boolean.TRUE.equals(record.getAddTodo())) { completeRecordAndEnd(record); } else { followRecordService.removeById(record.getId()); } } } } /** * 生成新的跟进 * * @param record * @return */ private boolean addTaskRecord(FollowRecord record, boolean overdue) { final FollowTask task = followTaskService.getById(record.getTaskId()); if (Objects.isNull(task)) { return true; } if (!TaskStateEnum.ONGOING.equals(task.getState())) { return true; } //任务截止日期 final Date deadline = task.getDeadline(); Optional fcsetting = settingBizService.querySettingByType(record.getType(), SettingTypeEnum.FOLLOW_CYCLE, record.getGroupId()); if (!fcsetting.isPresent()) { log.info("关键设置数据异常,请检查数据 : settingType[{}] groupId[{}]", SettingTypeEnum.FOLLOW_CYCLE.getName(), record.getGroupId()); return false; } Optional efsetting = settingBizService.querySettingByType(record.getType(), SettingTypeEnum.EFFECTIVE_TIME, record.getGroupId()); if (!efsetting.isPresent()) { log.info("关键设置数据异常,请检查数据 : settingType[{}] groupId[{}]", SettingTypeEnum.EFFECTIVE_TIME.getName(), record.getGroupId()); return false; } Date originTime = DateUtil.startDate(record.getDeadline()); if (!overdue) { originTime = DateUtil.startDate(record.getFollowTime()); } Date newTime = calDate(fcsetting.get(), originTime, false); if (Objects.isNull(newTime)) { return false; } final LocalDate minStartTime = DateUtil.date2LocalDate(originTime).plusDays(3L); final LocalDate maxDeadline = DateUtil.date2LocalDate(deadline); if (!deadline.after(newTime)) { LocalDate nextTime = maxDeadline.plusDays(-1L); if (minStartTime.isBefore(nextTime)) { FollowRecord entity = this.getEntity(task, DateUtil.localDate2Date(nextTime), DateUtil.localDate2Date(maxDeadline)); return followRecordService.queryAndSave(entity); } return true; } Date newDeadLine = calDate(efsetting.get(), newTime, false); if (Objects.isNull(newDeadLine)) { return false; } if (maxDeadline.isBefore(DateUtil.date2LocalDate(newDeadLine))) { newDeadLine = DateUtil.localDate2Date(maxDeadline); LocalDate nextTime = maxDeadline.plusDays(-1L); if (!minStartTime.isBefore(nextTime)) { return false; } newTime = DateUtil.localDate2Date(nextTime); } final FollowRecord newRecord = this.getEntity(task, newTime, newDeadLine); return followRecordService.queryAndSave(newRecord); } private FollowRecord getEntity(FollowTask task, Date newTime, Date deadline) { FollowRecord record = new FollowRecord(); record.setUserId(task.getFollowUser()); record.setUserName(task.getFollowUserName()); record.setCustomerId(task.getCustomerId()); record.setShopId(task.getFollowShop()); record.setTaskId(task.getId()); record.setType(task.getType()); record.setPlanTime(newTime); record.setDeadline(deadline); record.setOutTime(Boolean.FALSE); record.setAddTodo(Boolean.FALSE); record.setGroupId(task.getGroupId()); record.setCreateTime(DateUtil.localDateTime2Date(LocalDateTime.now())); return record; } private String getLockKey(Long recordId) { return String.format("%s:lock:%s", getKeyPrefix(), recordId); } }