1
This commit is contained in:
1 parent
fa2734ca89
commit
089d10a72d
3 files changed
+459
-2
No files matched your search
@@ -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<SmErpTasteStockEntity> 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。
|
||||
|
||||
+2
-2
@@ -22,7 +22,7 @@
|
||||
<result property="delFlag" column="del_flag"/>
|
||||
<result property="tenantId" column="tenant_id"/>
|
||||
<result property="isBase" column="is_base"/>
|
||||
<result property="taste" column="taste"/>
|
||||
<result property="tasteName" column="taste_name"/>
|
||||
<result property="tasteId" column="taste_id"/>
|
||||
</resultMap>
|
||||
|
||||
@@ -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
|
||||
|
||||
+228
@@ -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<SmErpTasteStockEntity> 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<SmErpUnitEntity> 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<SmErpTasteStockEntity> initialTastes = new ArrayList<>();
|
||||
SmErpTasteStockEntity initialTaste = new SmErpTasteStockEntity();
|
||||
initialTaste.setTasteName("原味");
|
||||
initialTaste.setStock(100.0);
|
||||
initialTastes.add(initialTaste);
|
||||
spu.setSmErpTasteStockEntityList(initialTastes);
|
||||
|
||||
// 创建包装单位
|
||||
List<SmErpUnitEntity> 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<SmErpTasteStockEntity> 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<SmErpTasteStockEntity> 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<SmErpUnitEntity> 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<SmErpTasteStockEntity> 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())), "不应该包含巧克力味");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user