Commit ba43933138c5c2afde72492c0990cf73d18734a3

Authored by YunaiV
1 parent 4355a7a5

review(mes):cal-calender 日历功能

yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/cal/calendar/MesCalCalendarController.java 0 → 100644
  1 +package cn.iocoder.yudao.module.mes.controller.admin.cal.calendar;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  4 +import cn.iocoder.yudao.module.mes.controller.admin.cal.calendar.vo.MesCalCalendarListReqVO;
  5 +import cn.iocoder.yudao.module.mes.controller.admin.cal.calendar.vo.MesCalCalendarRespVO;
  6 +import cn.iocoder.yudao.module.mes.service.cal.calendar.MesCalCalendarService;
  7 +import io.swagger.v3.oas.annotations.Operation;
  8 +import io.swagger.v3.oas.annotations.tags.Tag;
  9 +import jakarta.annotation.Resource;
  10 +import jakarta.validation.Valid;
  11 +import org.springframework.security.access.prepost.PreAuthorize;
  12 +import org.springframework.validation.annotation.Validated;
  13 +import org.springframework.web.bind.annotation.GetMapping;
  14 +import org.springframework.web.bind.annotation.RequestMapping;
  15 +import org.springframework.web.bind.annotation.RestController;
  16 +
  17 +import java.util.List;
  18 +
  19 +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
  20 +
  21 +@Tag(name = "管理后台 - MES 排班日历")
  22 +@RestController
  23 +@RequestMapping("/mes/cal/calendar")
  24 +@Validated
  25 +public class MesCalCalendarController {
  26 +
  27 + @Resource
  28 + private MesCalCalendarService calendarService;
  29 +
  30 + @GetMapping("/list")
  31 + @Operation(summary = "查询排班日历")
  32 + @PreAuthorize("@ss.hasPermission('mes:cal-team-shift:query')")
  33 + public CommonResult<List<MesCalCalendarRespVO>> getCalendarList(@Valid MesCalCalendarListReqVO reqVO) {
  34 + List<MesCalCalendarRespVO> list = calendarService.getCalendarList(reqVO);
  35 + return success(list);
  36 + }
  37 +
  38 +}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/cal/calendar/vo/MesCalCalendarListReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.mes.controller.admin.cal.calendar.vo;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import jakarta.validation.constraints.NotEmpty;
  5 +import lombok.Data;
  6 +import org.springframework.format.annotation.DateTimeFormat;
  7 +
  8 +import java.time.LocalDateTime;
  9 +
  10 +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
  11 +
  12 +@Schema(description = "管理后台 - MES 排班日历查询 Request VO")
  13 +@Data
  14 +public class MesCalCalendarListReqVO {
  15 +
  16 + @Schema(description = "查询类型:TYPE-按分类,TEAM-按班组,USER-按个人", requiredMode = Schema.RequiredMode.REQUIRED, example = "TYPE")
  17 + @NotEmpty(message = "查询类型不能为空")
  18 + private String queryType;
  19 +
  20 + @Schema(description = "班组类型(queryType=TYPE 时使用)", example = "1")
  21 + private Integer calendarType;
  22 +
  23 + @Schema(description = "班组编号(queryType=TEAM 时使用)", example = "201")
  24 + private Long teamId;
  25 +
  26 + @Schema(description = "用户编号(queryType=USER 时使用)", example = "1")
  27 + private Long userId;
  28 +
  29 + @Schema(description = "开始日期")
  30 + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
  31 + private LocalDateTime startDay;
  32 +
  33 + @Schema(description = "结束日期")
  34 + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
  35 + private LocalDateTime endDay;
  36 +
  37 +}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/cal/calendar/vo/MesCalCalendarRespVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.mes.controller.admin.cal.calendar.vo;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.AllArgsConstructor;
  5 +import lombok.Builder;
  6 +import lombok.Data;
  7 +import lombok.NoArgsConstructor;
  8 +
  9 +import java.util.List;
  10 +
  11 +@Schema(description = "管理后台 - MES 排班日历 Response VO")
  12 +@Data
  13 +@Builder
  14 +@NoArgsConstructor
  15 +@AllArgsConstructor
  16 +public class MesCalCalendarRespVO {
  17 +
  18 + @Schema(description = "日期,格式 yyyy-MM-dd", requiredMode = Schema.RequiredMode.REQUIRED, example = "2025-01-15")
  19 + private String day;
  20 +
  21 + @Schema(description = "轮班方式", example = "2")
  22 + private Integer shiftType;
  23 +
  24 + @Schema(description = "班组排班列表")
  25 + private List<TeamShiftItem> teamShifts;
  26 +
  27 + @Schema(description = "班组排班项")
  28 + @Data
  29 + @Builder
  30 + @NoArgsConstructor
  31 + @AllArgsConstructor
  32 + public static class TeamShiftItem {
  33 +
  34 + @Schema(description = "班组编号", example = "201")
  35 + private Long teamId;
  36 +
  37 + @Schema(description = "班组名称", example = "注塑A组")
  38 + private String teamName;
  39 +
  40 + @Schema(description = "班次编号", example = "1")
  41 + private Long shiftId;
  42 +
  43 + @Schema(description = "班次名称", example = "白班")
  44 + private String shiftName;
  45 +
  46 + @Schema(description = "排序", example = "1")
  47 + private Integer sort;
  48 +
  49 + }
  50 +
  51 +}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/cal/calendar/MesCalCalendarService.java 0 → 100644
  1 +package cn.iocoder.yudao.module.mes.service.cal.calendar;
  2 +
  3 +import cn.iocoder.yudao.module.mes.controller.admin.cal.calendar.vo.MesCalCalendarListReqVO;
  4 +import cn.iocoder.yudao.module.mes.controller.admin.cal.calendar.vo.MesCalCalendarRespVO;
  5 +
  6 +import java.util.List;
  7 +
  8 +/**
  9 + * MES 排班日历 Service 接口
  10 + *
  11 + * @author 芋道源码
  12 + */
  13 +public interface MesCalCalendarService {
  14 +
  15 + /**
  16 + * 查询排班日历列表
  17 + *
  18 + * @param reqVO 查询条件
  19 + * @return 排班日历列表
  20 + */
  21 + List<MesCalCalendarRespVO> getCalendarList(MesCalCalendarListReqVO reqVO);
  22 +
  23 +}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/cal/calendar/MesCalCalendarServiceImpl.java 0 → 100644
  1 +package cn.iocoder.yudao.module.mes.service.cal.calendar;
  2 +
  3 +import cn.hutool.core.collection.CollUtil;
  4 +import cn.iocoder.yudao.module.mes.controller.admin.cal.calendar.vo.MesCalCalendarListReqVO;
  5 +import cn.iocoder.yudao.module.mes.controller.admin.cal.calendar.vo.MesCalCalendarRespVO;
  6 +import cn.iocoder.yudao.module.mes.controller.admin.cal.team.vo.shift.MesCalTeamShiftListReqVO;
  7 +import cn.iocoder.yudao.module.mes.dal.dataobject.cal.holiday.MesCalHolidayDO;
  8 +import cn.iocoder.yudao.module.mes.dal.dataobject.cal.plan.MesCalPlanDO;
  9 +import cn.iocoder.yudao.module.mes.dal.dataobject.cal.plan.MesCalPlanShiftDO;
  10 +import cn.iocoder.yudao.module.mes.dal.dataobject.cal.team.MesCalTeamDO;
  11 +import cn.iocoder.yudao.module.mes.dal.dataobject.cal.team.MesCalTeamMemberDO;
  12 +import cn.iocoder.yudao.module.mes.dal.dataobject.cal.team.MesCalTeamShiftDO;
  13 +import cn.iocoder.yudao.module.mes.dal.mysql.cal.holiday.MesCalHolidayMapper;
  14 +import cn.iocoder.yudao.module.mes.dal.mysql.cal.plan.MesCalPlanMapper;
  15 +import cn.iocoder.yudao.module.mes.dal.mysql.cal.team.MesCalTeamMapper;
  16 +import cn.iocoder.yudao.module.mes.dal.mysql.cal.team.MesCalTeamMemberMapper;
  17 +import cn.iocoder.yudao.module.mes.service.cal.plan.MesCalPlanShiftService;
  18 +import cn.iocoder.yudao.module.mes.service.cal.team.MesCalTeamShiftService;
  19 +import jakarta.annotation.Resource;
  20 +import org.springframework.stereotype.Service;
  21 +import org.springframework.validation.annotation.Validated;
  22 +
  23 +import java.time.LocalDate;
  24 +import java.time.LocalDateTime;
  25 +import java.time.format.DateTimeFormatter;
  26 +import java.util.*;
  27 +import java.util.stream.Collectors;
  28 +
  29 +/**
  30 + * MES 排班日历 Service 实现类
  31 + *
  32 + * @author 芋道源码
  33 + */
  34 +@Service
  35 +@Validated
  36 +public class MesCalCalendarServiceImpl implements MesCalCalendarService {
  37 +
  38 + private static final DateTimeFormatter DAY_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");
  39 +
  40 + @Resource
  41 + private MesCalTeamShiftService teamShiftService;
  42 + @Resource
  43 + private MesCalPlanShiftService planShiftService;
  44 + @Resource
  45 + private MesCalTeamMapper teamMapper;
  46 + @Resource
  47 + private MesCalTeamMemberMapper teamMemberMapper;
  48 + @Resource
  49 + private MesCalPlanMapper planMapper;
  50 + @Resource
  51 + private MesCalHolidayMapper holidayMapper;
  52 +
  53 + // TODO @AI:放到 controller 实现。这里不要有;
  54 + @Override
  55 + public List<MesCalCalendarRespVO> getCalendarList(MesCalCalendarListReqVO reqVO) {
  56 + // 1. 计算月份范围
  57 + LocalDateTime startDay = reqVO.getStartDay();
  58 + LocalDateTime endDay = reqVO.getEndDay();
  59 + if (startDay == null || endDay == null) {
  60 + return Collections.emptyList();
  61 + }
  62 +
  63 + // 2. 根据查询类型查询班组排班记录
  64 + List<MesCalTeamShiftDO> teamShifts;
  65 + switch (reqVO.getQueryType()) {
  66 + // TODO @AI:枚举类,放到 MesCalCalendarListReqVO,直接使用 String 静态枚举;
  67 + case "TYPE":
  68 + teamShifts = getTeamShiftsByCalendarType(reqVO.getCalendarType(), startDay, endDay);
  69 + break;
  70 + case "TEAM":
  71 + teamShifts = getTeamShiftsByTeamId(reqVO.getTeamId(), startDay, endDay);
  72 + break;
  73 + case "USER":
  74 + teamShifts = getTeamShiftsByUserId(reqVO.getUserId(), startDay, endDay);
  75 + break;
  76 + default:
  77 + return Collections.emptyList();
  78 + }
  79 + if (CollUtil.isEmpty(teamShifts)) {
  80 + return Collections.emptyList();
  81 + }
  82 +
  83 + // 3. 查询假期列表,构建假期日期集合
  84 + Set<String> holidaySet = buildHolidaySet();
  85 +
  86 + // 4. 批量查询关联数据:班组名称、班次名称
  87 + // TODO @AI:批量查询;service 封装
  88 + Set<Long> teamIds = teamShifts.stream().map(MesCalTeamShiftDO::getTeamId).collect(Collectors.toSet());
  89 + Map<Long, String> teamNameMap = teamMapper.selectBatchIds(teamIds).stream()
  90 + .collect(Collectors.toMap(MesCalTeamDO::getId, MesCalTeamDO::getName));
  91 +
  92 + Set<Long> shiftIds = teamShifts.stream().map(MesCalTeamShiftDO::getShiftId).collect(Collectors.toSet());
  93 + Map<Long, MesCalPlanShiftDO> shiftMap = new HashMap<>();
  94 + if (CollUtil.isNotEmpty(shiftIds)) {
  95 + for (Long shiftId : shiftIds) {
  96 + MesCalPlanShiftDO shift = planShiftService.getPlanShift(shiftId);
  97 + if (shift != null) {
  98 + shiftMap.put(shiftId, shift);
  99 + }
  100 + }
  101 + }
  102 +
  103 + // 查询 plan 用于获取 shiftType
  104 + // TODO @AI:批量查询;service 封装
  105 + Set<Long> planIds = teamShifts.stream().map(MesCalTeamShiftDO::getPlanId)
  106 + .filter(Objects::nonNull).collect(Collectors.toSet());
  107 + Map<Long, MesCalPlanDO> planMap = new HashMap<>();
  108 + if (CollUtil.isNotEmpty(planIds)) {
  109 + planMap.putAll(planMapper.selectBatchIds(planIds).stream()
  110 + .collect(Collectors.toMap(MesCalPlanDO::getId, p -> p)));
  111 + }
  112 +
  113 + // 5. 按 day 分组聚合,过滤假期
  114 + Map<String, List<MesCalTeamShiftDO>> dayGroupMap = teamShifts.stream()
  115 + .collect(Collectors.groupingBy(
  116 + ts -> ts.getDay().toLocalDate().format(DAY_FORMATTER),
  117 + LinkedHashMap::new,
  118 + Collectors.toList()
  119 + ));
  120 +
  121 + List<MesCalCalendarRespVO> result = new ArrayList<>();
  122 + for (Map.Entry<String, List<MesCalTeamShiftDO>> entry : dayGroupMap.entrySet()) {
  123 + String dayStr = entry.getKey();
  124 + // 过滤假期
  125 + if (holidaySet.contains(dayStr)) {
  126 + continue;
  127 + }
  128 + List<MesCalTeamShiftDO> dayShifts = entry.getValue();
  129 + // 排序
  130 + dayShifts.sort(Comparator.comparing(ts -> ts.getSort() != null ? ts.getSort() : 0));
  131 +
  132 + // 获取 shiftType(取第一条记录的 planId 对应的 plan)
  133 + Integer shiftType = null;
  134 + MesCalTeamShiftDO first = dayShifts.get(0);
  135 + if (first.getPlanId() != null) {
  136 + MesCalPlanDO plan = planMap.get(first.getPlanId());
  137 + if (plan != null) {
  138 + shiftType = plan.getShiftType();
  139 + }
  140 + }
  141 +
  142 + List<MesCalCalendarRespVO.TeamShiftItem> items = dayShifts.stream()
  143 + .map(ts -> MesCalCalendarRespVO.TeamShiftItem.builder()
  144 + .teamId(ts.getTeamId())
  145 + .teamName(teamNameMap.getOrDefault(ts.getTeamId(), ""))
  146 + .shiftId(ts.getShiftId())
  147 + .shiftName(shiftMap.containsKey(ts.getShiftId())
  148 + ? shiftMap.get(ts.getShiftId()).getName() : "")
  149 + .sort(ts.getSort())
  150 + .build())
  151 + .collect(Collectors.toList());
  152 +
  153 + result.add(MesCalCalendarRespVO.builder()
  154 + .day(dayStr)
  155 + .shiftType(shiftType)
  156 + .teamShifts(items)
  157 + .build());
  158 + }
  159 + return result;
  160 + }
  161 +
  162 + /**
  163 + * 按班组类型查询排班记录
  164 + */
  165 + private List<MesCalTeamShiftDO> getTeamShiftsByCalendarType(Integer calendarType,
  166 + LocalDateTime startDay, LocalDateTime endDay) {
  167 + if (calendarType == null) {
  168 + return Collections.emptyList();
  169 + }
  170 + // 1. 查询指定类型的所有班组
  171 + List<MesCalTeamDO> teams = teamMapper.selectList(new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<MesCalTeamDO>()
  172 + .eq(MesCalTeamDO::getCalendarType, calendarType));
  173 + if (CollUtil.isEmpty(teams)) {
  174 + return Collections.emptyList();
  175 + }
  176 + // 2. 查询这些班组在日期范围内的排班记录
  177 + List<MesCalTeamShiftDO> result = new ArrayList<>();
  178 + for (MesCalTeamDO team : teams) {
  179 + MesCalTeamShiftListReqVO reqVO = new MesCalTeamShiftListReqVO();
  180 + reqVO.setTeamId(team.getId());
  181 + reqVO.setStartDay(startDay);
  182 + reqVO.setEndDay(endDay);
  183 + result.addAll(teamShiftService.getTeamShiftList(reqVO));
  184 + }
  185 + return result;
  186 + }
  187 +
  188 + /**
  189 + * 按班组编号查询排班记录
  190 + */
  191 + private List<MesCalTeamShiftDO> getTeamShiftsByTeamId(Long teamId,
  192 + LocalDateTime startDay, LocalDateTime endDay) {
  193 + if (teamId == null) {
  194 + return Collections.emptyList();
  195 + }
  196 + MesCalTeamShiftListReqVO reqVO = new MesCalTeamShiftListReqVO();
  197 + reqVO.setTeamId(teamId);
  198 + reqVO.setStartDay(startDay);
  199 + reqVO.setEndDay(endDay);
  200 + return teamShiftService.getTeamShiftList(reqVO);
  201 + }
  202 +
  203 + /**
  204 + * 按用户编号查询排班记录(先查用户所属班组,再查班组排班)
  205 + */
  206 + private List<MesCalTeamShiftDO> getTeamShiftsByUserId(Long userId,
  207 + LocalDateTime startDay, LocalDateTime endDay) {
  208 + if (userId == null) {
  209 + return Collections.emptyList();
  210 + }
  211 + // 1. 查询用户所属的班组
  212 + List<MesCalTeamMemberDO> members = teamMemberMapper.selectList(
  213 + new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<MesCalTeamMemberDO>()
  214 + .eq(MesCalTeamMemberDO::getUserId, userId));
  215 + if (CollUtil.isEmpty(members)) {
  216 + return Collections.emptyList();
  217 + }
  218 + // 2. 查询这些班组在日期范围内的排班记录
  219 + List<MesCalTeamShiftDO> result = new ArrayList<>();
  220 + for (MesCalTeamMemberDO member : members) {
  221 + MesCalTeamShiftListReqVO reqVO = new MesCalTeamShiftListReqVO();
  222 + reqVO.setTeamId(member.getTeamId());
  223 + reqVO.setStartDay(startDay);
  224 + reqVO.setEndDay(endDay);
  225 + result.addAll(teamShiftService.getTeamShiftList(reqVO));
  226 + }
  227 + return result;
  228 + }
  229 +
  230 + /**
  231 + * 构建假期日期集合
  232 + */
  233 + private Set<String> buildHolidaySet() {
  234 + List<MesCalHolidayDO> holidays = holidayMapper.selectList();
  235 + Set<String> holidaySet = new HashSet<>();
  236 + for (MesCalHolidayDO holiday : holidays) {
  237 + // type=2 表示节假日
  238 + if (holiday.getType() != null && holiday.getType() == 2) {
  239 + LocalDate day = holiday.getDay().toLocalDate();
  240 + holidaySet.add(day.format(DAY_FORMATTER));
  241 + }
  242 + }
  243 + return holidaySet;
  244 + }
  245 +
  246 +}