EvalRewardService.java 16.2 KB
package cn.fw.morax.service.biz.eval;

import cn.fw.backlog.sdk.api.result.FailBacklogItem;
import cn.fw.common.cache.locker.DistributedLocker;
import cn.fw.common.exception.BusinessException;
import cn.fw.common.web.auth.LoginAuthBean;
import cn.fw.morax.common.config.TodoVal;
import cn.fw.morax.common.pojo.event.ApprovalResultEvent;
import cn.fw.morax.common.utils.DateUtil;
import cn.fw.morax.common.utils.PublicUtil;
import cn.fw.morax.common.utils.ThreadPoolUtil;
import cn.fw.morax.domain.bo.eval.EvalGroupRewardDistributionBO;
import cn.fw.morax.domain.db.ApprovalRecord;
import cn.fw.morax.domain.db.eval.*;
import cn.fw.morax.domain.dto.eval.EvalRewardDistDTO;
import cn.fw.morax.domain.dto.eval.EvalRewardDistDetailDTO;
import cn.fw.morax.domain.enums.*;
import cn.fw.morax.domain.vo.eval.EvalRewardDistDetailVO;
import cn.fw.morax.domain.vo.eval.EvalRewardDistVO;
import cn.fw.morax.domain.vo.eval.EvalShopPoolVO;
import cn.fw.morax.rpc.backlog.TodoRpcService;
import cn.fw.morax.rpc.backlog.dto.BackLogItemDTO;
import cn.fw.morax.rpc.backlog.dto.BacklogBatchPlanItemReqDTO;
import cn.fw.morax.rpc.backlog.dto.BacklogBatchPlanReqDTO;
import cn.fw.morax.rpc.erp.ErpRpcService;
import cn.fw.morax.rpc.erp.dto.RpcUserRoleInfoDTO;
import cn.fw.morax.rpc.erp.dto.RpcUserRoleShopDTO;
import cn.fw.morax.service.biz.ApprovalBizService;
import cn.fw.morax.service.data.ApprovalRecordService;
import cn.fw.morax.service.data.eval.*;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.google.common.collect.Lists;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.BoundSetOperations;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.text.DecimalFormat;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.locks.Lock;
import java.util.stream.Collectors;

import static cn.fw.common.businessvalidator.Validator.BV;

/**
 * @author jiangchao
 * @des: 考评计算
 * @date 2023/1/12 17:39
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class EvalRewardService {

    private final TodoVal todoVal;
    private final EvalService evalService;
    private final ErpRpcService erpRpcService;
    private final TodoRpcService todoRpcService;
    private final EvalGroupService evalGroupService;
    private final DistributedLocker distributedLocker;
    private final ApprovalBizService approvalBizService;
    private final StringRedisTemplate stringRedisTemplate;
    private final EvalShopPoolService evalShopPoolService;
    private final ApprovalRecordService approvalRecordService;
    private final EvalRewardDistService evalRewardDistService;
    private final EvalRewardDistDetailService evalRewardDistDetailService;

    @Value("${spring.cache.custom.global-prefix}:dist:eval-reward")
    @Getter
    private String distributionKey;
    private final static String REWARD_LOCK_KEY = "eval:group:reward";

    /**
     * 缓存分配奖惩的考评池
     *
     */
    public void cacheDistEvalShopPoolIds(LocalDate localDate) {
        Lock lock = distributedLocker.lock(REWARD_LOCK_KEY);
        if (! ((RLock) lock).isLocked()) {
            return;
        }
        try {
            log.info("定时任务【考评奖惩待办】开始执行");

            Set<Long> evalGroupIds = evalGroupService.queryDistributionShopReward(localDate);
            if (PublicUtil.isEmpty(evalGroupIds)) {
                return;
            }
            String[] array = evalGroupIds.stream()
                    .map(evalGroupId -> new EvalGroupRewardDistributionBO(evalGroupId))
                    .map(JSONObject::toJSONString)
                    .toArray(String[]::new);
            stringRedisTemplate.opsForSet().add(getDistributionKey(), array);

        } catch (Exception e){
            log.error(e.getMessage(), e);
        } finally {
            lock.unlock();
        }
    }


    /**
     * 分配奖惩
     */
    public void distributionEvalReward() {
        BoundSetOperations<String, String> setOps = stringRedisTemplate.boundSetOps(getDistributionKey());
        ThreadPoolExecutor threadPool = ThreadPoolUtil.getInstance().getThreadPool();
        List<String> overflowsList = new ArrayList<>();
        String str;
        while ((str = setOps.pop()) != null) {
            final EvalGroupRewardDistributionBO bo = JSONObject.parseObject(str, EvalGroupRewardDistributionBO.class);
            if (Objects.isNull(bo)) {
                continue;
            }
            try {
                String finalStr = str;
                threadPool.execute(() -> {
                    try {
                        distributionEvalGroupReward(bo);
                    } catch (Exception ex) {
                        log.error("计算考评数据失败:{}", bo, ex);
                        setOps.add(finalStr);
                    }
                });
            } catch (RejectedExecutionException re) {
                overflowsList.add(str);
            }
        }
        if (!CollectionUtils.isEmpty(overflowsList)) {
            for (String s : overflowsList) {
                setOps.add(s);
            }
        }
    }


    private void distributionEvalGroupReward(EvalGroupRewardDistributionBO bo) {
        final Long evalGroupId = bo.getEvalGroupId();
        log.info("分配奖惩的考评组:{}", evalGroupId);
        EvalGroup evalGroup = evalGroupService.getById(evalGroupId);
        BV.notNull(evalGroup, "考评组不存在,终止计算!");

        List<EvalShopPool> pools = evalShopPoolService.list(Wrappers.<EvalShopPool>lambdaQuery()
                .eq(EvalShopPool::getEvalGroupId, evalGroupId)
                .eq(EvalShopPool::getYn, Boolean.TRUE)
        );
        BV.isNotEmpty(pools, "考评池不存在,终止计算!");

        sendDistRewardTodo(pools, evalGroup );
    }

    /**
     * 发送绩效组变动通知
     *
     * @param
     */
    @Transactional(rollbackFor = Exception.class)
    public void sendDistRewardTodo(List<EvalShopPool> pools, EvalGroup evalGroup) {
        Eval eval = evalService.getById(evalGroup.getEvalId());
        BV.notNull(eval, "考评配置不存在,请重试");

        DecimalFormat decimalFormat = new DecimalFormat("##########.##");
        final String roleCode = evalGroup.getRoleCodes().stream().findFirst().get();
        final String evalGroupName = evalGroup.getName();
        final String evalName = eval.getName();
        final EvalTypeEnum evalType = eval.getType();
        List<Long> shopIds = evalGroup.getShopIds();

        Date expireTime = DateUtil.localDateTime2Date(LocalDateTime.now().plusDays(2L));

        List<BacklogBatchPlanItemReqDTO> itemList = new ArrayList<>();
        List<RpcUserRoleInfoDTO> users = this.getUserEnableRoleInfos(roleCode, shopIds);
        for (RpcUserRoleInfoDTO user : users) {
            List<Long> manageShopIds = user.getRangeList().stream()
                    .filter(range -> shopIds.contains(range.getShopId()))
                    .map(RpcUserRoleShopDTO::getShopId).distinct().collect(Collectors.toList());
            if (PublicUtil.isEmpty(manageShopIds)) {
                continue;
            }

            List<Long> poolIds = new ArrayList<>();
            BigDecimal reward = BigDecimal.ZERO;
            for (EvalShopPool pool : pools) {
                if (manageShopIds.contains(pool.getShopId())) {
                    poolIds.add(pool.getId());
                    reward = reward.add(pool.getReward());
                }
            }

            EvalRewardDist rewardDist = transferPo(evalGroup);
            rewardDist.setUserId(user.getUserId());
            rewardDist.setEvalShopPoolIds(poolIds);
            rewardDist.setShopIds(manageShopIds);
            rewardDist.setEvalName(evalName);
            rewardDist.setEvalType(evalType);
            rewardDist.setReward(reward);
            evalRewardDistService.save(rewardDist);

            BacklogBatchPlanItemReqDTO planItemReq = new BacklogBatchPlanItemReqDTO();
            planItemReq.setDataId(rewardDist.getId().toString());
            planItemReq.setUserId(user.getUserId());
            planItemReq.setPlanTime(new Date());
            planItemReq.setExpireTime(expireTime);

            Map<String, String> dynamicMap = new HashMap<>(8);
            dynamicMap.put("evalGroupName", evalGroupName);
            dynamicMap.put("evalName", evalName);
            dynamicMap.put("evalType", evalType.getName());
            dynamicMap.put("amount", decimalFormat.format(rewardDist.getReward()));
            planItemReq.setDynamicMap(dynamicMap);

            Map<String, Object> extraData = new HashMap<>();
            planItemReq.setExtraData(extraData);
            itemList.add(planItemReq);
        }


        //推送待办
        BacklogBatchPlanReqDTO batchPlanReq = new BacklogBatchPlanReqDTO(todoVal.getDistEvalReward(), itemList);
        List<FailBacklogItem> failItems = Optional.ofNullable(todoRpcService.batchPush(batchPlanReq)).orElse(new ArrayList<>());
        if (PublicUtil.isNotEmpty(failItems)) {
            log.error("发送分配考评奖励待办失败,失败人员信息:{}", JSON.toJSONString(failItems));
        }

//        if (PublicUtil.isEmpty(failItems) || itemList.size() > failItems.size()) {
//            pool.setStatus(EvalShopPoolStatusEnum.WAIT_DIST_REWARD);
//            evalShopPoolService.updateById(pool);
//        }
    }

    public EvalRewardDist transferPo(EvalGroup evalGroup) {
        EvalRewardDist rewardDist = new EvalRewardDist();
        rewardDist.setEvalId(evalGroup.getEvalId());
        rewardDist.setEvalGroupId(evalGroup.getId());
        rewardDist.setEvalGroupName(evalGroup.getName());
        rewardDist.setStatus(EvalRewardDistStatusEnum.NO_DIST);
        rewardDist.setGroupId(evalGroup.getGroupId());
        rewardDist.setYn(Boolean.TRUE);
        return rewardDist;
    }

    /**
     * 对查询的人员去重
     *
     * @param roleCode
     * @param shopIds
     * @return
     */
    public List<RpcUserRoleInfoDTO> getUserEnableRoleInfos(String roleCode, List<Long> shopIds) {
        List<RpcUserRoleInfoDTO> userRoleInfos = erpRpcService.getUserEnableRoleInfos(roleCode, shopIds);
        if (PublicUtil.isEmpty(userRoleInfos)) {
            return new ArrayList<>();
        }
        Set<Long> staffIds = new HashSet<>();
        List<RpcUserRoleInfoDTO> repeatUserRoleInfos = Lists.newArrayListWithCapacity(userRoleInfos.size());
        for (RpcUserRoleInfoDTO rpcUserRoleInfoDTO : userRoleInfos) {
            if (staffIds.add(rpcUserRoleInfoDTO.getUserId())) {
                repeatUserRoleInfos.add(rpcUserRoleInfoDTO);
            }
        }
        return repeatUserRoleInfos;
    }


    /**
     * 奖惩分配详情
     * @param id
     * @return
     */
    public EvalRewardDistVO rewardDistDetail(Long id) {
        EvalRewardDist rewardDist = evalRewardDistService.getById(id);
        BV.notNull(rewardDist, "奖惩分配不存在,请重试");
        EvalRewardDistVO rewardDistVO = PublicUtil.copy(rewardDist, EvalRewardDistVO.class);

        List<EvalShopPool> pools = evalShopPoolService.list(Wrappers.<EvalShopPool>lambdaQuery()
                .in(EvalShopPool::getId, rewardDistVO.getEvalShopPoolIds())
                .eq(EvalShopPool::getYn, Boolean.TRUE)
        );
        BV.isNotEmpty(pools, "奖惩分配不存在,请重试");
        List<EvalShopPoolVO> shopPools = PublicUtil.copyList(pools, EvalShopPoolVO.class);
        rewardDistVO.setShopPools(shopPools);

        if (EvalRewardDistStatusEnum.APPROVAL.equals(rewardDist.getStatus()) ||
                EvalRewardDistStatusEnum.APPROVAL_REJECT.equals(rewardDist.getStatus())) {
            List<EvalRewardDistDetail> details = evalRewardDistDetailService.list(Wrappers.<EvalRewardDistDetail>lambdaQuery()
                    .eq(EvalRewardDistDetail::getDistId, rewardDistVO.getId())
                    .eq(EvalRewardDistDetail::getYn, Boolean.TRUE)
            );
            List<EvalRewardDistDetailVO> detailVOS = PublicUtil.copyList(details, EvalRewardDistDetailVO.class);
            rewardDistVO.setRewardDetails(detailVOS);

            ApprovalRecord approvalRecord = approvalRecordService.getOne(Wrappers.<ApprovalRecord>lambdaQuery()
                    .eq(ApprovalRecord::getDataId, id)
                    .eq(ApprovalRecord::getApprovalType, ApprovalTypeEnum.EVAL_REWARD_DIST)
                    .eq(ApprovalRecord::getYn, Boolean.TRUE)
                    .last("ORDER BY id DESC LIMIT 1")
            );
            rewardDistVO.setApprovalNo(Optional.ofNullable(approvalRecord).map(ApprovalRecord::getApprovalNo).orElse(""));
        }

        return rewardDistVO;
    }

    @Transactional(rollbackFor = Exception.class)
    public void rewardDist(EvalRewardDistDTO rewardDistDTO, LoginAuthBean currentUser) {
        Long rewardDistId = rewardDistDTO.getEvalShopDistId();
        EvalRewardDist rewardDist = evalRewardDistService.getById(rewardDistId);
        BV.notNull(rewardDist, "奖惩分配不存在,请重试");
        BV.isNotEmpty(rewardDistDTO.getUsers(), "奖惩分配为空,请重试");

        BigDecimal totalReward = rewardDistDTO.getUsers().stream()
                                .map(EvalRewardDistDetailDTO::getReward)
                                .reduce(BigDecimal.ZERO, BigDecimal::add);
        if (totalReward.compareTo(totalReward) != 0) {
            throw new BusinessException("分配金额不等于总金额,请重试");
        }

        //删除之前的分配
        evalRewardDistDetailService.update(Wrappers.<EvalRewardDistDetail>lambdaUpdate()
                .eq(EvalRewardDistDetail::getDistId, rewardDistId)
                .set(EvalRewardDistDetail::getYn, Boolean.FALSE)
        );

        List<EvalRewardDistDetail> distDetails = Lists.newArrayListWithCapacity(rewardDistDTO.getUsers().size());
        for (EvalRewardDistDetailDTO distDetailDTO : rewardDistDTO.getUsers()) {
            EvalRewardDistDetail evalRewardDistDetail = distDetailDTO.convertPo(rewardDistId);
            distDetails.add(evalRewardDistDetail);
        }

        approvalBizService.applyApproveDistReward(rewardDist, currentUser);
        evalRewardDistDetailService.saveBatch(distDetails);
        rewardDist.setStatus(EvalRewardDistStatusEnum.APPROVAL);
        evalRewardDistService.saveOrUpdate(rewardDist);
    }

    @Transactional(rollbackFor = Exception.class)
    public void approvalEvalRewardDist(ApprovalRecord approvalRecord, ApprovalResultEvent result) {
        log.info("收到考评门店奖惩分配审批信息:{}", JSON.toJSONString(result));
        Boolean agree = result.getAgree();
        Long rewardDistId = approvalRecord.getDataId();
        EvalRewardDist rewardDist = evalRewardDistService.getById(rewardDistId);
        BV.notNull(rewardDist, "奖惩分配不存在,请重试");
        rewardDist.setStatus(EvalRewardDistStatusEnum.APPROVAL_REJECT);
        if (agree) {
            BackLogItemDTO dto = new BackLogItemDTO(rewardDist.getUserId(), todoVal.getDistEvalReward(),
                    String.valueOf(rewardDist.getId()), new Date(), null);
            if (todoRpcService.cancel(dto)) {

            } else {
                log.error("奖惩分配待办取消失败:{}", JSON.toJSONString(rewardDist));
                throw new BusinessException("奖惩分配失败,请重试");
            }
            rewardDist.setStatus(EvalRewardDistStatusEnum.APPROVAL_AGREE);
            evalRewardDistDetailService.update(Wrappers.<EvalRewardDistDetail>lambdaUpdate()
                    .eq(EvalRewardDistDetail::getDistId, rewardDistId)
                    .eq(EvalRewardDistDetail::getYn, Boolean.TRUE)
                    .set(EvalRewardDistDetail::getStatus, EvalRewardPushStatusEnum.WAIT_PUSH)
                    .set(EvalRewardDistDetail::getUpdateTime, new Date())
            );
        }
        evalRewardDistService.saveOrUpdate(rewardDist);
    }

}