# Project Detail Transaction Query Backend Implementation Plan > **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. **Goal:** Build the backend query, filter options, detail, and export endpoints for the project detail bank statement page using local `ccdi_bank_statement` data. **Architecture:** Extend the existing `ccdi-project` module with a dedicated bank statement controller and service, while reusing the current `CcdiBankStatementMapper` plus XML dynamic SQL. Keep DTO/VO layering strict, compute direction and display amount in the query layer, and export through `ruoyi-common` `ExcelUtil` so the page and export share one query contract. **Tech Stack:** Java 21, Spring Boot 3, MyBatis Plus, MyBatis XML, Mockito + JUnit 5, RuoYi `ExcelUtil` --- ### Task 1: Scaffold DTO/VO contracts and the first failing service test **Files:** - Create: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/dto/CcdiBankStatementQueryDTO.java` - Create: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/vo/CcdiBankStatementOptionVO.java` - Create: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/vo/CcdiBankStatementFilterOptionsVO.java` - Create: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/vo/CcdiBankStatementListVO.java` - Create: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/vo/CcdiBankStatementDetailVO.java` - Create: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/ICcdiBankStatementService.java` - Create: `ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiBankStatementServiceImplTest.java` **Step 1: Write the failing test** ```java @ExtendWith(MockitoExtension.class) class CcdiBankStatementServiceImplTest { @InjectMocks private CcdiBankStatementServiceImpl service; @Mock private CcdiBankStatementMapper bankStatementMapper; @Test void getFilterOptions_shouldReturnProjectWideOptions() { CcdiBankStatementFilterOptionsVO options = new CcdiBankStatementFilterOptionsVO(); options.setOurSubjectOptions(List.of(new CcdiBankStatementOptionVO("主体A", "主体A"))); when(bankStatementMapper.selectFilterOptions(100L)).thenReturn(options); CcdiBankStatementFilterOptionsVO result = service.getFilterOptions(100L); assertEquals(1, result.getOurSubjectOptions().size()); } } ``` **Step 2: Run test to verify it fails** Run: `mvn test -pl ccdi-project -Dtest=CcdiBankStatementServiceImplTest` Expected: FAIL with missing `CcdiBankStatementServiceImpl`, DTO, or mapper methods. **Step 3: Write minimal implementation** ```java public interface ICcdiBankStatementService { CcdiBankStatementFilterOptionsVO getFilterOptions(Long projectId); } ``` ```java @Data public class CcdiBankStatementOptionVO { private String label; private String value; public CcdiBankStatementOptionVO(String label, String value) { this.label = label; this.value = value; } } ``` Add the remaining DTO/VO classes with only the fields already confirmed in the design. **Step 4: Run test to verify it still fails only on the missing service implementation** Run: `mvn test -pl ccdi-project -Dtest=CcdiBankStatementServiceImplTest` Expected: FAIL with `CcdiBankStatementServiceImpl` not found or method not implemented. **Step 5: Commit** ```bash git add ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/dto/CcdiBankStatementQueryDTO.java ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/vo/CcdiBankStatementOptionVO.java ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/vo/CcdiBankStatementFilterOptionsVO.java ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/vo/CcdiBankStatementListVO.java ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/vo/CcdiBankStatementDetailVO.java ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/ICcdiBankStatementService.java ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiBankStatementServiceImplTest.java git commit -m "新增流水明细查询后端契约与测试骨架" ``` ### Task 2: Implement mapper query methods for options, list, detail, and export **Files:** - Modify: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/mapper/CcdiBankStatementMapper.java` - Modify: `ccdi-project/src/main/resources/mapper/ccdi/project/CcdiBankStatementMapper.xml` - Test: `ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiBankStatementServiceImplTest.java` **Step 1: Write the failing test** Add tests that assert the service delegates to mapper methods with normalized arguments: ```java @Test void pageQuery_shouldNormalizeSortFieldAndDirection() { Page page = new Page<>(1, 10); CcdiBankStatementQueryDTO queryDTO = new CcdiBankStatementQueryDTO(); queryDTO.setProjectId(100L); queryDTO.setOrderBy("amount"); queryDTO.setOrderDirection("desc"); service.selectStatementPage(page, queryDTO); verify(bankStatementMapper).selectStatementPage(eq(page), same(queryDTO)); } ``` **Step 2: Run test to verify it fails** Run: `mvn test -pl ccdi-project -Dtest=CcdiBankStatementServiceImplTest` Expected: FAIL because mapper methods `selectStatementPage`, `selectStatementListForExport`, `selectStatementDetailById`, `selectFilterOptions` are missing. **Step 3: Write minimal implementation** In `CcdiBankStatementMapper.java`, add: ```java Page selectStatementPage(Page page, @Param("query") CcdiBankStatementQueryDTO query); List selectStatementListForExport(@Param("query") CcdiBankStatementQueryDTO query); CcdiBankStatementDetailVO selectStatementDetailById(@Param("bankStatementId") Long bankStatementId); CcdiBankStatementFilterOptionsVO selectFilterOptions(@Param("projectId") Long projectId); ``` In `CcdiBankStatementMapper.xml`, add: - a reusable SQL fragment for common filters - a reusable expression for parsed transaction time - a reusable expression for signed display amount - separate selects for page, export, detail, and distinct project options **Step 4: Run test to verify it passes or fails only on service logic** Run: `mvn test -pl ccdi-project -Dtest=CcdiBankStatementServiceImplTest` Expected: mapper-signature errors gone; remaining failures only relate to unimplemented service methods. **Step 5: Commit** ```bash git add ccdi-project/src/main/java/com/ruoyi/ccdi/project/mapper/CcdiBankStatementMapper.java ccdi-project/src/main/resources/mapper/ccdi/project/CcdiBankStatementMapper.xml ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiBankStatementServiceImplTest.java git commit -m "补充流水明细查询Mapper与动态SQL" ``` ### Task 3: Implement service normalization, page query, and project-wide filter options **Files:** - Create: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiBankStatementServiceImpl.java` - Modify: `ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiBankStatementServiceImplTest.java` **Step 1: Write the failing test** Add focused service tests for: - default `tabType=all` - invalid `orderBy` falling back to `trxDate` - invalid `orderDirection` falling back to `desc` - blank string fields trimming to `null` ```java @Test void normalizeQuery_shouldFallbackToSafeDefaults() { CcdiBankStatementQueryDTO queryDTO = new CcdiBankStatementQueryDTO(); queryDTO.setProjectId(100L); queryDTO.setOrderBy("drop table"); queryDTO.setOrderDirection("sideways"); service.selectStatementPage(new Page<>(1, 10), queryDTO); assertEquals("trxDate", queryDTO.getOrderBy()); assertEquals("desc", queryDTO.getOrderDirection()); assertEquals("all", queryDTO.getTabType()); } ``` **Step 2: Run test to verify it fails** Run: `mvn test -pl ccdi-project -Dtest=CcdiBankStatementServiceImplTest` Expected: FAIL because service normalization logic is missing. **Step 3: Write minimal implementation** In `CcdiBankStatementServiceImpl.java`: ```java @Service public class CcdiBankStatementServiceImpl implements ICcdiBankStatementService { @Resource private CcdiBankStatementMapper bankStatementMapper; @Override public Page selectStatementPage(Page page, CcdiBankStatementQueryDTO queryDTO) { normalizeQuery(queryDTO); return bankStatementMapper.selectStatementPage(page, queryDTO); } } ``` Implement `normalizeQuery(queryDTO)` to enforce the white-listed sort fields and normalize blank strings and booleans before delegating to mapper. **Step 4: Run test to verify it passes** Run: `mvn test -pl ccdi-project -Dtest=CcdiBankStatementServiceImplTest` Expected: PASS **Step 5: Commit** ```bash git add ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiBankStatementServiceImpl.java ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiBankStatementServiceImplTest.java git commit -m "实现流水明细查询服务层规范化逻辑" ``` ### Task 4: Add detail lookup and export model with a failing export test **Files:** - Create: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/excel/CcdiBankStatementExcel.java` - Modify: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/ICcdiBankStatementService.java` - Modify: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiBankStatementServiceImpl.java` - Modify: `ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiBankStatementServiceImplTest.java` **Step 1: Write the failing test** Add a service test for export mapping: ```java @Test void selectStatementListForExport_shouldMapDisplayColumns() { CcdiBankStatementListVO row = new CcdiBankStatementListVO(); row.setTrxDate("2024-02-01 10:33:44"); row.setLeAccountNo("6222"); row.setLeAccountName("张三"); row.setDisplayAmount(new BigDecimal("-8.00")); when(bankStatementMapper.selectStatementListForExport(any())).thenReturn(List.of(row)); List result = service.selectStatementListForExport(new CcdiBankStatementQueryDTO()); assertEquals(1, result.size()); assertEquals("6222", result.get(0).getLeAccountNo()); } ``` **Step 2: Run test to verify it fails** Run: `mvn test -pl ccdi-project -Dtest=CcdiBankStatementServiceImplTest` Expected: FAIL because export model and export service method do not exist. **Step 3: Write minimal implementation** Create `CcdiBankStatementExcel.java` with `@Excel` annotations for: - `trxDate` - `leAccountNo` - `leAccountName` - `customerAccountName` - `customerAccountNo` - `userMemo` - `cashType` - `displayAmount` Add service methods: ```java List selectStatementListForExport(CcdiBankStatementQueryDTO queryDTO); CcdiBankStatementDetailVO getStatementDetail(Long bankStatementId); ``` Map export rows from `CcdiBankStatementListVO` to `CcdiBankStatementExcel`. **Step 4: Run test to verify it passes** Run: `mvn test -pl ccdi-project -Dtest=CcdiBankStatementServiceImplTest` Expected: PASS **Step 5: Commit** ```bash git add ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/excel/CcdiBankStatementExcel.java ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/ICcdiBankStatementService.java ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiBankStatementServiceImpl.java ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiBankStatementServiceImplTest.java git commit -m "实现流水明细导出模型与详情查询" ``` ### Task 5: Add controller endpoints and the first failing controller test **Files:** - Create: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/controller/CcdiBankStatementController.java` - Create: `ccdi-project/src/test/java/com/ruoyi/ccdi/project/controller/CcdiBankStatementControllerTest.java` **Step 1: Write the failing test** ```java @ExtendWith(MockitoExtension.class) class CcdiBankStatementControllerTest { @InjectMocks private CcdiBankStatementController controller; @Mock private ICcdiBankStatementService bankStatementService; @Test void options_shouldReturnAjaxResultSuccess() { when(bankStatementService.getFilterOptions(100L)).thenReturn(new CcdiBankStatementFilterOptionsVO()); AjaxResult result = controller.getOptions(100L); assertEquals(200, result.get("code")); } } ``` **Step 2: Run test to verify it fails** Run: `mvn test -pl ccdi-project -Dtest=CcdiBankStatementControllerTest` Expected: FAIL because controller class and methods do not exist. **Step 3: Write minimal implementation** Add endpoints: - `GET /ccdi/project/bank-statement/list` - `GET /ccdi/project/bank-statement/options` - `GET /ccdi/project/bank-statement/detail/{bankStatementId}` - `POST /ccdi/project/bank-statement/export` Use `TableSupport.buildPageRequest()` for paging and `ExcelUtil` for export: ```java ExcelUtil util = new ExcelUtil<>(CcdiBankStatementExcel.class); util.exportExcel(response, list, "流水明细"); ``` Guard permissions with: - query/list/detail/options: `ccdi:project:query` - export: `ccdi:project:export` **Step 4: Run test to verify it passes** Run: `mvn test -pl ccdi-project -Dtest=CcdiBankStatementControllerTest` Expected: PASS **Step 5: Commit** ```bash git add ccdi-project/src/main/java/com/ruoyi/ccdi/project/controller/CcdiBankStatementController.java ccdi-project/src/test/java/com/ruoyi/ccdi/project/controller/CcdiBankStatementControllerTest.java git commit -m "新增流水明细查询控制器接口" ``` ### Task 6: Verify the backend end-to-end inside the module **Files:** - Modify if needed after failures: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/controller/CcdiBankStatementController.java` - Modify if needed after failures: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiBankStatementServiceImpl.java` - Modify if needed after failures: `ccdi-project/src/main/resources/mapper/ccdi/project/CcdiBankStatementMapper.xml` **Step 1: Run the focused backend tests** Run: `mvn test -pl ccdi-project -Dtest=CcdiBankStatementServiceImplTest,CcdiBankStatementControllerTest` Expected: PASS **Step 2: Run the module compile** Run: `mvn clean compile -pl ccdi-project -am` Expected: BUILD SUCCESS **Step 3: Smoke-check the mapper XML and endpoint contracts** Run: ```bash mvn test -pl ccdi-project -Dtest=CcdiBankStatementServiceImplTest -q ``` Expected: PASS without XML binding errors or missing mapper statements. **Step 4: Fix any compile or binding failures with the smallest possible patch** Typical fixes: - wrong `@Param("query")` names - missing VO field aliases in SQL - `Page<>` generic mismatch - `ExcelUtil` export field annotation issues **Step 5: Commit** ```bash git add ccdi-project/src/main/java/com/ruoyi/ccdi/project/controller/CcdiBankStatementController.java ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiBankStatementServiceImpl.java ccdi-project/src/main/resources/mapper/ccdi/project/CcdiBankStatementMapper.xml ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiBankStatementServiceImplTest.java ccdi-project/src/test/java/com/ruoyi/ccdi/project/controller/CcdiBankStatementControllerTest.java git commit -m "完成流水明细查询后端实现与校验" ```