package com.klp.service.impl; import cn.hutool.core.bean.BeanUtil; import com.klp.common.core.page.TableDataInfo; import com.klp.common.core.domain.PageQuery; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.klp.common.utils.StringUtils; import com.klp.domain.vo.WmsAttendanceShiftRuleVo; import com.klp.domain.vo.WmsAttendanceShiftVo; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import com.klp.domain.bo.WmsAttendanceScheduleBo; import com.klp.domain.bo.GenerateScheduleBo; import com.klp.domain.bo.BatchUpdateScheduleBo; import com.klp.domain.vo.WmsAttendanceScheduleVo; import com.klp.domain.WmsAttendanceSchedule; import com.klp.domain.WmsAttendanceShiftRule; import com.klp.domain.WmsAttendanceShift; import com.klp.mapper.WmsAttendanceScheduleMapper; import com.klp.service.IWmsAttendanceScheduleService; import com.klp.service.IWmsAttendanceShiftRuleService; import com.klp.service.IWmsAttendanceShiftService; import java.time.LocalDate; import java.time.temporal.ChronoUnit; import java.util.*; import java.util.stream.Collectors; /** * 排班(谁在哪天上班)Service业务层处理 * * @author klp * @date 2026-05-08 */ @RequiredArgsConstructor @Service public class WmsAttendanceScheduleServiceImpl implements IWmsAttendanceScheduleService { private final WmsAttendanceScheduleMapper baseMapper; private final IWmsAttendanceShiftRuleService shiftRuleService; private final IWmsAttendanceShiftService shiftService; /** * 查询排班(谁在哪天上班) */ @Override public WmsAttendanceScheduleVo queryById(Long scheduleId){ WmsAttendanceSchedule schedule = baseMapper.selectById(scheduleId); if (schedule == null) { return null; } // 使用关联查询获取详细信息 List list = baseMapper.selectScheduleWithDetails( schedule.getUserId(), schedule.getWorkDate(), schedule.getShiftId(),null, null); return list.isEmpty() ? null : list.get(0); } /** * 查询排班(谁在哪天上班)列表 */ @Override public TableDataInfo queryPageList(WmsAttendanceScheduleBo bo, PageQuery pageQuery) { // 1. 构建 MP 标准分页对象 Page page = pageQuery.build(); // 2. 调用 Mapper baseMapper.selectScheduleWithDetailsPage(page, bo); // 3. 直接返回 return TableDataInfo.build(page); } /** * 查询排班(谁在哪天上班)列表 */ @Override public List queryList(WmsAttendanceScheduleBo bo) { return baseMapper.selectScheduleWithDetails(bo.getUserId(), bo.getWorkDate(), bo.getShiftId(), bo.getStartDate(), bo.getEndDate()); } /** * 新增排班(谁在哪天上班) */ @Override public Boolean insertByBo(WmsAttendanceScheduleBo bo) { WmsAttendanceSchedule add = BeanUtil.toBean(bo, WmsAttendanceSchedule.class); validEntityBeforeSave(add); boolean flag = baseMapper.insert(add) > 0; if (flag) { bo.setScheduleId(add.getScheduleId()); } return flag; } /** * 修改排班(谁在哪天上班) */ @Override public Boolean updateByBo(WmsAttendanceScheduleBo bo) { WmsAttendanceSchedule update = BeanUtil.toBean(bo, WmsAttendanceSchedule.class); validEntityBeforeSave(update); return baseMapper.updateById(update) > 0; } /** * 保存前的数据校验 */ private void validEntityBeforeSave(WmsAttendanceSchedule entity){ //TODO 做一些数据校验,如唯一约束 } /** * 批量删除排班(谁在哪天上班) */ @Override public Boolean deleteWithValidByIds(Collection ids, Boolean isValid) { if(isValid){ //TODO 做一些业务上的校验,判断是否需要校验 } return baseMapper.deleteBatchIds(ids) > 0; } /** * 生成排班 */ @Override @Transactional(rollbackFor = Exception.class) public void generateSchedule(List boList) { for (GenerateScheduleBo bo : boList) { LocalDate startDate = bo.getStartDate().toInstant().atZone(java.time.ZoneId.systemDefault()).toLocalDate(); LocalDate endDate = bo.getEndDate().toInstant().atZone(java.time.ZoneId.systemDefault()).toLocalDate(); if (startDate.isAfter(endDate)) { throw new RuntimeException("开始时间不能大于结束时间"); } if (bo.getRuleId() == null) { // 正常排班,不倒班 generateNormalSchedule(bo, startDate, endDate); } else { // 倒班排班 generateShiftSchedule(bo, startDate, endDate); } } } /** * 批量修改指定日期多个员工的班次 */ @Override @Transactional(rollbackFor = Exception.class) public void batchUpdateSchedule(BatchUpdateScheduleBo bo) { Date workDate = bo.getWorkDate(); Long shiftId = bo.getShiftId(); String shiftName = bo.getShiftName(); List userIds = bo.getUserIds(); baseMapper.update(null, Wrappers.lambdaUpdate() .in(WmsAttendanceSchedule::getUserId, userIds) .eq(WmsAttendanceSchedule::getWorkDate, workDate) .set(WmsAttendanceSchedule::getShiftId, shiftId) .set(WmsAttendanceSchedule::getShiftName, shiftName)); Set existingUserIds = baseMapper.selectList(Wrappers.lambdaQuery() .in(WmsAttendanceSchedule::getUserId, userIds) .eq(WmsAttendanceSchedule::getWorkDate, workDate) .select(WmsAttendanceSchedule::getUserId)) .stream() .map(WmsAttendanceSchedule::getUserId) .collect(Collectors.toSet()); List insertList = userIds.stream() .filter(uid -> !existingUserIds.contains(uid)) .map(uid -> { WmsAttendanceSchedule s = new WmsAttendanceSchedule(); s.setUserId(uid); s.setWorkDate(workDate); s.setShiftId(shiftId); s.setShiftName(shiftName); return s; }) .collect(Collectors.toList()); if (!insertList.isEmpty()) { baseMapper.insertBatch(insertList); } } /** * 生成正常排班 */ private void generateNormalSchedule(GenerateScheduleBo bo, LocalDate startDate, LocalDate endDate) { WmsAttendanceShiftVo shift = shiftService.queryById(bo.getShiftId()); if (shift == null) { throw new RuntimeException("班次不存在"); } // 一次查询整个日期范围内已存在的排班 Set existingDates = getExistingScheduleDates(bo.getUserId(), startDate, endDate); List schedules = new ArrayList<>(); LocalDate currentDate = startDate; while (!currentDate.isAfter(endDate)) { if (!existingDates.contains(currentDate)) { WmsAttendanceSchedule schedule = new WmsAttendanceSchedule(); schedule.setUserId(bo.getUserId()); schedule.setWorkDate(java.sql.Date.valueOf(currentDate)); schedule.setShiftId(bo.getShiftId()); schedule.setShiftName(shift.getShiftName()); schedules.add(schedule); } currentDate = currentDate.plusDays(1); } // 批量插入 if (!schedules.isEmpty()) { boolean ok = baseMapper.insertBatch(schedules); if (!ok) { throw new RuntimeException("排班插入失败"); } } } /** * 生成倒班排班 * 根据用户选择的倒班规则和起始日期,生成指定日期范围内的倒班排班表 * * @param bo 生成排班业务对象,包含用户ID、规则ID、起始班次等信息 * @param startDate 排班开始日期 * @param endDate 排班结束日期 */ private void generateShiftSchedule(GenerateScheduleBo bo, LocalDate startDate, LocalDate endDate) { // 获取倒班规则 // 通过规则ID查询倒班规则信息 WmsAttendanceShiftRuleVo rule = shiftRuleService.queryById(bo.getRuleId()); if (rule == null) { throw new RuntimeException("倒班规则不存在"); } // 验证班次匹配 // 检查用户选择的班次是否与规则中的班次匹配 if (!bo.getShiftId().equals(rule.getShiftA()) && !bo.getShiftId().equals(rule.getShiftB())) { throw new RuntimeException("班次ID与倒班规则不匹配"); } // 获取班次信息 // 查询规则中定义的所有班次信息,包括常规班次和特殊交接班次 WmsAttendanceShiftVo shiftA = shiftService.queryById(rule.getShiftA()); WmsAttendanceShiftVo shiftB = shiftService.queryById(rule.getShiftB()); WmsAttendanceShiftVo changeShiftB = rule.getChangeShiftBId() != null ? shiftService.queryById(rule.getChangeShiftBId()) : null; WmsAttendanceShiftVo changeShiftA = rule.getChangeShiftAId() != null ? shiftService.queryById(rule.getChangeShiftAId()) : null; if (shiftA == null || shiftB == null) { throw new RuntimeException("倒班规则中的班次不存在"); } // 一次查询整个日期范围内已存在的排班 // 查询用户在指定日期范围内已有的排班日期,避免重复生成 Set existingDates = getExistingScheduleDates(bo.getUserId(), startDate, endDate); List schedules = new ArrayList<>(); LocalDate currentDate = startDate; boolean isCurrentShiftA = bo.getShiftId().equals(rule.getShiftA()); boolean firstDay = true; while (!currentDate.isAfter(endDate)) { int dayOfMonth = currentDate.getDayOfMonth(); // 判断是否为交接班日(每月1日、11日、21日) boolean isSwapDay = !firstDay && (dayOfMonth == 1 || dayOfMonth == 11 || dayOfMonth == 21) && dayOfMonth != 31; if (!existingDates.contains(currentDate)) { WmsAttendanceSchedule schedule = new WmsAttendanceSchedule(); schedule.setUserId(bo.getUserId()); schedule.setWorkDate(java.sql.Date.valueOf(currentDate)); // 处理交接班日 if (isSwapDay) { if (isCurrentShiftA) { // 白班转夜班 if (changeShiftB != null) { schedule.setShiftId(rule.getChangeShiftBId()); schedule.setShiftName(changeShiftB.getShiftName()); } else { schedule.setShiftId(rule.getShiftB()); schedule.setShiftName(shiftB.getShiftName()); } } else { // 夜班转白班 if (changeShiftA != null) { schedule.setShiftId(rule.getChangeShiftAId()); schedule.setShiftName(changeShiftA.getShiftName()); } else { schedule.setShiftId(rule.getShiftA()); schedule.setShiftName(shiftA.getShiftName()); } } // 切换班次标识 isCurrentShiftA = !isCurrentShiftA; } else { // 普通日 if (isCurrentShiftA) { schedule.setShiftId(rule.getShiftA()); schedule.setShiftName(shiftA.getShiftName()); } else { schedule.setShiftId(rule.getShiftB()); schedule.setShiftName(shiftB.getShiftName()); } } schedules.add(schedule); } currentDate = currentDate.plusDays(1); firstDay = false; } if (!schedules.isEmpty()) { boolean ok = baseMapper.insertBatch(schedules); if (!ok) { throw new RuntimeException("批量插入倒班排班失败"); } } } /** * 批量查询指定用户日期范围内已存在的排班日期 */ private Set getExistingScheduleDates(Long userId, LocalDate startDate, LocalDate endDate) { LambdaQueryWrapper wrapper = Wrappers.lambdaQuery(); wrapper.eq(WmsAttendanceSchedule::getUserId, userId) .between(WmsAttendanceSchedule::getWorkDate, java.sql.Date.valueOf(startDate), java.sql.Date.valueOf(endDate)) .select(WmsAttendanceSchedule::getWorkDate); List list = baseMapper.selectList(wrapper); return list.stream() .map(s -> new java.sql.Date(s.getWorkDate().getTime()).toLocalDate()) .collect(Collectors.toSet()); } }