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 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, "考评组不存在,终止计算!"); List pools = evalShopPoolService.list(Wrappers.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 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 shopIds = evalGroup.getShopIds(); Date expireTime = DateUtil.localDateTime2Date(LocalDateTime.now().plusDays(2L)); List itemList = new ArrayList<>(); List users = this.getUserEnableRoleInfos(roleCode, shopIds); for (RpcUserRoleInfoDTO user : users) { List manageShopIds = user.getRangeList().stream() .filter(range -> shopIds.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()); 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 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 extraData = new HashMap<>(); planItemReq.setExtraData(extraData); itemList.add(planItemReq); } //推送待办 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()) { // 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 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); } }