package cn.fw.morax.server.task; import cn.fw.common.cache.locker.DistributedLocker; import cn.fw.morax.common.constant.TimeTaskConstant; import cn.fw.morax.common.utils.PublicUtil; import cn.fw.morax.domain.db.kpi.KpiGroup; import cn.fw.morax.domain.db.kpi.KpiGroupRank; import cn.fw.morax.domain.db.kpi.KpiPool; import cn.fw.morax.domain.db.kpi.KpiRatio; import cn.fw.morax.domain.dto.GroupCalKpiRatioDTO; import cn.fw.morax.domain.enums.ReportDimensionEnum; import cn.fw.morax.rpc.ehr.EhrRpcService; import cn.fw.morax.rpc.ehr.dto.*; import cn.fw.morax.rpc.oop.OopRpcService; import cn.fw.morax.rpc.oop.dto.ShopDTO; import cn.fw.morax.service.data.kpi.KpiGroupRankService; import cn.fw.morax.service.data.kpi.KpiGroupService; import cn.fw.morax.service.data.kpi.KpiPoolService; import cn.fw.morax.service.data.kpi.KpiRatioService; 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.apache.commons.collections4.map.MultiKeyMap; import org.redisson.api.RLock; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.CollectionUtils; import java.math.BigDecimal; import java.math.RoundingMode; import java.time.LocalDate; import java.time.YearMonth; import java.util.*; import java.util.concurrent.locks.Lock; import java.util.function.Predicate; import java.util.stream.Collectors; /** * @author : jiangchao * @className : KpiReportRatioTask * @description : 绩效报表定时器 * @date : 2022-04-07 15:29 */ @Component @Slf4j @RequiredArgsConstructor @ConditionalOnProperty(prefix = "task", name = "switch", havingValue = "on") public class KpiReportRatioTask { private final KpiGroupRankService kpiGroupRankService; private final DistributedLocker distributedLocker; private final KpiGroupService kpiGroupService; private final KpiPoolService kpiPoolService; private final KpiRatioService kpiRatioService; private final EhrRpcService ehrRpcService; private final OopRpcService oopRpcService; @Value("${spring.cache.custom.global-prefix}:kpi:group:report") @Getter private String kpiGroupReportDistKey; /** * 绩效报表定时任务 * */ @Scheduled(cron = TimeTaskConstant.KPI_REPORT) @Transactional(rollbackFor = Exception.class) public void kpiReportTask() { this.kpiReport(LocalDate.now().minusDays(1)); } /** * 绩效报表定时任务 * * @param date */ @Transactional(rollbackFor = Exception.class) public void kpiReport(LocalDate date) { Lock lock = distributedLocker.lock(getKpiGroupReportDistKey()); if (! ((RLock) lock).isLocked()) { return; } try { log.info("定时任务【绩效报表数据抽取】开始执行"); kpiRatioService.update(Wrappers.lambdaUpdate() .set(KpiRatio::getYn, Boolean.FALSE) .eq(KpiRatio::getDate, date) ); List allKpiGroups = kpiGroupService.queryKgiGroupsByDay(date); Map> groupKpiGroupMap = allKpiGroups.stream().collect(Collectors.groupingBy(KpiGroup::getGroupId)); List kpiRatios = Lists.newArrayListWithCapacity(500); YearMonth curMonth = YearMonth.from(date); YearMonth preMonth = YearMonth.from(date.minusMonths(1)); YearMonth preTwoMonth = YearMonth.from(date.minusMonths(2)); //遍历集团 oopRpcService.allGroups().stream().forEach(group -> { //构建查询参数 Long groupId = group.getId(); GroupCalKpiRatioDTO calDTO = buildParam(curMonth, preMonth, preTwoMonth, groupId); calDTO.setKpiGroups(groupKpiGroupMap.getOrDefault(groupId, new ArrayList<>())); //查询所有门店 Set shopIds = calDTO.getKpiGroups().stream().map(salaryGroup -> { return salaryGroup.getShopIds().stream().map(Long::new).collect(Collectors.toList()); }).collect(HashSet::new, Set::addAll, Set::addAll); if (PublicUtil.isEmpty(shopIds)) { return; } List shopDTOs = oopRpcService.queryShops(new ArrayList<>(shopIds)); //计算管理者数据 calManagerData(calDTO, kpiRatios, shopDTOs, shopIds); //计算绩效排名组 门店 calKpiRankData(calDTO, kpiRatios, shopDTOs); //计算员工 calStaffData(calDTO, kpiRatios); //计算绩效组门店 calShopData(calDTO, kpiRatios, shopDTOs); }); if (PublicUtil.isNotEmpty(kpiRatios)) { kpiRatios.stream().forEach(kpiRatio -> { kpiRatio.setDate(date); kpiRatio.setYn(Boolean.TRUE); }); kpiRatioService.saveBatch(kpiRatios); } } catch (Exception e){ log.error(e.getMessage(), e); } finally { lock.unlock(); } } /** * * 构建查询参数 * @param curMonth * @param preMonth * @param preTwoMonth * @param groupId * @return */ public GroupCalKpiRatioDTO buildParam(YearMonth curMonth, YearMonth preMonth, YearMonth preTwoMonth, Long groupId) { List yearMonths = new ArrayList(){{add(curMonth);add(preMonth);add(preTwoMonth);}}; List kpiPools = kpiPoolService.list(Wrappers.lambdaQuery() .in(KpiPool::getMonthly, yearMonths) .eq(KpiPool::getYn, Boolean.TRUE) .eq(KpiPool::getGroupId, groupId) ); Map> poolMap = kpiPools.stream().collect(Collectors.groupingBy(KpiPool::getMonthly)); List curMonthPools = poolMap.getOrDefault(curMonth, new ArrayList<>()); List preMonthPools = poolMap.getOrDefault(preMonth, new ArrayList<>()); List preTwoMonthPools = poolMap.getOrDefault(preTwoMonth, new ArrayList<>()); List kpiGroupRanks = kpiGroupRankService.list(Wrappers.lambdaQuery() .eq(KpiGroupRank::getYn, Boolean.TRUE) .eq(KpiGroupRank::getGroupId, groupId) ); GroupCalKpiRatioDTO calDTO = new GroupCalKpiRatioDTO(); calDTO.setCurMonthPools(curMonthPools); calDTO.setPreMonthPools(preMonthPools); calDTO.setPreTwoMonthPools(preTwoMonthPools); calDTO.setGroupId(groupId); calDTO.setKpiGroupRanks(kpiGroupRanks); return calDTO; } /** * 计算管理者维度数据 (门店、绩效组) * * @param calDto */ public void calManagerData(GroupCalKpiRatioDTO calDto, List kpiRatios, List shopDTOs, Set shopIds ) { List curMonthPools = calDto.getCurMonthPools(); List preMonthPools = calDto.getPreMonthPools(); List preTwoMonthPools = calDto.getPreTwoMonthPools(); List kpiGroups = calDto.getKpiGroups(); Map> kpiGroupPostShopIds = kpiGroups.stream().collect(Collectors.toMap( KpiGroup::getPostId, kpiGroup -> new HashSet<>(kpiGroup.getShopIds()), (v1, v2) -> {v1.addAll(v2);return v1;})); MultiKeyMap kpiGroupPostShopIdMap = new MultiKeyMap<>(); for (KpiGroup kpiGroup : kpiGroups) { for (Long shopId : kpiGroup.getShopIds()) { kpiGroupPostShopIdMap.put(kpiGroup.getPostId(), shopId, kpiGroup.getId()); } } List managerDTOS = ehrRpcService.getRealTimeShopManager(new ArrayList<>(shopIds)); for (ManagerDTO manager : managerDTOS) { Map> createShopPostIdMap = new HashMap<>(); Set createKpiGroupIds = new HashSet<>(); //管理岗位 for (ManagerStaffDTO managerStaffDTO : manager.getScopeList()) { Long postId = managerStaffDTO.getPostId(); //绩效组所有岗位 if (! kpiGroupPostShopIds.keySet().contains(postId)) { continue; } //管理门店 for (ManagerStaffShopDTO shopDTO : managerStaffDTO.getShopList()) { //绩效组岗位所有门店 if (! kpiGroupPostShopIds.get(postId).contains(shopDTO.getShopId())) { continue; } Long shopId = shopDTO.getShopId(); if (createShopPostIdMap.containsKey(shopId)) { createShopPostIdMap.get(shopId).add(postId); } else { createShopPostIdMap.put(shopId, new HashSet(){{add(postId);}}); } if (kpiGroupPostShopIdMap.containsKey(postId, shopDTO.getShopId())) { createKpiGroupIds.add(kpiGroupPostShopIdMap.get(postId, shopDTO.getShopId())); } } } //管理者门店、岗位 与 绩效组门店、岗位没有匹配 if (PublicUtil.isEmpty(createShopPostIdMap)) { continue; } Set manageStaffIds = manager.getManageStaffList().stream().map(StaffBaseInfoDTO::getId).collect(Collectors.toSet()); kpiGroups.stream().filter(kpiGroup -> createKpiGroupIds.contains(kpiGroup.getId())).forEach(kpiGroup -> { KpiRatio kpiRatio = new KpiRatio(ReportDimensionEnum.MANAGER_KPI_GROUP, calDto.getGroupId()); kpiRatio.setS1(manager.getStaffName()); kpiRatio.setL1(manager.getStaffId()); kpiRatio.setS4(kpiGroup.getName()); kpiRatio.setL4(kpiGroup.getId()); kpiRatio.setS3(kpiGroup.getPostName()); kpiRatio.setL3(kpiGroup.getPostId()); Predicate predicate = pool -> { return kpiGroup.getId().equals(pool.getKpiGroupId()) && manageStaffIds.contains(pool.getUserId()) && Boolean.TRUE.equals(pool.getInclusion()); }; kpiRatio.setB1(averageRatio(curMonthPools, predicate)); kpiRatio.setB2(monthRatio(curMonthPools, predicate)); kpiRatio.setB3(monthRatio(preMonthPools, predicate)); kpiRatio.setB4(monthRatio(preTwoMonthPools, predicate)); kpiRatio.setS8(String.join(",", kpiGroup.getShopIds().stream().map(String::valueOf).collect(Collectors.toList()))); kpiRatios.add(kpiRatio); }); Set createShopIds = createShopPostIdMap.keySet(); shopDTOs.stream().filter(shopDTO -> createShopIds.contains(shopDTO.getId())).forEach(shopDTO -> { KpiRatio kpiRatio = new KpiRatio(ReportDimensionEnum.MANAGER_SHOP, calDto.getGroupId()); Long shopId = shopDTO.getId(); String shopName = shopDTO.getShopName(); kpiRatio.setS1(manager.getStaffName()); kpiRatio.setL1(manager.getStaffId()); kpiRatio.setL2(shopId); kpiRatio.setS2(shopName); Set postIds = createShopPostIdMap.get(shopDTO.getId()); if (PublicUtil.isNotEmpty(postIds)) { kpiRatio.setS8(String.join(",", postIds.stream().map(String::valueOf).collect(Collectors.toList()))); } Predicate predicate = pool -> { return shopId.equals(pool.getShopId()) && manageStaffIds.contains(pool.getUserId()) && Boolean.TRUE.equals(pool.getInclusion()); }; kpiRatio.setB1(averageRatio(curMonthPools, predicate)); kpiRatio.setB2(monthRatio(curMonthPools, predicate)); kpiRatio.setB3(monthRatio(preMonthPools, predicate)); kpiRatio.setB4(monthRatio(preTwoMonthPools, predicate)); kpiRatio.setB7(countStaffNum(curMonthPools, predicate)); kpiRatios.add(kpiRatio); }); KpiRatio kpiRatio = new KpiRatio(ReportDimensionEnum.MANAGER, calDto.getGroupId()); kpiRatio.setS1(manager.getStaffName()); kpiRatio.setL1(manager.getStaffId()); Set roleCodes = manager.getScopeList().stream() .flatMap(managerStaffVo -> managerStaffVo.getRoleList().stream()) .map(ManagerStaffRoleDTO::getRoleCode) .collect(Collectors.toSet()); Set roleNames = manager.getScopeList().stream() .flatMap(managerStaffVo -> managerStaffVo.getRoleList().stream()) .map(ManagerStaffRoleDTO::getRoleName) .collect(Collectors.toSet()); if (PublicUtil.isNotEmpty(roleCodes)) { kpiRatio.setS6(String.join(",", roleCodes)); } if (PublicUtil.isNotEmpty(roleNames)) { kpiRatio.setS7(String.join(",", roleNames)); } if (PublicUtil.isNotEmpty(createShopIds)) { kpiRatio.setS9(String.join(",", createShopIds.stream().map(String::valueOf).collect(Collectors.toList()))); } Predicate predicate = pool -> { return manageStaffIds.contains(pool.getUserId()) && Boolean.TRUE.equals(pool.getInclusion()); }; kpiRatio.setB1(averageRatio(curMonthPools, predicate)); kpiRatio.setB2(monthRatio(curMonthPools, predicate)); kpiRatio.setB3(monthRatio(preMonthPools, predicate)); kpiRatio.setB4(monthRatio(preTwoMonthPools, predicate)); kpiRatios.add(kpiRatio); } } /** * 计算绩效排名组-门店维度数据 * * @param calDto * @param kpiRatios */ public void calKpiRankData(GroupCalKpiRatioDTO calDto, List kpiRatios, List shopDTOs ) { List curMonthPools = calDto.getCurMonthPools(); List preMonthPools = calDto.getPreMonthPools(); List preTwoMonthPools = calDto.getPreTwoMonthPools(); List kpiGroups = calDto.getKpiGroups(); for (KpiGroupRank rank : calDto.getKpiGroupRanks()) { List kgcs = rank.getKgcs(); List rankKpiGroups = kpiGroups.stream() .filter(kpiGroup -> kgcs.contains(kpiGroup.getKgc())) .collect(Collectors.toList()); Set createShopIds = rankKpiGroups.stream().flatMap(kpiGroup -> kpiGroup.getShopIds().stream()).collect(Collectors.toSet()); shopDTOs.stream().filter(shop -> createShopIds.contains(shop.getId())).forEach(shop -> { KpiRatio kpiRatio = new KpiRatio(ReportDimensionEnum.KPI_RANK_SHOP, calDto.getGroupId());; Long shopId = shop.getId(); Predicate predicate = pool -> { return shopId.equals(pool.getShopId()) && kgcs.contains(pool.getKgc()) && Boolean.TRUE.equals(pool.getInclusion()); }; kpiRatio.setS5(rank.getName()); kpiRatio.setL5(rank.getId()); kpiRatio.setL2(shopId); kpiRatio.setS2(shop.getShopName()); kpiRatio.setB1(averageRatio(curMonthPools, predicate)); kpiRatio.setB7(countStaffNum(curMonthPools, predicate)); kpiRatio.setB2(monthRatio(curMonthPools, predicate)); kpiRatio.setB3(monthRatio(preMonthPools, predicate)); kpiRatio.setB4(monthRatio(preTwoMonthPools, predicate)); kpiRatios.add(kpiRatio); }); KpiRatio kpiRatio = new KpiRatio(ReportDimensionEnum.KPI_RANK, calDto.getGroupId());; Predicate predicate = pool -> { return kgcs.contains(pool.getKgc()) && Boolean.TRUE.equals(pool.getInclusion()); }; kpiRatio.setS5(rank.getName()); kpiRatio.setL5(rank.getId()); if (PublicUtil.isNotEmpty(createShopIds)) { kpiRatio.setS9(String.join(",", createShopIds.stream().map(String::valueOf).collect(Collectors.toList()))); } kpiRatio.setB1(averageRatio(curMonthPools, predicate)); kpiRatio.setB2(monthRatio(curMonthPools, predicate)); kpiRatio.setB3(monthRatio(preMonthPools, predicate)); kpiRatio.setB4(monthRatio(preTwoMonthPools, predicate)); kpiRatios.add(kpiRatio); } } /** * 计算绩效组门店维度数据 * * @param calDto * @param kpiRatios */ public void calShopData(GroupCalKpiRatioDTO calDto, List kpiRatios, List shopDTOs ) { List curMonthPools = calDto.getCurMonthPools(); List preMonthPools = calDto.getPreMonthPools(); List preTwoMonthPools = calDto.getPreTwoMonthPools(); List kpiGroups = calDto.getKpiGroups(); shopDTOs.stream().forEach(shop -> { KpiRatio kpiRatio = new KpiRatio(ReportDimensionEnum.SHOP, calDto.getGroupId()); Long shopId = shop.getId(); Predicate predicate = pool -> { return shopId.equals(pool.getShopId()) && Boolean.TRUE.equals(pool.getInclusion()); }; kpiRatio.setL2(shopId); kpiRatio.setS2(shop.getShopName()); kpiRatio.setB1(averageRatio(curMonthPools, predicate)); kpiRatio.setB7(countStaffNum(curMonthPools, predicate)); kpiRatio.setB2(monthRatio(curMonthPools, predicate)); kpiRatio.setB3(monthRatio(preMonthPools, predicate)); kpiRatio.setB4(monthRatio(preTwoMonthPools, predicate)); kpiRatios.add(kpiRatio); for (KpiGroup kpiGroup : kpiGroups) { List shopIds = kpiGroup.getShopIds(); if (! shopIds.contains(shopId)) { continue; } KpiRatio kpiRatioKpiGroup = new KpiRatio(ReportDimensionEnum.SHOP_KPI_GROUP, calDto.getGroupId()); Predicate predicateKpiGroup = pool -> { return shopId.equals(pool.getShopId()) && kpiGroup.getId().equals(pool.getKpiGroupId()) && Boolean.TRUE.equals(pool.getInclusion()); }; kpiRatioKpiGroup.setL2(shopId); kpiRatioKpiGroup.setS2(shop.getShopName()); kpiRatioKpiGroup.setS4(kpiGroup.getName()); kpiRatioKpiGroup.setL4(kpiGroup.getId()); kpiRatioKpiGroup.setB1(averageRatio(curMonthPools, predicateKpiGroup)); kpiRatioKpiGroup.setB2(monthRatio(curMonthPools, predicateKpiGroup)); kpiRatioKpiGroup.setB3(monthRatio(preMonthPools, predicateKpiGroup)); kpiRatioKpiGroup.setB4(monthRatio(preTwoMonthPools, predicateKpiGroup)); kpiRatios.add(kpiRatioKpiGroup); } }); } /** * 计算员工维度数据 * * @param calDto */ public void calStaffData(GroupCalKpiRatioDTO calDto, List kpiRatios ) { List curMonthPools = calDto.getCurMonthPools(); List preMonthPools = calDto.getPreMonthPools(); List preTwoMonthPools = calDto.getPreTwoMonthPools(); List kpiGroups = calDto.getKpiGroups(); List kpiGroupRanks = calDto.getKpiGroupRanks(); Map kpiGroupNameMap = kpiGroups.stream().collect(Collectors.toMap(KpiGroup::getId, KpiGroup::getName, (v1, v2) -> v1)); Map kgcRankMap = new HashMap<>(); for (KpiGroupRank rank : kpiGroupRanks) { for (String kgc : rank.getKgcs()) { kgcRankMap.put(kgc, rank); } } for (KpiPool pool : curMonthPools) { KpiRatio kpiRatio = new KpiRatio(ReportDimensionEnum.STAFF, calDto.getGroupId());; Long staffId = pool.getUserId(); kpiRatio.setS1(pool.getUserName()); kpiRatio.setS2(pool.getShopName()); kpiRatio.setS3(pool.getPostName()); kpiRatio.setI2(pool.getRank()); kpiRatio.setB5(pool.getInclusion()); kpiRatio.setI1(pool.getActualStar().getValue()); kpiRatio.setL1(pool.getUserId()); kpiRatio.setL2(pool.getShopId()); kpiRatio.setL3(pool.getPostId()); kpiRatio.setL6(pool.getId()); kpiRatio.setL4(pool.getKpiGroupId()); kpiRatio.setS4(kpiGroupNameMap.getOrDefault(pool.getKpiGroupId(), " ")); if (kgcRankMap.containsKey(pool.getKgc())) { KpiGroupRank rank = kgcRankMap.get(pool.getKgc()); kpiRatio.setL5(rank.getId()); kpiRatio.setS5(rank.getName()); } Predicate predicate = poolDto -> staffId.equals(poolDto.getUserId()) && Boolean.TRUE.equals(pool.getInclusion()); kpiRatio.setB1(averageRatio(curMonthPools, predicate)); Optional curMonthOp = curMonthPools.stream() .filter(poolDto -> poolDto.getUserId().equals(staffId) && Boolean.TRUE.equals(pool.getInclusion())) .findFirst() .map(KpiPool::getKpiScoreRatio); Optional preMonthOp = preMonthPools.stream() .filter(poolDto -> poolDto.getUserId().equals(staffId) && Boolean.TRUE.equals(pool.getInclusion())) .findFirst() .map(KpiPool::getKpiScoreRatio); Optional preTwoMonthOp = preTwoMonthPools.stream() .filter(poolDto -> poolDto.getUserId().equals(staffId) && Boolean.TRUE.equals(pool.getInclusion())) .findFirst() .map(KpiPool::getKpiScoreRatio); if (curMonthOp.isPresent()) { kpiRatio.setB2(curMonthOp.get().multiply(new BigDecimal("100")).setScale(2, RoundingMode.DOWN)); } if (preMonthOp.isPresent()) { kpiRatio.setB3(preMonthOp.get().multiply(new BigDecimal("100")).setScale(2, RoundingMode.DOWN)); } if (preTwoMonthOp.isPresent()) { kpiRatio.setB4(preTwoMonthOp.get().multiply(new BigDecimal("100")).setScale(2, RoundingMode.DOWN)); } kpiRatios.add(kpiRatio); } } /** * 绩效池平均绩效得分率 * * @param pools * @return */ public BigDecimal countStaffNum(Collection pools, Predicate predicate) { if (CollectionUtils.isEmpty(pools)) { return BigDecimal.ZERO; } Long staffNum = pools.stream() .filter(predicate) .count(); return new BigDecimal(staffNum); } /** * 绩效池平均绩效得分率 * * @param pools * @return */ public BigDecimal averageRatio(Collection pools, Predicate predicate) { if (CollectionUtils.isEmpty(pools)) { return BigDecimal.ZERO; } Double averageRatioDouble = pools.stream() .filter(predicate) .mapToDouble(r -> Optional.ofNullable(r.getAverageKpiScoreRatio()).orElse(BigDecimal.ZERO).doubleValue()) .average() .orElse(0); BigDecimal averageRatio = new BigDecimal(averageRatioDouble.toString()); return averageRatio.multiply(new BigDecimal("100")).setScale(2, RoundingMode.DOWN); } /** * 当月绩效池平均绩效得分率 * * @param pools * @return */ public BigDecimal monthRatio(Collection pools, Predicate predicate) { if (CollectionUtils.isEmpty(pools)) { return BigDecimal.ZERO; } Double averageRatioDouble = pools.stream() .filter(predicate) .mapToDouble(r -> Optional.ofNullable(r.getKpiScoreRatio()).orElse(BigDecimal.ZERO).doubleValue()) .average() .orElse(0); BigDecimal averageRatio = new BigDecimal(averageRatioDouble.toString()); return averageRatio.multiply(new BigDecimal("100")).setScale(2, RoundingMode.DOWN); } }