diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/cal/MesCalShiftTypeEnum.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/cal/MesCalShiftTypeEnum.java index 940cbc8..c85671e 100644 --- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/cal/MesCalShiftTypeEnum.java +++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/cal/MesCalShiftTypeEnum.java @@ -19,7 +19,7 @@ import java.util.List; public enum MesCalShiftTypeEnum implements ArrayValuable { SINGLE(1, "单白班", ListUtil.of( - Pair.of("白班", new String[]{"08:00", "17:00"}) + Pair.of("白班", new String[]{"08:00", "18:00"}) )), TWO(2, "两班倒", ListUtil.of( Pair.of("白班", new String[]{"08:00", "20:00"}), @@ -45,7 +45,7 @@ public enum MesCalShiftTypeEnum implements ArrayValuable { * 班次配置列表 * * key: 班次名称(如 "白班", "夜班", "中班") - * value: [startTime, endTime](如 ["08:00", "17:00"]) + * value: [startTime, endTime](如 ["08:00", "18:00"]) */ private final List> shifts; diff --git a/yudao-module-mes/src/test/java/cn/iocoder/yudao/module/mes/service/cal/plan/MesCalPlanServiceImplTest.java b/yudao-module-mes/src/test/java/cn/iocoder/yudao/module/mes/service/cal/plan/MesCalPlanServiceImplTest.java new file mode 100644 index 0000000..9f0016e --- /dev/null +++ b/yudao-module-mes/src/test/java/cn/iocoder/yudao/module/mes/service/cal/plan/MesCalPlanServiceImplTest.java @@ -0,0 +1,119 @@ +package cn.iocoder.yudao.module.mes.service.cal.plan; + +import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest; +import cn.iocoder.yudao.module.mes.controller.admin.cal.plan.vo.MesCalPlanSaveReqVO; +import cn.iocoder.yudao.module.mes.dal.dataobject.cal.plan.MesCalPlanDO; +import cn.iocoder.yudao.module.mes.dal.mysql.cal.plan.MesCalPlanMapper; +import cn.iocoder.yudao.module.mes.enums.cal.MesCalPlanStatusEnum; +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.team.MesCalTeamShiftService; +import org.junit.jupiter.api.Test; +import org.springframework.context.annotation.Import; +import org.springframework.test.context.bean.override.mockito.MockitoBean; + +import jakarta.annotation.Resource; + +import java.time.LocalDateTime; + +import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +/** + * {@link MesCalPlanServiceImpl} 的单元测试 + * + * @author 芋道源码 + */ +@Import(MesCalPlanServiceImpl.class) +public class MesCalPlanServiceImplTest extends BaseDbUnitTest { + + @Resource + private MesCalPlanServiceImpl planService; + + @Resource + private MesCalPlanMapper planMapper; + + @MockitoBean + private MesCalPlanShiftService planShiftService; + @MockitoBean + private MesCalPlanTeamService planTeamService; + @MockitoBean + private MesCalTeamShiftService teamShiftService; + + @Test + public void testCreatePlan_singleShift_addDefaultShift() { + // 准备参数 + MesCalPlanSaveReqVO reqVO = new MesCalPlanSaveReqVO(); + reqVO.setCode("PLAN-SINGLE-001"); + reqVO.setName("单白班默认班次"); + reqVO.setCalendarType(1); + reqVO.setStartDate(LocalDateTime.of(2026, 4, 1, 0, 0)); + reqVO.setEndDate(LocalDateTime.of(2026, 4, 30, 0, 0)); + reqVO.setShiftType(MesCalShiftTypeEnum.SINGLE.getType()); + reqVO.setShiftMethod(MesCalShiftMethodEnum.DAY.getMethod()); + reqVO.setShiftCount(1); + + // 调用 + Long planId = planService.createPlan(reqVO); + + // 断言 1:计划状态为草稿 + MesCalPlanDO plan = planMapper.selectById(planId); + assertEquals(MesCalPlanStatusEnum.PREPARE.getStatus(), plan.getStatus()); + // 断言 2:自动生成默认班次 + verify(planShiftService).addDefaultPlanShift(planId, MesCalShiftTypeEnum.SINGLE.getType()); + } + + @Test + public void testConfirmPlan_syncGenerateTeamShiftRecords() { + // mock 数据:插入一条草稿状态的排班计划 + MesCalPlanDO plan = randomPojo(MesCalPlanDO.class, o -> { + o.setCalendarType(1); + o.setStartDate(LocalDateTime.of(2026, 4, 1, 0, 0)); + o.setEndDate(LocalDateTime.of(2026, 4, 3, 0, 0)); + o.setStatus(MesCalPlanStatusEnum.PREPARE.getStatus()); + o.setShiftType(MesCalShiftTypeEnum.SINGLE.getType()); + o.setShiftMethod(MesCalShiftMethodEnum.DAY.getMethod()); + o.setShiftCount(1); + }); + planMapper.insert(plan); + // mock 方法:班组数量满足要求 + when(planTeamService.getPlanTeamCountByPlanId(plan.getId())).thenReturn(1L); + + // 调用 + planService.confirmPlan(plan.getId()); + + // 断言 1:计划状态更新为已确认 + MesCalPlanDO updatePlan = planMapper.selectById(plan.getId()); + assertEquals(MesCalPlanStatusEnum.CONFIRMED.getStatus(), updatePlan.getStatus()); + // 断言 2:触发生成班组排班记录 + verify(teamShiftService).generateTeamShiftRecords(plan.getId()); + } + + @Test + public void testDeletePlan_notCascadeDeleteTeamShift() { + // mock 数据:插入一条草稿状态的排班计划 + MesCalPlanDO plan = randomPojo(MesCalPlanDO.class, o -> { + o.setCalendarType(1); + o.setStartDate(LocalDateTime.of(2026, 4, 1, 0, 0)); + o.setEndDate(LocalDateTime.of(2026, 4, 3, 0, 0)); + o.setStatus(MesCalPlanStatusEnum.PREPARE.getStatus()); + o.setShiftType(MesCalShiftTypeEnum.SINGLE.getType()); + o.setShiftMethod(MesCalShiftMethodEnum.DAY.getMethod()); + o.setShiftCount(1); + }); + planMapper.insert(plan); + + // 调用 + planService.deletePlan(plan.getId()); + + // 断言 1:计划被删除 + assertNull(planMapper.selectById(plan.getId())); + // 断言 2:级联删除班次和班组关联 + verify(planShiftService).deletePlanShiftByPlanId(plan.getId()); + verify(planTeamService).deleteByPlanId(plan.getId()); + // 断言 3:不级联删除班组排班记录 + verify(teamShiftService, never()).deleteByPlanId(plan.getId()); + } + +} diff --git a/yudao-module-mes/src/test/java/cn/iocoder/yudao/module/mes/service/cal/plan/MesCalPlanShiftServiceImplTest.java b/yudao-module-mes/src/test/java/cn/iocoder/yudao/module/mes/service/cal/plan/MesCalPlanShiftServiceImplTest.java new file mode 100644 index 0000000..da31e70 --- /dev/null +++ b/yudao-module-mes/src/test/java/cn/iocoder/yudao/module/mes/service/cal/plan/MesCalPlanShiftServiceImplTest.java @@ -0,0 +1,53 @@ +package cn.iocoder.yudao.module.mes.service.cal.plan; + +import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest; +import cn.iocoder.yudao.module.mes.dal.dataobject.cal.plan.MesCalPlanShiftDO; +import cn.iocoder.yudao.module.mes.dal.mysql.cal.plan.MesCalPlanShiftMapper; +import cn.iocoder.yudao.module.mes.enums.cal.MesCalShiftTypeEnum; +import org.junit.jupiter.api.Test; +import org.springframework.context.annotation.Import; +import org.springframework.test.context.bean.override.mockito.MockitoBean; + +import jakarta.annotation.Resource; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * {@link MesCalPlanShiftServiceImpl} 的单元测试 + * + * @author 芋道源码 + */ +@Import(MesCalPlanShiftServiceImpl.class) +public class MesCalPlanShiftServiceImplTest extends BaseDbUnitTest { + + @Resource + private MesCalPlanShiftServiceImpl planShiftService; + + @Resource + private MesCalPlanShiftMapper planShiftMapper; + + @MockitoBean + private MesCalPlanService planService; + + @Test + public void testAddDefaultPlanShift_singleShift_use18Clock() { + // 准备参数 + Long planId = 1001L; + + // 调用 + planShiftService.addDefaultPlanShift(planId, MesCalShiftTypeEnum.SINGLE.getType()); + + // 断言:生成 1 条默认班次(白班 08:00~18:00) + List shifts = planShiftMapper.selectListByPlanId(planId); + assertEquals(1, shifts.size()); + MesCalPlanShiftDO shift = shifts.get(0); + assertEquals(planId, shift.getPlanId()); + assertEquals(1, shift.getSort()); + assertEquals("白班", shift.getName()); + assertEquals("08:00", shift.getStartTime()); + assertEquals("18:00", shift.getEndTime()); + } + +} diff --git a/yudao-module-mes/src/test/java/cn/iocoder/yudao/module/mes/service/cal/team/MesCalTeamShiftServiceImplTest.java b/yudao-module-mes/src/test/java/cn/iocoder/yudao/module/mes/service/cal/team/MesCalTeamShiftServiceImplTest.java new file mode 100644 index 0000000..ac784ff --- /dev/null +++ b/yudao-module-mes/src/test/java/cn/iocoder/yudao/module/mes/service/cal/team/MesCalTeamShiftServiceImplTest.java @@ -0,0 +1,107 @@ +package cn.iocoder.yudao.module.mes.service.cal.team; + +import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest; +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.MesCalShiftMethodEnum; +import cn.iocoder.yudao.module.mes.enums.cal.MesCalShiftTypeEnum; +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.junit.jupiter.api.Test; +import org.springframework.context.annotation.Import; +import org.springframework.test.context.bean.override.mockito.MockitoBean; + +import jakarta.annotation.Resource; + +import java.time.LocalDateTime; +import java.util.Comparator; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +/** + * {@link MesCalTeamShiftServiceImpl} 的单元测试 + * + * @author 芋道源码 + */ +@Import(MesCalTeamShiftServiceImpl.class) +public class MesCalTeamShiftServiceImplTest extends BaseDbUnitTest { + + @Resource + private MesCalTeamShiftServiceImpl teamShiftService; + + @Resource + private MesCalTeamShiftMapper teamShiftMapper; + + @MockitoBean + private MesCalPlanService planService; + @MockitoBean + private MesCalPlanShiftService planShiftService; + @MockitoBean + private MesCalPlanTeamService planTeamService; + + @Test + public void testGenerateTeamShiftRecords_threeShift_rotateThreeStates() { + // 准备参数 + Long planId = 3001L; + // mock 方法:排班计划(三班倒、按天轮班、3 天) + MesCalPlanDO plan = MesCalPlanDO.builder() + .id(planId) + .code("PLAN-THREE-001") + .name("三班倒轮转") + .calendarType(1) + .startDate(LocalDateTime.of(2026, 4, 1, 0, 0)) + .endDate(LocalDateTime.of(2026, 4, 3, 0, 0)) + .shiftType(MesCalShiftTypeEnum.THREE.getType()) + .shiftMethod(MesCalShiftMethodEnum.DAY.getMethod()) + .shiftCount(1) + .build(); + List shifts = List.of( + MesCalPlanShiftDO.builder().id(11L).planId(planId).sort(1).name("白班").startTime("08:00").endTime("16:00").build(), + MesCalPlanShiftDO.builder().id(12L).planId(planId).sort(2).name("中班").startTime("16:00").endTime("00:00").build(), + MesCalPlanShiftDO.builder().id(13L).planId(planId).sort(3).name("夜班").startTime("00:00").endTime("08:00").build() + ); + List teams = List.of( + MesCalPlanTeamDO.builder().id(21L).planId(planId).teamId(101L).build(), + MesCalPlanTeamDO.builder().id(22L).planId(planId).teamId(102L).build(), + MesCalPlanTeamDO.builder().id(23L).planId(planId).teamId(103L).build() + ); + when(planService.getPlan(planId)).thenReturn(plan); + when(planShiftService.getPlanShiftListByPlanId(planId)).thenReturn(shifts); + when(planTeamService.getPlanTeamListByPlanId(planId)).thenReturn(teams); + + // 调用 + teamShiftService.generateTeamShiftRecords(planId); + + // 断言:3 天 × 3 班次 = 9 条排班记录,班组按天轮转 + List records = teamShiftMapper.selectListByPlanId(planId); + records.sort(Comparator.comparing(MesCalTeamShiftDO::getDay).thenComparing(MesCalTeamShiftDO::getSort)); + assertEquals(9, records.size()); + // 断言:第 1 天(shiftIndex=0):A 白班、B 中班、C 夜班 + assertRecord(records.get(0), LocalDateTime.of(2026, 4, 1, 0, 0), 1, 101L, 11L); + assertRecord(records.get(1), LocalDateTime.of(2026, 4, 1, 0, 0), 2, 102L, 12L); + assertRecord(records.get(2), LocalDateTime.of(2026, 4, 1, 0, 0), 3, 103L, 13L); + // 断言:第 2 天(shiftIndex=1):C 白班、A 中班、B 夜班 + assertRecord(records.get(3), LocalDateTime.of(2026, 4, 2, 0, 0), 1, 103L, 11L); + assertRecord(records.get(4), LocalDateTime.of(2026, 4, 2, 0, 0), 2, 101L, 12L); + assertRecord(records.get(5), LocalDateTime.of(2026, 4, 2, 0, 0), 3, 102L, 13L); + // 断言:第 3 天(shiftIndex=2):B 白班、C 中班、A 夜班 + assertRecord(records.get(6), LocalDateTime.of(2026, 4, 3, 0, 0), 1, 102L, 11L); + assertRecord(records.get(7), LocalDateTime.of(2026, 4, 3, 0, 0), 2, 103L, 12L); + assertRecord(records.get(8), LocalDateTime.of(2026, 4, 3, 0, 0), 3, 101L, 13L); + } + + private static void assertRecord(MesCalTeamShiftDO actual, LocalDateTime day, Integer sort, + Long teamId, Long shiftId) { + assertEquals(day, actual.getDay()); + assertEquals(sort, actual.getSort()); + assertEquals(teamId, actual.getTeamId()); + assertEquals(shiftId, actual.getShiftId()); + } + +} diff --git a/yudao-module-mes/src/test/resources/application-unit-test.yaml b/yudao-module-mes/src/test/resources/application-unit-test.yaml index 6e02b80..1442d6d 100644 --- a/yudao-module-mes/src/test/resources/application-unit-test.yaml +++ b/yudao-module-mes/src/test/resources/application-unit-test.yaml @@ -10,7 +10,7 @@ spring: # 数据源配置项 datasource: name: ruoyi-vue-pro - url: jdbc:h2:mem:testdb;MODE=MYSQL;DATABASE_TO_UPPER=false;NON_KEYWORDS=value; # MODE 使用 MySQL 模式;DATABASE_TO_UPPER 配置表和字段使用小写 + url: jdbc:h2:mem:testdb;MODE=MYSQL;DATABASE_TO_UPPER=false;NON_KEYWORDS=value,day; # MODE 使用 MySQL 模式;DATABASE_TO_UPPER 配置表和字段使用小写 driver-class-name: org.h2.Driver username: sa password: diff --git a/yudao-module-mes/src/test/resources/sql/clean.sql b/yudao-module-mes/src/test/resources/sql/clean.sql index 6a96976..05b58c8 100644 --- a/yudao-module-mes/src/test/resources/sql/clean.sql +++ b/yudao-module-mes/src/test/resources/sql/clean.sql @@ -1,3 +1,7 @@ +DELETE FROM "mes_cal_team_shift"; +DELETE FROM "mes_cal_plan_team"; +DELETE FROM "mes_cal_plan_shift"; +DELETE FROM "mes_cal_plan"; DELETE FROM "mes_md_auto_code_rule"; DELETE FROM "mes_md_auto_code_part"; DELETE FROM "mes_md_auto_code_record"; diff --git a/yudao-module-mes/src/test/resources/sql/create_tables.sql b/yudao-module-mes/src/test/resources/sql/create_tables.sql index 4565b76..734f74e 100644 --- a/yudao-module-mes/src/test/resources/sql/create_tables.sql +++ b/yudao-module-mes/src/test/resources/sql/create_tables.sql @@ -1,4 +1,85 @@ -- ---------------------------- +-- MES 排班计划 +-- ---------------------------- +CREATE TABLE IF NOT EXISTS "mes_cal_plan" ( + "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY, + "code" varchar(64) DEFAULT NULL, + "name" varchar(255) DEFAULT NULL, + "calendar_type" tinyint DEFAULT NULL, + "start_date" timestamp DEFAULT NULL, + "end_date" timestamp DEFAULT NULL, + "shift_type" tinyint DEFAULT NULL, + "shift_method" tinyint DEFAULT NULL, + "shift_count" int DEFAULT NULL, + "status" tinyint DEFAULT NULL, + "remark" varchar(500) DEFAULT NULL, + "creator" varchar(64) DEFAULT '', + "create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updater" varchar(64) DEFAULT '', + "update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + "deleted" bit NOT NULL DEFAULT FALSE, + "tenant_id" bigint NOT NULL DEFAULT 0, + PRIMARY KEY ("id") +); + +-- ---------------------------- +-- MES 排班计划班次 +-- ---------------------------- +CREATE TABLE IF NOT EXISTS "mes_cal_plan_shift" ( + "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY, + "plan_id" bigint DEFAULT NULL, + "sort" int DEFAULT NULL, + "name" varchar(64) DEFAULT NULL, + "start_time" varchar(10) DEFAULT NULL, + "end_time" varchar(10) DEFAULT NULL, + "remark" varchar(500) DEFAULT NULL, + "creator" varchar(64) DEFAULT '', + "create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updater" varchar(64) DEFAULT '', + "update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + "deleted" bit NOT NULL DEFAULT FALSE, + "tenant_id" bigint NOT NULL DEFAULT 0, + PRIMARY KEY ("id") +); + +-- ---------------------------- +-- MES 排班计划班组 +-- ---------------------------- +CREATE TABLE IF NOT EXISTS "mes_cal_plan_team" ( + "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY, + "plan_id" bigint DEFAULT NULL, + "team_id" bigint DEFAULT NULL, + "remark" varchar(500) DEFAULT NULL, + "creator" varchar(64) DEFAULT '', + "create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updater" varchar(64) DEFAULT '', + "update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + "deleted" bit NOT NULL DEFAULT FALSE, + "tenant_id" bigint NOT NULL DEFAULT 0, + PRIMARY KEY ("id") +); + +-- ---------------------------- +-- MES 班组排班 +-- ---------------------------- +CREATE TABLE IF NOT EXISTS "mes_cal_team_shift" ( + "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY, + "plan_id" bigint DEFAULT NULL, + "team_id" bigint DEFAULT NULL, + "shift_id" bigint DEFAULT NULL, + "day" timestamp DEFAULT NULL, + "sort" int DEFAULT NULL, + "remark" varchar(500) DEFAULT NULL, + "creator" varchar(64) DEFAULT '', + "create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updater" varchar(64) DEFAULT '', + "update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + "deleted" bit NOT NULL DEFAULT FALSE, + "tenant_id" bigint NOT NULL DEFAULT 0, + PRIMARY KEY ("id") +); + +-- ---------------------------- -- MES 编码规则表 -- ---------------------------- CREATE TABLE IF NOT EXISTS "mes_md_auto_code_rule" (