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.DateUtil; import cn.fw.morax.common.utils.PublicUtil; import cn.fw.morax.domain.db.kpi.KpiPool; import cn.fw.morax.domain.db.salary.SalaryGroup; import cn.fw.morax.domain.db.salary.SalaryPool; import cn.fw.morax.domain.db.salary.SalaryReward; import cn.fw.morax.domain.dto.query.GroupCalSalaryDTO; 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.KpiPoolService; import cn.fw.morax.service.data.salary.SalaryGroupService; import cn.fw.morax.service.data.salary.SalaryPoolService; import cn.fw.morax.service.data.salary.SalaryRewardService; import com.alibaba.fastjson.JSON; 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 : SalaryReportTask * @description : 薪酬报表定时器 * @date : 2022-04-07 15:29 */ @Component @Slf4j @RequiredArgsConstructor @ConditionalOnProperty(prefix = "task", name = "switch", havingValue = "on") public class SalaryReportTask { private final SalaryGroupService salaryGroupService; private final SalaryPoolService salaryPoolService; private final SalaryRewardService salaryRewardService; private final EhrRpcService ehrRpcService; private final KpiPoolService kpiPoolService; private final OopRpcService oopRpcService; private final DistributedLocker distributedLocker; @Value("${spring.cache.custom.global-prefix}:salary:group:report") @Getter private String salaryGroupReport; /** * 绩效报表定时任务 */ @Scheduled(cron = TimeTaskConstant.SALARY_REPORT) @Transactional(rollbackFor = Exception.class) public void salaryReportTask() { this.salaryReport(LocalDate.now().minusDays(1)); } /** * 薪酬报表定时任务 * * @param date */ @Transactional(rollbackFor = Exception.class) public void salaryReport(LocalDate date) { Lock lock = distributedLocker.lock(getSalaryGroupReport()); if (!((RLock) lock).isLocked()) { return; } try { log.info("定时任务【薪酬报表数据抽取】开始执行"); salaryRewardService.update(Wrappers.lambdaUpdate() .set(SalaryReward::getYn, Boolean.FALSE) .eq(SalaryReward::getDate, date) ); List allSalaryGroups = salaryGroupService.getAllEffectGroups(DateUtil.localDate2Date(date)); Map> groupKpiGroupMap = allSalaryGroups.stream().collect(Collectors.groupingBy(SalaryGroup::getGroupId)); List salarys = Lists.newArrayListWithCapacity(500); oopRpcService.allGroups().stream().forEach(group -> { Long groupId = group.getId(); List pools = salaryPoolService.list(Wrappers.lambdaQuery() .eq(SalaryPool::getMonthly, YearMonth.from(date)) .eq(SalaryPool::getYn, Boolean.TRUE) .eq(SalaryPool::getGroupId, groupId) ); GroupCalSalaryDTO calDTO = new GroupCalSalaryDTO(); calDTO.setPools(pools); calDTO.setDate(date); calDTO.setGroupId(groupId); List salaryGroups = groupKpiGroupMap.getOrDefault(groupId, new ArrayList<>()); Set shopIds = salaryGroups.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)); //计算管理者数据 calManagerShopPostData(calDTO, salarys, salaryGroups, shopDTOs, shopIds); //计算员工 calStaffData(calDTO, salarys); //门店 calShopData(calDTO, salarys, salaryGroups, shopDTOs); //岗位 calPostData(calDTO, salarys, salaryGroups, shopDTOs); }); if (PublicUtil.isNotEmpty(salarys)) { salarys.stream().forEach(salary -> { salary.setDate(date); salary.setYn(Boolean.TRUE); }); salaryRewardService.saveBatch(salarys); } } catch (Exception e) { log.error(e.getMessage(), e); } finally { lock.unlock(); } } /** * 计算管理者维度数据 (门店、绩效组) * * @param calDto */ public void calManagerShopPostData(GroupCalSalaryDTO calDto, List salarys, List salaryGroups, List shopDTOs, Set shopIds ) { List pools = calDto.getPools(); Map> salaryGroupPostShopIds = salaryGroups.stream().collect(Collectors.toMap( SalaryGroup::getPostId, salaryGroup -> new HashSet<>(salaryGroup.getShopIds()), (v1, v2) -> { v1.addAll(v2); return v1; })); List managerDTOS = ehrRpcService.getRealTimeShopManager(new ArrayList<>(shopIds)); log.info("查询门店实时管理者:{},{}", JSON.toJSONString(shopIds), JSON.toJSONString(managerDTOS)); Map postMap = salaryGroups.stream().collect(Collectors.toMap(SalaryGroup::getPostId, SalaryGroup::getPostName, (v1, v2) -> v1)); for (ManagerDTO manager : managerDTOS) { Map> createShopPostIdMap = new HashMap<>(); Map> createPostShopIdMap = new HashMap<>(); //管理岗位 for (ManagerStaffDTO managerStaffDTO : manager.getScopeList()) { Long postId = managerStaffDTO.getPostId(); //绩效组所有岗位 if (!salaryGroupPostShopIds.keySet().contains(postId)) { continue; } //管理门店 for (ManagerStaffShopDTO shopDTO : managerStaffDTO.getShopList()) { //绩效组所有门店 if (!salaryGroupPostShopIds.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 (createPostShopIdMap.containsKey(postId)) { createPostShopIdMap.get(postId).add(postId); } else { createPostShopIdMap.put(postId, new HashSet() {{ add(shopId); }}); } } } //管理者门店、岗位 与 绩效组门店、岗位没有匹配 if (PublicUtil.isEmpty(createShopPostIdMap)) { continue; } Set manageStaffIds = manager.getManageStaffList().stream().map(StaffBaseInfoDTO::getId).collect(Collectors.toSet()); Set createPostIds = createPostShopIdMap.keySet(); for (Long postId : createPostIds) { SalaryReward salary = new SalaryReward(ReportDimensionEnum.MANAGER_POST, calDto.getGroupId()); salary.setS1(manager.getStaffName()); salary.setL1(manager.getStaffId()); salary.setL3(postId); salary.setS3(postMap.getOrDefault(postId, "")); Set postShopIds = createPostShopIdMap.get(postId); if (PublicUtil.isNotEmpty(postShopIds)) { salary.setS6(String.join(",", postShopIds.stream().map(String::valueOf).collect(Collectors.toList()))); } Predicate predicate = pool -> { return postId.equals(pool.getPostId()) && manageStaffIds.contains(pool.getUserId()); }; salary.setB1(totalSalary(pools, predicate)); salary.setB2(averageSalary(pools, predicate)); salary.setB3(null); salary.setB4(null); salarys.add(salary); } Set createShopIds = createShopPostIdMap.keySet(); shopDTOs.stream().filter(shopDTO -> createShopIds.contains(shopDTO.getId())).forEach(shopDTO -> { SalaryReward salary = new SalaryReward(ReportDimensionEnum.MANAGER_SHOP, calDto.getGroupId()); Long shopId = shopDTO.getId(); String shopName = shopDTO.getShopName(); salary.setS1(manager.getStaffName()); salary.setL1(manager.getStaffId()); salary.setL2(shopId); salary.setS2(shopName); Set postIds = createShopPostIdMap.get(shopDTO.getId()); if (PublicUtil.isNotEmpty(postIds)) { salary.setS6(String.join(",", postIds.stream().map(String::valueOf).collect(Collectors.toList()))); } Predicate predicate = pool -> { return shopId.equals(pool.getShopId()) && manageStaffIds.contains(pool.getUserId()); }; salary.setB1(totalSalary(pools, predicate)); salary.setB5(countStaffNum(pools, predicate)); salary.setB2(averageSalary(pools, predicate)); salary.setB3(null); salary.setB4(null); salarys.add(salary); }); SalaryReward salary = new SalaryReward(ReportDimensionEnum.MANAGER, calDto.getGroupId()); salary.setS1(manager.getStaffName()); salary.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)) { salary.setS4(String.join(",", roleCodes)); } if (PublicUtil.isNotEmpty(roleNames)) { salary.setS5(String.join(",", roleNames)); } if (PublicUtil.isNotEmpty(createShopIds)) { salary.setS6(String.join(",", createShopIds.stream().map(String::valueOf).collect(Collectors.toList()))); } Predicate predicate = pool -> { return manageStaffIds.contains(pool.getUserId()); }; salary.setB1(totalSalary(pools, predicate)); salary.setB2(averageSalary(pools, predicate)); salary.setB3(null); salary.setB4(null); salarys.add(salary); } } /** * 计算员工维度数据 * * @param calDto */ public void calStaffData(GroupCalSalaryDTO calDto, List salarys) { List pools = calDto.getPools(); List kpiPools = kpiPoolService.list(Wrappers.lambdaQuery() .eq(KpiPool::getMonthly, YearMonth.from(calDto.getDate())) .eq(KpiPool::getYn, Boolean.TRUE) .eq(KpiPool::getGroupId, calDto.getGroupId()) ); MultiKeyMap staffShopPostStar = new MultiKeyMap(); kpiPools.stream() .filter(pool -> PublicUtil.isNotEmpty(pool.getStarLevel())) .forEach(pool -> { staffShopPostStar.put(pool.getUserId(), pool.getShopId(), pool.getPostId(), pool.getStarLevel().getValue()); }); for (SalaryPool pool : pools) { SalaryReward salary = new SalaryReward(ReportDimensionEnum.STAFF, calDto.getGroupId()); salary.setL1(pool.getUserId()); salary.setS1(pool.getUserName()); salary.setL2(pool.getShopId()); salary.setS2(pool.getShopName()); salary.setL3(pool.getPostId()); salary.setS3(pool.getPostName()); salary.setL4(pool.getId()); salary.setB1(pool.getReward()); salary.setB2(pool.getReward()); if (staffShopPostStar.containsKey(pool.getUserId(), pool.getShopId(), pool.getPostId())) { salary.setI1(staffShopPostStar.get(pool.getUserId(), pool.getShopId(), pool.getPostId())); } salarys.add(salary); } } /** * 计算绩效排名组维度数据 */ public void calShopData(GroupCalSalaryDTO calDto, List salarys, List salaryGroups, List shopDTOs ) { List pools = calDto.getPools(); //门店 岗位 Map> salaryGroupShopPostMap = new HashMap<>(); for (SalaryGroup salaryGroup : salaryGroups) { Long postId = salaryGroup.getPostId(); for (Long shopId : salaryGroup.getShopIds()) { if (salaryGroupShopPostMap.containsKey(shopId)) { salaryGroupShopPostMap.get(shopId).add(postId); } else { salaryGroupShopPostMap.put(shopId, new HashSet() {{ add(postId); }}); } } } Map postMap = salaryGroups.stream().collect(Collectors.toMap(SalaryGroup::getPostId, SalaryGroup::getPostName, (v1, v2) -> v1)); Set createShopIds = salaryGroupShopPostMap.keySet(); shopDTOs.stream().filter(shop -> createShopIds.contains(shop.getId())).forEach(shop -> { SalaryReward salary = new SalaryReward(ReportDimensionEnum.SHOP, calDto.getGroupId()); Long shopId = shop.getId(); String shopName = shop.getShopName(); Predicate predicate = pool -> { return shopId.equals(pool.getShopId()); }; salary.setL2(shopId); salary.setS2(shopName); salary.setB1(totalSalary(pools, predicate)); salary.setB5(countStaffNum(pools, predicate)); salary.setB2(averageSalary(pools, predicate)); salary.setB3(null); salary.setB4(null); salarys.add(salary); for (Long postId : salaryGroupShopPostMap.get(shop.getId())) { SalaryReward salaryPost = new SalaryReward(ReportDimensionEnum.SHOP_POST, calDto.getGroupId()); Predicate predicatePost = pool -> { return shopId.equals(pool.getShopId()) && postId.equals(pool.getPostId()); }; salaryPost.setL2(shopId); salaryPost.setS2(shopName); salaryPost.setL3(postId); salaryPost.setS3(postMap.getOrDefault(postId, "")); salaryPost.setB1(totalSalary(pools, predicatePost)); salaryPost.setB2(averageSalary(pools, predicatePost)); salaryPost.setB3(null); salaryPost.setB4(null); salarys.add(salaryPost); } }); } /** * 计算绩效排名组维度数据 * */ public void calPostData(GroupCalSalaryDTO calDto, List salarys, List salaryGroups, List shopDTOs ) { List pools = calDto.getPools(); //门店 岗位 Map> salaryGroupPostShopMap = new HashMap<>(); for (SalaryGroup salaryGroup : salaryGroups) { Long postId = salaryGroup.getPostId(); if (salaryGroupPostShopMap.containsKey(postId)) { salaryGroupPostShopMap.get(postId).addAll(salaryGroup.getShopIds()); } else { salaryGroupPostShopMap.put(postId, new HashSet(){{addAll(salaryGroup.getShopIds());}}); } } Map postMap = salaryGroups.stream().collect(Collectors.toMap(SalaryGroup::getPostId, SalaryGroup::getPostName, (v1, v2) -> v1)); for (Map.Entry> entry : salaryGroupPostShopMap.entrySet()) { SalaryReward salary = new SalaryReward(ReportDimensionEnum.POST, calDto.getGroupId()); Long postId = entry.getKey(); Set createShopIds = entry.getValue(); Predicate predicate = pool -> { return postId.equals(pool.getPostId()); }; salary.setL3(postId); salary.setS3(postMap.getOrDefault(postId, "")); if (PublicUtil.isNotEmpty(createShopIds)) { salary.setS6(String.join(",", createShopIds.stream().map(String::valueOf).collect(Collectors.toList()))); } salary.setB1(totalSalary(pools, predicate)); salary.setB2(averageSalary(pools, predicate)); salary.setB3(null); salary.setB4(null); salarys.add(salary); shopDTOs.stream().filter(shop -> createShopIds.contains(shop.getId())).forEach(shop -> { SalaryReward salaryShop = new SalaryReward(ReportDimensionEnum.POST_SHOP, calDto.getGroupId()); Long shopId = shop.getId(); String shopName = shop.getShopName(); Predicate predicateShop = pool -> { return postId.equals(pool.getPostId()) && shopId.equals(pool.getShopId()); }; salaryShop.setL2(shopId); salaryShop.setS2(shopName); salaryShop.setL3(postId); salaryShop.setS3(postMap.getOrDefault(postId, "")); salaryShop.setB1(totalSalary(pools, predicateShop)); salaryShop.setB5(countStaffNum(pools, predicateShop)); salaryShop.setB2(averageSalary(pools, predicateShop)); salaryShop.setB3(null); salaryShop.setB4(null); salarys.add(salaryShop); }); } } /** * 绩效池平均绩效得分率 * * @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 averageSalary(Collection pools, Predicate predicate) { if (CollectionUtils.isEmpty(pools)) { return BigDecimal.ZERO; } Double averageRatioDouble = pools.stream() .filter(predicate) .mapToDouble(r -> Optional.ofNullable(r.getReward()).orElse(BigDecimal.ZERO).doubleValue()) .average() .orElse(0); BigDecimal averageRatio = new BigDecimal(averageRatioDouble.toString()); return averageRatio.setScale(2, RoundingMode.DOWN); } /** * 总薪酬 * * @param pools * @return */ public BigDecimal totalSalary(Collection pools, Predicate predicate) { if (CollectionUtils.isEmpty(pools)) { return BigDecimal.ZERO; } BigDecimal totalSalary = pools.stream() .filter(predicate) .map(r -> Optional.ofNullable(r.getReward()).orElse(BigDecimal.ZERO)) .reduce(BigDecimal.ZERO, BigDecimal::add); return totalSalary.setScale(2, RoundingMode.DOWN); } // // Map> shopPostIdMap = new HashMap<>(); // for (ManagerStaffDTO managerStaffDTO : manager.getScopeList()) { // Long postId = managerStaffDTO.getPostId(); // // if (! postShopIds.keySet().contains(postId)) { // continue; // } // for (ManagerStaffShopDTO shopDTO : managerStaffDTO.getShopList()) { // // if () { // // } // Long shopId = shopDTO.getShopId(); // if (shopPostIdMap.containsKey(shopId)) { // shopPostIdMap.get(shopId).add(postId); // } else { // shopPostIdMap.put(shopId, new HashSet(){{add(postId);}}); // } // } // } // //管理者门店、岗位没有 // if (PublicUtil.isEmpty(shopPostIdMap)) { // continue; // } }