PubFollowStrategy.java 16.4 KB
package cn.fw.valhalla.service.bus.follow.strategy.impl;

import cn.fw.shirasawa.sdk.enums.BusinessTypeEnum;
import cn.fw.shirasawa.sdk.enums.DataTypeEnum;
import cn.fw.shirasawa.sdk.enums.TerminationReason;
import cn.fw.valhalla.common.utils.DateUtil;
import cn.fw.valhalla.domain.db.OriginalData;
import cn.fw.valhalla.domain.db.follow.ClueTask;
import cn.fw.valhalla.domain.db.follow.FollowClue;
import cn.fw.valhalla.domain.db.pub.PubCluePool;
import cn.fw.valhalla.domain.dto.CustomerDetailDto;
import cn.fw.valhalla.domain.enums.*;
import cn.fw.valhalla.domain.vo.setting.SettingVO;
import cn.fw.valhalla.rpc.ehr.EhrRpcService;
import cn.fw.valhalla.rpc.ehr.dto.StaffInfoDTO;
import cn.fw.valhalla.rpc.oop.OopService;
import cn.fw.valhalla.rpc.oop.dto.ShopDTO;
import cn.fw.valhalla.rpc.shirasawa.ShirasawaRpcService;
import cn.fw.valhalla.rpc.shirasawa.dto.ClueStopDTO;
import cn.fw.valhalla.rpc.shirasawa.dto.FollowInitDTO;
import cn.fw.valhalla.service.bus.cust.CustomerBizService;
import cn.fw.valhalla.service.bus.follow.strategy.FollowStrategy;
import cn.fw.valhalla.service.bus.setting.SettingBizService;
import cn.fw.valhalla.service.data.ClueTaskService;
import cn.fw.valhalla.service.data.PubCluePoolService;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import java.time.Duration;
import java.time.LocalDateTime;
import java.util.*;

import static cn.fw.common.businessvalidator.Validator.BV;
import static cn.fw.valhalla.service.bus.setting.strategy.SettingStrategy.COMMON_BRAND_ID;

/**
 * 公共池线索跟进
 *
 * @author : kurisu
 * @version : 1.0
 * @className : PubFollowStrategy
 * @description : 公共池线索跟进
 * @date : 2023-03-21 16:52
 */
@Slf4j
@Component
public class PubFollowStrategy implements FollowStrategy {
    private final PubCluePoolService pubCluePoolService;
    private final ClueTaskService clueTaskService;
    private final CustomerBizService customerBizService;
    private final ShirasawaRpcService shirasawaRpcService;
    private final OopService oopService;
    private final SettingBizService settingBizService;
    private final EhrRpcService ehrRpcService;
    private final StringRedisTemplate redisTemplate;

    @Value("${spring.cache.custom.global-prefix}:follow:clue:change:STOP")
    @Getter
    private String clueChangeKey;

    @Autowired
    public PubFollowStrategy(final PubCluePoolService pubCluePoolService,
                             final ClueTaskService clueTaskService,
                             final CustomerBizService customerBizService,
                             final ShirasawaRpcService shirasawaRpcService,
                             final OopService oopService,
                             final SettingBizService settingBizService,
                             final EhrRpcService ehrRpcService,
                             final StringRedisTemplate redisTemplate) {
        this.pubCluePoolService = pubCluePoolService;
        this.clueTaskService = clueTaskService;
        this.customerBizService = customerBizService;
        this.shirasawaRpcService = shirasawaRpcService;
        this.oopService = oopService;
        this.settingBizService = settingBizService;
        this.ehrRpcService = ehrRpcService;
        this.redisTemplate = redisTemplate;
    }

    @Override
    public FollowTypeEnum getFollowType() {
        return FollowTypeEnum.PL;
    }

    /**
     * 公共池线索跟进此方法无效
     *
     * @param originalData
     * @return
     */
    @Override
    public boolean origin2task(final OriginalData originalData) {
        // 公共池线索不存在这种场景
        return false;
    }

    /**
     * 公共池线索跟进此方法无效
     *
     * @param followClue
     * @return
     */
    @Override
    public void startClue(final FollowClue followClue) {
        // 公共池线索不存在这种场景
    }

    @Override
    public void settingChanged(List<SettingVO> setting, Long groupId) {
        // 更改设置不影响公共池线索跟进
    }

    @Override
    public void forceStopClue(final FollowClue clue, final boolean fromTask) {
        // 公共池线索不存在这种场景
    }

    @Transactional(rollbackFor = Exception.class)
    public void startClue(final PubCluePool pubClue) {
        ClueTask clueTask = new ClueTask();
        clueTask.setClueId(pubClue.getId());
        clueTask.setType(getFollowType());
        clueTask.setBeginTime(pubClue.getStartTime().atStartOfDay());
        clueTask.setRedistribution(Boolean.FALSE);
        clueTask.setDeadline(pubClue.getDeadline().atStartOfDay());
        clueTask.setState(TaskStateEnum.ONGOING);
        clueTask.setGroupId(pubClue.getGroupId());
        clueTask.setFollowShop(pubClue.getShopId());
        clueTask.setFollowUser(pubClue.getAdviserId());
        clueTask.setFollowUserName(pubClue.getAdviserName());
        clueTask.setVersion(2);
        clueTaskService.save(clueTask);
        final FollowInitDTO followInitDTO = creteFollowInitDTO(pubClue, clueTask);
        shirasawaRpcService.createFollowData(followInitDTO);
    }

    @Transactional(rollbackFor = Exception.class)
    public void clueConvertSuccess(final PubCluePool pubClue) {
        pubClue.setState(PublicClueStateEnum.COMPLETE);
        pubClue.setCloseTime(LocalDateTime.now());

        ClueTask clueTask = clueTaskService.queryOngoingTaskByClueId(pubClue.getId(), FollowTypeEnum.PL);
        if (Objects.nonNull(clueTask)) {
            clueTask.setCloseTime(LocalDateTime.now());
            clueTask.setFinishUser(pubClue.getAdviserId());
            StaffInfoDTO infoDTO = ehrRpcService.queryStaffInfo(pubClue.getAdviserId());
            if (Objects.nonNull(infoDTO)) {
                clueTask.setFinishUserName(infoDTO.getName());
            }
            clueTask.setState(TaskStateEnum.COMPLETE);
            clueTask.setFinishShop(pubClue.getShopId());
            boolean rpcSucess = rpcStopTask(clueTask);
            clueTask.setRpcSuccess(rpcSucess);
            clueTaskService.updateById(clueTask);

            redisTemplate.opsForSet().add(getClueChangeKey(), String.valueOf(clueTask.getId()));
        }
        pubCluePoolService.updateById(pubClue);
    }

    @Transactional(rollbackFor = Exception.class)
    public void clueConvertFailed(final PubCluePool pubClue, Long userId, Long shopId) {
        pubClue.setState(PublicClueStateEnum.DEFEAT);
        pubClue.setCloseTime(LocalDateTime.now());
        pubClue.setDefeatReason(TaskDefeatTypeEnum.F);

        ClueTask clueTask = clueTaskService.queryOngoingTaskByClueId(pubClue.getId(), FollowTypeEnum.PL);
        if (Objects.nonNull(clueTask)) {
            clueTask.setCloseTime(LocalDateTime.now());
            clueTask.setFinishUser(pubClue.getAdviserId());
            StaffInfoDTO infoDTO = ehrRpcService.queryStaffInfo(userId);
            if (Objects.nonNull(infoDTO)) {
                clueTask.setFinishUserName(infoDTO.getName());
            }
            clueTask.setState(TaskStateEnum.DEFEAT);
            clueTask.setReason(TaskDefeatTypeEnum.F);
            clueTask.setFinishShop(shopId);
            boolean rpcSucess = rpcStopTask(clueTask);
            clueTask.setRpcSuccess(rpcSucess);
            clueTaskService.updateById(clueTask);

            redisTemplate.opsForSet().add(getClueChangeKey(), String.valueOf(clueTask.getId()));
        }
        pubCluePoolService.updateById(pubClue);
    }

    /**
     * 审批而来的数据
     *
     * @param vin
     * @param groupId
     */
    @Transactional(rollbackFor = Exception.class)
    public void clueConvertFailedFromFlow(final String vin, final Long groupId) {
        PubCluePool cluePool = pubCluePoolService.getOne(Wrappers.<PubCluePool>lambdaQuery()
                        .eq(PubCluePool::getVin, vin)
                        .eq(PubCluePool::getGroupId, groupId)
                , false);
        if (Objects.isNull(cluePool)) {
            return;
        }

        Long clueId = cluePool.getId();
        ClueTask clueTask = clueTaskService.queryOngoingTaskByClueId(clueId, FollowTypeEnum.PL);
        if (Objects.nonNull(clueTask)) {
            clueTask.setCloseTime(LocalDateTime.now());
            clueTask.setState(TaskStateEnum.DEFEAT);
            clueTask.setReason(TaskDefeatTypeEnum.A);
            boolean rpcSucess = rpcStopTask(clueTask);
            clueTask.setRpcSuccess(rpcSucess);
            clueTaskService.updateById(clueTask);

            redisTemplate.opsForSet().add(getClueChangeKey(), String.valueOf(clueTask.getId()));
        }
        if (PublicClueStateEnum.ONGOING.equals(cluePool.getState())) {
            cluePool.setState(PublicClueStateEnum.DEFEAT);
            cluePool.setCloseTime(LocalDateTime.now());
            cluePool.setDefeatReason(TaskDefeatTypeEnum.A);
            pubCluePoolService.updateById(cluePool);
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void onRoleChangeCloseTask(final ClueTask task) {
        task.setState(TaskStateEnum.DEFEAT);
        task.setCloseTime(LocalDateTime.now());
        task.setReason(TaskDefeatTypeEnum.D);

        boolean rpcSucess = rpcStopTask(task);
        if (!rpcSucess) {
            log.info("跟进系统终止任务失败");
        }
        task.setRpcSuccess(rpcSucess);
        clueTaskService.updateById(task);
        redisTemplate.opsForSet().add(getClueChangeKey(), String.valueOf(task.getId()));
    }

    @Override
    public void forceStopTask(final ClueTask task) {
        // 公共池线索不存在这种场景
    }

    /**
     * 维护跟进次数
     *
     * @param clue    虚拟的跟进线索[只有id 和类型 其他信息没有]
     * @param overdue
     */
    @Override
    public void onFollowComplete(final FollowClue clue, final boolean overdue) {
        ClueTask task = clueTaskService.queryOngoingTaskByClueId(clue.getId(), FollowTypeEnum.PL);
        if (!overdue && Objects.nonNull(task)) {
            Integer times = Optional.ofNullable(task.getTimes()).orElse(0);
            task.setTimes(times + 1);
            clueTaskService.updateById(task);
        }
    }

    @Override
    public void syncEndTask(final ClueTask task) {
        if (TaskStateEnum.ONGOING.equals(task.getState())) {
            return;
        }
        boolean res = rpcStopTask(task);
        task.setRpcSuccess(res);
        clueTaskService.updateById(task);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void closeTask(final ClueTask task) {
        final Long clueId = task.getClueId();
        PubCluePool pubCluePool = pubCluePoolService.getById(clueId);
        BV.notNull(pubCluePool, () -> "公共池跟进线索不存在: " + clueId);
        task.setState(TaskStateEnum.DEFEAT);
        task.setCloseTime(task.getDeadline().minusSeconds(1L));
        task.setReason(TaskDefeatTypeEnum.C);
        boolean rpcSucess = rpcStopTask(task);
        if (!rpcSucess) {
            log.info("跟进系统终止任务失败");
        }
        task.setRpcSuccess(rpcSucess);
        clueTaskService.updateById(task);
        pubCluePool.setCloseTime(task.getCloseTime());
        pubCluePool.setState(PublicClueStateEnum.DEFEAT);
        pubCluePool.setDefeatReason(task.getReason());
        pubCluePoolService.updateById(pubCluePool);
        customerBizService.pubTaskEndAbandon(pubCluePool);
        redisTemplate.opsForSet().add(getClueChangeKey(), String.valueOf(task.getId()));
    }

    private boolean rpcStopTask(ClueTask clueTask) {
        ClueStopDTO clueStopDTO = new ClueStopDTO();
        clueStopDTO.setType(DataTypeEnum.ofValue(clueTask.getType().getValue()));
        clueStopDTO.setBusinessType(BusinessTypeEnum.AS);
        clueStopDTO.setDetailId(String.valueOf(clueTask.getClueId()));
        clueStopDTO.setShopId(clueTask.getFinishShop());
        ShopDTO shop = oopService.shop(clueTask.getFinishShop());
        if (Objects.nonNull(shop)) {
            clueStopDTO.setShopName(shop.getShortName());
        }
        clueStopDTO.setCompleteTime(DateUtil.localDateTime2Date(clueTask.getCloseTime()));
        clueStopDTO.setGroupId(clueTask.getGroupId());
        clueStopDTO.setUserId(clueTask.getFollowUser());
        clueStopDTO.setUserName(clueTask.getFollowUserName());
        clueStopDTO.setReason(TerminationReason.ABANDON);

        if (TaskStateEnum.DEFEAT.equals(clueTask.getState())) {
            clueStopDTO.setTermination(Boolean.TRUE);
            if (TaskDefeatTypeEnum.A.equals(clueTask.getReason())) {
                clueStopDTO.setReason(TerminationReason.ABANDON);
            }
            if (TaskDefeatTypeEnum.F.equals(clueTask.getReason())) {
                clueStopDTO.setReason(TerminationReason.ANOTHER);
            }
            if (TaskDefeatTypeEnum.D.equals(clueTask.getReason())) {
                clueStopDTO.setReason(TerminationReason.ROLE_CHANGE);
            }
        }
        return shirasawaRpcService.stopTask(clueStopDTO);
    }

    private FollowInitDTO creteFollowInitDTO(PubCluePool pubClue, ClueTask clueTask) {
        CustomerDetailDto customerDetailDto = customerBizService.queryByFrameNo(pubClue.getVin(), pubClue.getGroupId());
        BV.notNull(customerDetailDto, () -> "档案信息有误");

        final FollowInitDTO followInitDTO = FollowInitDTO.builder()
                .businessType(BusinessTypeEnum.AS)
                .type(DataTypeEnum.ofValue(getFollowType().getValue()))
                .customerId(customerDetailDto.getId())
                .customerName(customerDetailDto.getName())
                .memberId(customerDetailDto.getMemberId())
                .plateNo(customerDetailDto.getPlateNo())
                .frameNo(pubClue.getVin())
                .contacts(customerDetailDto.getMobile())
                .detailId(String.valueOf(clueTask.getClueId()))
                .generateTime(DateUtil.localDateTime2Date(clueTask.getBeginTime()))
                .deadline(DateUtil.localDateTime2Date(clueTask.getDeadline()))
                .groupId(clueTask.getGroupId())
                .shopId(clueTask.getFollowShop())
                .userId(clueTask.getFollowUser())
                .userName(clueTask.getFollowUserName())
                .bizId(pubClue.getVin())
                .followDuration(Duration.ofHours(36L))
                .build();
        followInitDTO.setShopName(pubClue.getShopName());
        Optional<SettingVO> setting = settingBizService.querySettingByType(FollowTypeEnum.OT, SettingTypeEnum.EFFECTIVE_TIME, clueTask.getGroupId(), COMMON_BRAND_ID);
        setting.ifPresent(r -> {
            Integer unit = r.getUnit();
            int detailValue = Optional.ofNullable(r.getDetailValue()).orElse(0);
            Duration duration = transferDuration(detailValue, unit);
            BV.notNull(duration, () -> "设置有误,周期计算不成功");
            followInitDTO.setFollowDuration(duration);
        });
        Map<String, String> noteMap = createNoteMap(pubClue, customerDetailDto);
        followInitDTO.setNoteMap(noteMap);
        return followInitDTO;
    }


    private Map<String, String> createNoteMap(PubCluePool pubClue, CustomerDetailDto detailDto) {
        Map<String, String> dynamicMap = new HashMap<>();
        if (Objects.nonNull(pubClue)) {
            int count = Optional.ofNullable(detailDto.getArrivalCount()).orElse(0);
            Date buyDate = detailDto.getBuyDate();
            Date expires = detailDto.getInsuranceExpires();

            String name = detailDto.getName();
            String vin = pubClue.getVin();
            String plate = detailDto.getPlateNo();
            String arrivalCount = String.valueOf(count);
            String defeatCount = "0";
            String vehicleAge = String.valueOf(Math.abs(DateUtil.sub(buyDate, new Date(), "y")));
            String _buyDate = DateUtil.getStringDateShort(buyDate);
            String insuranceExpires = DateUtil.getStringDateShort(expires);

            dynamicMap.put("name", name);
            dynamicMap.put("vin", vin);
            dynamicMap.put("plate", plate);
            dynamicMap.put("arrivalCount", arrivalCount);
            dynamicMap.put("defeatCount", defeatCount);
            dynamicMap.put("vehicleAge", vehicleAge + "年");
            dynamicMap.put("buyDate", _buyDate);
            dynamicMap.put("insuranceExpires", insuranceExpires);
        }
        return dynamicMap;
    }
}