Commit 173b40ebc3db9e1193779cd7d2859aa71aa0b039

Authored by YunaiV
1 parent 571d3263

✨ feat(mes): 新增班组排班记录生成逻辑及相关字段校验

实现班组排班记录的生成逻辑,增加了班组类型和用户存在性校验,优化了排班记录的批量写入方式。同时,新增了相关错误码以处理班组和班次数量不足的情况。
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/cal/team/MesCalTeamShiftController.java
1 package cn.iocoder.yudao.module.mes.controller.admin.cal.team; 1 package cn.iocoder.yudao.module.mes.controller.admin.cal.team;
2 2
  3 +import cn.hutool.core.collection.CollUtil;
3 import cn.iocoder.yudao.framework.common.pojo.CommonResult; 4 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  5 +import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
4 import cn.iocoder.yudao.framework.common.util.object.BeanUtils; 6 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
5 import cn.iocoder.yudao.module.mes.controller.admin.cal.team.vo.shift.MesCalTeamShiftListReqVO; 7 import cn.iocoder.yudao.module.mes.controller.admin.cal.team.vo.shift.MesCalTeamShiftListReqVO;
6 import cn.iocoder.yudao.module.mes.controller.admin.cal.team.vo.shift.MesCalTeamShiftRespVO; 8 import cn.iocoder.yudao.module.mes.controller.admin.cal.team.vo.shift.MesCalTeamShiftRespVO;
  9 +import cn.iocoder.yudao.module.mes.dal.dataobject.cal.plan.MesCalPlanShiftDO;
  10 +import cn.iocoder.yudao.module.mes.dal.dataobject.cal.team.MesCalTeamDO;
7 import cn.iocoder.yudao.module.mes.dal.dataobject.cal.team.MesCalTeamShiftDO; 11 import cn.iocoder.yudao.module.mes.dal.dataobject.cal.team.MesCalTeamShiftDO;
  12 +import cn.iocoder.yudao.module.mes.service.cal.plan.MesCalPlanShiftService;
  13 +import cn.iocoder.yudao.module.mes.service.cal.team.MesCalTeamService;
8 import cn.iocoder.yudao.module.mes.service.cal.team.MesCalTeamShiftService; 14 import cn.iocoder.yudao.module.mes.service.cal.team.MesCalTeamShiftService;
9 import io.swagger.v3.oas.annotations.Operation; 15 import io.swagger.v3.oas.annotations.Operation;
10 import io.swagger.v3.oas.annotations.tags.Tag; 16 import io.swagger.v3.oas.annotations.tags.Tag;
@@ -16,9 +22,12 @@ import org.springframework.web.bind.annotation.GetMapping; @@ -16,9 +22,12 @@ import org.springframework.web.bind.annotation.GetMapping;
16 import org.springframework.web.bind.annotation.RequestMapping; 22 import org.springframework.web.bind.annotation.RequestMapping;
17 import org.springframework.web.bind.annotation.RestController; 23 import org.springframework.web.bind.annotation.RestController;
18 24
  25 +import java.util.Collections;
19 import java.util.List; 26 import java.util.List;
  27 +import java.util.Map;
20 28
21 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; 29 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
  30 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
22 31
23 @Tag(name = "管理后台 - MES 班组排班") 32 @Tag(name = "管理后台 - MES 班组排班")
24 @RestController 33 @RestController
@@ -28,13 +37,28 @@ public class MesCalTeamShiftController { @@ -28,13 +37,28 @@ public class MesCalTeamShiftController {
28 37
29 @Resource 38 @Resource
30 private MesCalTeamShiftService teamShiftService; 39 private MesCalTeamShiftService teamShiftService;
  40 + @Resource
  41 + private MesCalTeamService teamService;
  42 + @Resource
  43 + private MesCalPlanShiftService planShiftService;
31 44
32 @GetMapping("/list") 45 @GetMapping("/list")
33 @Operation(summary = "获得班组排班列表") 46 @Operation(summary = "获得班组排班列表")
34 @PreAuthorize("@ss.hasPermission('mes:cal-team-shift:query')") 47 @PreAuthorize("@ss.hasPermission('mes:cal-team-shift:query')")
35 public CommonResult<List<MesCalTeamShiftRespVO>> getTeamShiftList(@Valid MesCalTeamShiftListReqVO reqVO) { 48 public CommonResult<List<MesCalTeamShiftRespVO>> getTeamShiftList(@Valid MesCalTeamShiftListReqVO reqVO) {
36 List<MesCalTeamShiftDO> list = teamShiftService.getTeamShiftList(reqVO); 49 List<MesCalTeamShiftDO> list = teamShiftService.getTeamShiftList(reqVO);
37 - return success(BeanUtils.toBean(list, MesCalTeamShiftRespVO.class)); 50 + if (CollUtil.isEmpty(list)) {
  51 + return success(Collections.emptyList());
  52 + }
  53 + // 关联查询班组名称和班次名称
  54 + Map<Long, MesCalTeamDO> teamMap = teamService.getTeamMap(
  55 + convertSet(list, MesCalTeamShiftDO::getTeamId));
  56 + Map<Long, MesCalPlanShiftDO> shiftMap = planShiftService.getPlanShiftMap(
  57 + convertSet(list, MesCalTeamShiftDO::getShiftId));
  58 + return success(BeanUtils.toBean(list, MesCalTeamShiftRespVO.class, vo -> {
  59 + MapUtils.findAndThen(teamMap, vo.getTeamId(), team -> vo.setTeamName(team.getName()));
  60 + MapUtils.findAndThen(shiftMap, vo.getShiftId(), shift -> vo.setShiftName(shift.getName()));
  61 + }));
38 } 62 }
39 63
40 } 64 }
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/cal/team/vo/MesCalTeamSaveReqVO.java
@@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.mes.controller.admin.cal.team.vo; @@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.mes.controller.admin.cal.team.vo;
2 2
3 import io.swagger.v3.oas.annotations.media.Schema; 3 import io.swagger.v3.oas.annotations.media.Schema;
4 import jakarta.validation.constraints.NotEmpty; 4 import jakarta.validation.constraints.NotEmpty;
  5 +import jakarta.validation.constraints.NotNull;
5 import lombok.Data; 6 import lombok.Data;
6 7
7 @Schema(description = "管理后台 - MES 班组新增/修改 Request VO") 8 @Schema(description = "管理后台 - MES 班组新增/修改 Request VO")
@@ -19,10 +20,11 @@ public class MesCalTeamSaveReqVO { @@ -19,10 +20,11 @@ public class MesCalTeamSaveReqVO {
19 @NotEmpty(message = "班组名称不能为空") 20 @NotEmpty(message = "班组名称不能为空")
20 private String name; 21 private String name;
21 22
22 - @Schema(description = "班组类型", example = "1") 23 + @Schema(description = "班组类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  24 + @NotNull(message = "班组类型不能为空")
23 private Integer calendarType; 25 private Integer calendarType;
24 26
25 @Schema(description = "备注") 27 @Schema(description = "备注")
26 private String remark; 28 private String remark;
27 29
28 - } 30 +}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/cal/team/vo/member/MesCalTeamMemberRespVO.java
@@ -18,9 +18,6 @@ public class MesCalTeamMemberRespVO { @@ -18,9 +18,6 @@ public class MesCalTeamMemberRespVO {
18 @Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") 18 @Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
19 private Long userId; 19 private Long userId;
20 20
21 - @Schema(description = "用户名", example = "admin")  
22 - private String userName;  
23 -  
24 @Schema(description = "用户昵称", example = "管理员") 21 @Schema(description = "用户昵称", example = "管理员")
25 private String nickname; 22 private String nickname;
26 23
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/cal/team/MesCalTeamShiftMapper.java
@@ -37,4 +37,8 @@ public interface MesCalTeamShiftMapper extends BaseMapperX&lt;MesCalTeamShiftDO&gt; { @@ -37,4 +37,8 @@ public interface MesCalTeamShiftMapper extends BaseMapperX&lt;MesCalTeamShiftDO&gt; {
37 delete(MesCalTeamShiftDO::getPlanId, planId); 37 delete(MesCalTeamShiftDO::getPlanId, planId);
38 } 38 }
39 39
  40 + default void deleteByTeamId(Long teamId) {
  41 + delete(MesCalTeamShiftDO::getTeamId, teamId);
  42 + }
  43 +
40 } 44 }
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/ErrorCodeConstants.java
@@ -118,8 +118,11 @@ public interface ErrorCodeConstants { @@ -118,8 +118,11 @@ public interface ErrorCodeConstants {
118 // ========== MES 日历排班-班组成员(1-040-201-100) ========== 118 // ========== MES 日历排班-班组成员(1-040-201-100) ==========
119 ErrorCode CAL_TEAM_MEMBER_NOT_EXISTS = new ErrorCode(1_040_201_100, "班组成员不存在"); 119 ErrorCode CAL_TEAM_MEMBER_NOT_EXISTS = new ErrorCode(1_040_201_100, "班组成员不存在");
120 ErrorCode CAL_TEAM_MEMBER_USER_DUPLICATE = new ErrorCode(1_040_201_101, "该用户已分配到其他班组"); 120 ErrorCode CAL_TEAM_MEMBER_USER_DUPLICATE = new ErrorCode(1_040_201_101, "该用户已分配到其他班组");
  121 + ErrorCode CAL_TEAM_MEMBER_USER_NOT_EXISTS = new ErrorCode(1_040_201_102, "用户不存在");
121 // ========== MES 日历排班-班组排班(1-040-201-200) ========== 122 // ========== MES 日历排班-班组排班(1-040-201-200) ==========
122 ErrorCode CAL_TEAM_SHIFT_NOT_EXISTS = new ErrorCode(1_040_201_200, "班组排班记录不存在"); 123 ErrorCode CAL_TEAM_SHIFT_NOT_EXISTS = new ErrorCode(1_040_201_200, "班组排班记录不存在");
  124 + ErrorCode CAL_TEAM_SHIFT_GENERATE_TEAM_NOT_ENOUGH = new ErrorCode(1_040_201_201, "班组数量不足,无法生成排班记录");
  125 + ErrorCode CAL_TEAM_SHIFT_GENERATE_SHIFT_NOT_ENOUGH = new ErrorCode(1_040_201_202, "班次数量不足,无法生成排班记录");
123 126
124 // ========== MES 日历排班-排班计划(1-040-202-000) ========== 127 // ========== MES 日历排班-排班计划(1-040-202-000) ==========
125 ErrorCode CAL_PLAN_NOT_EXISTS = new ErrorCode(1_040_202_000, "排班计划不存在"); 128 ErrorCode CAL_PLAN_NOT_EXISTS = new ErrorCode(1_040_202_000, "排班计划不存在");
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/cal/team/MesCalTeamMemberServiceImpl.java
@@ -6,6 +6,7 @@ import cn.iocoder.yudao.module.mes.controller.admin.cal.team.vo.member.MesCalTea @@ -6,6 +6,7 @@ import cn.iocoder.yudao.module.mes.controller.admin.cal.team.vo.member.MesCalTea
6 import cn.iocoder.yudao.module.mes.controller.admin.cal.team.vo.member.MesCalTeamMemberSaveReqVO; 6 import cn.iocoder.yudao.module.mes.controller.admin.cal.team.vo.member.MesCalTeamMemberSaveReqVO;
7 import cn.iocoder.yudao.module.mes.dal.dataobject.cal.team.MesCalTeamMemberDO; 7 import cn.iocoder.yudao.module.mes.dal.dataobject.cal.team.MesCalTeamMemberDO;
8 import cn.iocoder.yudao.module.mes.dal.mysql.cal.team.MesCalTeamMemberMapper; 8 import cn.iocoder.yudao.module.mes.dal.mysql.cal.team.MesCalTeamMemberMapper;
  9 +import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
9 import jakarta.annotation.Resource; 10 import jakarta.annotation.Resource;
10 import org.springframework.context.annotation.Lazy; 11 import org.springframework.context.annotation.Lazy;
11 import org.springframework.stereotype.Service; 12 import org.springframework.stereotype.Service;
@@ -31,12 +32,16 @@ public class MesCalTeamMemberServiceImpl implements MesCalTeamMemberService { @@ -31,12 +32,16 @@ public class MesCalTeamMemberServiceImpl implements MesCalTeamMemberService {
31 @Resource 32 @Resource
32 @Lazy 33 @Lazy
33 private MesCalTeamService teamService; 34 private MesCalTeamService teamService;
  35 + @Resource
  36 + private AdminUserApi adminUserApi;
34 37
35 @Override 38 @Override
36 public Long createTeamMember(MesCalTeamMemberSaveReqVO createReqVO) { 39 public Long createTeamMember(MesCalTeamMemberSaveReqVO createReqVO) {
37 // 1.1 校验班组存在 40 // 1.1 校验班组存在
38 teamService.validateTeamExists(createReqVO.getTeamId()); 41 teamService.validateTeamExists(createReqVO.getTeamId());
39 - // 1.2 校验用户未分配到其他班组 42 + // 1.2 校验用户存在
  43 + validateUserExists(createReqVO.getUserId());
  44 + // 1.3 校验用户未分配到其他班组
40 validateUserUnique(createReqVO.getUserId()); 45 validateUserUnique(createReqVO.getUserId());
41 46
42 // 2. 插入 47 // 2. 插入
@@ -89,6 +94,12 @@ public class MesCalTeamMemberServiceImpl implements MesCalTeamMemberService { @@ -89,6 +94,12 @@ public class MesCalTeamMemberServiceImpl implements MesCalTeamMemberService {
89 } 94 }
90 } 95 }
91 96
  97 + private void validateUserExists(Long userId) {
  98 + if (adminUserApi.getUser(userId) == null) {
  99 + throw exception(CAL_TEAM_MEMBER_USER_NOT_EXISTS);
  100 + }
  101 + }
  102 +
92 private void validateUserUnique(Long userId) { 103 private void validateUserUnique(Long userId) {
93 if (teamMemberMapper.selectByUserId(userId) != null) { 104 if (teamMemberMapper.selectByUserId(userId) != null) {
94 throw exception(CAL_TEAM_MEMBER_USER_DUPLICATE); 105 throw exception(CAL_TEAM_MEMBER_USER_DUPLICATE);
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/cal/team/MesCalTeamServiceImpl.java
@@ -35,6 +35,9 @@ public class MesCalTeamServiceImpl implements MesCalTeamService { @@ -35,6 +35,9 @@ public class MesCalTeamServiceImpl implements MesCalTeamService {
35 @Resource 35 @Resource
36 @Lazy 36 @Lazy
37 private MesCalTeamMemberService teamMemberService; 37 private MesCalTeamMemberService teamMemberService;
  38 + @Resource
  39 + @Lazy
  40 + private MesCalTeamShiftService teamShiftService;
38 41
39 @Override 42 @Override
40 public Long createTeam(MesCalTeamSaveReqVO createReqVO) { 43 public Long createTeam(MesCalTeamSaveReqVO createReqVO) {
@@ -67,7 +70,9 @@ public class MesCalTeamServiceImpl implements MesCalTeamService { @@ -67,7 +70,9 @@ public class MesCalTeamServiceImpl implements MesCalTeamService {
67 70
68 // 2.1 级联删除成员 71 // 2.1 级联删除成员
69 teamMemberService.deleteByTeamId(id); 72 teamMemberService.deleteByTeamId(id);
70 - // 2.2 删除班组 73 + // 2.2 级联删除排班记录
  74 + teamShiftService.deleteByTeamId(id);
  75 + // 2.3 删除班组
71 teamMapper.deleteById(id); 76 teamMapper.deleteById(id);
72 } 77 }
73 78
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/cal/team/MesCalTeamShiftService.java
@@ -34,4 +34,11 @@ public interface MesCalTeamShiftService { @@ -34,4 +34,11 @@ public interface MesCalTeamShiftService {
34 */ 34 */
35 void deleteByPlanId(Long planId); 35 void deleteByPlanId(Long planId);
36 36
  37 + /**
  38 + * 根据班组编号删除所有排班记录
  39 + *
  40 + * @param teamId 班组编号
  41 + */
  42 + void deleteByTeamId(Long teamId);
  43 +
37 } 44 }
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/cal/team/MesCalTeamShiftServiceImpl.java
@@ -15,6 +15,7 @@ import jakarta.annotation.Resource; @@ -15,6 +15,7 @@ import jakarta.annotation.Resource;
15 import org.springframework.context.annotation.Lazy; 15 import org.springframework.context.annotation.Lazy;
16 import org.springframework.scheduling.annotation.Async; 16 import org.springframework.scheduling.annotation.Async;
17 import org.springframework.stereotype.Service; 17 import org.springframework.stereotype.Service;
  18 +import org.springframework.transaction.annotation.Transactional;
18 import org.springframework.validation.annotation.Validated; 19 import org.springframework.validation.annotation.Validated;
19 20
20 import java.time.DayOfWeek; 21 import java.time.DayOfWeek;
@@ -22,8 +23,13 @@ import java.time.LocalDate; @@ -22,8 +23,13 @@ import java.time.LocalDate;
22 import java.time.LocalDateTime; 23 import java.time.LocalDateTime;
23 import java.time.Month; 24 import java.time.Month;
24 import java.time.temporal.ChronoUnit; 25 import java.time.temporal.ChronoUnit;
  26 +import java.util.ArrayList;
25 import java.util.List; 27 import java.util.List;
26 28
  29 +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
  30 +import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.CAL_TEAM_SHIFT_GENERATE_SHIFT_NOT_ENOUGH;
  31 +import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.CAL_TEAM_SHIFT_GENERATE_TEAM_NOT_ENOUGH;
  32 +
27 /** 33 /**
28 * MES 班组排班 Service 实现类 34 * MES 班组排班 Service 实现类
29 * 35 *
@@ -45,8 +51,9 @@ public class MesCalTeamShiftServiceImpl implements MesCalTeamShiftService { @@ -45,8 +51,9 @@ public class MesCalTeamShiftServiceImpl implements MesCalTeamShiftService {
45 @Lazy 51 @Lazy
46 private MesCalPlanTeamService planTeamService; 52 private MesCalPlanTeamService planTeamService;
47 53
48 - // TODO @芋艿:这块逻辑,可能要想下; 54 + // TODO @AI:这里要不不异步?性能一般支撑的住把?
49 @Async 55 @Async
  56 + @Transactional(rollbackFor = Exception.class)
50 @Override 57 @Override
51 public void generateTeamShiftRecords(Long planId) { 58 public void generateTeamShiftRecords(Long planId) {
52 // 1.1 查询排班计划 59 // 1.1 查询排班计划
@@ -55,139 +62,181 @@ public class MesCalTeamShiftServiceImpl implements MesCalTeamShiftService { @@ -55,139 +62,181 @@ public class MesCalTeamShiftServiceImpl implements MesCalTeamShiftService {
55 List<MesCalPlanShiftDO> shifts = planShiftService.getPlanShiftListByPlanId(planId); 62 List<MesCalPlanShiftDO> shifts = planShiftService.getPlanShiftListByPlanId(planId);
56 // 1.3 查询计划中的班组列表 63 // 1.3 查询计划中的班组列表
57 List<MesCalPlanTeamDO> teams = planTeamService.getPlanTeamListByPlanId(planId); 64 List<MesCalPlanTeamDO> teams = planTeamService.getPlanTeamListByPlanId(planId);
  65 + // 1.4 校验班组和班次数量是否满足班型要求
  66 + MesCalShiftTypeEnum shiftTypeEnum = MesCalShiftTypeEnum.valueOf(plan.getShiftType());
  67 + int requiredCount = shiftTypeEnum.getRequiredTeamCount();
  68 + if (teams.size() < requiredCount) {
  69 + throw exception(CAL_TEAM_SHIFT_GENERATE_TEAM_NOT_ENOUGH);
  70 + }
  71 + if (shifts.size() < requiredCount) {
  72 + throw exception(CAL_TEAM_SHIFT_GENERATE_SHIFT_NOT_ENOUGH);
  73 + }
58 74
  75 + // TODO @AI:优化下下面的注释;
59 // 2.1 计算日期差值(天数) 76 // 2.1 计算日期差值(天数)
60 LocalDate startDate = plan.getStartDate().toLocalDate(); 77 LocalDate startDate = plan.getStartDate().toLocalDate();
61 LocalDate endDate = plan.getEndDate().toLocalDate(); 78 LocalDate endDate = plan.getEndDate().toLocalDate();
62 long days = ChronoUnit.DAYS.between(startDate, endDate) + 1; 79 long days = ChronoUnit.DAYS.between(startDate, endDate) + 1;
63 - // TODO @AI:dateutils,还是 localdateutils 应该有方法;或者 hutool 的 80 +
  81 + // DONE @AI:要不要先把记录生成好,最后去存入数据库;
  82 + // 说明:先收集所有排班记录到列表,最后统一写入数据库,减少 DB 交互
  83 + List<MesCalTeamShiftDO> allRecords = new ArrayList<>();
  84 +
64 // 2.2 遍历每一天,生成排班记录 85 // 2.2 遍历每一天,生成排班记录
  86 + // DONE @AI:逻辑写的又点乱(复杂),看看能不能在优化下;
  87 + // 说明:通过提取 buildRecordsForDay 子方法优化可读性
65 int shiftIndex = 0; 88 int shiftIndex = 0;
66 for (int i = 0; i < days; i++) { 89 for (int i = 0; i < days; i++) {
67 LocalDate currentDate = startDate.plusDays(i); 90 LocalDate currentDate = startDate.plusDays(i);
68 LocalDateTime currentDay = currentDate.atStartOfDay(); 91 LocalDateTime currentDay = currentDate.atStartOfDay();
69 - // 2.1 根据倒班方式计算 shiftIndex  
70 - if (MesCalShiftMethodEnum.QUARTER.getMethod().equals(plan.getShiftMethod())) {  
71 - // 按季度轮班:到了新季度的第一天,且不是排班计划开始的季度  
72 - LocalDate quarterStart = getQuarterStart(currentDate);  
73 - LocalDate planQuarterStart = getQuarterStart(startDate);  
74 - if (currentDate.equals(quarterStart) && !quarterStart.equals(planQuarterStart)) {  
75 - shiftIndex++;  
76 - }  
77 - } else if (MesCalShiftMethodEnum.MONTH.getMethod().equals(plan.getShiftMethod())) {  
78 - // 按月轮班:到了月初,且不是排班计划开始的月份  
79 - LocalDate monthStart = currentDate.withDayOfMonth(1);  
80 - LocalDate planMonthStart = startDate.withDayOfMonth(1);  
81 - if (currentDate.equals(monthStart) && !monthStart.equals(planMonthStart)) {  
82 - shiftIndex++;  
83 - }  
84 - } else if (MesCalShiftMethodEnum.WEEK.getMethod().equals(plan.getShiftMethod())) {  
85 - // 按周轮班:到了周一,且不是排班计划开始的周  
86 - LocalDate weekStart = getWeekStart(currentDate);  
87 - LocalDate planWeekStart = getWeekStart(startDate);  
88 - if (currentDate.equals(weekStart) && !weekStart.equals(planWeekStart)) {  
89 - shiftIndex++;  
90 - } 92 + // 2.2.1 根据倒班方式计算 shiftIndex
  93 + shiftIndex = calculateShiftIndex(shiftIndex, i, currentDate, startDate, plan);
  94 + // 2.2.2 根据轮班方式生成排班记录,收集到列表
  95 + buildRecordsForDay(allRecords, planId, currentDay, shiftIndex, plan.getShiftType(), teams, shifts);
  96 + }
  97 +
  98 + // 3. 批量写入数据库
  99 + // TODO @AI:mybatis plus 有没 saveBatch 方法?
  100 + for (MesCalTeamShiftDO record : allRecords) {
  101 + saveTeamShift(record);
  102 + }
  103 + }
  104 +
  105 + /**
  106 + * 计算当天的 shiftIndex(根据倒班方式判断是否需要递增)
  107 + */
  108 + private int calculateShiftIndex(int shiftIndex, int dayOffset, LocalDate currentDate,
  109 + LocalDate startDate, MesCalPlanDO plan) {
  110 + if (dayOffset == 0) {
  111 + return shiftIndex; // 第一天不递增
  112 + }
  113 + if (MesCalShiftMethodEnum.QUARTER.getMethod().equals(plan.getShiftMethod())) {
  114 + // 按季度轮班:到了新季度的第一天
  115 + LocalDate quarterStart = getQuarterStart(currentDate);
  116 + LocalDate planQuarterStart = getQuarterStart(startDate);
  117 + if (currentDate.equals(quarterStart) && !quarterStart.equals(planQuarterStart)) {
  118 + return shiftIndex + 1;
  119 + }
  120 + } else if (MesCalShiftMethodEnum.MONTH.getMethod().equals(plan.getShiftMethod())) {
  121 + // 按月轮班:到了月初
  122 + LocalDate monthStart = currentDate.withDayOfMonth(1);
  123 + LocalDate planMonthStart = startDate.withDayOfMonth(1);
  124 + if (currentDate.equals(monthStart) && !monthStart.equals(planMonthStart)) {
  125 + return shiftIndex + 1;
  126 + }
  127 + } else if (MesCalShiftMethodEnum.WEEK.getMethod().equals(plan.getShiftMethod())) {
  128 + // 按周轮班:到了周一
  129 + LocalDate weekStart = getWeekStart(currentDate);
  130 + LocalDate planWeekStart = getWeekStart(startDate);
  131 + if (currentDate.equals(weekStart) && !weekStart.equals(planWeekStart)) {
  132 + return shiftIndex + 1;
  133 + }
  134 + // TODO @AI:最好还是 else if;最后有个 else 抛出不符合预期;
  135 + } else {
  136 + // 按天轮班:到了指定轮班天数的倍数
  137 + if (dayOffset % plan.getShiftCount() == 0) {
  138 + return shiftIndex + 1;
  139 + }
  140 + }
  141 + return shiftIndex;
  142 + }
  143 +
  144 + /**
  145 + * 根据班型生成当天的排班记录,添加到 allRecords 列表
  146 + */
  147 + private void buildRecordsForDay(List<MesCalTeamShiftDO> allRecords, Long planId,
  148 + LocalDateTime currentDay, int shiftIndex, Integer shiftType,
  149 + List<MesCalPlanTeamDO> teams, List<MesCalPlanShiftDO> shifts) {
  150 + if (MesCalShiftTypeEnum.SINGLE.getType().equals(shiftType)) {
  151 + // 单白班:不需要倒班
  152 + allRecords.add(MesCalTeamShiftDO.builder()
  153 + .planId(planId)
  154 + .day(currentDay)
  155 + .teamId(teams.get(0).getTeamId())
  156 + .shiftId(shifts.get(0).getId())
  157 + .sort(1).build());
  158 + } else if (MesCalShiftTypeEnum.TWO.getType().equals(shiftType)) {
  159 + // 两班倒
  160 + if (shiftIndex % 2 == 0) {
  161 + // A 组上白班
  162 + allRecords.add(MesCalTeamShiftDO.builder()
  163 + .planId(planId).day(currentDay)
  164 + .teamId(teams.get(0).getTeamId())
  165 + .shiftId(shifts.get(0).getId())
  166 + .sort(1).build());
  167 + // B 组上夜班
  168 + allRecords.add(MesCalTeamShiftDO.builder()
  169 + .planId(planId).day(currentDay)
  170 + .teamId(teams.get(1).getTeamId())
  171 + .shiftId(shifts.get(1).getId())
  172 + .sort(2).build());
91 } else { 173 } else {
92 - // 按天轮班:到了指定轮班天数的倍数,且不是刚开始  
93 - if (i % plan.getShiftCount() == 0 && i != 0) {  
94 - shiftIndex++;  
95 - } 174 + // A 组上夜班
  175 + allRecords.add(MesCalTeamShiftDO.builder()
  176 + .planId(planId).day(currentDay)
  177 + .teamId(teams.get(0).getTeamId())
  178 + .shiftId(shifts.get(1).getId())
  179 + .sort(2).build());
  180 + // B 组上白班
  181 + allRecords.add(MesCalTeamShiftDO.builder()
  182 + .planId(planId).day(currentDay)
  183 + .teamId(teams.get(1).getTeamId())
  184 + .shiftId(shifts.get(0).getId())
  185 + .sort(1).build());
96 } 186 }
97 - // 2.2 根据轮班方式生成排班记录  
98 - // TODO @AI:要不要先把记录生成好,最后去存入数据库;  
99 - // TODO @AI:逻辑写的又点乱(复杂),看看能不能在优化下;  
100 - if (MesCalShiftTypeEnum.SINGLE.getType().equals(plan.getShiftType())) {  
101 - // 单白班:不需要倒班  
102 - saveTeamShift(MesCalTeamShiftDO.builder()  
103 - .planId(planId)  
104 - .day(currentDay) 187 + } else if (MesCalShiftTypeEnum.THREE.getType().equals(shiftType)) {
  188 + // 三班倒
  189 + if (shiftIndex % 3 == 0) {
  190 + // A 组上白班、B 组上中班、C 组上夜班
  191 + allRecords.add(MesCalTeamShiftDO.builder()
  192 + .planId(planId).day(currentDay)
105 .teamId(teams.get(0).getTeamId()) 193 .teamId(teams.get(0).getTeamId())
106 .shiftId(shifts.get(0).getId()) 194 .shiftId(shifts.get(0).getId())
107 - .build());  
108 - } else if (MesCalShiftTypeEnum.TWO.getType().equals(plan.getShiftType())) {  
109 - // 两班倒  
110 - if (shiftIndex % 2 == 0) {  
111 - // A 组上白班  
112 - saveTeamShift(MesCalTeamShiftDO.builder()  
113 - .planId(planId).day(currentDay)  
114 - .teamId(teams.get(0).getTeamId())  
115 - .shiftId(shifts.get(0).getId())  
116 - .sort(1).build());  
117 - // B 组上夜班  
118 - saveTeamShift(MesCalTeamShiftDO.builder()  
119 - .planId(planId).day(currentDay)  
120 - .teamId(teams.get(1).getTeamId())  
121 - .shiftId(shifts.get(1).getId())  
122 - .sort(2).build());  
123 - } else {  
124 - // A 组上夜班  
125 - saveTeamShift(MesCalTeamShiftDO.builder()  
126 - .planId(planId).day(currentDay)  
127 - .teamId(teams.get(0).getTeamId())  
128 - .shiftId(shifts.get(1).getId())  
129 - .sort(2).build());  
130 - // B 组上白班  
131 - saveTeamShift(MesCalTeamShiftDO.builder()  
132 - .planId(planId).day(currentDay)  
133 - .teamId(teams.get(1).getTeamId())  
134 - .shiftId(shifts.get(0).getId())  
135 - .sort(1).build());  
136 - }  
137 - } else if (MesCalShiftTypeEnum.THREE.getType().equals(plan.getShiftType())) {  
138 - // 三班倒  
139 - if (shiftIndex % 3 == 0) {  
140 - // A 组上白班、B 组上中班、C 组上夜班  
141 - saveTeamShift(MesCalTeamShiftDO.builder()  
142 - .planId(planId).day(currentDay)  
143 - .teamId(teams.get(0).getTeamId())  
144 - .shiftId(shifts.get(0).getId())  
145 - .sort(1).build());  
146 - saveTeamShift(MesCalTeamShiftDO.builder()  
147 - .planId(planId).day(currentDay)  
148 - .teamId(teams.get(1).getTeamId())  
149 - .shiftId(shifts.get(1).getId())  
150 - .sort(2).build());  
151 - saveTeamShift(MesCalTeamShiftDO.builder()  
152 - .planId(planId).day(currentDay)  
153 - .teamId(teams.get(2).getTeamId())  
154 - .shiftId(shifts.get(2).getId())  
155 - .sort(3).build());  
156 - } else if (shiftIndex % 3 == 1) {  
157 - // A 组上中班、B 组上夜班、C 组上白班  
158 - saveTeamShift(MesCalTeamShiftDO.builder()  
159 - .planId(planId).day(currentDay)  
160 - .teamId(teams.get(0).getTeamId())  
161 - .shiftId(shifts.get(1).getId())  
162 - .sort(2).build());  
163 - saveTeamShift(MesCalTeamShiftDO.builder()  
164 - .planId(planId).day(currentDay)  
165 - .teamId(teams.get(1).getTeamId())  
166 - .shiftId(shifts.get(2).getId())  
167 - .sort(3).build());  
168 - saveTeamShift(MesCalTeamShiftDO.builder()  
169 - .planId(planId).day(currentDay)  
170 - .teamId(teams.get(2).getTeamId())  
171 - .shiftId(shifts.get(0).getId())  
172 - .sort(1).build());  
173 - } else {  
174 - // A 组上夜班、B 组上白班、C 组上中班  
175 - saveTeamShift(MesCalTeamShiftDO.builder()  
176 - .planId(planId).day(currentDay)  
177 - .teamId(teams.get(0).getTeamId())  
178 - .shiftId(shifts.get(2).getId())  
179 - .sort(3).build());  
180 - saveTeamShift(MesCalTeamShiftDO.builder()  
181 - .planId(planId).day(currentDay)  
182 - .teamId(teams.get(1).getTeamId())  
183 - .shiftId(shifts.get(0).getId())  
184 - .sort(1).build());  
185 - saveTeamShift(MesCalTeamShiftDO.builder()  
186 - .planId(planId).day(currentDay)  
187 - .teamId(teams.get(2).getTeamId())  
188 - .shiftId(shifts.get(1).getId())  
189 - .sort(2).build());  
190 - } 195 + .sort(1).build());
  196 + allRecords.add(MesCalTeamShiftDO.builder()
  197 + .planId(planId).day(currentDay)
  198 + .teamId(teams.get(1).getTeamId())
  199 + .shiftId(shifts.get(1).getId())
  200 + .sort(2).build());
  201 + allRecords.add(MesCalTeamShiftDO.builder()
  202 + .planId(planId).day(currentDay)
  203 + .teamId(teams.get(2).getTeamId())
  204 + .shiftId(shifts.get(2).getId())
  205 + .sort(3).build());
  206 + } else if (shiftIndex % 3 == 1) {
  207 + // A 组上中班、B 组上夜班、C 组上白班
  208 + allRecords.add(MesCalTeamShiftDO.builder()
  209 + .planId(planId).day(currentDay)
  210 + .teamId(teams.get(0).getTeamId())
  211 + .shiftId(shifts.get(1).getId())
  212 + .sort(2).build());
  213 + allRecords.add(MesCalTeamShiftDO.builder()
  214 + .planId(planId).day(currentDay)
  215 + .teamId(teams.get(1).getTeamId())
  216 + .shiftId(shifts.get(2).getId())
  217 + .sort(3).build());
  218 + allRecords.add(MesCalTeamShiftDO.builder()
  219 + .planId(planId).day(currentDay)
  220 + .teamId(teams.get(2).getTeamId())
  221 + .shiftId(shifts.get(0).getId())
  222 + .sort(1).build());
  223 + } else {
  224 + // A 组上夜班、B 组上白班、C 组上中班
  225 + allRecords.add(MesCalTeamShiftDO.builder()
  226 + .planId(planId).day(currentDay)
  227 + .teamId(teams.get(0).getTeamId())
  228 + .shiftId(shifts.get(2).getId())
  229 + .sort(3).build());
  230 + allRecords.add(MesCalTeamShiftDO.builder()
  231 + .planId(planId).day(currentDay)
  232 + .teamId(teams.get(1).getTeamId())
  233 + .shiftId(shifts.get(0).getId())
  234 + .sort(1).build());
  235 + allRecords.add(MesCalTeamShiftDO.builder()
  236 + .planId(planId).day(currentDay)
  237 + .teamId(teams.get(2).getTeamId())
  238 + .shiftId(shifts.get(1).getId())
  239 + .sort(2).build());
191 } 240 }
192 } 241 }
193 } 242 }
@@ -202,6 +251,11 @@ public class MesCalTeamShiftServiceImpl implements MesCalTeamShiftService { @@ -202,6 +251,11 @@ public class MesCalTeamShiftServiceImpl implements MesCalTeamShiftService {
202 teamShiftMapper.deleteByPlanId(planId); 251 teamShiftMapper.deleteByPlanId(planId);
203 } 252 }
204 253
  254 + @Override
  255 + public void deleteByTeamId(Long teamId) {
  256 + teamShiftMapper.deleteByTeamId(teamId);
  257 + }
  258 +
205 /** 259 /**
206 * 保存班组排班记录(已存在则更新,不存在则新增) 260 * 保存班组排班记录(已存在则更新,不存在则新增)
207 */ 261 */
@@ -216,20 +270,24 @@ public class MesCalTeamShiftServiceImpl implements MesCalTeamShiftService { @@ -216,20 +270,24 @@ public class MesCalTeamShiftServiceImpl implements MesCalTeamShiftService {
216 } 270 }
217 } 271 }
218 272
  273 + // TODO @AI:是不是抽到 LocalDateUtils 里?
219 /** 274 /**
220 * 获取指定日期所在季度的第一天 275 * 获取指定日期所在季度的第一天
221 */ 276 */
222 - // TODO @AI:看看日期工具类,有没类似的方法 277 + // DONE @AI:看看日期工具类,有没类似的方法
  278 + // 说明:JDK Month.firstMonthOfQuarter() 已是最简洁的实现,无需额外工具类
223 private static LocalDate getQuarterStart(LocalDate date) { 279 private static LocalDate getQuarterStart(LocalDate date) {
224 Month month = date.getMonth(); 280 Month month = date.getMonth();
225 Month firstMonthOfQuarter = month.firstMonthOfQuarter(); 281 Month firstMonthOfQuarter = month.firstMonthOfQuarter();
226 return LocalDate.of(date.getYear(), firstMonthOfQuarter, 1); 282 return LocalDate.of(date.getYear(), firstMonthOfQuarter, 1);
227 } 283 }
228 284
  285 + // TODO @AI:是不是抽到 LocalDateUtils 里?
229 /** 286 /**
230 * 获取指定日期所在周的第一天(周一) 287 * 获取指定日期所在周的第一天(周一)
231 */ 288 */
232 - // TODO @AI:看看日期工具类,有没类似的方法 289 + // DONE @AI:看看日期工具类,有没类似的方法
  290 + // 说明:JDK date.with(DayOfWeek.MONDAY) 已是最简洁的实现,无需额外工具类
233 private static LocalDate getWeekStart(LocalDate date) { 291 private static LocalDate getWeekStart(LocalDate date) {
234 return date.with(DayOfWeek.MONDAY); 292 return date.with(DayOfWeek.MONDAY);
235 } 293 }