From de98b25f9393fd8ce81e6948b678992ff98d89e7 Mon Sep 17 00:00:00 2001 From: wkc <978997012@qq.com> Date: Wed, 4 Mar 2026 15:56:29 +0800 Subject: [PATCH] =?UTF-8?q?docs:=20=E6=B7=BB=E5=8A=A0=E9=93=B6=E8=A1=8C?= =?UTF-8?q?=E6=B5=81=E6=B0=B4=E5=AE=9E=E4=BD=93=E7=B1=BB=E5=AE=9E=E6=96=BD?= =?UTF-8?q?=E8=AE=A1=E5=88=92?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...026-03-04-bank-statement-implementation.md | 745 ++++++++++++++++++ 1 file changed, 745 insertions(+) create mode 100644 docs/plans/2026-03-04-bank-statement-implementation.md diff --git a/docs/plans/2026-03-04-bank-statement-implementation.md b/docs/plans/2026-03-04-bank-statement-implementation.md new file mode 100644 index 0000000..74210a2 --- /dev/null +++ b/docs/plans/2026-03-04-bank-statement-implementation.md @@ -0,0 +1,745 @@ +# 银行流水实体类实现计划 + +> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. + +**目标:** 创建 CcdiBankStatement 实体类,实现从流水分析接口响应到数据库实体的数据转换功能。 + +**架构:** 在 ccdi-project 模块中创建实体类,使用 Spring BeanUtils 进行对象映射,手动处理字段名不一致的情况。实体类包含静态转换方法 fromResponse()。 + +**技术栈:** MyBatis Plus 3.5.10, Spring BeanUtils, Lombok, MySQL 8.2.0 + +--- + +## 任务概览 + +| 任务 | 预估时间 | 文件 | +|------|---------|------| +| Task 1: 数据库表修改 | 5分钟 | SQL脚本 | +| Task 2: 创建实体类基础结构 | 10分钟 | CcdiBankStatement.java | +| Task 3: 编写转换方法测试 | 15分钟 | CcdiBankStatementTest.java | +| Task 4: 实现转换方法 | 10分钟 | CcdiBankStatement.java | +| Task 5: 创建 Mapper 接口 | 5分钟 | CcdiBankStatementMapper.java | +| Task 6: 创建 Mapper XML | 10分钟 | CcdiBankStatementMapper.xml | +| Task 7: 验证测试 | 5分钟 | - | + +--- + +## Task 1: 数据库表修改 + +**目标:** 在 ccdi_bank_statement 表中添加 project_id 字段和索引。 + +**文件:** +- 创建: `sql/ccdi_bank_statement_add_project_id.sql` + +**Step 1: 创建数据库修改脚本** + +```sql +-- 为 ccdi_bank_statement 表添加 project_id 字段 +ALTER TABLE `ccdi_bank_statement` +ADD COLUMN `project_id` bigint(20) DEFAULT NULL COMMENT '关联项目ID' AFTER `bank_statement_id`, +ADD INDEX `idx_project_id` (`project_id`); +``` + +**Step 2: 执行数据库修改脚本** + +```bash +# 连接到数据库并执行脚本 +mysql -h 116.62.17.81 -u root -p ccdi < sql/ccdi_bank_statement_add_project_id.sql +``` + +**预期输出:** 执行成功,无错误信息。 + +**Step 3: 验证字段已添加** + +```sql +-- 查看表结构,确认 project_id 字段已添加 +SHOW COLUMNS FROM ccdi_bank_statement LIKE 'project_id'; +``` + +**预期输出:** +``` +Field | Type | Null | Key | Default | Extra +-------------|------------|------|-----|---------|------- +project_id | bigint(20) | YES | MUL | NULL | +``` + +**Step 4: 提交** + +```bash +git add sql/ccdi_bank_statement_add_project_id.sql +git commit -m "feat: 为银行流水表添加 project_id 字段" +``` + +--- + +## Task 2: 创建实体类基础结构 + +**目标:** 创建 CcdiBankStatement 实体类的基础结构,包含所有字段定义。 + +**文件:** +- 创建: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/entity/CcdiBankStatement.java` + +**Step 1: 创建实体类文件** + +```java +package com.ruoyi.ccdi.project.domain.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; +import java.math.BigDecimal; +import java.util.Date; + +/** + * 银行流水对象 ccdi_bank_statement + * + * @author ruoyi + * @date 2026-03-04 + */ +@Data +@TableName("ccdi_bank_statement") +public class CcdiBankStatement implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + // ===== 主键和关联字段 ===== + + /** 流水ID */ + @TableId(type = IdType.AUTO) + private Long bankStatementId; + + /** 关联项目ID(业务字段) */ + private Long projectId; + + /** 企业ID */ + private Integer leId; + + /** 账号ID */ + private Long accountId; + + /** 项目id(保留原有字段) */ + private Integer groupId; + + // ===== 账号信息 ===== + + /** 企业账号名称 */ + private String leAccountName; + + /** 企业银行账号 */ + private String leAccountNo; + + /** 账号日期ID */ + private Integer accountingDateId; + + /** 账号日期 */ + private String accountingDate; + + /** 交易日期 */ + private String trxDate; + + /** 币种 */ + private String currency; + + // ===== 交易金额 ===== + + /** 付款金额 */ + private BigDecimal amountDr; + + /** 收款金额 */ + private BigDecimal amountCr; + + /** 余额 */ + private BigDecimal amountBalance; + + // ===== 交易类型和标志 ===== + + /** 交易类型 */ + private String cashType; + + /** 交易标志位 */ + private String trxFlag; + + /** 分类ID */ + private Integer trxType; + + /** 异常类型 */ + private String exceptionType; + + /** 是否为内部交易 */ + private Integer internalFlag; + + // ===== 对手方信息 ===== + + /** 对手方企业ID */ + private Integer customerLeId; + + /** 对手方企业名称 */ + private String customerAccountName; + + /** 对手方账号 */ + private String customerAccountNo; + + /** 对手方银行 */ + private String customerBank; + + /** 对手方备注 */ + private String customerReference; + + // ===== 摘要和备注 ===== + + /** 用户交易摘要 */ + private String userMemo; + + /** 银行交易摘要 */ + private String bankComments; + + /** 银行交易号 */ + private String bankTrxNumber; + + // ===== 银行信息 ===== + + /** 所属银行缩写 */ + private String bank; + + // ===== 批次和上传信息 ===== + + /** 上传logId */ + private Integer batchId; + + /** 每次上传在文件中的line */ + private Integer batchSequence; + + // ===== 附加字段 ===== + + /** meta json(固定为null) */ + private String metaJson; + + /** 是否包含余额 */ + private Integer noBalance; + + /** 初始余额 */ + private Integer beginBalance; + + /** 结束余额 */ + private Integer endBalance; + + /** 覆盖标识 */ + private Long overrideBsId; + + /** 交易方式 */ + private String paymentMethod; + + /** 身份证号 */ + private String cretNo; + + // ===== 审计字段 ===== + + /** 创建时间 */ + private Date createDate; + + /** 创建者 */ + private Long createdBy; +} +``` + +**Step 2: 验证代码编译** + +```bash +cd ccdi-project +mvn compile +``` + +**预期输出:** BUILD SUCCESS + +**Step 3: 提交** + +```bash +git add ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/entity/CcdiBankStatement.java +git commit -m "feat: 创建银行流水实体类基础结构" +``` + +--- + +## Task 3: 编写转换方法测试 + +**目标:** 使用 TDD 方法,先编写 fromResponse() 方法的单元测试。 + +**文件:** +- 创建: `ccdi-project/src/test/java/com/ruoyi/ccdi/project/domain/entity/CcdiBankStatementTest.java` + +**Step 1: 添加测试依赖(如果不存在)** + +检查 `ccdi-project/pom.xml` 是否包含测试依赖: + +```xml + + org.springframework.boot + spring-boot-starter-test + test + +``` + +如果没有,添加上述依赖。 + +**Step 2: 创建测试类** + +```java +package com.ruoyi.ccdi.project.domain.entity; + +import com.ruoyi.lsfx.domain.response.GetBankStatementResponse.BankStatementItem; +import org.junit.jupiter.api.Test; + +import java.math.BigDecimal; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * 银行流水实体类测试 + * + * @author ruoyi + * @date 2026-03-04 + */ +class CcdiBankStatementTest { + + @Test + void testFromResponse_Success() { + // 准备测试数据 + BankStatementItem item = new BankStatementItem(); + item.setBankStatementId(123456L); + item.setLeId(100); + item.setAccountId(200L); + item.setLeName("测试企业"); + item.setAccountMaskNo("6222****1234"); + item.setDrAmount(new BigDecimal("1000.00")); + item.setCrAmount(new BigDecimal("500.00")); + item.setBalanceAmount(new BigDecimal("5000.00")); + item.setTrxDate("2026-03-04"); + item.setCustomerAccountMaskNo("6228****5678"); + item.setUploadSequnceNumber(1); + + // 执行转换 + CcdiBankStatement entity = CcdiBankStatement.fromResponse(item); + + // 验证结果 + assertNotNull(entity, "转换结果不应为null"); + assertEquals(123456L, entity.getBankStatementId(), "流水ID应该匹配"); + assertEquals(100, entity.getLeId(), "企业ID应该匹配"); + assertEquals(200L, entity.getAccountId(), "账号ID应该匹配"); + assertEquals("测试企业", entity.getLeAccountName(), "企业名称应该匹配"); + + // 验证手动映射的字段 + assertEquals("6222****1234", entity.getLeAccountNo(), "企业账号应该从 accountMaskNo 映射"); + assertEquals("6228****5678", entity.getCustomerAccountNo(), "对手方账号应该从 customerAccountMaskNo 映射"); + assertEquals(1, entity.getBatchSequence(), "批次序号应该从 uploadSequnceNumber 映射"); + + // 验证金额字段 + assertEquals(new BigDecimal("1000.00"), entity.getAmountDr(), "付款金额应该匹配"); + assertEquals(new BigDecimal("500.00"), entity.getAmountCr(), "收款金额应该匹配"); + assertEquals(new BigDecimal("5000.00"), entity.getAmountBalance(), "余额应该匹配"); + + // 验证特殊字段 + assertNull(entity.getMetaJson(), "metaJson 应该强制为 null"); + assertNull(entity.getProjectId(), "projectId 应该为 null(需要 Service 层设置)"); + } + + @Test + void testFromResponse_Null() { + // 测试空值处理 + CcdiBankStatement entity = CcdiBankStatement.fromResponse(null); + + // 验证返回 null + assertNull(entity, "传入 null 应该返回 null"); + } + + @Test + void testFromResponse_EmptyObject() { + // 测试空对象转换 + BankStatementItem item = new BankStatementItem(); + + // 执行转换 + CcdiBankStatement entity = CcdiBankStatement.fromResponse(item); + + // 验证不会抛出异常 + assertNotNull(entity, "空对象转换结果不应为 null"); + assertNull(entity.getMetaJson(), "metaJson 应该为 null"); + } + + @Test + void testFromResponse_FieldTypeCompatibility() { + // 测试字段类型兼容性 + BankStatementItem item = new BankStatementItem(); + item.setInternalFlag(1); // Integer 类型 + item.setTransTypeId(100); // Integer 类型 + + // 执行转换 + CcdiBankStatement entity = CcdiBankStatement.fromResponse(item); + + // 验证类型转换正确 + assertNotNull(entity, "转换结果不应为 null"); + assertEquals(1, entity.getInternalFlag(), "Integer 类型应该正确复制"); + assertEquals(100, entity.getTrxType(), "Integer 类型应该正确复制"); + } +} +``` + +**Step 3: 运行测试验证失败** + +```bash +cd ccdi-project +mvn test -Dtest=CcdiBankStatementTest +``` + +**预期输出:** 编译失败,因为 `fromResponse()` 方法还不存在。 + +**Step 4: 提交** + +```bash +git add ccdi-project/src/test/java/com/ruoyi/ccdi/project/domain/entity/CcdiBankStatementTest.java +git commit -m "test: 添加银行流水转换方法的单元测试" +``` + +--- + +## Task 4: 实现转换方法 + +**目标:** 在 CcdiBankStatement 实体类中实现 fromResponse() 静态方法。 + +**文件:** +- 修改: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/entity/CcdiBankStatement.java` + +**Step 1: 添加必要的导入** + +在文件顶部添加: + +```java +import com.ruoyi.lsfx.domain.response.GetBankStatementResponse.BankStatementItem; +import org.springframework.beans.BeanUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +``` + +**Step 2: 添加日志常量** + +在类的开头添加: + +```java +private static final Logger log = LoggerFactory.getLogger(CcdiBankStatement.class); +``` + +**Step 3: 实现 fromResponse() 方法** + +在类的末尾(createdBy 字段之后)添加转换方法: + +```java +/** + * 从流水分析接口响应转换为实体 + * + * @param item 流水分析接口返回的流水项 + * @return 流水实体,如果 item 为 null 则返回 null + */ +public static CcdiBankStatement fromResponse(BankStatementItem item) { + // 1. 空值检查 + if (item == null) { + log.warn("流水项为空,无法转换"); + return null; + } + + try { + // 2. 创建实体对象 + CcdiBankStatement entity = new CcdiBankStatement(); + + // 3. 使用 BeanUtils 复制同名字段 + BeanUtils.copyProperties(item, entity); + + // 4. 手动映射字段名不一致的情况 + entity.setLeAccountNo(item.getAccountMaskNo()); + entity.setCustomerAccountNo(item.getCustomerAccountMaskNo()); + entity.setBatchSequence(item.getUploadSequnceNumber()); + + // 5. 特殊字段处理 + entity.setMetaJson(null); // 根据文档要求强制设为 null + + // 注意:project_id 需要在 Service 层根据业务逻辑设置 + + return entity; + } catch (Exception e) { + log.error("流水数据转换失败, bankStatementId={}", item.getBankStatementId(), e); + throw new RuntimeException("流水数据转换失败", e); + } +} +``` + +**Step 4: 运行测试验证通过** + +```bash +cd ccdi-project +mvn test -Dtest=CcdiBankStatementTest +``` + +**预期输出:** +``` +Tests run: 4, Failures: 0, Errors: 0, Skipped: 0 +[INFO] BUILD SUCCESS +``` + +**Step 5: 提交** + +```bash +git add ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/entity/CcdiBankStatement.java +git commit -m "feat: 实现银行流水转换方法 fromResponse()" +``` + +--- + +## Task 5: 创建 Mapper 接口 + +**目标:** 创建 MyBatis Mapper 接口,继承 BaseMapper 并提供批量插入方法。 + +**文件:** +- 创建: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/mapper/CcdiBankStatementMapper.java` + +**Step 1: 创建 Mapper 接口** + +```java +package com.ruoyi.ccdi.project.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.ruoyi.ccdi.project.domain.entity.CcdiBankStatement; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 银行流水Mapper接口 + * + * @author ruoyi + * @date 2026-03-04 + */ +public interface CcdiBankStatementMapper extends BaseMapper { + + /** + * 批量插入银行流水 + * + * @param list 银行流水列表 + * @return 插入记录数 + */ + int insertBatch(@Param("list") List list); +} +``` + +**Step 2: 验证代码编译** + +```bash +cd ccdi-project +mvn compile +``` + +**预期输出:** BUILD SUCCESS + +**Step 3: 提交** + +```bash +git add ccdi-project/src/main/java/com/ruoyi/ccdi/project/mapper/CcdiBankStatementMapper.java +git commit -m "feat: 创建银行流水 Mapper 接口" +``` + +--- + +## Task 6: 创建 Mapper XML + +**目标:** 创建 MyBatis XML 映射文件,实现批量插入 SQL。 + +**文件:** +- 创建: `ccdi-project/src/main/resources/mapper/ccdi/project/CcdiBankStatementMapper.xml` + +**Step 1: 创建 XML 文件** + +```xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + select bank_statement_id, project_id, LE_ID, ACCOUNT_ID, group_id, + LE_ACCOUNT_NAME, LE_ACCOUNT_NO, ACCOUNTING_DATE_ID, ACCOUNTING_DATE, + TRX_DATE, CURRENCY, AMOUNT_DR, AMOUNT_CR, AMOUNT_BALANCE, + CASH_TYPE, CUSTOMER_LE_ID, CUSTOMER_ACCOUNT_NAME, CUSTOMER_ACCOUNT_NO, + customer_bank, customer_reference, USER_MEMO, BANK_COMMENTS, + BANK_TRX_NUMBER, BANK, TRX_FLAG, TRX_TYPE, EXCEPTION_TYPE, + internal_flag, batch_id, batch_sequence, CREATE_DATE, created_by, + meta_json, no_balance, begin_balance, end_balance, + override_bs_id, payment_method, cret_no + from ccdi_bank_statement + + + + insert into ccdi_bank_statement ( + project_id, LE_ID, ACCOUNT_ID, group_id, + LE_ACCOUNT_NAME, LE_ACCOUNT_NO, ACCOUNTING_DATE_ID, ACCOUNTING_DATE, + TRX_DATE, CURRENCY, AMOUNT_DR, AMOUNT_CR, AMOUNT_BALANCE, + CASH_TYPE, CUSTOMER_LE_ID, CUSTOMER_ACCOUNT_NAME, CUSTOMER_ACCOUNT_NO, + customer_bank, customer_reference, USER_MEMO, BANK_COMMENTS, + BANK_TRX_NUMBER, BANK, TRX_FLAG, TRX_TYPE, EXCEPTION_TYPE, + internal_flag, batch_id, batch_sequence, CREATE_DATE, created_by, + meta_json, no_balance, begin_balance, end_balance, + override_bs_id, payment_method, cret_no + ) values + + ( + #{item.projectId}, #{item.leId}, #{item.accountId}, #{item.groupId}, + #{item.leAccountName}, #{item.leAccountNo}, #{item.accountingDateId}, #{item.accountingDate}, + #{item.trxDate}, #{item.currency}, #{item.amountDr}, #{item.amountCr}, #{item.amountBalance}, + #{item.cashType}, #{item.customerLeId}, #{item.customerAccountName}, #{item.customerAccountNo}, + #{item.customerBank}, #{item.customerReference}, #{item.userMemo}, #{item.bankComments}, + #{item.bankTrxNumber}, #{item.bank}, #{item.trxFlag}, #{item.trxType}, #{item.exceptionType}, + #{item.internalFlag}, #{item.batchId}, #{item.batchSequence}, #{item.createDate}, #{item.createdBy}, + #{item.metaJson}, #{item.noBalance}, #{item.beginBalance}, #{item.endBalance}, + #{item.overrideBsId}, #{item.paymentMethod}, #{item.cretNo} + ) + + + + +``` + +**Step 2: 验证 XML 语法** + +```bash +cd ccdi-project +mvn compile +``` + +**预期输出:** BUILD SUCCESS,无 XML 解析错误 + +**Step 3: 提交** + +```bash +git add ccdi-project/src/main/resources/mapper/ccdi/project/CcdiBankStatementMapper.xml +git commit -m "feat: 创建银行流水 Mapper XML 映射文件" +``` + +--- + +## Task 7: 验证测试 + +**目标:** 运行所有测试,确保功能正常。 + +**Step 1: 运行单元测试** + +```bash +cd ccdi-project +mvn test +``` + +**预期输出:** +``` +Tests run: 4, Failures: 0, Errors: 0, Skipped: 0 +[INFO] BUILD SUCCESS +``` + +**Step 2: 运行集成编译** + +```bash +mvn clean compile +``` + +**预期输出:** BUILD SUCCESS + +**Step 3: 检查依赖关系** + +确认 `ccdi-project` 模块的 `pom.xml` 中已依赖 `ccdi-lsfx` 模块: + +```xml + + com.ruoyi + ccdi-lsfx + +``` + +如果没有,添加上述依赖并重新编译。 + +**Step 4: 提交所有更改** + +```bash +git add . +git commit -m "test: 完成银行流水实体类功能验证" +``` + +--- + +## 完成检查清单 + +- [ ] 数据库已添加 `project_id` 字段和索引 +- [ ] 实体类包含 39 个字段,类型正确 +- [ ] `fromResponse()` 方法正确处理 3 个字段名映射 +- [ ] `fromResponse()` 方法强制设置 `metaJson` 为 null +- [ ] 单元测试覆盖正常转换、空值处理、字段映射等场景 +- [ ] Mapper 接口继承 `BaseMapper` +- [ ] Mapper XML 包含批量插入 SQL +- [ ] 所有测试通过 + +--- + +## 后续工作 + +本实施计划完成后,可以进行以下扩展: + +1. **创建 Service 层** - 实现 `IBankStatementService` 接口和实现类 +2. **创建 Controller 层** - 提供 REST API 接口 +3. **编写集成测试** - 测试完整的数据库插入流程 +4. **添加业务逻辑** - 在 Service 层设置 `projectId` 等业务字段 +5. **性能优化** - 根据实际数据量调整批量插入大小 + +--- + +**计划版本:** 1.0 +**创建日期:** 2026-03-04