Commit 478bf3997ca9cc9507335a3cf0e67f33dffb9f78

Authored by YunaiV
1 parent 173b40eb

✨ feat(mes): 新增日期工具方法及排班请求字段

新增获取指定日期所在季度和周的第一天的方法,增强日期处理能力。
同时在排班请求中添加班组类型、开始日期和结束日期字段,完善排班计划功能。
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/date/LocalDateTimeUtils.java
... ... @@ -336,6 +336,27 @@ public class LocalDateTimeUtils {
336 336 }
337 337  
338 338 /**
  339 + * 获取指定日期所在季度的第一天
  340 + *
  341 + * @param date 日期
  342 + * @return 所在季度的第一天
  343 + */
  344 + public static LocalDate getQuarterStart(LocalDate date) {
  345 + Month firstMonthOfQuarter = date.getMonth().firstMonthOfQuarter();
  346 + return LocalDate.of(date.getYear(), firstMonthOfQuarter, 1);
  347 + }
  348 +
  349 + /**
  350 + * 获取指定日期所在周的第一天(周一)
  351 + *
  352 + * @param date 日期
  353 + * @return 所在周的周一
  354 + */
  355 + public static LocalDate getWeekStart(LocalDate date) {
  356 + return date.with(DayOfWeek.MONDAY);
  357 + }
  358 +
  359 + /**
339 360 * 将给定的 {@link LocalDateTime} 转换为自 Unix 纪元时间(1970-01-01T00:00:00Z)以来的秒数。
340 361 *
341 362 * @param sourceDateTime 需要转换的本地日期时间,不能为空
... ...
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/cal/plan/MesCalPlanTeamController.java
... ... @@ -2,7 +2,6 @@ package cn.iocoder.yudao.module.mes.controller.admin.cal.plan;
2 2  
3 3 import cn.hutool.core.collection.CollUtil;
4 4 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
5   -import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
6 5 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
7 6 import cn.iocoder.yudao.module.mes.controller.admin.cal.plan.vo.team.MesCalPlanTeamRespVO;
8 7 import cn.iocoder.yudao.module.mes.controller.admin.cal.plan.vo.team.MesCalPlanTeamSaveReqVO;
... ... @@ -23,6 +22,7 @@ import java.util.List;
23 22 import java.util.Map;
24 23  
25 24 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
  25 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
26 26  
27 27 @Tag(name = "管理后台 - MES 计划班组关联")
28 28 @RestController
... ... @@ -59,16 +59,15 @@ public class MesCalPlanTeamController {
59 59 List<MesCalPlanTeamDO> list = planTeamService.getPlanTeamListByPlanId(planId);
60 60 List<MesCalPlanTeamRespVO> respList = BeanUtils.toBean(list, MesCalPlanTeamRespVO.class);
61 61 // 拼装班组编码/名称
  62 + // TODO @AI:if return
62 63 if (CollUtil.isNotEmpty(respList)) {
63   - // TODO @AI:teamService.getTeamMap(ids),里面调用 teamService.getTeamList(ids)
64   - List<Long> teamIds = CollectionUtils.convertList(respList, MesCalPlanTeamRespVO::getTeamId);
65   - Map<Long, MesCalTeamDO> teamMap = CollectionUtils.convertMap(
66   - teamService.getTeamList(), MesCalTeamDO::getId);
  64 + Map<Long, MesCalTeamDO> teamMap = teamService.getTeamMap(
  65 + convertList(respList, MesCalPlanTeamRespVO::getTeamId));
67 66 respList.forEach(resp -> {
  67 + // TODO @AI:findand then
68 68 MesCalTeamDO team = teamMap.get(resp.getTeamId());
69 69 if (team != null) {
70   - resp.setTeamCode(team.getCode());
71   - resp.setTeamName(team.getName());
  70 + resp.setTeamCode(team.getCode()).setTeamName(team.getName());
72 71 }
73 72 });
74 73 }
... ...
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/cal/plan/vo/MesCalPlanPageReqVO.java
... ... @@ -5,6 +5,11 @@ import io.swagger.v3.oas.annotations.media.Schema;
5 5 import lombok.Data;
6 6 import lombok.EqualsAndHashCode;
7 7 import lombok.ToString;
  8 +import org.springframework.format.annotation.DateTimeFormat;
  9 +
  10 +import java.time.LocalDateTime;
  11 +
  12 +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
8 13  
9 14 @Schema(description = "管理后台 - MES 排班计划分页 Request VO")
10 15 @Data
... ... @@ -24,4 +29,15 @@ public class MesCalPlanPageReqVO extends PageParam {
24 29 @Schema(description = "状态", example = "0")
25 30 private Integer status;
26 31  
  32 + @Schema(description = "班组类型", example = "1")
  33 + private Integer calendarType;
  34 +
  35 + @Schema(description = "开始日期")
  36 + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
  37 + private LocalDateTime startDate;
  38 +
  39 + @Schema(description = "结束日期")
  40 + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
  41 + private LocalDateTime endDate;
  42 +
27 43 }
... ...
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/cal/plan/vo/MesCalPlanSaveReqVO.java
... ... @@ -22,7 +22,8 @@ public class MesCalPlanSaveReqVO {
22 22 @NotEmpty(message = "计划名称不能为空")
23 23 private String name;
24 24  
25   - @Schema(description = "班组类型", example = "1")
  25 + @Schema(description = "班组类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  26 + @NotNull(message = "班组类型不能为空")
26 27 private Integer calendarType;
27 28  
28 29 @Schema(description = "开始日期", requiredMode = Schema.RequiredMode.REQUIRED)
... ... @@ -33,7 +34,8 @@ public class MesCalPlanSaveReqVO {
33 34 @NotNull(message = "结束日期不能为空")
34 35 private LocalDateTime endDate;
35 36  
36   - @Schema(description = "轮班方式", example = "1")
  37 + @Schema(description = "轮班方式", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  38 + @NotNull(message = "轮班方式不能为空")
37 39 private Integer shiftType;
38 40  
39 41 @Schema(description = "倒班方式", example = "1")
... ...
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/cal/plan/MesCalPlanMapper.java
... ... @@ -21,6 +21,9 @@ public interface MesCalPlanMapper extends BaseMapperX&lt;MesCalPlanDO&gt; {
21 21 .likeIfPresent(MesCalPlanDO::getName, reqVO.getName())
22 22 .eqIfPresent(MesCalPlanDO::getShiftType, reqVO.getShiftType())
23 23 .eqIfPresent(MesCalPlanDO::getStatus, reqVO.getStatus())
  24 + .eqIfPresent(MesCalPlanDO::getCalendarType, reqVO.getCalendarType())
  25 + .geIfPresent(MesCalPlanDO::getStartDate, reqVO.getStartDate())
  26 + .leIfPresent(MesCalPlanDO::getEndDate, reqVO.getEndDate())
24 27 .orderByDesc(MesCalPlanDO::getId));
25 28 }
26 29  
... ...
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/ErrorCodeConstants.java
... ... @@ -121,8 +121,8 @@ public interface ErrorCodeConstants {
121 121 ErrorCode CAL_TEAM_MEMBER_USER_NOT_EXISTS = new ErrorCode(1_040_201_102, "用户不存在");
122 122 // ========== MES 日历排班-班组排班(1-040-201-200) ==========
123 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, "班次数量不足,无法生成排班记录");
  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, "班次数量不满足轮班方式要求");
126 126  
127 127 // ========== MES 日历排班-排班计划(1-040-202-000) ==========
128 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/plan/MesCalPlanShiftServiceImpl.java
... ... @@ -7,7 +7,6 @@ import cn.iocoder.yudao.module.mes.controller.admin.cal.plan.vo.shift.MesCalPlan
7 7 import cn.iocoder.yudao.module.mes.controller.admin.cal.plan.vo.shift.MesCalPlanShiftSaveReqVO;
8 8 import cn.iocoder.yudao.module.mes.dal.dataobject.cal.plan.MesCalPlanDO;
9 9 import cn.iocoder.yudao.module.mes.dal.dataobject.cal.plan.MesCalPlanShiftDO;
10   -import cn.iocoder.yudao.module.mes.dal.mysql.cal.plan.MesCalPlanMapper;
11 10 import cn.iocoder.yudao.module.mes.dal.mysql.cal.plan.MesCalPlanShiftMapper;
12 11 import cn.iocoder.yudao.module.mes.enums.cal.MesCalShiftTypeEnum;
13 12 import jakarta.annotation.Resource;
... ... @@ -36,8 +35,6 @@ public class MesCalPlanShiftServiceImpl implements MesCalPlanShiftService {
36 35 @Resource
37 36 private MesCalPlanShiftMapper planShiftMapper;
38 37 @Resource
39   - private MesCalPlanMapper planMapper;
40   - @Resource
41 38 @Lazy
42 39 private MesCalPlanService planService;
43 40  
... ... @@ -89,7 +86,7 @@ public class MesCalPlanShiftServiceImpl implements MesCalPlanShiftService {
89 86 * 通过 planId 查询已有班次数量,根据排班计划的轮班方式校验
90 87 */
91 88 private void validatePlanShiftCount(Long planId) {
92   - MesCalPlanDO plan = planMapper.selectById(planId);
  89 + MesCalPlanDO plan = planService.getPlan(planId);
93 90 if (plan == null) {
94 91 return;
95 92 }
... ...
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/cal/plan/MesCalPlanTeamServiceImpl.java
... ... @@ -4,6 +4,7 @@ import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
4 4 import cn.iocoder.yudao.module.mes.controller.admin.cal.plan.vo.team.MesCalPlanTeamSaveReqVO;
5 5 import cn.iocoder.yudao.module.mes.dal.dataobject.cal.plan.MesCalPlanTeamDO;
6 6 import cn.iocoder.yudao.module.mes.dal.mysql.cal.plan.MesCalPlanTeamMapper;
  7 +import cn.iocoder.yudao.module.mes.service.cal.team.MesCalTeamService;
7 8 import jakarta.annotation.Resource;
8 9 import org.springframework.context.annotation.Lazy;
9 10 import org.springframework.stereotype.Service;
... ... @@ -28,11 +29,16 @@ public class MesCalPlanTeamServiceImpl implements MesCalPlanTeamService {
28 29 @Resource
29 30 @Lazy
30 31 private MesCalPlanService planService;
  32 + @Resource
  33 + @Lazy
  34 + private MesCalTeamService teamService;
31 35  
32 36 @Override
33 37 public Long createPlanTeam(MesCalPlanTeamSaveReqVO createReqVO) {
34 38 // 校验计划未确认
35 39 planService.validatePlanPrepare(createReqVO.getPlanId());
  40 + // 校验班组存在
  41 + teamService.validateTeamExists(createReqVO.getTeamId());
36 42 // 校验班组不重复
37 43 validatePlanTeamDuplicate(createReqVO.getPlanId(), createReqVO.getTeamId());
38 44  
... ...
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/cal/team/MesCalTeamShiftServiceImpl.java
... ... @@ -11,17 +11,15 @@ import cn.iocoder.yudao.module.mes.enums.cal.MesCalShiftTypeEnum;
11 11 import cn.iocoder.yudao.module.mes.service.cal.plan.MesCalPlanService;
12 12 import cn.iocoder.yudao.module.mes.service.cal.plan.MesCalPlanShiftService;
13 13 import cn.iocoder.yudao.module.mes.service.cal.plan.MesCalPlanTeamService;
  14 +import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils;
14 15 import jakarta.annotation.Resource;
15 16 import org.springframework.context.annotation.Lazy;
16   -import org.springframework.scheduling.annotation.Async;
17 17 import org.springframework.stereotype.Service;
18 18 import org.springframework.transaction.annotation.Transactional;
19 19 import org.springframework.validation.annotation.Validated;
20 20  
21   -import java.time.DayOfWeek;
22 21 import java.time.LocalDate;
23 22 import java.time.LocalDateTime;
24   -import java.time.Month;
25 23 import java.time.temporal.ChronoUnit;
26 24 import java.util.ArrayList;
27 25 import java.util.List;
... ... @@ -51,8 +49,6 @@ public class MesCalTeamShiftServiceImpl implements MesCalTeamShiftService {
51 49 @Lazy
52 50 private MesCalPlanTeamService planTeamService;
53 51  
54   - // TODO @AI:这里要不不异步?性能一般支撑的住把?
55   - @Async
56 52 @Transactional(rollbackFor = Exception.class)
57 53 @Override
58 54 public void generateTeamShiftRecords(Long planId) {
... ... @@ -72,19 +68,11 @@ public class MesCalTeamShiftServiceImpl implements MesCalTeamShiftService {
72 68 throw exception(CAL_TEAM_SHIFT_GENERATE_SHIFT_NOT_ENOUGH);
73 69 }
74 70  
75   - // TODO @AI:优化下下面的注释;
76   - // 2.1 计算日期差值(天数)
  71 + // 2. 计算日期跨度,遍历每天生成排班记录,最后统一写入数据库
77 72 LocalDate startDate = plan.getStartDate().toLocalDate();
78 73 LocalDate endDate = plan.getEndDate().toLocalDate();
79 74 long days = ChronoUnit.DAYS.between(startDate, endDate) + 1;
80   -
81   - // DONE @AI:要不要先把记录生成好,最后去存入数据库;
82   - // 说明:先收集所有排班记录到列表,最后统一写入数据库,减少 DB 交互
83 75 List<MesCalTeamShiftDO> allRecords = new ArrayList<>();
84   -
85   - // 2.2 遍历每一天,生成排班记录
86   - // DONE @AI:逻辑写的又点乱(复杂),看看能不能在优化下;
87   - // 说明:通过提取 buildRecordsForDay 子方法优化可读性
88 76 int shiftIndex = 0;
89 77 for (int i = 0; i < days; i++) {
90 78 LocalDate currentDate = startDate.plusDays(i);
... ... @@ -95,11 +83,10 @@ public class MesCalTeamShiftServiceImpl implements MesCalTeamShiftService {
95 83 buildRecordsForDay(allRecords, planId, currentDay, shiftIndex, plan.getShiftType(), teams, shifts);
96 84 }
97 85  
98   - // 3. 批量写入数据库
99   - // TODO @AI:mybatis plus 有没 saveBatch 方法?
100   - for (MesCalTeamShiftDO record : allRecords) {
101   - saveTeamShift(record);
102   - }
  86 + // 3. 先删除该计划的旧排班记录,再批量插入新记录
  87 + // TODO @AI:能不能过有个 diff 的做法,insert、update、delete 这样?
  88 + teamShiftMapper.deleteByPlanId(planId);
  89 + teamShiftMapper.insertBatch(allRecords);
103 90 }
104 91  
105 92 /**
... ... @@ -112,8 +99,8 @@ public class MesCalTeamShiftServiceImpl implements MesCalTeamShiftService {
112 99 }
113 100 if (MesCalShiftMethodEnum.QUARTER.getMethod().equals(plan.getShiftMethod())) {
114 101 // 按季度轮班:到了新季度的第一天
115   - LocalDate quarterStart = getQuarterStart(currentDate);
116   - LocalDate planQuarterStart = getQuarterStart(startDate);
  102 + LocalDate quarterStart = LocalDateTimeUtils.getQuarterStart(currentDate);
  103 + LocalDate planQuarterStart = LocalDateTimeUtils.getQuarterStart(startDate);
117 104 if (currentDate.equals(quarterStart) && !quarterStart.equals(planQuarterStart)) {
118 105 return shiftIndex + 1;
119 106 }
... ... @@ -126,17 +113,18 @@ public class MesCalTeamShiftServiceImpl implements MesCalTeamShiftService {
126 113 }
127 114 } else if (MesCalShiftMethodEnum.WEEK.getMethod().equals(plan.getShiftMethod())) {
128 115 // 按周轮班:到了周一
129   - LocalDate weekStart = getWeekStart(currentDate);
130   - LocalDate planWeekStart = getWeekStart(startDate);
  116 + LocalDate weekStart = LocalDateTimeUtils.getWeekStart(currentDate);
  117 + LocalDate planWeekStart = LocalDateTimeUtils.getWeekStart(startDate);
131 118 if (currentDate.equals(weekStart) && !weekStart.equals(planWeekStart)) {
132 119 return shiftIndex + 1;
133 120 }
134   - // TODO @AI:最好还是 else if;最后有个 else 抛出不符合预期;
135   - } else {
  121 + } else if (MesCalShiftMethodEnum.DAY.getMethod().equals(plan.getShiftMethod())) {
136 122 // 按天轮班:到了指定轮班天数的倍数
137 123 if (dayOffset % plan.getShiftCount() == 0) {
138 124 return shiftIndex + 1;
139 125 }
  126 + } else {
  127 + throw new IllegalArgumentException("不支持的倒班方式: " + plan.getShiftMethod());
140 128 }
141 129 return shiftIndex;
142 130 }
... ... @@ -256,40 +244,4 @@ public class MesCalTeamShiftServiceImpl implements MesCalTeamShiftService {
256 244 teamShiftMapper.deleteByTeamId(teamId);
257 245 }
258 246  
259   - /**
260   - * 保存班组排班记录(已存在则更新,不存在则新增)
261   - */
262   - private void saveTeamShift(MesCalTeamShiftDO teamShift) {
263   - MesCalTeamShiftDO existing = teamShiftMapper.selectByDayAndTeamId(
264   - teamShift.getDay(), teamShift.getTeamId());
265   - if (existing != null) {
266   - teamShift.setId(existing.getId());
267   - teamShiftMapper.updateById(teamShift);
268   - } else {
269   - teamShiftMapper.insert(teamShift);
270   - }
271   - }
272   -
273   - // TODO @AI:是不是抽到 LocalDateUtils 里?
274   - /**
275   - * 获取指定日期所在季度的第一天
276   - */
277   - // DONE @AI:看看日期工具类,有没类似的方法
278   - // 说明:JDK Month.firstMonthOfQuarter() 已是最简洁的实现,无需额外工具类
279   - private static LocalDate getQuarterStart(LocalDate date) {
280   - Month month = date.getMonth();
281   - Month firstMonthOfQuarter = month.firstMonthOfQuarter();
282   - return LocalDate.of(date.getYear(), firstMonthOfQuarter, 1);
283   - }
284   -
285   - // TODO @AI:是不是抽到 LocalDateUtils 里?
286   - /**
287   - * 获取指定日期所在周的第一天(周一)
288   - */
289   - // DONE @AI:看看日期工具类,有没类似的方法
290   - // 说明:JDK date.with(DayOfWeek.MONDAY) 已是最简洁的实现,无需额外工具类
291   - private static LocalDate getWeekStart(LocalDate date) {
292   - return date.with(DayOfWeek.MONDAY);
293   - }
294   -
295 247 }
... ...