feat(auth): U2 组织架构 Schema 扩展 org_type=5 岗位
V5 migration 新增岗位类型(5) + 种子岗位数据(维修工/维修组长), OrgTypeEnum 枚举, OrgService.list 增加 orgType 可选过滤. 满足 R5/R6, 解决 OQ-7/8
This commit is contained in:
parent
f3110d0ce6
commit
444381e831
|
|
@ -31,8 +31,9 @@ public class OrgController {
|
|||
@GetMapping
|
||||
public Result<List<OrgDTO>> list(@RequestParam(required = false) Long parentId,
|
||||
@RequestParam(required = false) Integer status,
|
||||
@RequestParam(required = false) Long projectId) {
|
||||
return Result.success(orgService.list(parentId, status, projectId));
|
||||
@RequestParam(required = false) Long projectId,
|
||||
@RequestParam(required = false) Integer orgType) {
|
||||
return Result.success(orgService.list(parentId, status, projectId, orgType));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ public class OrgDTO implements Serializable {
|
|||
|
||||
private String orgName;
|
||||
|
||||
/** 类型:1公司 2部门 3小组 4项目部 */
|
||||
/** 类型:1公司 2部门 3小组 4项目部 5岗位 */
|
||||
private Integer orgType;
|
||||
|
||||
private Long leaderId;
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ public class Org extends BaseEntity {
|
|||
/** 组织名称 */
|
||||
private String orgName;
|
||||
|
||||
/** 类型:1公司 2部门 3小组 4项目部 */
|
||||
/** 类型:1公司 2部门 3小组 4项目部 5岗位 */
|
||||
private Integer orgType;
|
||||
|
||||
/** 负责人用户ID */
|
||||
|
|
|
|||
|
|
@ -0,0 +1,32 @@
|
|||
package com.pms.auth.enums;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 组织类型枚举
|
||||
*/
|
||||
@Getter
|
||||
public enum OrgTypeEnum {
|
||||
|
||||
COMPANY(1, "公司"),
|
||||
DEPARTMENT(2, "部门"),
|
||||
TEAM(3, "小组"),
|
||||
PROJECT_DEPT(4, "项目部"),
|
||||
POSITION(5, "岗位");
|
||||
|
||||
private final int code;
|
||||
private final String desc;
|
||||
|
||||
OrgTypeEnum(int code, String desc) {
|
||||
this.code = code;
|
||||
this.desc = desc;
|
||||
}
|
||||
|
||||
public static OrgTypeEnum fromCode(Integer code) {
|
||||
if (code == null) return null;
|
||||
for (OrgTypeEnum type : values()) {
|
||||
if (type.code == code) return type;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
@ -12,8 +12,11 @@ public interface OrgService {
|
|||
|
||||
/**
|
||||
* 组织列表(树形)
|
||||
*
|
||||
* @param orgType 组织类型过滤(1公司 2部门 3小组 4项目部 5岗位),null 表示不过滤;
|
||||
* 指定时返回扁平列表(岗位等节点无根节点,无法成树)
|
||||
*/
|
||||
List<OrgDTO> list(Long parentId, Integer status, Long projectId);
|
||||
List<OrgDTO> list(Long parentId, Integer status, Long projectId, Integer orgType);
|
||||
|
||||
/**
|
||||
* 创建组织
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ public class OrgServiceImpl implements OrgService {
|
|||
private final OrgMapper orgMapper;
|
||||
|
||||
@Override
|
||||
public List<OrgDTO> list(Long parentId, Integer status, Long projectId) {
|
||||
public List<OrgDTO> list(Long parentId, Integer status, Long projectId, Integer orgType) {
|
||||
Long currentProjectId = projectId != null ? projectId : UserContext.getProjectId();
|
||||
|
||||
LambdaQueryWrapper<Org> wrapper = new LambdaQueryWrapper<>();
|
||||
|
|
@ -38,6 +38,9 @@ public class OrgServiceImpl implements OrgService {
|
|||
if (status != null) {
|
||||
wrapper.eq(Org::getStatus, status);
|
||||
}
|
||||
if (orgType != null) {
|
||||
wrapper.eq(Org::getOrgType, orgType);
|
||||
}
|
||||
if (currentProjectId != null) {
|
||||
wrapper.and(w -> w.eq(Org::getProjectId, currentProjectId).or().isNull(Org::getProjectId));
|
||||
}
|
||||
|
|
@ -45,6 +48,11 @@ public class OrgServiceImpl implements OrgService {
|
|||
|
||||
List<Org> orgs = orgMapper.selectList(wrapper);
|
||||
|
||||
// 按组织类型过滤时返回扁平列表:岗位等节点无 parent_id=0 根节点,无法成树
|
||||
if (orgType != null) {
|
||||
return orgs.stream().map(this::toDTO).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
// 构建树
|
||||
List<OrgDTO> tree = buildTree(orgs);
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,19 @@
|
|||
-- ========================================
|
||||
-- V5: 扩展 t_org.org_type 枚举:新增 5=岗位 类型
|
||||
-- 岗位挂在部门下,t_project_user.org_id 指向 org_type=5 的节点
|
||||
-- ========================================
|
||||
|
||||
-- 更新 org_type 列注释(无需修改数据类型,TINYINT 已足够)
|
||||
ALTER TABLE `t_org` MODIFY COLUMN `org_type` TINYINT NOT NULL DEFAULT 1 COMMENT '类型:1公司 2部门 3小组 4项目部 5岗位';
|
||||
|
||||
-- 种子岗位数据:在总部(id=1)下创建工程部,工程部下创建岗位
|
||||
-- id=2,3,4 与 V1 种子数据(仅 t_org.id=1)无冲突
|
||||
-- 先创建工程部(如果不存在)
|
||||
INSERT INTO `t_org` (`id`, `project_id`, `parent_id`, `org_code`, `org_name`, `org_type`, `leader_id`, `phone`, `sort`, `status`, `created_at`, `updated_at`, `created_by`, `updated_by`, `deleted`)
|
||||
SELECT 2, NULL, 1, 'ENG', '工程部', 2, NULL, NULL, 1, 1, UNIX_TIMESTAMP() * 1000, UNIX_TIMESTAMP() * 1000, NULL, NULL, 0
|
||||
WHERE NOT EXISTS (SELECT 1 FROM `t_org` WHERE `org_code` = 'ENG' AND `deleted` = 0);
|
||||
|
||||
-- 工程部下创建岗位节点
|
||||
INSERT INTO `t_org` (`id`, `project_id`, `parent_id`, `org_code`, `org_name`, `org_type`, `leader_id`, `phone`, `sort`, `status`, `created_at`, `updated_at`, `created_by`, `updated_by`, `deleted`) VALUES
|
||||
(3, NULL, 2, 'POS_REPAIR', '维修工', 5, NULL, NULL, 1, 1, UNIX_TIMESTAMP() * 1000, UNIX_TIMESTAMP() * 1000, NULL, NULL, 0),
|
||||
(4, NULL, 2, 'POS_REPAIR_LEAD', '维修组长', 5, NULL, NULL, 2, 1, UNIX_TIMESTAMP() * 1000, UNIX_TIMESTAMP() * 1000, NULL, NULL, 0);
|
||||
|
|
@ -0,0 +1,172 @@
|
|||
package com.pms.auth.service;
|
||||
|
||||
import com.pms.auth.dto.OrgDTO;
|
||||
import com.pms.auth.entity.Org;
|
||||
import com.pms.auth.enums.OrgTypeEnum;
|
||||
import com.pms.auth.mapper.OrgMapper;
|
||||
import com.pms.auth.service.impl.OrgServiceImpl;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.mockito.junit.jupiter.MockitoSettings;
|
||||
import org.mockito.quality.Strictness;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* 组织岗位(org_type=5)服务测试
|
||||
* 使用 Mockito Mock OrgMapper,验证 org_type 过滤与岗位种子数据查询
|
||||
*/
|
||||
@DisplayName("OrgService 岗位(org_type=5)测试")
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
@MockitoSettings(strictness = Strictness.LENIENT)
|
||||
class OrgServicePositionTest {
|
||||
|
||||
@Mock
|
||||
private OrgMapper orgMapper;
|
||||
|
||||
@InjectMocks
|
||||
private OrgServiceImpl orgService;
|
||||
|
||||
private Org buildOrg(Long id, Long parentId, String code, String name, int type, int sort) {
|
||||
Org org = new Org();
|
||||
org.setId(id);
|
||||
org.setParentId(parentId);
|
||||
org.setOrgCode(code);
|
||||
org.setOrgName(name);
|
||||
org.setOrgType(type);
|
||||
org.setSort(sort);
|
||||
org.setStatus(1);
|
||||
org.setDeleted(0);
|
||||
return org;
|
||||
}
|
||||
|
||||
@Nested
|
||||
@DisplayName("OrgTypeEnum 枚举测试")
|
||||
class OrgTypeEnumTest {
|
||||
|
||||
@Test
|
||||
@DisplayName("fromCode 正确映射全部类型(含 5=岗位)")
|
||||
void fromCode_allTypes() {
|
||||
assertThat(OrgTypeEnum.fromCode(1)).isEqualTo(OrgTypeEnum.COMPANY);
|
||||
assertThat(OrgTypeEnum.fromCode(2)).isEqualTo(OrgTypeEnum.DEPARTMENT);
|
||||
assertThat(OrgTypeEnum.fromCode(3)).isEqualTo(OrgTypeEnum.TEAM);
|
||||
assertThat(OrgTypeEnum.fromCode(4)).isEqualTo(OrgTypeEnum.PROJECT_DEPT);
|
||||
assertThat(OrgTypeEnum.fromCode(5)).isEqualTo(OrgTypeEnum.POSITION);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("POSITION 编码与描述正确")
|
||||
void position_codeAndDesc() {
|
||||
assertThat(OrgTypeEnum.POSITION.getCode()).isEqualTo(5);
|
||||
assertThat(OrgTypeEnum.POSITION.getDesc()).isEqualTo("岗位");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("fromCode(null) 返回 null")
|
||||
void fromCode_null_returnsNull() {
|
||||
assertThat(OrgTypeEnum.fromCode(null)).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("fromCode 非法值(0 或 >5)返回 null")
|
||||
void fromCode_invalid_returnsNull() {
|
||||
assertThat(OrgTypeEnum.fromCode(0)).isNull();
|
||||
assertThat(OrgTypeEnum.fromCode(6)).isNull();
|
||||
assertThat(OrgTypeEnum.fromCode(-1)).isNull();
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
@DisplayName("list 按 org_type 过滤测试")
|
||||
class ListFilterTest {
|
||||
|
||||
@Test
|
||||
@DisplayName("orgType=5 返回岗位扁平列表(含种子岗位名称)")
|
||||
void list_orgType5_returnsPositionsFlat() {
|
||||
Org pos1 = buildOrg(3L, 2L, "POS_REPAIR", "维修工", 5, 1);
|
||||
Org pos2 = buildOrg(4L, 2L, "POS_REPAIR_LEAD", "维修组长", 5, 2);
|
||||
when(orgMapper.selectList(any())).thenReturn(List.of(pos1, pos2));
|
||||
|
||||
List<OrgDTO> result = orgService.list(null, null, null, 5);
|
||||
|
||||
// 扁平列表:岗位 parent_id=2 无根节点,若走树形逻辑则返回空;
|
||||
// 返回 2 条证明走的是 orgType 过滤的扁平路径,且 DTO 映射保留了类型与名称
|
||||
assertThat(result).hasSize(2);
|
||||
assertThat(result).extracting(OrgDTO::getOrgType).containsOnly(5);
|
||||
assertThat(result).extracting(OrgDTO::getOrgName)
|
||||
.containsExactly("维修工", "维修组长");
|
||||
|
||||
// ponytail: wrapper.eq(Org::getOrgType, orgType) 为平凡一行,不单独断言其 SQL;
|
||||
// MyBatis-Plus LambdaQueryWrapper 在无 Mybatis 上下文的纯单测中无法 getSqlSegment(),
|
||||
// 扁平列表行为已证明 orgType 分支被命中
|
||||
verify(orgMapper).selectList(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("orgType=0(非法)返回空列表")
|
||||
void list_orgType0_returnsEmpty() {
|
||||
when(orgMapper.selectList(any())).thenReturn(List.of());
|
||||
|
||||
List<OrgDTO> result = orgService.list(null, null, null, 0);
|
||||
|
||||
assertThat(result).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("orgType=99(超出范围)返回空列表")
|
||||
void list_orgType99_returnsEmpty() {
|
||||
when(orgMapper.selectList(any())).thenReturn(List.of());
|
||||
|
||||
List<OrgDTO> result = orgService.list(null, null, null, 99);
|
||||
|
||||
assertThat(result).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("orgType=null 不过滤,返回树形结构")
|
||||
void list_orgTypeNull_returnsTree() {
|
||||
Org hq = buildOrg(1L, 0L, "HQ", "物业公司总部", 1, 1);
|
||||
Org dept = buildOrg(2L, 1L, "ENG", "工程部", 2, 1);
|
||||
when(orgMapper.selectList(any())).thenReturn(List.of(hq, dept));
|
||||
|
||||
List<OrgDTO> result = orgService.list(null, null, null, null);
|
||||
|
||||
// 树根为 HQ,工程部为其子节点
|
||||
assertThat(result).hasSize(1);
|
||||
OrgDTO root = result.get(0);
|
||||
assertThat(root.getOrgName()).isEqualTo("物业公司总部");
|
||||
assertThat(root.getChildren()).hasSize(1);
|
||||
assertThat(root.getChildren().get(0).getOrgName()).isEqualTo("工程部");
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
@DisplayName("t_project_user.org_id 指向岗位节点测试")
|
||||
class PositionLookupTest {
|
||||
|
||||
@Test
|
||||
@DisplayName("通过 org_id 查询岗位节点返回正确岗位名称与类型")
|
||||
void lookupPositionByOrgId_returnsCorrectName() {
|
||||
// 模拟 t_project_user.org_id=3 指向岗位节点
|
||||
Org position = buildOrg(3L, 2L, "POS_REPAIR", "维修工", 5, 1);
|
||||
when(orgMapper.selectById(3L)).thenReturn(position);
|
||||
|
||||
Org org = orgMapper.selectById(3L);
|
||||
|
||||
assertThat(org).isNotNull();
|
||||
assertThat(org.getOrgType()).isEqualTo(5);
|
||||
assertThat(org.getOrgName()).isEqualTo("维修工");
|
||||
assertThat(OrgTypeEnum.fromCode(org.getOrgType())).isEqualTo(OrgTypeEnum.POSITION);
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue