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 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 EvalGroupRankService evalGroupRankService; private final EvalGroupRankStageService evalGroupRankStageService; private final EvalGroupRewardDimService evalGroupRewardDimService; 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 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 setOps = stringRedisTemplate.boundSetOps(getDistributionKey()); ThreadPoolExecutor threadPool = ThreadPoolUtil.getInstance().getThreadPool(); List 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, "考评组不存在,终止计算!"); EvalGroupRank evalGroupRank = evalGroupRankService.getById(evalGroup.getEvalGroupRankId()); BV.notNull(evalGroupRank, "考评排名组不存在,终止计算!"); EvalGroupRankStage evalGroupRankStage = evalGroupRankStageService.getById(evalGroup.getEvalGroupRankStageId()); BV.notNull(evalGroupRankStage, "考评阶段不存在,终止计算!"); List shopRewardDims = evalGroupRewardDimService.list(Wrappers.lambdaQuery() .eq(EvalGroupRewardDim::getEvalGroupId, evalGroup.getId()) .eq(EvalGroupRewardDim::getType, EvalScopeEnum.SHOP) .eq(EvalGroupRewardDim::getYn, Boolean.TRUE) ); if (PublicUtil.isEmpty(shopRewardDims)) { log.error("考评奖惩没有门店:{}", JSON.toJSONString(evalGroup)); return; } // sendDistRewardTodo(shopRewardDims, evalGroup, evalGroupRank, evalGroupRankStage); } /** * 发送绩效组变动通知 * * @param */ @Transactional(rollbackFor = Exception.class) public void sendDistRewardTodo(List shopRewardDims, EvalGroup evalGroup, EvalGroupRank evalGroupRank, EvalGroupRankStage evalGroupRankStage) { String evalGroupRankName = evalGroupRank.getName(); String evalGroupRankStageName = evalGroupRankStage.getName(); DecimalFormat decimalFormat = new DecimalFormat("##########.##"); List itemList = new ArrayList<>(); List distPoolIds = new ArrayList<>(); final Date expireTime = DateUtil.localDateTime2Date(LocalDateTime.now().plusDays(2L)); for (EvalGroupRewardDim rewardDim : shopRewardDims) { final String roleCode = rewardDim.getDistRoleCode(); List pools = evalShopPoolService.list(Wrappers.lambdaQuery() .ne(EvalShopPool::getReward, BigDecimal.ZERO) .eq(EvalShopPool::getEvalGroupId, evalGroup.getId()) .in(EvalShopPool::getShopId, rewardDim.getShopIds()) .eq(EvalShopPool::getYn, Boolean.TRUE) ); if (PublicUtil.isEmpty(pools)) { log.error("分配奖惩待办,未找到考评池:{}", JSON.toJSONString(rewardDim)); continue; } List distShopIds = pools.stream().map(EvalShopPool::getShopId).collect(Collectors.toList()); List users = this.getUserEnableRoleInfos(roleCode, distShopIds); for (RpcUserRoleInfoDTO user : users) { List manageShopIds = user.getRangeList().stream() .filter(range -> distShopIds.contains(range.getShopId())) .map(RpcUserRoleShopDTO::getShopId).distinct().collect(Collectors.toList()); if (PublicUtil.isEmpty(manageShopIds)) { continue; } List poolIds = new ArrayList<>(); BigDecimal reward = BigDecimal.ZERO; for (EvalShopPool pool : pools) { if (manageShopIds.contains(pool.getShopId())) { poolIds.add(pool.getId()); distPoolIds.add(pool.getId()); reward = reward.add(pool.getReward()); } } //奖惩为0不分配 if (BigDecimal.ZERO.compareTo(reward) == 0) { return; } EvalRewardDist rewardDist = transferPo(evalGroup); rewardDist.setUserId(user.getUserId()); rewardDist.setEvalShopPoolIds(poolIds); rewardDist.setShopIds(manageShopIds); rewardDist.setEvalName(evalGroupRankName); // 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 dynamicMap = new HashMap<>(8); dynamicMap.put("evalGroupName", evalGroupRankName); dynamicMap.put("evalName", evalGroupRankStageName); // dynamicMap.put("evalType", evalType.getName()); dynamicMap.put("amount", decimalFormat.format(rewardDist.getReward())); planItemReq.setDynamicMap(dynamicMap); Map extraData = new HashMap<>(); planItemReq.setExtraData(extraData); itemList.add(planItemReq); } } if (PublicUtil.isNotEmpty(itemList)) { //推送待办 BacklogBatchPlanReqDTO batchPlanReq = new BacklogBatchPlanReqDTO(todoVal.getDistEvalReward(), itemList); List 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()) { if (PublicUtil.isNotEmpty(distPoolIds)) { evalShopPoolService.update(Wrappers.lambdaUpdate() .in(EvalShopPool::getId, distPoolIds) .set(EvalShopPool::getStatus, EvalShopPoolStatusEnum.WAIT_DIST_REWARD) .set(EvalShopPool::getUpdateTime, new Date()) ); } } } } public EvalRewardDist transferPo(EvalGroup evalGroup) { EvalRewardDist rewardDist = new EvalRewardDist(); rewardDist.setEvalId(evalGroup.getEvalGroupRankId()); 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 getUserEnableRoleInfos(String roleCode, List shopIds) { List userRoleInfos = erpRpcService.getUserEnableRoleInfos(roleCode, shopIds); if (PublicUtil.isEmpty(userRoleInfos)) { return new ArrayList<>(); } Set staffIds = new HashSet<>(); List 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 pools = evalShopPoolService.list(Wrappers.lambdaQuery() .in(EvalShopPool::getId, rewardDistVO.getEvalShopPoolIds()) .eq(EvalShopPool::getYn, Boolean.TRUE) ); BV.isNotEmpty(pools, "奖惩分配不存在,请重试"); List shopPools = PublicUtil.copyList(pools, EvalShopPoolVO.class); rewardDistVO.setShopPools(shopPools); if (EvalRewardDistStatusEnum.APPROVAL.equals(rewardDist.getStatus()) || EvalRewardDistStatusEnum.APPROVAL_REJECT.equals(rewardDist.getStatus())) { List details = evalRewardDistDetailService.list(Wrappers.lambdaQuery() .eq(EvalRewardDistDetail::getDistId, rewardDistVO.getId()) .eq(EvalRewardDistDetail::getYn, Boolean.TRUE) ); List detailVOS = PublicUtil.copyList(details, EvalRewardDistDetailVO.class); rewardDistVO.setRewardDetails(detailVOS); ApprovalRecord approvalRecord = approvalRecordService.getOne(Wrappers.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.lambdaUpdate() .eq(EvalRewardDistDetail::getDistId, rewardDistId) .set(EvalRewardDistDetail::getYn, Boolean.FALSE) ); List 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.lambdaUpdate() .eq(EvalRewardDistDetail::getDistId, rewardDistId) .eq(EvalRewardDistDetail::getYn, Boolean.TRUE) .set(EvalRewardDistDetail::getStatus, EvalRewardPushStatusEnum.WAIT_PUSH) .set(EvalRewardDistDetail::getUpdateTime, new Date()) ); } evalRewardDistService.saveOrUpdate(rewardDist); } }