Commit bbceb21d4a237ee575ff957700875657f300330a

Authored by zichun
1 parent 0cc2b2eb

feat(mod): module list tree service + vo REQ-MOD-004

backend/src/main/java/com/xly/erp/module/mod/service/ModuleService.java
... ... @@ -2,6 +2,9 @@ package com.xly.erp.module.mod.service;
2 2  
3 3 import com.xly.erp.module.mod.dto.CreateModuleDTO;
4 4 import com.xly.erp.module.mod.dto.UpdateModuleDTO;
  5 +import com.xly.erp.module.mod.vo.ModuleTreeVO;
  6 +
  7 +import java.util.List;
5 8  
6 9 public interface ModuleService {
7 10 Integer create(CreateModuleDTO dto);
... ... @@ -9,4 +12,6 @@ public interface ModuleService {
9 12 Integer update(Integer id, UpdateModuleDTO dto);
10 13  
11 14 void delete(Integer id);
  15 +
  16 + List<ModuleTreeVO> listTree(String keyword);
12 17 }
... ...
backend/src/main/java/com/xly/erp/module/mod/service/impl/ModuleServiceImpl.java
... ... @@ -7,6 +7,7 @@ import com.xly.erp.common.security.SecurityContextHelper;
7 7 import com.xly.erp.module.mod.dto.CreateModuleDTO;
8 8 import com.xly.erp.module.mod.dto.UpdateModuleDTO;
9 9 import com.xly.erp.module.mod.entity.Module;
  10 +import com.xly.erp.module.mod.vo.ModuleTreeVO;
10 11 import com.xly.erp.module.mod.mapper.ModuleMapper;
11 12 import com.xly.erp.module.mod.service.ModuleService;
12 13 import org.springframework.dao.DuplicateKeyException;
... ... @@ -14,6 +15,10 @@ import org.springframework.stereotype.Service;
14 15 import org.springframework.transaction.annotation.Transactional;
15 16  
16 17 import java.time.LocalDateTime;
  18 +import java.util.ArrayList;
  19 +import java.util.HashMap;
  20 +import java.util.List;
  21 +import java.util.Map;
17 22 import java.util.Set;
18 23  
19 24 @Service
... ... @@ -112,6 +117,42 @@ public class ModuleServiceImpl implements ModuleService {
112 117 moduleMapper.updateById(entity);
113 118 }
114 119  
  120 + @Override
  121 + @Transactional(readOnly = true)
  122 + public List<ModuleTreeVO> listTree(String keyword) {
  123 + String normalized = keyword == null ? "" : keyword.trim();
  124 + if (normalized.length() > 100) {
  125 + throw new BizException(40001, "keyword 长度超过 100 字符");
  126 + }
  127 + List<Module> rows = moduleMapper.selectActiveByKeyword(normalized);
  128 + Map<Integer, ModuleTreeVO> idIndex = new HashMap<>();
  129 + for (Module m : rows) {
  130 + idIndex.put(m.getIIncrement(), toTreeVO(m));
  131 + }
  132 + List<ModuleTreeVO> roots = new ArrayList<>();
  133 + for (Module m : rows) {
  134 + ModuleTreeVO vo = idIndex.get(m.getIIncrement());
  135 + Integer parentId = m.getIParentId();
  136 + if (parentId != null && idIndex.containsKey(parentId)) {
  137 + idIndex.get(parentId).getChildren().add(vo);
  138 + } else {
  139 + roots.add(vo);
  140 + }
  141 + }
  142 + return roots;
  143 + }
  144 +
  145 + private ModuleTreeVO toTreeVO(Module m) {
  146 + ModuleTreeVO vo = new ModuleTreeVO();
  147 + vo.setIIncrement(m.getIIncrement());
  148 + vo.setSModuleNameZh(m.getSModuleNameZh());
  149 + vo.setSDisplayType(m.getSDisplayType());
  150 + vo.setSManageDeptEn(m.getSManageDeptEn());
  151 + vo.setIParentId(m.getIParentId());
  152 + vo.setISortOrder(m.getISortOrder());
  153 + return vo;
  154 + }
  155 +
115 156 private void validateParent(Integer id, Integer parentId) {
116 157 if (parentId == null) {
117 158 return;
... ...
backend/src/main/java/com/xly/erp/module/mod/vo/ModuleTreeVO.java 0 → 100644
  1 +package com.xly.erp.module.mod.vo;
  2 +
  3 +import com.fasterxml.jackson.annotation.JsonProperty;
  4 +
  5 +import java.util.ArrayList;
  6 +import java.util.List;
  7 +
  8 +public class ModuleTreeVO {
  9 +
  10 + @JsonProperty("iIncrement")
  11 + private Integer iIncrement;
  12 +
  13 + @JsonProperty("sModuleNameZh")
  14 + private String sModuleNameZh;
  15 +
  16 + @JsonProperty("sDisplayType")
  17 + private String sDisplayType;
  18 +
  19 + @JsonProperty("sManageDeptEn")
  20 + private String sManageDeptEn;
  21 +
  22 + @JsonProperty("iParentId")
  23 + private Integer iParentId;
  24 +
  25 + @JsonProperty("iSortOrder")
  26 + private Integer iSortOrder;
  27 +
  28 + @JsonProperty("children")
  29 + private List<ModuleTreeVO> children = new ArrayList<>();
  30 +
  31 + public Integer getIIncrement() { return iIncrement; }
  32 + public void setIIncrement(Integer iIncrement) { this.iIncrement = iIncrement; }
  33 + public String getSModuleNameZh() { return sModuleNameZh; }
  34 + public void setSModuleNameZh(String sModuleNameZh) { this.sModuleNameZh = sModuleNameZh; }
  35 + public String getSDisplayType() { return sDisplayType; }
  36 + public void setSDisplayType(String sDisplayType) { this.sDisplayType = sDisplayType; }
  37 + public String getSManageDeptEn() { return sManageDeptEn; }
  38 + public void setSManageDeptEn(String sManageDeptEn) { this.sManageDeptEn = sManageDeptEn; }
  39 + public Integer getIParentId() { return iParentId; }
  40 + public void setIParentId(Integer iParentId) { this.iParentId = iParentId; }
  41 + public Integer getISortOrder() { return iSortOrder; }
  42 + public void setISortOrder(Integer iSortOrder) { this.iSortOrder = iSortOrder; }
  43 + public List<ModuleTreeVO> getChildren() { return children; }
  44 + public void setChildren(List<ModuleTreeVO> children) { this.children = children; }
  45 +}
... ...
backend/src/test/java/com/xly/erp/module/mod/service/ModuleServiceImplTest.java
... ... @@ -8,6 +8,7 @@ import com.xly.erp.module.mod.dto.UpdateModuleDTO;
8 8 import com.xly.erp.module.mod.entity.Module;
9 9 import com.xly.erp.module.mod.mapper.ModuleMapper;
10 10 import com.xly.erp.module.mod.service.impl.ModuleServiceImpl;
  11 +import com.xly.erp.module.mod.vo.ModuleTreeVO;
11 12 import org.junit.jupiter.api.AfterEach;
12 13 import org.junit.jupiter.api.BeforeEach;
13 14 import org.junit.jupiter.api.Test;
... ... @@ -17,6 +18,7 @@ import org.springframework.security.authentication.UsernamePasswordAuthenticatio
17 18 import org.springframework.security.core.context.SecurityContextHolder;
18 19  
19 20 import java.util.Collections;
  21 +import java.util.List;
20 22  
21 23 import static org.assertj.core.api.Assertions.assertThat;
22 24 import static org.assertj.core.api.Assertions.assertThatThrownBy;
... ... @@ -306,6 +308,85 @@ class ModuleServiceImplTest {
306 308 assertThat(captor.getValue().getSDeletedBy()).isEqualTo("BOB");
307 309 }
308 310  
  311 + @Test
  312 + void listTree_emptyKeyword_invokesMapperWithEmptyString_returnsAssembledTree() {
  313 + Module root1 = treeRow(1, "根1", null, 0);
  314 + Module root2 = treeRow(2, "根2", null, 0);
  315 + Module child1 = treeRow(3, "子1", 1, 0);
  316 + Module child2 = treeRow(4, "子2", 1, 1);
  317 + Module grand1 = treeRow(5, "孙1", 3, 0);
  318 + when(moduleMapper.selectActiveByKeyword("")).thenReturn(List.of(root1, root2, child1, child2, grand1));
  319 +
  320 + List<ModuleTreeVO> result = service.listTree("");
  321 +
  322 + assertThat(result).extracting(ModuleTreeVO::getIIncrement).containsExactly(1, 2);
  323 + assertThat(result.get(0).getChildren()).extracting(ModuleTreeVO::getIIncrement).containsExactly(3, 4);
  324 + assertThat(result.get(0).getChildren().get(0).getChildren()).extracting(ModuleTreeVO::getIIncrement).containsExactly(5);
  325 + assertThat(result.get(1).getChildren()).isEmpty();
  326 + }
  327 +
  328 + @Test
  329 + void listTree_nullKeyword_treatedAsEmpty() {
  330 + when(moduleMapper.selectActiveByKeyword("")).thenReturn(List.of());
  331 + service.listTree(null);
  332 + ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class);
  333 + verify(moduleMapper).selectActiveByKeyword(captor.capture());
  334 + assertThat(captor.getValue()).isEqualTo("");
  335 + }
  336 +
  337 + @Test
  338 + void listTree_blankKeyword_treatedAsEmpty() {
  339 + when(moduleMapper.selectActiveByKeyword("")).thenReturn(List.of());
  340 + service.listTree(" ");
  341 + verify(moduleMapper).selectActiveByKeyword("");
  342 + }
  343 +
  344 + @Test
  345 + void listTree_keywordTooLong_throws40001() {
  346 + String longKw = "x".repeat(101);
  347 + assertThatThrownBy(() -> service.listTree(longKw))
  348 + .isInstanceOf(BizException.class)
  349 + .hasFieldOrPropertyWithValue("code", 40001);
  350 + verify(moduleMapper, never()).selectActiveByKeyword(any());
  351 + }
  352 +
  353 + @Test
  354 + void listTree_returnsEmptyListWhenNoMatch() {
  355 + when(moduleMapper.selectActiveByKeyword("xyz")).thenReturn(List.of());
  356 + List<ModuleTreeVO> result = service.listTree("xyz");
  357 + assertThat(result).isEmpty();
  358 + }
  359 +
  360 + @Test
  361 + void listTree_orphansBecomeRootsInForest() {
  362 + Module orphan = treeRow(3, "孤儿", 99, 0);
  363 + when(moduleMapper.selectActiveByKeyword("")).thenReturn(List.of(orphan));
  364 +
  365 + List<ModuleTreeVO> result = service.listTree("");
  366 +
  367 + assertThat(result).hasSize(1);
  368 + assertThat(result.get(0).getIIncrement()).isEqualTo(3);
  369 + assertThat(result.get(0).getChildren()).isEmpty();
  370 + }
  371 +
  372 + @Test
  373 + void listTree_keywordIsTrimmedBeforeQuery() {
  374 + when(moduleMapper.selectActiveByKeyword("系统")).thenReturn(List.of());
  375 + service.listTree(" 系统 ");
  376 + verify(moduleMapper).selectActiveByKeyword("系统");
  377 + }
  378 +
  379 + private Module treeRow(int id, String name, Integer parentId, int sortOrder) {
  380 + Module m = new Module();
  381 + m.setIIncrement(id);
  382 + m.setSModuleNameZh(name);
  383 + m.setSDisplayType("手机端");
  384 + m.setSManageDeptEn("IT");
  385 + m.setIParentId(parentId);
  386 + m.setISortOrder(sortOrder);
  387 + return m;
  388 + }
  389 +
309 390 private UpdateModuleDTO baseUpdateDto() {
310 391 UpdateModuleDTO dto = new UpdateModuleDTO();
311 392 dto.setSDisplayType("手机端");
... ...