SalaryGroupBizService.java 17.9 KB
package cn.fw.morax.service.biz.salary;

import cn.fw.common.data.mybatis.pagination.PageData;
import cn.fw.common.exception.BusinessException;
import cn.fw.common.page.AppPage;
import cn.fw.common.web.annotation.DisLock;
import cn.fw.common.web.auth.LoginAuthBean;
import cn.fw.morax.common.pojo.event.ApprovalResultEvent;
import cn.fw.morax.common.pojo.event.SalaryGroupChangeEvent;
import cn.fw.morax.common.utils.EventBusUtil;
import cn.fw.morax.common.utils.MessageFormatUtil;
import cn.fw.morax.common.utils.PublicUtil;
import cn.fw.morax.domain.db.ApprovalRecord;
import cn.fw.morax.domain.db.kpi.KpiGroup;
import cn.fw.morax.domain.db.salary.SalaryGroup;
import cn.fw.morax.domain.db.salary.SalaryGroupProject;
import cn.fw.morax.domain.db.salary.SalaryGroupProjectSettin;
import cn.fw.morax.domain.db.salary.SalaryProject;
import cn.fw.morax.domain.dto.*;
import cn.fw.morax.domain.dto.query.SalaryGroupQueryDTO;
import cn.fw.morax.domain.dto.query.SalaryGroupRepeatQueryDTO;
import cn.fw.morax.domain.enums.EffectMonthEnum;
import cn.fw.morax.domain.enums.SettingStatusEnum;
import cn.fw.morax.domain.vo.salary.SalaryGroupProjectSettinVO;
import cn.fw.morax.domain.vo.salary.SalaryGroupProjectVO;
import cn.fw.morax.domain.vo.salary.SalaryGroupVO;
import cn.fw.morax.service.biz.ApprovalBizService;
import cn.fw.morax.service.biz.kpi.KpiGroupUserBizService;
import cn.fw.morax.service.data.salary.SalaryGroupProjectService;
import cn.fw.morax.service.data.salary.SalaryGroupProjectSettinService;
import cn.fw.morax.service.data.salary.SalaryGroupService;
import cn.fw.morax.service.data.salary.SalaryProjectService;
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.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.YearMonth;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

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

@RequiredArgsConstructor
@Service
@Slf4j
public class SalaryGroupBizService {

    private final SalaryGroupProjectSettinService salaryGroupProjectSettinService;
    private final SalaryGroupProjectService salaryGroupProjectService;
    private final SalaryProjectService salaryProjectService;
    private final SalaryGroupDataService salaryGroupDataService;
    private final KpiGroupUserBizService kpiGroupUserBizService;
    private final SalaryGroupService salaryGroupService;
    private final ApprovalBizService approvalBizService;

    @Value("${spring.cache.custom.global-prefix}:salary:group:save:")
    @Getter
    private String savePrefix;

    /**
     * 分页查询
     *
     * @param dto
     * @return
     */
    public AppPage<SalaryGroupVO> salaryGroupConfigPage(SalaryGroupQueryDTO dto) {
        PageData<SalaryGroup> pageData = salaryGroupService.page(new PageData<>(dto.getCurrent(), dto.getPageSize()),
                Wrappers.<SalaryGroup>lambdaQuery()
                        .eq(PublicUtil.isNotEmpty(dto.getPostId()), SalaryGroup::getPostId, dto.getPostId())
                        .eq(SalaryGroup::getGroupId, dto.getGroupId())
                        .apply(PublicUtil.isNotEmpty(dto.getShopId()),
                                MessageFormatUtil.transferWithQuotationMarks("FIND_IN_SET({0}, shop_ids)", dto.getShopId()))
                        .eq(SalaryGroup::getYn, Boolean.TRUE)
                        .last("ORDER BY FIELD(`status`,3,1,2,4)")
        );
        return PublicUtil.toPage(pageData, salaryGroup -> {
            return SalaryGroupVO.convertSalaryGroup(salaryGroup);
        });
    }

    /**
     * 薪酬组设置详情
     *
     * @param salaryGroupId
     * @return
     */
    public SalaryGroupVO getSalaryGroupDetail(Long salaryGroupId) {
        SalaryGroup salaryGroup = salaryGroupService.getById(salaryGroupId);
        BV.notNull(salaryGroup, "薪酬组配置不存在,请重试");

        SalaryGroupVO salaryGroupVo = SalaryGroupVO.convertSalaryGroup(salaryGroup);
        List<SalaryGroupProject> projects = salaryGroupProjectService.list(Wrappers.<SalaryGroupProject>lambdaQuery()
                .eq(SalaryGroupProject::getSalaryGroupId, salaryGroupId)
                .eq(SalaryGroupProject::getYn, Boolean.TRUE)
        );
        List<SalaryGroupProjectVO> projectVos = PublicUtil.copyList(projects, SalaryGroupProjectVO.class);
        List<Long> salaryGroupProjectIds = projectVos.stream().map(SalaryGroupProjectVO::getId).collect(Collectors.toList());
        if (PublicUtil.isEmpty(salaryGroupProjectIds)) {
            salaryGroupVo.setProjects(projectVos);
            return salaryGroupVo;
        }

        List<SalaryGroupProjectSettin> settings = salaryGroupProjectSettinService.list(Wrappers.<SalaryGroupProjectSettin>lambdaQuery()
                .in(SalaryGroupProjectSettin::getSalaryGroupProjectId, salaryGroupProjectIds)
                .eq(SalaryGroupProjectSettin::getYn, Boolean.TRUE)
                .orderByAsc(SalaryGroupProjectSettin::getId)
        );

        Map<Long, List<SalaryGroupProjectSettin>> settingMap = settings.stream().collect(Collectors.groupingBy(SalaryGroupProjectSettin::getSalaryGroupProjectId));
        for (SalaryGroupProjectVO projectVO : projectVos) {
            if (settingMap.containsKey(projectVO.getId())) {
                List<SalaryGroupProjectSettinVO> settingVOS = PublicUtil.copyList(settingMap.get(projectVO.getId()), SalaryGroupProjectSettinVO.class);
                projectVO.setSettings(settingVOS);
            }
        }
        salaryGroupVo.setProjects(projectVos);
        return salaryGroupVo;
    }

    /**
     * 薪酬组项目设置详情
     *
     * @param salaryGroupProjectId
     * @return
     */
    public SalaryGroupProjectVO getSalaryGroupProjectDetail(Long salaryGroupProjectId) {
        SalaryGroupProject salaryGroupProject = salaryGroupProjectService.getById(salaryGroupProjectId);
        BV.notNull(salaryGroupProject, "薪酬组配置不存在,请重试");
        SalaryGroupProjectVO projectVo = PublicUtil.copy(salaryGroupProject, SalaryGroupProjectVO.class);

        List<SalaryGroupProjectSettin> settings = salaryGroupProjectSettinService.list(Wrappers.<SalaryGroupProjectSettin>lambdaQuery()
                .eq(SalaryGroupProjectSettin::getSalaryGroupProjectId, salaryGroupProjectId)
                .eq(SalaryGroupProjectSettin::getYn, Boolean.TRUE)
                .orderByDesc(SalaryGroupProjectSettin::getId)
        );
        List<SalaryGroupProjectSettinVO> settingVos = PublicUtil.copyList(settings, SalaryGroupProjectSettinVO.class);
        projectVo.setSettings(settingVos);
        return projectVo;
    }

    /**
     * 保存薪酬项目组
     *
     * @param dto
     */
    @Transactional(rollbackFor = Exception.class)
    @DisLock(prefix = "#this.getSavePrefix()", key = "#dto.getPostId()", message = "保存中,请勿重复操作")
    public void saveSalaryGroup(SalaryGroupDTO dto, LoginAuthBean user) {
        this.initSalaryGroupDate(dto);
        this.checkSalaryGroup(dto);
        this.checkSalaryGroupProjectSetting(dto.getProjects());
        SalaryGroup salaryGroup = salaryGroupDataService.saveSalaryGroup(dto);
        salaryGroupDataService.saveSalaryGroupProjects(dto.getProjects(), salaryGroup);
        Integer staffNum = kpiGroupUserBizService.queryShopPostCurStaffNum(dto.getPostId(), dto.getShopIds());
        approvalBizService.applyApproveSalaryGroup(salaryGroup, user, staffNum);
    }


    /**
     * 检查绩效组配置状态
     *
     * @param dto
     */
    public void checkSalaryGroup(SalaryGroupDTO dto) {
        checkNameRepetition(dto.getId(), dto.getName());
        //原来的绩效组配置不能在审批中
        if (PublicUtil.isNotEmpty(dto.getId())) {
            SalaryGroup salaryGroup = salaryGroupService.getById(dto.getId());
            BV.notNull(salaryGroup, "薪酬组配置不存在,请重试");
            BV.isFalse((SettingStatusEnum.APPROVING.equals(salaryGroup.getStatus())), "审批中的薪酬组配置不能编辑");
            dto.setSgc(salaryGroup.getSgc());
        }

        Long postId = dto.getPostId();
        List<Long> shopIds = dto.getShopIds();
        List<SalaryGroup> approvingConfigs = salaryGroupService.getApprovingConfigs(postId, shopIds, dto.getId(), dto.getBeginTime());
        if (PublicUtil.isNotEmpty(approvingConfigs)) {
            SalaryGroup approving = approvingConfigs.get(0);
            List<String> shopNames = new ArrayList<>(approving.getShopNames());
            shopNames.retainAll(dto.getShopNames());
            throw new BusinessException("薪酬组门店【" + String.join(",", shopNames) + "】存在" +
                    approving.getStatus().getName() + "配置");
        }
    }

    /**
     * 检查薪酬组名称是否重复
     *
     * @param id
     * @param name
     */
    public void checkNameRepetition(Long id, String name) {
        int count = salaryGroupService.count(Wrappers.<SalaryGroup>lambdaQuery()
                .eq(SalaryGroup::getName, name)
                .ne(PublicUtil.isNotEmpty(id), SalaryGroup::getId, id)
                .eq(SalaryGroup::getYn, Boolean.TRUE)
        );
        BV.isTrue(count <= 0, "薪酬组名称重复,请重新输入");
    }

    /**
     * 检查指标修改的权限
     *
     * @param dto
     */
    private void initSalaryGroupDate(SalaryGroupDTO dto) {
        LocalDate currentTime = LocalDate.now();
        dto.setBeginTime((EffectMonthEnum.CURRENT_MONTH.equals(dto.getBeginTimeType()) ? currentTime : PublicUtil.getNextMonthFirstDay()));
    }

    /**
     * 检查薪酬计算方式
     *
     * @param projects
     */
    private void checkSalaryGroupProjectSetting(List<SalaryGroupProjectDTO> projects) {
        List<SalaryProject> salaryProjects = salaryProjectService.list(Wrappers.<SalaryProject>lambdaQuery()
                .in(SalaryProject::getId, projects.stream().map(SalaryGroupProjectDTO::getSalaryProjectId).collect(Collectors.toList()))
                .eq(SalaryProject::getYn, Boolean.TRUE)
        );
        Map<Long, SalaryProject> salaryProjectMap = salaryProjects.stream()
                .collect(Collectors.toMap(SalaryProject::getId, Function.identity(), (v1, v2) -> v2));
        Set<Long> projectIds = new HashSet<>();
        for (SalaryGroupProjectDTO project : projects) {
            if (projectIds.contains(project.getSalaryProjectId())) {
                throw new BusinessException("【" + project.getSalaryProjectName() + "】薪酬项目重复添加");
            }
            projectIds.add(project.getSalaryProjectId());
            if (! salaryProjectMap.containsKey(project.getSalaryProjectId())) {
                throw new BusinessException("【" + project.getSalaryProjectName() + "】薪酬项目不存在,请重试");
            }

//            Set<Integer> optionalMethods = salaryProjectMap.get(project.getSalaryProjectId()).getOptionalMethod()
//                    .stream().map(method -> Integer.parseInt(method.toString())).collect(Collectors.toSet());
            List<Integer> optionalMethods = salaryProjectMap.get(project.getSalaryProjectId()).getOptionalMethod();
            if (! optionalMethods.contains(project.getCalMethod().getValue())) {
                throw new BusinessException("【" + project.getSalaryProjectName() + "】薪酬项目计算方式选择错误,请重试");
            }

            switch (project.getCalMethod()) {
                case STAR: {
                    this.checkRepetition(project);
                    break;
                }
                case LADDER_FIXED:
                case LADDER_SINGLE: {
                    this.checkLadders(project.getSettings(), project.getSalaryProjectName());
                    break;
                }
            }

        }
    }

    /**
     * 检查薪酬项目阶梯
     *
     * @param settings
     * @param errorPromptName
     */
    private void checkLadders(List<SalaryGroupProjectSettinDTO> settings, String errorPromptName) {
        Collections.sort(settings, new Comparator<SalaryGroupProjectSettinDTO>() {
            @Override
            public int compare(SalaryGroupProjectSettinDTO o1, SalaryGroupProjectSettinDTO o2) {
                return o1.getStairMax().compareTo(o2.getStairMax());
            }
        });

        BigDecimal lastUpper = null;
        //阶梯值校验  标准分不能超过指标基础分,阶梯值必须连续
        for (SalaryGroupProjectSettinDTO dto : settings) {
            if (PublicUtil.isEmpty(dto.getStairMin()) || PublicUtil.isEmpty(dto.getStairMax()) ) {
                throw new BusinessException("【" + errorPromptName + "】的阶梯上限与下限不能为空,请编辑后重试");
            }
            BigDecimal lower = dto.getStairMin();
            BigDecimal upper = dto.getStairMax();
            //上一条的下限要等于下一条的上限
            if (PublicUtil.isNotEmpty(lastUpper) && (lastUpper.compareTo(lower) != 0) ) {
                throw new BusinessException("【" + errorPromptName + "】的阶梯上限与下限不是连续,请编辑后重试");
            }
            //每条上限大于下限
            if (lower.compareTo(upper) >= 0) {
                throw new BusinessException("【" + errorPromptName + "】的阶梯下限大于或等于阶梯上限,请修改错误阶梯");
            }
            lastUpper = dto.getStairMax();
        }
    }

    /**
     * 检查重复
     * @param project
     */
    private void checkRepetition(SalaryGroupProjectDTO project) {
        List<SalaryGroupProjectSettinDTO> settings = project.getSettings();
        Set<Long> stairKey = new HashSet<>();
        for (SalaryGroupProjectSettinDTO setting : settings) {
            if (! stairKey.add(setting.getStairKey())) {
                throw new BusinessException("【" + project.getSalaryProjectName() + "】出现重复配置,请修改");
            }
        }
    }


    /**
     * 薪酬项调整审批
     * @param approvalRecord
     * @param result
     */
    @Transactional(rollbackFor = Exception.class)
    public void approvalSalaryGroupConfig(ApprovalRecord approvalRecord, ApprovalResultEvent result) {
        log.info("收到薪酬组配置变更审批信息:{}", JSON.toJSONString(result));
        SalaryGroup salaryGroup = salaryGroupService.getById(approvalRecord.getDataId());
        if (! result.getAgree()) {
            salaryGroupDataService.modifyStatusById(salaryGroup, SettingStatusEnum.INEFFECTIVE);
            return;
        }
        //通知薪酬组人员
        SalaryGroupChangeEvent salaryGroupChangeEvent = PublicUtil.copy(salaryGroup, SalaryGroupChangeEvent.class);
        EventBusUtil.asyncPost(salaryGroupChangeEvent);

        YearMonth yearMonth = YearMonth.now();
        YearMonth beginTime = YearMonth.from(salaryGroup.getBeginTime());
        if (yearMonth.isAfter(beginTime)) {
            log.error("审批通过时间在薪酬组生效时间之后,审批无效");
            return;
        }

        //处理重复薪酬组配置数据
        this.processRepeatSalaryGroup(salaryGroup);
    }

    /**
     * 处理重复数据
     * 1. 次月之后生效,需要处理次月后重复的待生效数据
     * 2. 当月生效,   需要处理所有和当前重复的待生效数据、生效数据
     * @param salaryGroup
     */
    public void processRepeatSalaryGroup(SalaryGroup salaryGroup){
        YearMonth yearMonth = YearMonth.now();
        YearMonth beginTime = YearMonth.from(salaryGroup.getBeginTime());
        //删除重复的待生效配置数据(时间在此薪酬组配置之后、同岗位、同门店)
        SalaryGroupRepeatQueryDTO beEffectRepeatDTO = SalaryGroupRepeatQueryDTO.convertToBeEffectiveDTO(salaryGroup);
        List<SalaryGroup> beSalaryGroups = salaryGroupService.queryRepeatSalaryGroup(beEffectRepeatDTO);
        salaryGroupDataService.delSalaryGroups(beSalaryGroups);

        SettingStatusEnum status = SettingStatusEnum.BE_EFFECTIVE;
        //当月生效,处理当前重复的生效数据(次月生效,这一步延迟到次月进行)
        if (yearMonth.equals(beginTime)) {
            status = SettingStatusEnum.EFFECTIVE;
            SalaryGroupRepeatQueryDTO effectRepeatDTO = SalaryGroupRepeatQueryDTO.convertToEffectiveDTO(salaryGroup);
            List<SalaryGroup> effectKpiGroups = salaryGroupService.queryRepeatSalaryGroup(effectRepeatDTO);
            salaryGroupDataService.modifyStatusBySalaryGroups(effectKpiGroups, SettingStatusEnum.INEFFECTIVE);
        }

        salaryGroupDataService.modifyStatusById(salaryGroup, status);
    }


    /**
     * 获取正在使用指标的薪酬组
     * @param salaryProjectId
     * @return
     */
    public List<SalaryGroup> getSalaryGroupByIndicatorId(Long salaryProjectId) {
        List<SalaryGroupProject> salaryGroupIndicators = salaryGroupProjectService.list(Wrappers.<SalaryGroupProject>lambdaQuery()
                .eq(SalaryGroupProject::getSalaryProjectId, salaryProjectId)
        );
        if (PublicUtil.isEmpty(salaryGroupIndicators)) {
            return Lists.newArrayList();
        }
        List<SettingStatusEnum> status = new ArrayList<SettingStatusEnum>(){{
//            add(SettingStatusEnum.APPROVING);
//            add(SettingStatusEnum.BE_EFFECTIVE);
            add(SettingStatusEnum.EFFECTIVE);
        }};
        List<Long> kpiGroupIds = salaryGroupIndicators.stream().map(SalaryGroupProject::getSalaryGroupId).collect(Collectors.toList());
        List<SalaryGroup> salaryGroups = salaryGroupService.list(Wrappers.<SalaryGroup>lambdaQuery()
                .in(SalaryGroup::getId, kpiGroupIds)
                .in(SalaryGroup::getStatus, status)
                .eq(SalaryGroup::getYn, Boolean.TRUE)
        );
        if (PublicUtil.isEmpty(salaryGroups)) {
            return Lists.newArrayList();
        }

        return salaryGroups;
    }

}