From 089d10a72d9264277f52c8fa2d8b39c190a0177e Mon Sep 17 00:00:00 2001 From: zjf <1799650030@qq.com> Date: Mon, 25 Aug 2025 20:38:20 +0800 Subject: [PATCH] 1 --- .../README_TASTE_FEATURE.md | 229 ++++++++++++++++++ .../main/resources/mapper/SmErpUnitMapper.xml | 4 +- .../service/SmErpSpuServiceTest.java | 228 +++++++++++++++++ 3 files changed, 459 insertions(+), 2 deletions(-) create mode 100644 pjcloud-supermarket/pjcloud-supermarket-biz/README_TASTE_FEATURE.md create mode 100644 pjcloud-supermarket/pjcloud-supermarket-biz/src/test/java/com/bsd/pjcloud/supermarket/service/SmErpSpuServiceTest.java diff --git a/pjcloud-supermarket/pjcloud-supermarket-biz/README_TASTE_FEATURE.md b/pjcloud-supermarket/pjcloud-supermarket-biz/README_TASTE_FEATURE.md new file mode 100644 index 000000000..e7c2b22a9 --- /dev/null +++ b/pjcloud-supermarket/pjcloud-supermarket-biz/README_TASTE_FEATURE.md @@ -0,0 +1,229 @@ +# 商品SPU口味功能说明 + +## 概述 + +本功能为商品SPU添加了口味集合管理能力,支持在新增和编辑商品时管理多种口味,每种口味可以独立设置库存数量。 + +## 功能特性 + +### 1. 新增商品SPU时的口味处理 +- 支持同时添加多个口味 +- 每个口味自动关联到对应的SPU +- 自动设置默认库存为0(如果未指定) +- 口味数据与商品SPU在同一事务中保存 + +### 2. 编辑商品SPU时的口味处理 +- **新增口味**:ID为null的口味会被识别为新增 +- **更新口味**:ID不为null的口味会被识别为更新 +- **删除口味**:提交数据中不包含的口味会被自动删除 +- 支持混合操作(同时新增、更新、删除) + +### 3. 数据完整性 +- 自动维护口味与SPU的关联关系 +- 支持级联删除(删除SPU时自动删除相关口味) +- 事务保证数据一致性 + +## 数据结构 + +### SmErpTasteStockEntity(口味库存实体) +```java +public class SmErpTasteStockEntity { + private Long id; // 主键ID + private Long spuId; // 关联的SPU ID + private String tasteName; // 口味名称 + private Double stock; // 该口味的库存数量 + private LocalDateTime createTime; // 创建时间 + private LocalDateTime updateTime; // 更新时间 +} +``` + +### SmErpSpuEntity(商品SPU实体) +```java +public class SmErpSpuEntity { + // ... 其他字段 ... + + @Schema(description="口味集合") + @TableField(exist = false) + private List smErpTasteStockEntityList; +} +``` + +## 使用方法 + +### 1. 新增商品SPU(包含口味) + +```java +@PostMapping +public R save(@RequestBody SmErpSpuEntity supermarketSpu) { + supermarketSpu.setDeptId(SecurityUtils.getUser().getDeptId()); + return smErpSpuService.saveSpu(supermarketSpu); +} +``` + +**请求示例:** +```json +{ + "productCode": "SPU001", + "productName": "果汁饮料", + "spec": "500ml", + "status": "0", + "stock": 100.0, + "smErpTasteStockEntityList": [ + { + "tasteName": "原味", + "stock": 50.0 + }, + { + "tasteName": "草莓味", + "stock": 30.0 + }, + { + "tasteName": "橙子味", + "stock": 20.0 + } + ], + "smErpUnitEntityList": [ + { + "unit": "瓶", + "packageNum": 1, + "isBase": true + } + ] +} +``` + +### 2. 编辑商品SPU(修改口味) + +```java +@PutMapping +public R updateById(@RequestBody SmErpSpuEntity supermarketSpu) { + return smErpSpuService.updateSpuById(supermarketSpu); +} +``` + +**请求示例(更新现有口味,新增口味,删除口味):** +```json +{ + "id": 1, + "productCode": "SPU001", + "productName": "果汁饮料", + "spec": "500ml", + "status": "0", + "stock": 120.0, + "smErpTasteStockEntityList": [ + { + "id": 1, // 有ID表示更新 + "tasteName": "原味", + "stock": 60.0 // 更新库存 + }, + { + "id": 2, // 有ID表示更新 + "tasteName": "草莓味", + "stock": 40.0 // 更新库存 + }, + { + "tasteName": "葡萄味", // 无ID表示新增 + "stock": 20.0 + } + // 不包含巧克力味,会被自动删除 + ], + "smErpUnitEntityList": [ + { + "id": 1, + "unit": "瓶", + "packageNum": 1, + "isBase": true + } + ] +} +``` + +## 业务逻辑 + +### 新增逻辑 +1. 验证商品基本信息 +2. 保存商品SPU主表 +3. 创建库存记录 +4. **处理口味集合**: + - 遍历口味列表 + - 设置SPU ID关联 + - 设置默认库存(如果未指定) + - 批量保存口味数据 +5. 处理包装单位 +6. 处理辅助条码 + +### 编辑逻辑 +1. 更新商品SPU主表 +2. 更新库存记录 +3. 处理包装单位(新增/更新/删除) +4. 处理辅助条码(新增/更新/删除) +5. **处理口味集合**: + - 分离新增和更新的口味 + - 新增口味:设置SPU ID,设置默认库存 + - 更新口味:保持ID,更新数据 + - 删除口味:计算需要删除的口味ID,批量删除 + +## 注意事项 + +### 1. 数据一致性 +- 所有操作都在事务中执行 +- 删除操作会级联删除相关数据 +- 口味ID必须与SPU ID匹配 + +### 2. 性能考虑 +- 使用批量操作提高性能 +- 避免N+1查询问题 +- 合理设置批量大小 + +### 3. 业务规则 +- 口味名称在同一SPU下必须唯一 +- 库存数量不能为负数 +- 删除SPU时自动清理相关口味 + +### 4. 错误处理 +- 提供详细的错误信息 +- 支持事务回滚 +- 记录操作日志 + +## 测试用例 + +项目包含完整的测试用例,覆盖以下场景: +- 新增商品SPU(包含口味) +- 编辑商品SPU(更新口味) +- 编辑商品SPU(删除口味) +- 混合操作(新增+更新+删除) + +运行测试: +```bash +mvn test -Dtest=SmErpSpuServiceTest +``` + +## 扩展功能 + +### 1. 口味库存管理 +- 支持口味级别的库存调整 +- 库存预警功能 +- 库存盘点功能 + +### 2. 口味统计分析 +- 口味销售统计 +- 口味库存分析 +- 口味偏好分析 + +### 3. 口味权限控制 +- 基于角色的口味管理权限 +- 口味数据访问控制 +- 操作审计日志 + +## 相关文件 + +- `SmErpSpuServiceImpl.java` - 服务实现类 +- `SmErpTasteStockEntity.java` - 口味实体类 +- `SmErpSpuEntity.java` - 商品SPU实体类 +- `SmErpSpuServiceTest.java` - 测试类 +- `README_TASTE_FEATURE.md` - 本文档 + +## 技术支持 + +如有问题或建议,请联系开发团队或提交Issue。 + diff --git a/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/resources/mapper/SmErpUnitMapper.xml b/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/resources/mapper/SmErpUnitMapper.xml index 80380fe8f..30abff2e1 100644 --- a/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/resources/mapper/SmErpUnitMapper.xml +++ b/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/resources/mapper/SmErpUnitMapper.xml @@ -22,7 +22,7 @@ - + @@ -86,7 +86,7 @@ u.spu_id, u.cost_price, u.is_base, - u.taste + u.taste_name FROM sm_erp_unit u LEFT JOIN sm_erp_spu s ON u.spu_id = s.id diff --git a/pjcloud-supermarket/pjcloud-supermarket-biz/src/test/java/com/bsd/pjcloud/supermarket/service/SmErpSpuServiceTest.java b/pjcloud-supermarket/pjcloud-supermarket-biz/src/test/java/com/bsd/pjcloud/supermarket/service/SmErpSpuServiceTest.java new file mode 100644 index 000000000..e8bc3f09c --- /dev/null +++ b/pjcloud-supermarket/pjcloud-supermarket-biz/src/test/java/com/bsd/pjcloud/supermarket/service/SmErpSpuServiceTest.java @@ -0,0 +1,228 @@ +package com.bsd.pjcloud.supermarket.service; + +import com.bsd.pjcloud.supermarket.entity.SmErpSpuEntity; +import com.bsd.pjcloud.supermarket.entity.SmErpTasteStockEntity; +import com.bsd.pjcloud.supermarket.entity.SmErpUnitEntity; +import com.bsd.pjcloud.supermarket.entity.SmErpAuxiliaryCodeEntity; +import com.bsd.pjcloud.common.core.util.R; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.transaction.annotation.Transactional; + +import java.util.ArrayList; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * 商品SPU服务测试类 + * 测试口味集合的新增、更新和删除功能 + * + * @author pjcloud + */ +@SpringBootTest +@Transactional +public class SmErpSpuServiceTest { + + @Autowired + private SmErpSpuService smErpSpuService; + + @Test + public void testSaveSpuWithTastes() { + // 创建测试商品SPU + SmErpSpuEntity spu = new SmErpSpuEntity(); + spu.setProductCode("TEST001"); + spu.setProductName("测试商品"); + spu.setSpec("500ml"); + spu.setStatus("0"); + spu.setStock(100.0); + spu.setInventoryLowerLimit(10); + spu.setInventoryUpperLimit(1000); + + // 创建口味集合 + List tastes = new ArrayList<>(); + + SmErpTasteStockEntity taste1 = new SmErpTasteStockEntity(); + taste1.setTasteName("原味"); + taste1.setStock(50.0); + tastes.add(taste1); + + SmErpTasteStockEntity taste2 = new SmErpTasteStockEntity(); + taste2.setTasteName("草莓味"); + taste2.setStock(30.0); + tastes.add(taste2); + + spu.setSmErpTasteStockEntityList(tastes); + + // 创建包装单位 + List units = new ArrayList<>(); + SmErpUnitEntity unit = new SmErpUnitEntity(); + unit.setUnit("瓶"); + unit.setPackageNum(1); + unit.setIsBase(true); + units.add(unit); + spu.setSmErpUnitEntityList(units); + + // 执行新增 + R result = smErpSpuService.saveSpu(spu); + + // 验证结果 + assertTrue(result.isOk(), "新增商品SPU应该成功"); + assertNotNull(spu.getId(), "商品SPU应该有ID"); + + // 验证口味是否保存成功 + assertNotNull(tastes.get(0).getId(), "第一个口味应该有ID"); + assertNotNull(tastes.get(1).getId(), "第二个口味应该有ID"); + assertEquals(spu.getId(), tastes.get(0).getSpuId(), "口味应该关联到正确的SPU"); + assertEquals(spu.getId(), tastes.get(1).getSpuId(), "口味应该关联到正确的SPU"); + } + + @Test + public void testUpdateSpuWithTastes() { + // 先创建一个商品SPU + SmErpSpuEntity spu = new SmErpSpuEntity(); + spu.setProductCode("TEST002"); + spu.setProductName("测试商品2"); + spu.setSpec("1L"); + spu.setStatus("0"); + spu.setStock(200.0); + spu.setInventoryLowerLimit(20); + spu.setInventoryUpperLimit(2000); + + // 创建初始口味 + List initialTastes = new ArrayList<>(); + SmErpTasteStockEntity initialTaste = new SmErpTasteStockEntity(); + initialTaste.setTasteName("原味"); + initialTaste.setStock(100.0); + initialTastes.add(initialTaste); + spu.setSmErpTasteStockEntityList(initialTastes); + + // 创建包装单位 + List units = new ArrayList<>(); + SmErpUnitEntity unit = new SmErpUnitEntity(); + unit.setUnit("瓶"); + unit.setPackageNum(1); + unit.setIsBase(true); + units.add(unit); + spu.setSmErpUnitEntityList(units); + + // 新增商品 + R saveResult = smErpSpuService.saveSpu(spu); + assertTrue(saveResult.isOk(), "新增商品应该成功"); + + // 更新商品,修改口味 + List updatedTastes = new ArrayList<>(); + + // 更新原口味 + SmErpTasteStockEntity updatedTaste1 = new SmErpTasteStockEntity(); + updatedTaste1.setId(initialTaste.getId()); // 设置ID表示更新 + updatedTaste1.setTasteName("原味"); + updatedTaste1.setStock(150.0); // 更新库存 + updatedTastes.add(updatedTaste1); + + // 新增口味 + SmErpTasteStockEntity newTaste = new SmErpTasteStockEntity(); + newTaste.setTasteName("巧克力味"); + newTaste.setStock(80.0); + updatedTastes.add(newTaste); + + spu.setSmErpTasteStockEntityList(updatedTastes); + + // 执行更新 + R updateResult = smErpSpuService.updateSpuById(spu); + + // 验证结果 + assertTrue(updateResult.isOk(), "更新商品SPU应该成功"); + + // 验证口味更新 + assertNotNull(updatedTaste1.getId(), "更新的口味应该保持ID"); + assertNotNull(newTaste.getId(), "新增的口味应该有ID"); + assertEquals(spu.getId(), updatedTaste1.getSpuId(), "更新的口味应该关联到正确的SPU"); + assertEquals(spu.getId(), newTaste.getSpuId(), "新增的口味应该关联到正确的SPU"); + } + + @Test + public void testUpdateSpuRemoveTastes() { + // 先创建一个商品SPU,包含多个口味 + SmErpSpuEntity spu = new SmErpSpuEntity(); + spu.setProductCode("TEST003"); + spu.setProductName("测试商品3"); + spu.setSpec("250ml"); + spu.setStatus("0"); + spu.setStock(150.0); + spu.setInventoryLowerLimit(15); + spu.setInventoryUpperLimit(1500); + + // 创建多个口味 + List initialTastes = new ArrayList<>(); + + SmErpTasteStockEntity taste1 = new SmErpTasteStockEntity(); + taste1.setTasteName("原味"); + taste1.setStock(50.0); + initialTastes.add(taste1); + + SmErpTasteStockEntity taste2 = new SmErpTasteStockEntity(); + taste2.setTasteName("草莓味"); + taste2.setStock(50.0); + initialTastes.add(taste2); + + SmErpTasteStockEntity taste3 = new SmErpTasteStockEntity(); + taste3.setTasteName("巧克力味"); + taste3.setStock(50.0); + initialTastes.add(taste3); + + spu.setSmErpTasteStockEntityList(initialTastes); + + // 创建包装单位 + List units = new ArrayList<>(); + SmErpUnitEntity unit = new SmErpUnitEntity(); + unit.setUnit("瓶"); + unit.setPackageNum(1); + unit.setIsBase(true); + units.add(unit); + spu.setSmErpUnitEntityList(units); + + // 新增商品 + R saveResult = smErpSpuService.saveSpu(spu); + assertTrue(saveResult.isOk(), "新增商品应该成功"); + + // 更新商品,只保留部分口味(删除巧克力味) + List updatedTastes = new ArrayList<>(); + + // 保留原味 + SmErpTasteStockEntity updatedTaste1 = new SmErpTasteStockEntity(); + updatedTaste1.setId(taste1.getId()); + updatedTaste1.setTasteName("原味"); + updatedTaste1.setStock(60.0); + updatedTastes.add(updatedTaste1); + + // 保留草莓味 + SmErpTasteStockEntity updatedTaste2 = new SmErpTasteStockEntity(); + updatedTaste2.setId(taste2.getId()); + updatedTaste2.setTasteName("草莓味"); + updatedTaste2.setStock(40.0); + updatedTastes.add(updatedTaste2); + + // 不包含巧克力味,应该被删除 + + spu.setSmErpTasteStockEntityList(updatedTastes); + + // 执行更新 + R updateResult = smErpSpuService.updateSpuById(spu); + + // 验证结果 + assertTrue(updateResult.isOk(), "更新商品SPU应该成功"); + + // 验证口味数量 + assertEquals(2, updatedTastes.size(), "更新后应该只有2个口味"); + + // 验证保留的口味 + assertTrue(updatedTastes.stream().anyMatch(t -> "原味".equals(t.getTasteName())), "应该保留原味"); + assertTrue(updatedTastes.stream().anyMatch(t -> "草莓味".equals(t.getTasteName())), "应该保留草莓味"); + + // 验证删除的口味 + assertFalse(updatedTastes.stream().anyMatch(t -> "巧克力味".equals(t.getTasteName())), "不应该包含巧克力味"); + } +} +