From 716b0b5b884839517ab1d0aba1985662e3ef6e99 Mon Sep 17 00:00:00 2001 From: zichun Date: Wed, 6 May 2026 17:59:19 +0800 Subject: [PATCH] feat(mod): DELETE /api/modules/{id} controller REQ-MOD-003 --- backend/src/main/java/com/xly/erp/module/mod/controller/ModuleController.java | 8 ++++++++ backend/src/test/java/com/xly/erp/module/mod/controller/ModuleControllerIT.java | 85 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 93 insertions(+), 0 deletions(-) diff --git a/backend/src/main/java/com/xly/erp/module/mod/controller/ModuleController.java b/backend/src/main/java/com/xly/erp/module/mod/controller/ModuleController.java index 111a1ea..0b177b3 100644 --- a/backend/src/main/java/com/xly/erp/module/mod/controller/ModuleController.java +++ b/backend/src/main/java/com/xly/erp/module/mod/controller/ModuleController.java @@ -4,9 +4,11 @@ import com.xly.erp.common.response.ApiResponse; import com.xly.erp.module.mod.dto.ModuleCreateDTO; import com.xly.erp.module.mod.dto.ModuleUpdateDTO; import com.xly.erp.module.mod.service.ModuleService; +import com.xly.erp.module.mod.vo.ModuleDeleteResultVO; import com.xly.erp.module.mod.vo.ModuleVO; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PutMapping; @@ -32,4 +34,10 @@ public class ModuleController { public ApiResponse update(@PathVariable Integer id, @Valid @RequestBody ModuleUpdateDTO dto) { return ApiResponse.ok(moduleService.update(id, dto)); } + + /** REQ-MOD-003 模块软删除 — REQ-USR-004 完成后追加 @PreAuthorize("hasAuthority('MOD:DELETE')") */ + @DeleteMapping("/{id}") + public ApiResponse delete(@PathVariable Integer id) { + return ApiResponse.ok(moduleService.delete(id)); + } } diff --git a/backend/src/test/java/com/xly/erp/module/mod/controller/ModuleControllerIT.java b/backend/src/test/java/com/xly/erp/module/mod/controller/ModuleControllerIT.java index e9ed767..ca53dda 100644 --- a/backend/src/test/java/com/xly/erp/module/mod/controller/ModuleControllerIT.java +++ b/backend/src/test/java/com/xly/erp/module/mod/controller/ModuleControllerIT.java @@ -18,6 +18,7 @@ import org.springframework.transaction.annotation.Transactional; import java.time.LocalDateTime; import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; @@ -267,6 +268,90 @@ class ModuleControllerIT { .andExpect(jsonPath("$.code").value(40010)); } + // ============================================================ + // REQ-MOD-003 DELETE 系列 + // ============================================================ + + @Test + void delete_validLeaf_returns200WithBDeletedTrue() throws Exception { + Integer id = insertExisting("sp_del_leaf_" + System.nanoTime(), null); + + mockMvc.perform(delete("/api/modules/" + id)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value(200)) + .andExpect(jsonPath("$.data.iIncrement").value(id)) + .andExpect(jsonPath("$.data.bDeleted").value(true)); + + ModuleEntity reloaded = moduleMapper.selectById(id); + assertThat(reloaded.getBDeleted()).isTrue(); + assertThat(reloaded.getTDeletedDate()).isNotNull(); + } + + @Test + void delete_targetNotFound_returns40421() throws Exception { + mockMvc.perform(delete("/api/modules/999999")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value(40421)); + } + + @Test + void delete_targetAlreadyDeleted_returns40421() throws Exception { + Integer id = insertExisting("sp_del_already_" + System.nanoTime(), null); + // 手工置 bDeleted=true + ModuleEntity patch = new ModuleEntity(); + patch.setIIncrement(id); + patch.setBDeleted(true); + moduleMapper.updateById(patch); + + mockMvc.perform(delete("/api/modules/" + id)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value(40421)); + } + + @Test + void delete_hasUndeletedChildren_returns40912() throws Exception { + Integer parentId = insertExisting("sp_del_par_" + System.nanoTime(), null); + insertExisting("sp_del_chi_" + System.nanoTime(), parentId); + + mockMvc.perform(delete("/api/modules/" + parentId)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value(40912)); + + // parent 仍未删除 + assertThat(moduleMapper.selectById(parentId).getBDeleted()).isFalse(); + } + + @Test + void delete_softDeletedChildren_doesNotBlock_returns200() throws Exception { + Integer parentId = insertExisting("sp_del_pp_" + System.nanoTime(), null); + Integer childId = insertExisting("sp_del_cc_" + System.nanoTime(), parentId); + + // 先 DELETE child(应成功) + mockMvc.perform(delete("/api/modules/" + childId)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value(200)); + + // 再 DELETE parent(已删除子不阻塞) + mockMvc.perform(delete("/api/modules/" + parentId)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value(200)); + + assertThat(moduleMapper.selectById(parentId).getBDeleted()).isTrue(); + } + + @Test + void delete_responseVOContainsOnlyIIncrementAndBDeleted() throws Exception { + Integer id = insertExisting("sp_del_vo_" + System.nanoTime(), null); + + mockMvc.perform(delete("/api/modules/" + id)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.data.iIncrement").value(id)) + .andExpect(jsonPath("$.data.bDeleted").value(true)) + .andExpect(jsonPath("$.data.sProcedureName").doesNotExist()) + .andExpect(jsonPath("$.data.sDisplayType").doesNotExist()) + .andExpect(jsonPath("$.data.sModuleNameZh").doesNotExist()); + } + @Test void put_ignoresProcedureNameField_doesNotChange() throws Exception { String origProc = "sp_put_keep_" + System.nanoTime(); -- libgit2 0.22.2