Files
openclaw-config/workspace-backend/memory/2026-05-27.md
T
2026-07-14 16:36:24 +08:00

14 KiB
Raw Blame History

2026-05-27 会话记录

问题修复

1. MyBatis-Plus 空集合 IN 子句异常

问题: selectBatchIds 传入空集合时生成 IN () 非法 SQL,导致 JSQLParser 解析失败。

修复位置:

  • InboundServiceImpl.listHistory() - 供应商、仓库查询
  • OutboundServiceImpl.listHistory() - 客户、仓库查询

解决方案: 使用三元表达式避免空集合查询

Map<Long, String> supplierNameMap = supplierIds.isEmpty() ? new HashMap<>() :
    supplierMapper.selectBatchIds(supplierIds).stream()
        .collect(Collectors.toMap(Supplier::getId, Supplier::getName));

2. JavaScript 精度丢失问题

问题: 雪花算法生成的 Long 类型 ID(如 2059467259520360450)超出 JS 安全整数范围,前端接收后精度丢失为 2059467259520360400

解决方案: 创建 JacksonConfig.java,全局配置 Long 类型序列化为字符串

@Configuration
public class JacksonConfig {
    @Bean
    public Module longToStringModule() {
        return new SimpleModule()
            .addSerializer(Long.class, ToStringSerializer.instance)
            .addSerializer(long.class, ToStringSerializer.instance);
    }
}

效果:

  • Long 类型 → 字符串("2059467259520360450"
  • Integer 类型 → 保持数字(code: 200

3. creatorName 为 null

根因: BaseEntity.creator 字段自动填充时存的是用户昵称(如"管理员"),但查询逻辑错误地用 creator 的值去匹配 sys_user.username 字段。

修复:

  1. MyMetaObjectHandler.java - 添加默认值兜底:昵称 → 登录名 → "系统"
  2. InboundServiceImpl / OutboundServiceImpl - 直接使用 creator 字段值作为 creatorName

4. 安全库存字段命名

问题: StockSafeQtyReqDTO.safeQty 实际含义是最小安全库存,字段名不准确。

修复:

  • 字段名:safeQtyminSafeQty
  • 注释和验证提示同步更新

5. 库存详情接口填充物料信息

需求: /im/stock/{materialId} 接口需要返回物料相关信息。

实现:

  1. 新建 StockDetailDTO - 包含库存、物料、仓库信息
  2. StockService.getStockDetail() - 使用 MaterialFillService 填充物料信息
  3. 查询仓库信息填充仓库名称

部署操作

测试环境 Docker 重新部署

命令:

cd /home/yangxuan/Projects/IdeaProjects/tiny-erp
docker compose stop app
docker compose rm -f app
docker compose build --no-cache app
docker compose up -d app

状态: 部署成功,健康检查通过


待办事项

  • 清理 im_inbound_item / im_outbound_item 脏数据(主表记录不存在的明细)
  • 考虑添加外键约束防止未来产生脏数据
  • 创建测试环境部署文档 → /home/yangxuan/Projects/IdeaProjects/tiny-erp/doc/测试环境部署文档.md
  • 强调 Maven 构建前提(mvn clean install

2026-05-27 会话记录

问题修复

1. MyBatis-Plus 空集合 IN 子句异常

问题: selectBatchIds 传入空集合时生成 IN () 非法 SQL,导致 JSQLParser 解析失败。

修复位置:

  • InboundServiceImpl.listHistory() - 供应商、仓库查询
  • OutboundServiceImpl.listHistory() - 客户、仓库查询

解决方案: 使用三元表达式避免空集合查询

Map<Long, String> supplierNameMap = supplierIds.isEmpty() ? new HashMap<>() :
    supplierMapper.selectBatchIds(supplierIds).stream()
        .collect(Collectors.toMap(Supplier::getId, Supplier::getName));

2. JavaScript 精度丢失问题

问题: 雪花算法生成的 Long 类型 ID(如 2059467259520360450)超出 JS 安全整数范围,前端接收后精度丢失为 2059467259520360400

解决方案: 创建 JacksonConfig.java,全局配置 Long 类型序列化为字符串

@Configuration
public class JacksonConfig {
    @Bean
    public Module longToStringModule() {
        return new SimpleModule()
            .addSerializer(Long.class, ToStringSerializer.instance)
            .addSerializer(long.class, ToStringSerializer.instance);
    }
}

效果:

  • Long 类型 → 字符串("2059467259520360450"
  • Integer 类型 → 保持数字(code: 200

3. creatorName 为 null

根因: BaseEntity.creator 字段自动填充时存的是用户昵称(如"管理员"),但查询逻辑错误地用 creator 的值去匹配 sys_user.username 字段。

修复:

  1. MyMetaObjectHandler.java - 添加默认值兜底:昵称 → 登录名 → "系统"
  2. InboundServiceImpl / OutboundServiceImpl - 直接使用 creator 字段值作为 creatorName

4. 安全库存字段命名

问题: StockSafeQtyReqDTO.safeQty 实际含义是最小安全库存,字段名不准确。

修复:

  • 字段名:safeQtyminSafeQty
  • 注释和验证提示同步更新

5. 库存详情接口填充物料信息

需求: /im/stock/{materialId} 接口需要返回物料相关信息。

实现:

  1. 新建 StockDetailDTO - 包含库存、物料、仓库信息
  2. StockService.getStockDetail() - 使用 MaterialFillService 填充物料信息
  3. 查询仓库信息填充仓库名称

6. 安全库存接口统一

需求: 将安全库存相关接口从 MaterialController 统一迁移到 StockController

修改内容:

  • StockController - 新增 3 个接口(设置、批量设置、清除安全库存)
  • StockService / StockServiceImpl - 实现新增方法
  • MaterialController - 删除 2 个安全库存接口
  • MaterialService / MaterialServiceImpl - 删除相关方法
  • MaterialMapper / MaterialMapper.xml - 删除相关 SQL

新 API

  • PUT /im/stock/safe-qty - 设置安全库存(单个)
  • PUT /im/stock/safe-qty/batch - 批量设置安全库存
  • PUT /im/stock/{materialId}/safe-qty/clear - 清除安全库存

7. 库存详情接口填充 minSafeQty

问题: StockServiceImpl.getStockDetail() 漏掉了 minSafeQty 字段填充。

修复: 添加 dto.setMinSafeQty(material.getMinSafeQty());


8. /home/welcome 接口数据写死

问题: 用户名、公司名、欢迎语都是硬编码。

修复:

  • 使用 UserContext.getNickname() 获取真实用户名
  • 使用 TenantContext.getTenantName() 获取企业名称
  • 保留兜底逻辑("用户"、"企业"

数据库变更

增量脚本执行

脚本 1 scripts/postgresql/fix-fin-add-order-date.sql

  • fin_ar 表:添加 order_date 字段(DATE 类型)
  • fin_ap 表:添加 order_date 字段(DATE 类型)
  • 数据初始化:将 create_time 转换为日期赋值给 order_date

脚本 2 scripts/postgresql/fix-write-off-add-updater.sql

  • fin_ar_write_off 表:添加 updaterupdate_time 字段
  • fin_ap_write_off 表:添加 updaterupdate_time 字段
  • 数据初始化:将 creator/create_time 赋值给 updater/update_time

执行方式:

docker compose exec db psql -U postgres -d tinyerp \
  -f /docker-entrypoint-initdb.d/migrations/20260527_add_column.sql

文档更新

1. 部署文档合并

  • ~/tiny/测试环境部署文档.md 合并到 DOCKER.md
  • 新增 4 个部署场景(首次部署、重新部署、只重启、彻底重置)
  • 强调 Maven 构建前提(mvn clean install -DskipTests
  • 完善故障排查表格
  • 删除重复文档 ~/tiny/测试环境部署文档.md

2. 库存接口文档

  • 创建 doc/stock-api.md
  • 包含 6 个库存管理接口 + 1 个相关接口
  • 字段说明、物料类型枚举、注意事项

部署操作

测试环境 Docker 重新部署

命令:

cd /home/yangxuan/Projects/IdeaProjects/tiny-erp
mvn clean install -DskipTests
docker compose stop app
docker compose rm -f app
docker compose build --no-cache app
docker compose up -d app

状态: 部署成功,健康检查通过


经验教训

  1. Maven 构建前提Docker 镜像构建复制本地 target/ 目录,必须先执行 mvn clean install
  2. Long 类型精度丢失:雪花算法 ID 超出 JS 安全范围,全局序列化为字符串
  3. 空集合 IN 子句MyBatis-Plus selectBatchIds 不处理空集合,需要手动判断
  4. creator 字段含义:自动填充存的是昵称,不是用户名,查询时要注意匹配字段
  5. 接口职责分离:库存相关操作统一在 StockController,物料相关在 MaterialController

数据库环境(双数据库)

环境 类型 端口 连接方式 sys_tenant.tenant_name
测试库 Docker 容器 tiny-erp-db (postgres:18.1) 未暴露 仅 Docker 内部访问 (db:5432) 默认租户 → 已改为水晶科技有限公司
本地库 WSL apt 安装的 pgsql 16 localhost:31983 IntelliJ / psql 直接连接 水晶科技有限公司
  • Docker 应用 tiny-erp-app 连接的是测试库Docker 内部 tiny-erp-db
  • application-local.ymllocalhost:31983(本地库),绑定 server.port=8081
  • application-test.ymldb:5432(测试库),Docker 运行时通过 SPRING_PROFILES_ACTIVE=test 激活

待办事项

  • 更新 Java 实体类(FinArFinApFinArWriteOffFinApWriteOff
  • 记录数据库变更日志到 doc/数据库变更日志.md
  • 考虑添加外键约束防止 im_inbound_item / im_outbound_item 脏数据

新增功能

9. 物料选择器返回上次单价

需求: 采购入库/销售出库时单价必填,物料选择器返回上次单价供用户参考。

修改内容:

  1. StockPageDTO - 新增 lastInboundPricelastOutboundPrice 字段
  2. Material 实体 - 新增 lastInboundPricelastOutboundPrice 字段(数据库已有 last_inbound_pricelast_outbound_price
  3. MaterialSelectorServiceImpl - 实现批量查询和填充逻辑
    • queryLastInboundPrices() - 批量查询上次入库单价
    • queryLastOutboundPrices() - 批量查询上次出库单价
    • pageInboundMaterial() / pageOutboundMaterial() - 填充单价字段
    • 常用物料场景也支持单价填充

API 响应示例:

{
  "materialId": 4,
  "materialName": "测试物料 A",
  "lastInboundPrice": 12.50,    // 采购入库参考
  "lastOutboundPrice": 18.80    // 销售出库参考
}

前端使用:

  • 采购入库单:显示 lastInboundPrice 作为参考,单价必填
  • 销售出库单:显示 lastOutboundPrice 作为参考,单价必填
  • 后端自动填充逻辑:如果用户未填单价,保存时自动使用上次单价

测试验证: 66 个集成测试全部通过(物料 20 + 入库 23 + 出库 23


10. 首页动态数量格式化

问题: 首页动态显示的数量带有末尾多余的 0(如 10.5000 个)。

修复: HomeServiceImpl.getLatestNews() 中使用 stripTrailingZeros().toPlainString() 格式化数量

// 修改前
String.format("%s入库%s%s", materialName, item.getQuantity(), unit)
// 修改后
String.format("%s入库%s%s", materialName, item.getQuantity().stripTrailingZeros().toPlainString(), unit)

效果对比:

修改前 修改后
测试物料入库 10.5000 个 测试物料入库 10.5 个
测试物料出库 100.0000 个 测试物料出库 100 个

问题修复

11. operator 字段违反 NOT NULL 约束

问题: 出入库单自动生成应收/应付时,fin_ar_receipt 表的 operator 字段为 null,导致插入失败。

错误日志:

ERROR: null value in column "operator" of relation "fin_ar_receipt" violates not-null constraint

根因: MyMetaObjectHandler 自动填充处理器没有填充 operator 字段,当 UserContext.getUserName() 返回 null 时导致插入失败。

修复方案:MyMetaObjectHandler.insertFill() 中添加 operator 字段的自动填充,但仅在用户未设置时才填充

// 操作人(operator 字段,用于财务收款/付款单)- 仅当未设置时自动填充
String operatorName = UserContext.getUserName();
if (operatorName == null || operatorName.isEmpty()) {
    operatorName = "管理员";
}
// 检查是否已有值,有值则不覆盖
if (metaObject.hasGetter("operator") && metaObject.getValue("operator") == null) {
    this.strictInsertFill(metaObject, "operator", String.class, operatorName);
}

行为说明:

  • 用户手动设置 operator → 保留用户设置的值
  • 用户未设置 operator → 自动填充当前登录用户(兜底"管理员")
  • 自动生成场景(无用户上下文)→ 自动填充"管理员"

影响范围: 适用于 fin_ar_receipt(收款单)和 fin_ap_payment(付款单)等需要 operator 的表


测试进展

集成测试覆盖率

测试类 测试数 通过 失败 覆盖率
MaterialControllerTest 20 20 0 100%
InboundControllerTest 23 23 0 100%
OutboundControllerTest 23 23 0 100%
总计 66 66 0 100%

测试覆盖的功能:

  • 物料 CRUD、分页查询、详情查询
  • 采购入库单保存、自动填充单价、更新物料最后入库价
  • 销售出库单保存、自动填充单价、更新物料最后出库价
  • 编码唯一性校验、必填字段校验、BigDecimal 精度比较

经验教训

  1. BigDecimal 比较:测试断言使用 compareTo() 而非 equals(),避免精度差异(10.50 vs 10.5000
  2. 自动填充优先级:用户手动设置的值优先于自动填充,MyMetaObjectHandler 需要先检查字段是否已有值
  3. 批量查询优化:物料选择器使用 selectBatchIds 批量查询上次单价,避免 N+1 查询问题
  4. 数量格式化BigDecimal 转字符串时使用 stripTrailingZeros().toPlainString() 去除末尾多余的 0,同时避免科学计数法

待办事项

  • 前端对接物料选择器接口,显示上次单价参考
  • 验证采购入库/销售出库单价必填的前端校验
  • 运行完整测试套件确保无回归
  • 记录数据库变更日志到 doc/数据库变更日志.md