RosteringServiceImpl.java 13.4 KB
package com.pipihelper.project.rostering.service.impl;

import cn.afterturn.easypoi.excel.ExcelExportUtil;
import cn.afterturn.easypoi.excel.entity.ExportParams;
import cn.afterturn.easypoi.excel.entity.params.ExcelExportEntity;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.RandomUtil;
import com.google.common.collect.Lists;
import com.pipihelper.project.feishu.dao.RosteringStaffDao;
import com.pipihelper.project.feishu.entity.RosteringStaff;
import com.pipihelper.project.rostering.model.DateRuleModel;
import com.pipihelper.project.rostering.model.RosteringExportModel;
import com.pipihelper.project.rostering.model.RosteringModel;
import com.pipihelper.project.rostering.model.ShiftRuleModel;
import com.pipihelper.project.rostering.model.StaffRuleModel;
import com.pipihelper.project.rostering.service.RosteringService;
import org.apache.commons.lang3.StringUtils;
import org.apache.poi.ss.usermodel.Workbook;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * @description:
 * @author: zsw
 * @create: 2022-10-14 16:51
 **/
@Service
public class RosteringServiceImpl implements RosteringService {


    @Autowired
    private RosteringStaffDao rosteringStaffDao;

    @Override
    public Workbook export(List<RosteringExportModel> rosteringModels) {
        Set<String> names = rosteringModels.stream().map(RosteringExportModel::getName).collect(Collectors.toSet());
        Map<String, RosteringStaff> rosteringStaffMap = Optional.ofNullable(rosteringStaffDao.findByNames(names))
                .orElse(Lists.newArrayList())
                .stream()
                .collect(Collectors.toMap(RosteringStaff::getName, Function.identity()));

        Map<String, List<RosteringExportModel>> dataMap = rosteringModels.stream().collect(Collectors.groupingBy(RosteringExportModel::getName));


        List<ExcelExportEntity> colList = new ArrayList<>();
        ExcelExportEntity departmentCol = new ExcelExportEntity();
        departmentCol.setName("部门");
        departmentCol.setKey("department");
        departmentCol.setOrderNum(1);
        colList.add(departmentCol);

        ExcelExportEntity dingIdCol = new ExcelExportEntity();
        dingIdCol.setName("用户ID");
        dingIdCol.setKey("dingId");
        dingIdCol.setOrderNum(2);
        colList.add(dingIdCol);

        ExcelExportEntity jobNoCol = new ExcelExportEntity();
        jobNoCol.setName("工号");
        jobNoCol.setKey("jobNo");
        jobNoCol.setOrderNum(3);
        colList.add(jobNoCol);

        ExcelExportEntity nameCol = new ExcelExportEntity();
        nameCol.setName("姓名");
        nameCol.setKey("name");
        nameCol.setOrderNum(4);
        colList.add(nameCol);

        List<String> dates = rosteringModels.stream().map(e -> {
            String dateStr = DateUtil.format(e.getDate(), "yyyy/MM/dd");
            return dateStr + e.getWeek();
        }).distinct().collect(Collectors.toList());
        for (int i = 0; i < dates.size(); i++) {
            int orderNum = i + 5;
            String name = dates.get(i);
            ExcelExportEntity excelExportEntity = new ExcelExportEntity();
            excelExportEntity.setName(name);
            excelExportEntity.setKey(name);
            excelExportEntity.setOrderNum(orderNum);
            colList.add(excelExportEntity);
        }


        List<Map> datas = new ArrayList<>();
        dataMap.forEach((k, v) -> {

            Map<String, Object> map = new HashMap<>();
            map.put("name", k);

            RosteringStaff rosteringStaff = rosteringStaffMap.get(k);
            map.put("department", rosteringStaff.getDepartment());
            map.put("dingId", rosteringStaff.getDingId());
            map.put("jobNo", rosteringStaff.getJobNo());

            v.forEach(e -> {
                String dateStr = DateUtil.format(e.getDate(), "yyyy/MM/dd");
                String date = dateStr + e.getWeek();
                map.put(date, e.getShift());
            });
            datas.add(map);
        });
        return ExcelExportUtil.exportExcel(new ExportParams(), colList, datas);
    }

    @Override
    public void createStaff(RosteringStaff rosteringStaff) {

    }

    @Override
    public void deleteStaff(Integer id) {

    }

    @Override
    public void updateStaff(RosteringStaff rosteringStaff) {

    }

    @Override
    public List<RosteringStaff> findAllStaff() {
        return rosteringStaffDao.findAll();
    }

    @Override
    public List<RosteringModel> gen(Date date,
                                    List<ShiftRuleModel> shiftRuleModels,
                                    List<StaffRuleModel> staffRuleModels,
                                    List<DateRuleModel> dateRuleModels) {

        int daysOfMonth = getDaysOfNextMonth(date);

        Map<String, StaffRuleModel> staffRuleModelMap = staffRuleModels.stream().collect(Collectors.toMap(StaffRuleModel::getName, Function.identity()));


        //记录用户当次排版已经排了多少次
        Map<String, List<ShiftRuleModel>> staffMap = new HashMap<>();
        for (int i = 0; i < daysOfMonth; i++) {

            int currentDay = i + 1;

            int year = DateUtil.year(date);
            int month = DateUtil.month(date);
            Calendar calendar = Calendar.getInstance();
            calendar.set(year, month, currentDay);
            Date time = calendar.getTime();

            DateRuleModel dateRuleModel = dateRuleModels.stream().filter(e -> DateUtil.isSameDay(e.getDate(), time)).findAny().orElse(null);

            List<ShiftRuleModel> dateShifts = new ArrayList<>();

            staffRuleModels.forEach(e -> {
                List<ShiftRuleModel> currentShifts = Optional.ofNullable(staffMap.get(e.getName())).orElse(Lists.newArrayList());

                ShiftRuleModel shiftRuleModel = gen(e, shiftRuleModels, currentDay, currentShifts);

                //添加到map中
                currentShifts.add(shiftRuleModel);
                staffMap.putIfAbsent(e.getName(), currentShifts);

                dateShifts.add(shiftRuleModel);
            });

            //日期规则配置
            if (dateRuleModel != null) {
                Integer times = dateRuleModel.getTimes();
                String shift = dateRuleModel.getShift();
                int size = (int) dateShifts.stream().filter(e -> ObjectUtil.equal(e.getName(), shift)).count();

                //判断是否满足每天指定班次的人数
                if (size < times) {

                }

            }

        }

        List<RosteringModel> result = new ArrayList<>();
        staffRuleModels.forEach(e -> {
            List<String> shifts = Optional.ofNullable(staffMap.get(e.getName())).orElse(Lists.newArrayList())
                    .stream().map(ShiftRuleModel::getName).collect(Collectors.toList());

            StaffRuleModel.Threshold minTimes = e.getMinTimes();
            if (minTimes != null && minTimes.getTimes() != null && StringUtils.isNotBlank(minTimes.getShift())) {
                int count = (int) shifts.stream().filter(shift -> ObjectUtil.equal(shift, minTimes.getShift())).count();

                int diff = minTimes.getTimes() - count;
                if (diff > 0) {
                    int index = 0;
                    for (int i = 0; i < diff; i++) {
                        index = getRandomInt(shifts.size() - 1, index);
                        shifts.set(index, minTimes.getShift());
                    }
                }
            }

            RosteringModel rosteringModel = new RosteringModel();
            rosteringModel.setName(e.getName());
            rosteringModel.setShift(shifts);
            result.add(rosteringModel);
        });
        return result;
    }


    private ShiftRuleModel gen(StaffRuleModel staffRuleModel, List<ShiftRuleModel> shiftRuleModels, Integer currentDay, List<ShiftRuleModel> currentShifts) {
        //指定日期班次
        StaffRuleModel.Threshold fixedShift = staffRuleModel.getFixedShift();
        boolean isFixed = Optional.ofNullable(fixedShift).map(StaffRuleModel.Threshold::getDate).filter(fixedDate -> DateUtil.dayOfMonth(fixedDate) == currentDay).isPresent();
        ShiftRuleModel shiftRuleModel = null;
        if (isFixed) {
            shiftRuleModel = shiftRuleModels.stream().filter(shift -> ObjectUtil.equal(shift.getName(), fixedShift.getShift())).findFirst().orElse(null);
        }

        if (shiftRuleModel == null) {
            shiftRuleModel = getByShiftRule(shiftRuleModels, currentShifts, staffRuleModel);
        }

        //判断是否已经达到了最大次数
        StaffRuleModel.Threshold maxTimes = staffRuleModel.getMaxTimes();
        if (maxTimes != null && maxTimes.getTimes() != null && StringUtils.isNotBlank(maxTimes.getShift())) {
            String shift = maxTimes.getShift();
            int count = (int) currentShifts.stream().filter(result -> ObjectUtil.equal(result.getName(), shift)).count();
            count = ObjectUtil.equal(shiftRuleModel.getName(), shift) ? count + 1 : count;
            if (count > maxTimes.getTimes()) {
                shiftRuleModel = getRandom(shiftRuleModels.stream().filter(ignore -> ObjectUtil.notEqual(ignore.getName(), shift)).collect(Collectors.toList()));
            }
        }
        return shiftRuleModel;
    }

    /**
     * 获取某个月的天数
     *
     * @return
     */
    private int getDaysOfNextMonth(Date date) {
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(date);
        calendar.set(Calendar.DATE, 1);
        calendar.roll(Calendar.DATE, -1);
        return calendar.get(Calendar.DATE);
    }

    /**
     * 根据班次规则获取
     *
     * @param shiftRuleModels
     * @param currentShifts
     * @return
     */
    private ShiftRuleModel getByShiftRule(List<ShiftRuleModel> shiftRuleModels, List<ShiftRuleModel> currentShifts, StaffRuleModel staffRuleModel) {
        //随机获取,当前排班为null 则代表是第一次排班
        if (CollectionUtil.isEmpty(currentShifts)) {
            return getRandom(shiftRuleModels);
        }

        //获取最后一次分配的班次
        int size = currentShifts.size();
        ShiftRuleModel lastShift = currentShifts.get(size - 1);
        //获取最后一次分配的班次的连续次数
        int currentShiftTimes = 0;
        for (int i = size - 1; i >= 0; i--) {
            ShiftRuleModel currentShift = currentShifts.get(i);
            if (ObjectUtil.equal(lastShift.getName(), currentShift.getName())) {
                currentShiftTimes++;
            } else {
                break;
            }
        }

        //根据次数获取
        ShiftRuleModel result = getByTimes(shiftRuleModels, lastShift, currentShiftTimes);
        if (result == null) {
            //证明没有设置连续班,首先判断允许班次是否为空,不为空则从允许班次中随机获取一个班次
            List<String> backAllow = lastShift.getBackAllow();
            if (CollectionUtil.isNotEmpty(backAllow)) {
                result = getRandom(shiftRuleModels.stream().filter(e -> backAllow.contains(e.getName())).collect(Collectors.toList()));
            }

            //证明没有设置连续班,首先判断不允许允许班次是否为空,不为空则排除不允许班次随机获取班次
            List<String> backDenied = lastShift.getBackDenied();
            if (CollectionUtil.isNotEmpty(backDenied)) {
                result = getRandom(shiftRuleModels.stream().filter(e -> !backDenied.contains(e.getName())).collect(Collectors.toList()));
            }
        }

        if (result == null) {
            result = getRandom(shiftRuleModels);
        }

        //两个休息间的班次保证一致
        if (BooleanUtil.isFalse(result.isRest()) && BooleanUtil.isFalse(lastShift.isRest())) {
            result = shiftRuleModels
                    .stream()
                    .filter(e -> ObjectUtil.equal(e.getName(), lastShift.getName()))
                    .findFirst()
                    .orElse(null);
        }
        return result;
    }

    private ShiftRuleModel getByTimes(List<ShiftRuleModel> shiftRuleModels, ShiftRuleModel lastShift, Integer currentShiftTimes) {
        //当前班次的次数小于最大连续次数,则返回同样班次
        Integer maxContinuity = lastShift.getMaxContinuity();
        if (maxContinuity == null) {
            return null;
        }
        if (currentShiftTimes < maxContinuity) {
            return lastShift;
        }
        List<String> maxContinuityFollows = Optional.ofNullable(lastShift.getMaxContinuityFollow()).orElse(Lists.newArrayList());
        return shiftRuleModels.stream().filter(e -> maxContinuityFollows.contains(e.getName())).findFirst().orElse(null);
    }

    private ShiftRuleModel getRandom(List<ShiftRuleModel> shiftRuleModels) {
        return shiftRuleModels.get(RandomUtil.randomInt(0, shiftRuleModels.size()));
    }

    private int getRandomInt(int size, int index) {
        int i = RandomUtil.randomInt(0, size);
        if (i == index) {
            return getRandomInt(size, index);
        }
        return i;
    }

}