| | |
| | | package cn.iocoder.yudao.module.mes.service.cal.team; |
| | | |
| | | import cn.iocoder.yudao.module.mes.controller.admin.cal.team.vo.shift.MesCalTeamShiftListReqVO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.cal.holiday.MesCalHolidayDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.cal.plan.MesCalPlanDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.cal.plan.MesCalPlanShiftDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.cal.plan.MesCalPlanTeamDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.cal.team.MesCalTeamShiftDO; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.cal.team.MesCalTeamShiftMapper; |
| | | import cn.iocoder.yudao.module.mes.enums.cal.MesCalHolidayTypeEnum; |
| | | import cn.iocoder.yudao.module.mes.enums.cal.MesCalShiftMethodEnum; |
| | | import cn.iocoder.yudao.module.mes.enums.cal.MesCalShiftTypeEnum; |
| | | import cn.iocoder.yudao.module.mes.service.cal.holiday.MesCalHolidayService; |
| | | import cn.iocoder.yudao.module.mes.service.cal.plan.MesCalPlanService; |
| | | import cn.iocoder.yudao.module.mes.service.cal.plan.MesCalPlanShiftService; |
| | | import cn.iocoder.yudao.module.mes.service.cal.plan.MesCalPlanTeamService; |
| | |
| | | import org.springframework.transaction.annotation.Transactional; |
| | | import org.springframework.validation.annotation.Validated; |
| | | |
| | | import java.time.DayOfWeek; |
| | | import java.time.LocalDate; |
| | | import java.time.LocalDateTime; |
| | | import java.time.temporal.ChronoUnit; |
| | | import java.util.ArrayList; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | import java.util.stream.Collectors; |
| | | |
| | | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; |
| | | import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.CAL_TEAM_SHIFT_GENERATE_SHIFT_NOT_ENOUGH; |
| | |
| | | @Resource |
| | | @Lazy |
| | | private MesCalPlanTeamService planTeamService; |
| | | @Resource |
| | | @Lazy |
| | | private MesCalHolidayService holidayService; |
| | | |
| | | @Transactional(rollbackFor = Exception.class) |
| | | @Override |
| | |
| | | throw exception(CAL_TEAM_SHIFT_GENERATE_SHIFT_NOT_ENOUGH); |
| | | } |
| | | |
| | | // 2. 计算日期跨度,遍历每天生成排班记录,最后统一写入数据库 |
| | | // 1.5 加载日期区间内的假日数据,构建 Map<日期, 类型> |
| | | Map<LocalDate, Integer> holidayMap = holidayService.getHolidayList(plan.getStartDate(), plan.getEndDate()) |
| | | .stream().collect(Collectors.toMap(h -> h.getDay().toLocalDate(), MesCalHolidayDO::getType)); |
| | | |
| | | // 2. 计算日期跨度,逐天判断工作日,生成排班记录 |
| | | LocalDate startDate = plan.getStartDate().toLocalDate(); |
| | | LocalDate endDate = plan.getEndDate().toLocalDate(); |
| | | long days = ChronoUnit.DAYS.between(startDate, endDate) + 1; |
| | | boolean excludeWeekend = plan.getExcludeWeekend() != null && plan.getExcludeWeekend(); |
| | | List<MesCalTeamShiftDO> allRecords = new ArrayList<>(); |
| | | int shiftIndex = 0; |
| | | int workdayOffset = 0; // 仅工作日递增,用于倒班轮换计数 |
| | | for (int i = 0; i < days; i++) { |
| | | LocalDate currentDate = startDate.plusDays(i); |
| | | // 2.1 跳过非工作日 |
| | | if (!isWorkday(currentDate, holidayMap, excludeWeekend)) { |
| | | continue; |
| | | } |
| | | LocalDateTime currentDay = currentDate.atStartOfDay(); |
| | | // 2.2.1 根据倒班方式计算 shiftIndex |
| | | shiftIndex = calculateShiftIndex(shiftIndex, i, currentDate, startDate, plan); |
| | | // 2.2.1 根据倒班方式计算 shiftIndex(以工作日偏移为准) |
| | | shiftIndex = calculateShiftIndex(shiftIndex, workdayOffset, currentDate, startDate, plan); |
| | | // 2.2.2 根据轮班方式生成排班记录,收集到列表 |
| | | buildRecordsForDay(allRecords, planId, currentDay, shiftIndex, plan.getShiftType(), teams, shifts); |
| | | workdayOffset++; |
| | | } |
| | | |
| | | // 3. 批量插入排班记录 |
| | | teamShiftMapper.insertBatch(allRecords); |
| | | } |
| | | |
| | | /** |
| | | * 判断指定日期是否为工作日 |
| | | * |
| | | * 优先级:holiday 表 > 周末排除开关 > 默认工作日 |
| | | */ |
| | | private boolean isWorkday(LocalDate date, Map<LocalDate, Integer> holidayMap, boolean excludeWeekend) { |
| | | // 1. holiday 表优先:显式标记的日期以标记为准 |
| | | Integer holidayType = holidayMap.get(date); |
| | | if (holidayType != null) { |
| | | return MesCalHolidayTypeEnum.WORKDAY.getType().equals(holidayType); |
| | | } |
| | | // 2. 若开启了排除周末,周六日默认为休息日 |
| | | if (excludeWeekend) { |
| | | DayOfWeek dow = date.getDayOfWeek(); |
| | | if (dow == DayOfWeek.SATURDAY || dow == DayOfWeek.SUNDAY) { |
| | | return false; |
| | | } |
| | | } |
| | | // 3. 默认工作日 |
| | | return true; |
| | | } |
| | | |
| | | /** |
| | |
| | | if (dayOffset == 0) { |
| | | return shiftIndex; // 第一天不递增 |
| | | } |
| | | // 单白班不需要倒班,shiftMethod 为 null,直接返回 |
| | | if (plan.getShiftMethod() == null) { |
| | | return shiftIndex; |
| | | } |
| | | if (MesCalShiftMethodEnum.QUARTER.getMethod().equals(plan.getShiftMethod())) { |
| | | // 按季度轮班:到了新季度的第一天 |
| | | LocalDate quarterStart = LocalDateTimeUtils.getQuarterStart(currentDate); |