Files
ccdi/docs/plans/2026-03-10-project-detail-transaction-query-backend-implementation.md

15 KiB

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

@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

public interface ICcdiBankStatementService {
    CcdiBankStatementFilterOptionsVO getFilterOptions(Long projectId);
}
@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

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:

@Test
void pageQuery_shouldNormalizeSortFieldAndDirection() {
    Page<CcdiBankStatementListVO> 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:

Page<CcdiBankStatementListVO> selectStatementPage(Page<CcdiBankStatementListVO> page,
                                                  @Param("query") CcdiBankStatementQueryDTO query);

List<CcdiBankStatementListVO> 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

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
@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:

@Service
public class CcdiBankStatementServiceImpl implements ICcdiBankStatementService {

    @Resource
    private CcdiBankStatementMapper bankStatementMapper;

    @Override
    public Page<CcdiBankStatementListVO> selectStatementPage(Page<CcdiBankStatementListVO> 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

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:

@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<CcdiBankStatementExcel> 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:

List<CcdiBankStatementExcel> 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

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

@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<CcdiBankStatementExcel> for export:

ExcelUtil<CcdiBankStatementExcel> 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

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:

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

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 "完成流水明细查询后端实现与校验"