Commit 716b0b5b884839517ab1d0aba1985662e3ef6e99
1 parent
fa2044fd
feat(mod): DELETE /api/modules/{id} controller REQ-MOD-003
Showing
2 changed files
with
93 additions
and
0 deletions
backend/src/main/java/com/xly/erp/module/mod/controller/ModuleController.java
| @@ -4,9 +4,11 @@ import com.xly.erp.common.response.ApiResponse; | @@ -4,9 +4,11 @@ import com.xly.erp.common.response.ApiResponse; | ||
| 4 | import com.xly.erp.module.mod.dto.ModuleCreateDTO; | 4 | import com.xly.erp.module.mod.dto.ModuleCreateDTO; |
| 5 | import com.xly.erp.module.mod.dto.ModuleUpdateDTO; | 5 | import com.xly.erp.module.mod.dto.ModuleUpdateDTO; |
| 6 | import com.xly.erp.module.mod.service.ModuleService; | 6 | import com.xly.erp.module.mod.service.ModuleService; |
| 7 | +import com.xly.erp.module.mod.vo.ModuleDeleteResultVO; | ||
| 7 | import com.xly.erp.module.mod.vo.ModuleVO; | 8 | import com.xly.erp.module.mod.vo.ModuleVO; |
| 8 | import jakarta.validation.Valid; | 9 | import jakarta.validation.Valid; |
| 9 | import lombok.RequiredArgsConstructor; | 10 | import lombok.RequiredArgsConstructor; |
| 11 | +import org.springframework.web.bind.annotation.DeleteMapping; | ||
| 10 | import org.springframework.web.bind.annotation.PathVariable; | 12 | import org.springframework.web.bind.annotation.PathVariable; |
| 11 | import org.springframework.web.bind.annotation.PostMapping; | 13 | import org.springframework.web.bind.annotation.PostMapping; |
| 12 | import org.springframework.web.bind.annotation.PutMapping; | 14 | import org.springframework.web.bind.annotation.PutMapping; |
| @@ -32,4 +34,10 @@ public class ModuleController { | @@ -32,4 +34,10 @@ public class ModuleController { | ||
| 32 | public ApiResponse<ModuleVO> update(@PathVariable Integer id, @Valid @RequestBody ModuleUpdateDTO dto) { | 34 | public ApiResponse<ModuleVO> update(@PathVariable Integer id, @Valid @RequestBody ModuleUpdateDTO dto) { |
| 33 | return ApiResponse.ok(moduleService.update(id, dto)); | 35 | return ApiResponse.ok(moduleService.update(id, dto)); |
| 34 | } | 36 | } |
| 37 | + | ||
| 38 | + /** REQ-MOD-003 模块软删除 — REQ-USR-004 完成后追加 @PreAuthorize("hasAuthority('MOD:DELETE')") */ | ||
| 39 | + @DeleteMapping("/{id}") | ||
| 40 | + public ApiResponse<ModuleDeleteResultVO> delete(@PathVariable Integer id) { | ||
| 41 | + return ApiResponse.ok(moduleService.delete(id)); | ||
| 42 | + } | ||
| 35 | } | 43 | } |
backend/src/test/java/com/xly/erp/module/mod/controller/ModuleControllerIT.java
| @@ -18,6 +18,7 @@ import org.springframework.transaction.annotation.Transactional; | @@ -18,6 +18,7 @@ import org.springframework.transaction.annotation.Transactional; | ||
| 18 | import java.time.LocalDateTime; | 18 | import java.time.LocalDateTime; |
| 19 | 19 | ||
| 20 | import static org.assertj.core.api.Assertions.assertThat; | 20 | import static org.assertj.core.api.Assertions.assertThat; |
| 21 | +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; | ||
| 21 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; | 22 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; |
| 22 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; | 23 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; |
| 23 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; | 24 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; |
| @@ -267,6 +268,90 @@ class ModuleControllerIT { | @@ -267,6 +268,90 @@ class ModuleControllerIT { | ||
| 267 | .andExpect(jsonPath("$.code").value(40010)); | 268 | .andExpect(jsonPath("$.code").value(40010)); |
| 268 | } | 269 | } |
| 269 | 270 | ||
| 271 | + // ============================================================ | ||
| 272 | + // REQ-MOD-003 DELETE 系列 | ||
| 273 | + // ============================================================ | ||
| 274 | + | ||
| 275 | + @Test | ||
| 276 | + void delete_validLeaf_returns200WithBDeletedTrue() throws Exception { | ||
| 277 | + Integer id = insertExisting("sp_del_leaf_" + System.nanoTime(), null); | ||
| 278 | + | ||
| 279 | + mockMvc.perform(delete("/api/modules/" + id)) | ||
| 280 | + .andExpect(status().isOk()) | ||
| 281 | + .andExpect(jsonPath("$.code").value(200)) | ||
| 282 | + .andExpect(jsonPath("$.data.iIncrement").value(id)) | ||
| 283 | + .andExpect(jsonPath("$.data.bDeleted").value(true)); | ||
| 284 | + | ||
| 285 | + ModuleEntity reloaded = moduleMapper.selectById(id); | ||
| 286 | + assertThat(reloaded.getBDeleted()).isTrue(); | ||
| 287 | + assertThat(reloaded.getTDeletedDate()).isNotNull(); | ||
| 288 | + } | ||
| 289 | + | ||
| 290 | + @Test | ||
| 291 | + void delete_targetNotFound_returns40421() throws Exception { | ||
| 292 | + mockMvc.perform(delete("/api/modules/999999")) | ||
| 293 | + .andExpect(status().isOk()) | ||
| 294 | + .andExpect(jsonPath("$.code").value(40421)); | ||
| 295 | + } | ||
| 296 | + | ||
| 297 | + @Test | ||
| 298 | + void delete_targetAlreadyDeleted_returns40421() throws Exception { | ||
| 299 | + Integer id = insertExisting("sp_del_already_" + System.nanoTime(), null); | ||
| 300 | + // 手工置 bDeleted=true | ||
| 301 | + ModuleEntity patch = new ModuleEntity(); | ||
| 302 | + patch.setIIncrement(id); | ||
| 303 | + patch.setBDeleted(true); | ||
| 304 | + moduleMapper.updateById(patch); | ||
| 305 | + | ||
| 306 | + mockMvc.perform(delete("/api/modules/" + id)) | ||
| 307 | + .andExpect(status().isOk()) | ||
| 308 | + .andExpect(jsonPath("$.code").value(40421)); | ||
| 309 | + } | ||
| 310 | + | ||
| 311 | + @Test | ||
| 312 | + void delete_hasUndeletedChildren_returns40912() throws Exception { | ||
| 313 | + Integer parentId = insertExisting("sp_del_par_" + System.nanoTime(), null); | ||
| 314 | + insertExisting("sp_del_chi_" + System.nanoTime(), parentId); | ||
| 315 | + | ||
| 316 | + mockMvc.perform(delete("/api/modules/" + parentId)) | ||
| 317 | + .andExpect(status().isOk()) | ||
| 318 | + .andExpect(jsonPath("$.code").value(40912)); | ||
| 319 | + | ||
| 320 | + // parent 仍未删除 | ||
| 321 | + assertThat(moduleMapper.selectById(parentId).getBDeleted()).isFalse(); | ||
| 322 | + } | ||
| 323 | + | ||
| 324 | + @Test | ||
| 325 | + void delete_softDeletedChildren_doesNotBlock_returns200() throws Exception { | ||
| 326 | + Integer parentId = insertExisting("sp_del_pp_" + System.nanoTime(), null); | ||
| 327 | + Integer childId = insertExisting("sp_del_cc_" + System.nanoTime(), parentId); | ||
| 328 | + | ||
| 329 | + // 先 DELETE child(应成功) | ||
| 330 | + mockMvc.perform(delete("/api/modules/" + childId)) | ||
| 331 | + .andExpect(status().isOk()) | ||
| 332 | + .andExpect(jsonPath("$.code").value(200)); | ||
| 333 | + | ||
| 334 | + // 再 DELETE parent(已删除子不阻塞) | ||
| 335 | + mockMvc.perform(delete("/api/modules/" + parentId)) | ||
| 336 | + .andExpect(status().isOk()) | ||
| 337 | + .andExpect(jsonPath("$.code").value(200)); | ||
| 338 | + | ||
| 339 | + assertThat(moduleMapper.selectById(parentId).getBDeleted()).isTrue(); | ||
| 340 | + } | ||
| 341 | + | ||
| 342 | + @Test | ||
| 343 | + void delete_responseVOContainsOnlyIIncrementAndBDeleted() throws Exception { | ||
| 344 | + Integer id = insertExisting("sp_del_vo_" + System.nanoTime(), null); | ||
| 345 | + | ||
| 346 | + mockMvc.perform(delete("/api/modules/" + id)) | ||
| 347 | + .andExpect(status().isOk()) | ||
| 348 | + .andExpect(jsonPath("$.data.iIncrement").value(id)) | ||
| 349 | + .andExpect(jsonPath("$.data.bDeleted").value(true)) | ||
| 350 | + .andExpect(jsonPath("$.data.sProcedureName").doesNotExist()) | ||
| 351 | + .andExpect(jsonPath("$.data.sDisplayType").doesNotExist()) | ||
| 352 | + .andExpect(jsonPath("$.data.sModuleNameZh").doesNotExist()); | ||
| 353 | + } | ||
| 354 | + | ||
| 270 | @Test | 355 | @Test |
| 271 | void put_ignoresProcedureNameField_doesNotChange() throws Exception { | 356 | void put_ignoresProcedureNameField_doesNotChange() throws Exception { |
| 272 | String origProc = "sp_put_keep_" + System.nanoTime(); | 357 | String origProc = "sp_put_keep_" + System.nanoTime(); |