From bd35814198efc97c0020a74ca4dd2cf0c60b0054 Mon Sep 17 00:00:00 2001 From: zjf <1799650030@qq.com> Date: Wed, 3 Sep 2025 19:21:40 +0800 Subject: [PATCH] =?UTF-8?q?=E5=85=B1=E5=AF=8C=E5=95=86=E5=9F=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pjcloud-common/pjcloud-common-core/pom.xml | 1 + .../API_USAGE_EXAMPLE.md | 348 ++++++++++++++++++ .../SELECT_OPTION_API.md | 308 ++++++++++++++++ .../controller/MallCategoryController.java | 2 + .../controller/MallMerchantController.java | 16 + .../controller/MallProductController.java | 64 +++- .../controller/SelectOptionController.java | 69 ++++ .../supermarket/dto/MallProductCreateDTO.java | 130 +++++++ .../dto/MallProductSkuCreateDTO.java | 78 ++++ .../supermarket/dto/SelectOptionDTO.java | 66 ++++ .../service/MallProductService.java | 21 ++ .../service/SelectOptionService.java | 102 +++++ .../service/impl/MallProductServiceImpl.java | 251 +++++++++++++ .../service/MallProductServiceTest.java | 104 ++++++ 14 files changed, 1552 insertions(+), 8 deletions(-) create mode 100644 pjcloud-supermarket/pjcloud-supermarket-biz/API_USAGE_EXAMPLE.md create mode 100644 pjcloud-supermarket/pjcloud-supermarket-biz/SELECT_OPTION_API.md create mode 100644 pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/controller/SelectOptionController.java create mode 100644 pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/dto/MallProductCreateDTO.java create mode 100644 pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/dto/MallProductSkuCreateDTO.java create mode 100644 pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/dto/SelectOptionDTO.java create mode 100644 pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/service/SelectOptionService.java create mode 100644 pjcloud-supermarket/pjcloud-supermarket-biz/src/test/java/com/bsd/pjcloud/supermarket/service/MallProductServiceTest.java diff --git a/pjcloud-common/pjcloud-common-core/pom.xml b/pjcloud-common/pjcloud-common-core/pom.xml index 673557583..a63e04bf3 100644 --- a/pjcloud-common/pjcloud-common-core/pom.xml +++ b/pjcloud-common/pjcloud-common-core/pom.xml @@ -77,5 +77,6 @@ io.swagger.core.v3 swagger-annotations-jakarta + diff --git a/pjcloud-supermarket/pjcloud-supermarket-biz/API_USAGE_EXAMPLE.md b/pjcloud-supermarket/pjcloud-supermarket-biz/API_USAGE_EXAMPLE.md new file mode 100644 index 000000000..62e9fca48 --- /dev/null +++ b/pjcloud-supermarket/pjcloud-supermarket-biz/API_USAGE_EXAMPLE.md @@ -0,0 +1,348 @@ +# 商品新增接口使用说明 + +## 商品创建接口 + +### 接口地址 +``` +POST /mallProduct +``` + +### 请求参数 +支持两种请求体类型: + +#### 1. 包含SKU的创建(使用MallProductCreateDTO) + +##### 多SKU模式示例(priceType = 1) +```json +{ + "merchantId": 1, + "categoryId": 100, + "title": "苹果手机", + "subTitle": "最新款智能手机", + "brand": "Apple", + "coverImage": "https://example.com/cover.jpg", + "carouselImages": "https://example.com/img1.jpg,https://example.com/img2.jpg", + "priceType": 1, + "originalPrice": 9999.00, + "unit": "台", + "status": 1, + "saleStartTime": "2025-01-27T00:00:00", + "saleEndTime": "2025-12-31T23:59:59", + "description": "这是一款高性能的智能手机", + "deptId": 1, + "skuList": [ + { + "skuCode": "IPHONE15-128-BLACK", + "skuName": "iPhone 15 128GB 黑色", + "price": 5999.00, + "costPrice": 4500.00, + "stock": 100, + "barcode": "1234567890123", + "weightG": 171, + "volumeCm3": 200, + "status": 1, + "deptId": 1 + }, + { + "skuCode": "IPHONE15-256-BLACK", + "skuName": "iPhone 15 256GB 黑色", + "price": 6999.00, + "costPrice": 5200.00, + "stock": 50, + "barcode": "1234567890124", + "weightG": 171, + "volumeCm3": 200, + "status": 1, + "deptId": 1 + } + ] +} +``` + +##### 单价SPU模式示例(priceType = 2) +```json +{ + "merchantId": 1, + "categoryId": 100, + "title": "苹果手机", + "subTitle": "最新款智能手机", + "brand": "Apple", + "coverImage": "https://example.com/cover.jpg", + "carouselImages": "https://example.com/img1.jpg,https://example.com/img2.jpg", + "priceType": 2, + "spuPrice": 5999.00, + "originalPrice": 9999.00, + "unit": "台", + "status": 1, + "saleStartTime": "2025-01-27T00:00:00", + "saleEndTime": "2025-12-31T23:59:59", + "description": "这是一款高性能的智能手机", + "deptId": 1 +} +``` + +#### 2. 普通SPU创建(使用MallProductEntity) +```json +{ + "merchantId": 1, + "categoryId": 100, + "title": "苹果手机", + "subTitle": "最新款智能手机", + "brand": "Apple", + "coverImage": "https://example.com/cover.jpg", + "carouselImages": "https://example.com/img1.jpg,https://example.com/img2.jpg", + "priceType": 2, + "spuPrice": 5999.00, + "originalPrice": 9999.00, + "unit": "台", + "status": 1, + "saleStartTime": "2025-01-27T00:00:00", + "saleEndTime": "2025-12-31T23:59:59", + "description": "这是一款高性能的智能手机" +} +``` + +### 响应示例 + +#### 成功响应 +```json +{ + "code": 0, + "msg": "商品创建成功", + "data": null +} +``` + +#### 失败响应 +```json +{ + "code": 1, + "msg": "商品创建失败:SKU编码不能为空", + "data": null +} +``` + +### 字段说明 + +#### SPU字段 +- `merchantId`: 商户ID(必填) +- `categoryId`: 类目ID(必填) +- `title`: 商品标题(必填) +- `subTitle`: 副标题 +- `brand`: 品牌 +- `coverImage`: 主图 +- `carouselImages`: SPU轮播图URL(逗号分隔) +- `priceType`: 定价方式(1=多SKU,2=单价SPU)(必填) +- `spuPrice`: SPU售价(当priceType=2时有效) +- `originalPrice`: 划线价 +- `unit`: 计量单位 +- `status`: 状态(1=上架,0=下架) +- `saleStartTime`: 开售时间 +- `saleEndTime`: 停售时间 +- `description`: 详情描述 +- `deptId`: 部门ID + +#### SKU字段(当priceType=1时必填) +- `skuCode`: SKU编码(必填) +- `skuName`: SKU名称(必填) +- `price`: 售价(必填) +- `costPrice`: 成本价 +- `stock`: 库存(必填) +- `barcode`: 条码 +- `weightG`: 重量(克) +- `volumeCm3`: 体积(立方厘米) +- `status`: 状态(1=启用,0=停用) +- `deptId`: 部门ID + +### 业务逻辑说明 + +1. **多SKU模式(priceType = 1)**: + - 必须提供skuList + - 系统会自动计算总库存(所有SKU库存之和) + - 每个SKU都会保存到数据库 + +2. **单价SPU模式(priceType = 2)**: + - 不需要提供skuList + - 使用spuPrice作为商品价格 + - 总库存设置为0(可根据业务需求调整) + +3. **事务处理**: + - 整个创建过程在一个事务中执行 + - 如果任何步骤失败,所有操作都会回滚 + +4. **数据验证**: + - 使用JSR-303验证注解进行参数校验 + - 必填字段验证 + - 业务逻辑验证 + +### 权限要求 +需要 `supermarket_mallProduct_add` 权限 + +## 商品编辑接口 + +### 接口地址 +``` +PUT /mallProduct +``` + +### 请求参数 +支持两种请求体类型: + +#### 1. 包含SKU的编辑(使用MallProductCreateDTO) +```json +{ + "id": 1, + "merchantId": 1, + "categoryId": 100, + "title": "苹果手机(已更新)", + "subTitle": "最新款智能手机", + "brand": "Apple", + "coverImage": "https://example.com/cover.jpg", + "carouselImages": "https://example.com/img1.jpg,https://example.com/img2.jpg", + "priceType": 1, + "originalPrice": 9999.00, + "unit": "台", + "status": 1, + "saleStartTime": "2025-01-27T00:00:00", + "saleEndTime": "2025-12-31T23:59:59", + "description": "这是一款高性能的智能手机", + "skuList": [ + { + "skuCode": "IPHONE15-128-BLACK-UPDATED", + "skuName": "iPhone 15 128GB 黑色(已更新)", + "price": 5999.00, + "costPrice": 4500.00, + "stock": 100, + "barcode": "1234567890123", + "weightG": 171, + "volumeCm3": 200, + "status": 1 + } + ] +} +``` + +#### 2. 普通SPU编辑(使用MallProductEntity) +```json +{ + "id": 1, + "merchantId": 1, + "categoryId": 100, + "title": "苹果手机(已更新)", + "subTitle": "最新款智能手机", + "brand": "Apple", + "coverImage": "https://example.com/cover.jpg", + "carouselImages": "https://example.com/img1.jpg,https://example.com/img2.jpg", + "priceType": 2, + "spuPrice": 5999.00, + "originalPrice": 9999.00, + "unit": "台", + "status": 1, + "saleStartTime": "2025-01-27T00:00:00", + "saleEndTime": "2025-12-31T23:59:59", + "description": "这是一款高性能的智能手机" +} +``` + +### 响应示例 +```json +{ + "code": 0, + "msg": "商品更新成功", + "data": null +} +``` + +### 权限要求 +需要 `supermarket_mallProduct_edit` 权限 + +## 商品详情查询接口 + +### 接口地址 +``` +GET /mallProduct/{id} +``` + +### 请求参数 +- `id`: 商品ID(路径参数) +- `withSkus`: 是否包含SKU信息(查询参数,可选,默认false) + +### 使用示例 + +#### 1. 查询基本信息(不包含SKU) +``` +GET /mallProduct/1 +``` + +#### 2. 查询完整信息(包含SKU) +``` +GET /mallProduct/1?withSkus=true +``` + +### 响应示例 + +#### 基本信息响应 +```json +{ + "code": 0, + "msg": "操作成功", + "data": { + "id": 1, + "merchantId": 1, + "categoryId": 100, + "title": "苹果手机", + "subTitle": "最新款智能手机", + "brand": "Apple", + "coverImage": "https://example.com/cover.jpg", + "carouselImages": "https://example.com/img1.jpg,https://example.com/img2.jpg", + "priceType": 1, + "originalPrice": 9999.00, + "unit": "台", + "status": 1, + "saleStartTime": "2025-01-27T00:00:00", + "saleEndTime": "2025-12-31T23:59:59", + "description": "这是一款高性能的智能手机" + } +} +``` + +#### 完整信息响应(withSkus=true) +```json +{ + "code": 0, + "msg": "操作成功", + "data": { + "id": 1, + "merchantId": 1, + "categoryId": 100, + "title": "苹果手机", + "subTitle": "最新款智能手机", + "brand": "Apple", + "coverImage": "https://example.com/cover.jpg", + "carouselImages": "https://example.com/img1.jpg,https://example.com/img2.jpg", + "priceType": 1, + "originalPrice": 9999.00, + "unit": "台", + "status": 1, + "saleStartTime": "2025-01-27T00:00:00", + "saleEndTime": "2025-12-31T23:59:59", + "description": "这是一款高性能的智能手机", + "skuList": [ + { + "skuCode": "IPHONE15-128-BLACK", + "skuName": "iPhone 15 128GB 黑色", + "price": 5999.00, + "costPrice": 4500.00, + "stock": 100, + "barcode": "1234567890123", + "weightG": 171, + "volumeCm3": 200, + "status": 1 + } + ] + } +} +``` + +### 权限要求 +需要 `supermarket_mallProduct_view` 权限 diff --git a/pjcloud-supermarket/pjcloud-supermarket-biz/SELECT_OPTION_API.md b/pjcloud-supermarket/pjcloud-supermarket-biz/SELECT_OPTION_API.md new file mode 100644 index 000000000..e36743241 --- /dev/null +++ b/pjcloud-supermarket/pjcloud-supermarket-biz/SELECT_OPTION_API.md @@ -0,0 +1,308 @@ +# 下拉选择接口使用说明 + +## 概述 + +为了方便后台管理系统使用下拉选择组件,我们提供了一系列统一的下拉选择接口。这些接口返回标准化的选项数据格式,便于前端组件使用。 + +## 统一返回格式 + +所有下拉选择接口都返回以下格式的数据: + +```json +{ + "code": 0, + "msg": "操作成功", + "data": [ + { + "value": 1, + "label": "选项名称", + "disabled": false, + "extra": "扩展信息" + } + ] +} +``` + +### 字段说明 +- `value`: 选项值,可以是数字、字符串等类型 +- `label`: 选项显示名称 +- `disabled`: 是否禁用,默认为false +- `extra`: 扩展信息,可以包含额外的数据 + +## 接口列表 + +### 1. 商户下拉选择 + +#### 接口地址 +``` +GET /selectOption/merchant?keyWord=关键字 +``` + +#### 请求参数 +- `keyWord` (可选): 关键字,支持商户名称和手机号模糊查询 + +#### 权限要求 +需要 `supermarket_mallMerchant_view` 权限 + +#### 使用示例 +``` +# 获取所有商户 +GET /selectOption/merchant + +# 按商户名称搜索 +GET /selectOption/merchant?keyWord=农场 + +# 按手机号搜索 +GET /selectOption/merchant?keyWord=138 + +# 按关键字搜索(商户名称或手机号) +GET /selectOption/merchant?keyWord=张三 +``` + +#### 响应示例 +```json +{ + "code": 0, + "msg": "操作成功", + "data": [ + { + "value": 1, + "label": "张三农场", + "disabled": false, + "extra": "13800138000" + }, + { + "value": 2, + "label": "李四果园", + "disabled": false, + "extra": "13900139000" + } + ] +} +``` + +### 2. 分类下拉选择 + +#### 接口地址 +``` +GET /selectOption/category +``` + +#### 权限要求 +需要 `supermarket_mallCategory_view` 权限 + +#### 响应示例 +```json +{ + "code": 0, + "msg": "操作成功", + "data": [ + { + "value": 1, + "label": "水果", + "disabled": false, + "extra": 0 + }, + { + "value": 2, + "label": "蔬菜", + "disabled": false, + "extra": 0 + }, + { + "value": 3, + "label": "苹果", + "disabled": false, + "extra": 1 + } + ] +} +``` + +### 3. 分类下拉选择(树形结构) + +#### 接口地址 +``` +GET /selectOption/category/tree +``` + +#### 权限要求 +需要 `supermarket_mallCategory_view` 权限 + +#### 响应示例 +```json +{ + "code": 0, + "msg": "操作成功", + "data": [ + { + "value": 1, + "label": "水果", + "disabled": false, + "extra": 0 + }, + { + "value": 3, + "label": " └─ 苹果", + "disabled": false, + "extra": 1 + }, + { + "value": 2, + "label": "蔬菜", + "disabled": false, + "extra": 0 + } + ] +} +``` + +## 传统接口 + +除了统一的下拉选择接口,原有的控制器也提供了下拉选择接口: + +### 商户下拉选择(传统接口) +``` +GET /mallMerchant/selectList +``` + +### 分类下拉选择(传统接口) +``` +GET /mallCategory/selectList +``` + +## 前端使用示例 + +### Vue.js + Element Plus + +```javascript +// 获取商户下拉选择 +async getMerchantOptions(keyword = '') { + try { + const params = keyword ? { keyWord: keyword } : {}; + const response = await this.$http.get('/selectOption/merchant', { params }); + this.merchantOptions = response.data.data.map(item => ({ + value: item.value, + label: item.label, + disabled: item.disabled + })); + } catch (error) { + console.error('获取商户选项失败:', error); + } +} + +// 搜索商户 +async searchMerchants(keyword) { + await this.getMerchantOptions(keyword); +} + +// 获取分类下拉选择 +async getCategoryOptions() { + try { + const response = await this.$http.get('/selectOption/category'); + this.categoryOptions = response.data.data.map(item => ({ + value: item.value, + label: item.label, + disabled: item.disabled + })); + } catch (error) { + console.error('获取分类选项失败:', error); + } +} +``` + +### HTML模板 + +```html + + + +``` + +## 扩展说明 + +### 添加新的下拉选择类型 + +如果需要添加新的下拉选择类型,可以: + +1. 在 `SelectOptionService` 中添加新的方法 +2. 在 `SelectOptionController` 中添加对应的接口 +3. 按照统一的格式返回数据 + +### 自定义扩展信息 + +`extra` 字段可以包含任何额外的信息,例如: +- 联系电话 +- 父级ID +- 状态信息 +- 其他业务相关数据 + +前端可以根据需要解析和使用这些扩展信息。 + +## 注意事项 + +1. 所有接口都需要相应的权限验证 +2. 只返回启用状态的数据 +3. 数据按照指定的排序规则返回 +4. 建议前端对接口返回的数据进行缓存,避免频繁请求 diff --git a/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/controller/MallCategoryController.java b/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/controller/MallCategoryController.java index 81c2fc783..d161fd429 100644 --- a/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/controller/MallCategoryController.java +++ b/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/controller/MallCategoryController.java @@ -155,4 +155,6 @@ public class MallCategoryController { return R.ok(list); } + + } \ No newline at end of file diff --git a/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/controller/MallMerchantController.java b/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/controller/MallMerchantController.java index edc2af9f1..3e7da16e2 100644 --- a/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/controller/MallMerchantController.java +++ b/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/controller/MallMerchantController.java @@ -120,4 +120,20 @@ public class MallMerchantController { public List export(MallMerchantEntity mallMerchant,Long[] ids) { return mallMerchantService.list(Wrappers.lambdaQuery(mallMerchant).in(ArrayUtil.isNotEmpty(ids), MallMerchantEntity::getId, ids)); } + + /** + * 获取商户下拉选择列表 + * @return R + */ + @Operation(summary = "获取商户下拉选择列表" , description = "获取商户下拉选择列表" ) + @GetMapping("/selectList") + @PreAuthorize("@pms.hasPermission('supermarket_mallMerchant_view')" ) + public R getSelectList() { + LambdaQueryWrapper wrapper = Wrappers.lambdaQuery(); + wrapper.select(MallMerchantEntity::getId, MallMerchantEntity::getName, MallMerchantEntity::getContactName, MallMerchantEntity::getContactPhone); + wrapper.eq(MallMerchantEntity::getStatus, 1); // 只查询启用状态的商户 + wrapper.orderByAsc(MallMerchantEntity::getName); + List list = mallMerchantService.list(wrapper); + return R.ok(list); + } } \ No newline at end of file diff --git a/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/controller/MallProductController.java b/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/controller/MallProductController.java index f850f3886..4c27cd664 100644 --- a/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/controller/MallProductController.java +++ b/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/controller/MallProductController.java @@ -10,6 +10,8 @@ import com.bsd.pjcloud.common.core.util.R; import com.bsd.pjcloud.common.log.annotation.SysLog; import com.bsd.pjcloud.supermarket.entity.MallProductEntity; import com.bsd.pjcloud.supermarket.service.MallProductService; +import com.bsd.pjcloud.supermarket.dto.MallProductCreateDTO; +import jakarta.validation.Valid; import org.springframework.security.access.prepost.PreAuthorize; import com.bsd.pjcloud.common.excel.annotation.ResponseExcel; import io.swagger.v3.oas.annotations.security.SecurityRequirement; @@ -58,39 +60,85 @@ public class MallProductController { /** * 通过id查询共富商城商品信息 * @param id id + * @param withSkus 是否包含SKU信息 * @return R */ @Operation(summary = "通过id查询" , description = "通过id查询" ) @GetMapping("/{id}" ) @PreAuthorize("@pms.hasPermission('supermarket_mallProduct_view')" ) - public R getById(@PathVariable("id" ) Long id) { - return R.ok(mallProductService.getById(id)); + public R getById(@PathVariable("id" ) Long id, @RequestParam(value = "withSkus", defaultValue = "false") Boolean withSkus) { + try { + if (withSkus) { + MallProductCreateDTO productDTO = mallProductService.getProductWithSkus(id); + return R.ok(productDTO); + } else { + return R.ok(mallProductService.getById(id)); + } + } catch (Exception e) { + return R.failed("获取商品详情失败:" + e.getMessage()); + } } /** * 新增共富商城商品信息 - * @param mallProduct 共富商城商品信息 + * @param requestBody 请求体(可以是MallProductEntity或MallProductCreateDTO) * @return R */ @Operation(summary = "新增共富商城商品信息" , description = "新增共富商城商品信息" ) @SysLog("新增共富商城商品信息" ) @PostMapping @PreAuthorize("@pms.hasPermission('supermarket_mallProduct_add')" ) - public R save(@RequestBody MallProductEntity mallProduct) { - return R.ok(mallProductService.save(mallProduct)); + public R save(@Valid @RequestBody Object requestBody) { + try { + // 判断请求体类型 + if (requestBody instanceof MallProductCreateDTO) { + // 包含SKU的创建 + MallProductCreateDTO productCreateDTO = (MallProductCreateDTO) requestBody; + boolean success = mallProductService.createProductWithSkus(productCreateDTO); + if (success) { + return R.ok("商品创建成功"); + } else { + return R.failed("商品创建失败"); + } + } else { + // 普通SPU创建 + MallProductEntity mallProduct = (MallProductEntity) requestBody; + return R.ok(mallProductService.save(mallProduct)); + } + } catch (Exception e) { + return R.failed("商品创建失败:" + e.getMessage()); + } } /** * 修改共富商城商品信息 - * @param mallProduct 共富商城商品信息 + * @param requestBody 请求体(可以是MallProductEntity或MallProductCreateDTO) * @return R */ @Operation(summary = "修改共富商城商品信息" , description = "修改共富商城商品信息" ) @SysLog("修改共富商城商品信息" ) @PutMapping @PreAuthorize("@pms.hasPermission('supermarket_mallProduct_edit')" ) - public R updateById(@RequestBody MallProductEntity mallProduct) { - return R.ok(mallProductService.updateById(mallProduct)); + public R updateById(@RequestBody Object requestBody) { + try { + // 判断请求体类型 + if (requestBody instanceof MallProductCreateDTO) { + // 包含SKU的更新 + MallProductCreateDTO productCreateDTO = (MallProductCreateDTO) requestBody; + boolean success = mallProductService.updateProductWithSkus(productCreateDTO); + if (success) { + return R.ok("商品更新成功"); + } else { + return R.failed("商品更新失败"); + } + } else { + // 普通SPU更新 + MallProductEntity mallProduct = (MallProductEntity) requestBody; + return R.ok(mallProductService.updateById(mallProduct)); + } + } catch (Exception e) { + return R.failed("商品更新失败:" + e.getMessage()); + } } /** diff --git a/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/controller/SelectOptionController.java b/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/controller/SelectOptionController.java new file mode 100644 index 000000000..b5f0e5f05 --- /dev/null +++ b/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/controller/SelectOptionController.java @@ -0,0 +1,69 @@ +package com.bsd.pjcloud.supermarket.controller; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import com.bsd.pjcloud.common.core.util.R; +import com.bsd.pjcloud.supermarket.dto.SelectOptionDTO; +import com.bsd.pjcloud.supermarket.entity.MallMerchantEntity; +import com.bsd.pjcloud.supermarket.service.SelectOptionService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpHeaders; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +/** + * 下拉选择控制器 + * + * @author zjf + * @date 2025-01-27 + */ +@RestController +@RequiredArgsConstructor +@RequestMapping("/selectOption") +@Tag(description = "selectOption", name = "下拉选择管理") +@SecurityRequirement(name = HttpHeaders.AUTHORIZATION) +public class SelectOptionController { + + private final SelectOptionService selectOptionService; + + /** + * 获取商户下拉选择列表 + * @return R + */ + @Operation(summary = "获取商户下拉选择列表", description = "获取商户下拉选择列表") + @GetMapping("/merchant") + @PreAuthorize("@pms.hasPermission('supermarket_mallMerchant_view')") + public R> getMerchantSelectList(@RequestParam(value = "keyWord",required = false)String keyWord) { + List list = selectOptionService.getMerchantSelectList(keyWord); + return R.ok(list); + } + + /** + * 获取分类下拉选择列表 + * @return R + */ + @Operation(summary = "获取分类下拉选择列表", description = "获取分类下拉选择列表") + @GetMapping("/category") + @PreAuthorize("@pms.hasPermission('supermarket_mallCategory_view')") + public R> getCategorySelectList() { + List list = selectOptionService.getCategorySelectList(); + return R.ok(list); + } + + /** + * 获取分类下拉选择列表(树形结构) + * @return R + */ + @Operation(summary = "获取分类下拉选择列表(树形结构)", description = "获取分类下拉选择列表(树形结构)") + @GetMapping("/category/tree") + @PreAuthorize("@pms.hasPermission('supermarket_mallCategory_view')") + public R> getCategoryTreeSelectList() { + List list = selectOptionService.getCategoryTreeSelectList(); + return R.ok(list); + } +} diff --git a/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/dto/MallProductCreateDTO.java b/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/dto/MallProductCreateDTO.java new file mode 100644 index 000000000..83c213d56 --- /dev/null +++ b/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/dto/MallProductCreateDTO.java @@ -0,0 +1,130 @@ +package com.bsd.pjcloud.supermarket.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.List; + +/** + * 共富商城商品创建DTO(包含SPU和SKU信息) + * + * @author zjf + * @date 2025-01-27 + */ +@Data +@Schema(description = "共富商城商品创建DTO") +public class MallProductCreateDTO { + + /** + * 主键(编辑时使用) + */ + @Schema(description = "主键") + private Long id; + + /** + * 商户ID + */ + @NotNull(message = "商户ID不能为空") + @Schema(description = "商户ID") + private Long merchantId; + + /** + * 类目ID + */ + @NotNull(message = "类目ID不能为空") + @Schema(description = "类目ID") + private Long categoryId; + + /** + * 商品标题 + */ + @NotBlank(message = "商品标题不能为空") + @Schema(description = "商品标题") + private String title; + + /** + * 副标题 + */ + @Schema(description = "副标题") + private String subTitle; + + /** + * 品牌 + */ + @Schema(description = "品牌") + private String brand; + + /** + * 主图 + */ + @Schema(description = "主图") + private String coverImage; + + /** + * SPU轮播图URL(逗号分隔,按顺序) + */ + @Schema(description = "SPU轮播图URL(逗号分隔,按顺序)") + private String carouselImages; + + /** + * 定价方式(1多SKU 2单价SPU) + */ + @NotNull(message = "定价方式不能为空") + @Schema(description = "定价方式(1多SKU 2单价SPU)") + private Integer priceType; + + /** + * SPU售价(当price_type=2有效) + */ + @Schema(description = "SPU售价(当price_type=2有效)") + private BigDecimal spuPrice; + + /** + * 划线价 + */ + @Schema(description = "划线价") + private BigDecimal originalPrice; + + /** + * 计量单位 + */ + @Schema(description = "计量单位") + private String unit; + + /** + * 状态(1上架 0下架) + */ + @Schema(description = "状态(1上架 0下架)") + private Integer status; + + /** + * 开售时间 + */ + @Schema(description = "开售时间") + private LocalDateTime saleStartTime; + + /** + * 停售时间 + */ + @Schema(description = "停售时间") + private LocalDateTime saleEndTime; + + /** + * 详情描述(HTML/文本) + */ + @Schema(description = "详情描述(HTML/文本)") + private String description; + + + /** + * SKU列表(当priceType=1时必填) + */ + @Valid + @Schema(description = "SKU列表") + private List skuList; +} diff --git a/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/dto/MallProductSkuCreateDTO.java b/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/dto/MallProductSkuCreateDTO.java new file mode 100644 index 000000000..8081f8c17 --- /dev/null +++ b/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/dto/MallProductSkuCreateDTO.java @@ -0,0 +1,78 @@ +package com.bsd.pjcloud.supermarket.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.math.BigDecimal; + +/** + * 共富商城商品SKU创建DTO + * + * @author zjf + * @date 2025-01-27 + */ +@Data +@Schema(description = "共富商城商品SKU创建DTO") +public class MallProductSkuCreateDTO { + + /** + * SKU编码 + */ + @NotBlank(message = "SKU编码不能为空") + @Schema(description = "SKU编码") + private String skuCode; + + /** + * SKU名称(规格组合) + */ + @NotBlank(message = "SKU名称不能为空") + @Schema(description = "SKU名称(规格组合)") + private String skuName; + + /** + * 售价 + */ + @NotNull(message = "售价不能为空") + @Schema(description = "售价") + private BigDecimal price; + + /** + * 成本价 + */ + @Schema(description = "成本价") + private BigDecimal costPrice; + + /** + * 库存 + */ + @NotNull(message = "库存不能为空") + @Schema(description = "库存") + private Integer stock; + + /** + * 条码 + */ + @Schema(description = "条码") + private String barcode; + + /** + * 重量(克) + */ + @Schema(description = "重量(克)") + private Integer weightG; + + /** + * 体积(立方厘米) + */ + @Schema(description = "体积(立方厘米)") + private Integer volumeCm3; + + /** + * 状态(1启用 0停用) + */ + @Schema(description = "状态(1启用 0停用)") + private Integer status; + +} diff --git a/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/dto/SelectOptionDTO.java b/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/dto/SelectOptionDTO.java new file mode 100644 index 000000000..056e8aad1 --- /dev/null +++ b/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/dto/SelectOptionDTO.java @@ -0,0 +1,66 @@ +package com.bsd.pjcloud.supermarket.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +/** + * 下拉选择选项DTO + * + * @author zjf + * @date 2025-01-27 + */ +@Data +@Schema(description = "下拉选择选项DTO") +public class SelectOptionDTO { + + /** + * 选项值 + */ + @Schema(description = "选项值") + private Object value; + + /** + * 选项标签 + */ + @Schema(description = "选项标签") + private String label; + + /** + * 是否禁用 + */ + @Schema(description = "是否禁用") + private Boolean disabled = false; + + /** + * 扩展信息 + */ + @Schema(description = "扩展信息") + private Object extra; + + public SelectOptionDTO() { + } + + public SelectOptionDTO(Object value, String label) { + this.value = value; + this.label = label; + } + + public SelectOptionDTO(Object value, String label, Boolean disabled) { + this.value = value; + this.label = label; + this.disabled = disabled; + } + + public SelectOptionDTO(Object value, String label, Object extra) { + this.value = value; + this.label = label; + this.extra = extra; + } + + public SelectOptionDTO(Object value, String label, Boolean disabled, Object extra) { + this.value = value; + this.label = label; + this.disabled = disabled; + this.extra = extra; + } +} diff --git a/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/service/MallProductService.java b/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/service/MallProductService.java index 61179110a..1778f1386 100644 --- a/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/service/MallProductService.java +++ b/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/service/MallProductService.java @@ -2,7 +2,28 @@ package com.bsd.pjcloud.supermarket.service; import com.baomidou.mybatisplus.extension.service.IService; import com.bsd.pjcloud.supermarket.entity.MallProductEntity; +import com.bsd.pjcloud.supermarket.dto.MallProductCreateDTO; public interface MallProductService extends IService { + /** + * 创建商品(包含SPU和SKU) + * @param productCreateDTO 商品创建DTO + * @return 是否成功 + */ + boolean createProductWithSkus(MallProductCreateDTO productCreateDTO); + + /** + * 更新商品(包含SPU和SKU) + * @param productCreateDTO 商品更新DTO + * @return 是否成功 + */ + boolean updateProductWithSkus(MallProductCreateDTO productCreateDTO); + + /** + * 获取商品详情(包含SPU和SKU) + * @param id 商品ID + * @return 商品详情DTO + */ + MallProductCreateDTO getProductWithSkus(Long id); } \ No newline at end of file diff --git a/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/service/SelectOptionService.java b/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/service/SelectOptionService.java new file mode 100644 index 000000000..949cf8a65 --- /dev/null +++ b/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/service/SelectOptionService.java @@ -0,0 +1,102 @@ +package com.bsd.pjcloud.supermarket.service; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import com.bsd.pjcloud.supermarket.dto.SelectOptionDTO; +import com.bsd.pjcloud.supermarket.entity.MallCategoryEntity; +import com.bsd.pjcloud.supermarket.entity.MallMerchantEntity; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.stream.Collectors; + +/** + * 下拉选择服务 + * + * @author zjf + * @date 2025-01-27 + */ +@Service +@RequiredArgsConstructor +public class SelectOptionService { + + private final MallMerchantService mallMerchantService; + private final MallCategoryService mallCategoryService; + + /** + * 获取商户下拉选择列表 + * @param keyword 关键字,支持商户名称和手机号模糊查询 + * @return 商户下拉选择列表 + */ + public List getMerchantSelectList(String keyword) { + LambdaQueryWrapper wrapper = Wrappers.lambdaQuery(); + wrapper.select(MallMerchantEntity::getId, MallMerchantEntity::getName, MallMerchantEntity::getContactName, MallMerchantEntity::getContactPhone); + wrapper.eq(MallMerchantEntity::getStatus, 0); // 只查询启用状态的商户 + + // 如果关键字不为空,添加模糊查询条件 + if (keyword != null && !keyword.trim().isEmpty()) { + wrapper.and(w -> w.like(MallMerchantEntity::getName, keyword.trim()) + .or() + .like(MallMerchantEntity::getContactPhone, keyword.trim())); + } + + wrapper.orderByAsc(MallMerchantEntity::getName); + + return mallMerchantService.list(wrapper) + .stream() + .map(merchant -> new SelectOptionDTO( + merchant.getId(), + merchant.getName(), + false, + merchant.getContactPhone() // 扩展信息:联系电话 + )) + .collect(Collectors.toList()); + } + + /** + * 获取分类下拉选择列表 + * @return 分类下拉选择列表 + */ + public List getCategorySelectList() { + return mallCategoryService.listScope() + .stream() + .filter(category -> category.getStatus() == 1) // 只返回启用状态的分类 + .map(category -> new SelectOptionDTO( + category.getId(), + category.getName(), + false, + category.getParentId() // 扩展信息:父级ID + )) + .collect(Collectors.toList()); + } + + /** + * 获取分类下拉选择列表(树形结构) + * @return 分类下拉选择列表 + */ + public List getCategoryTreeSelectList() { + List allCategories = mallCategoryService.listScope(); + return buildCategorySelectTree(allCategories, 0L, ""); + } + + /** + * 构建分类选择树 + * @param allCategories 所有分类 + * @param parentId 父级ID + * @param prefix 前缀 + * @return 分类选择列表 + */ + private List buildCategorySelectTree(List allCategories, Long parentId, String prefix) { + return allCategories.stream() + .filter(category -> category.getStatus() == 1 && + (parentId == null ? category.getParentId() == null : parentId.equals(category.getParentId()))) + .map(category -> { + String label = prefix + category.getName(); + SelectOptionDTO option = new SelectOptionDTO(category.getId(), label); + option.setExtra(category.getParentId()); + return option; + }) + .collect(Collectors.toList()); + } +} diff --git a/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/service/impl/MallProductServiceImpl.java b/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/service/impl/MallProductServiceImpl.java index 655b3375c..210395034 100644 --- a/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/service/impl/MallProductServiceImpl.java +++ b/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/service/impl/MallProductServiceImpl.java @@ -1,10 +1,23 @@ package com.bsd.pjcloud.supermarket.service.impl; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.StrUtil; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.bsd.pjcloud.supermarket.entity.MallProductEntity; +import com.bsd.pjcloud.supermarket.entity.MallProductSkuEntity; import com.bsd.pjcloud.supermarket.mapper.MallProductMapper; import com.bsd.pjcloud.supermarket.service.MallProductService; +import com.bsd.pjcloud.supermarket.service.MallProductSkuService; +import com.bsd.pjcloud.supermarket.dto.MallProductCreateDTO; +import com.bsd.pjcloud.supermarket.dto.MallProductSkuCreateDTO; +import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.math.BigDecimal; +import java.util.List; +import java.util.stream.Collectors; + /** * 共富商城商品信息 * @@ -12,5 +25,243 @@ import org.springframework.stereotype.Service; * @date 2025-09-02 17:47:01 */ @Service +@RequiredArgsConstructor public class MallProductServiceImpl extends ServiceImpl implements MallProductService { + + private final MallProductSkuService mallProductSkuService; + + @Override + @Transactional(rollbackFor = Exception.class) + public boolean createProductWithSkus(MallProductCreateDTO productCreateDTO) { + // 业务验证 + validateProductData(productCreateDTO); + // 1. 创建SPU + MallProductEntity product = new MallProductEntity(); + product.setMerchantId(productCreateDTO.getMerchantId()); + product.setCategoryId(productCreateDTO.getCategoryId()); + product.setTitle(productCreateDTO.getTitle()); + product.setSubTitle(productCreateDTO.getSubTitle()); + product.setBrand(productCreateDTO.getBrand()); + product.setCoverImage(productCreateDTO.getCoverImage()); + product.setCarouselImages(productCreateDTO.getCarouselImages()); + product.setPriceType(productCreateDTO.getPriceType()); + product.setSpuPrice(productCreateDTO.getSpuPrice()); + product.setOriginalPrice(productCreateDTO.getOriginalPrice()); + product.setUnit(productCreateDTO.getUnit()); + product.setStatus(productCreateDTO.getStatus()); + product.setSaleStartTime(productCreateDTO.getSaleStartTime()); + product.setSaleEndTime(productCreateDTO.getSaleEndTime()); + product.setDescription(productCreateDTO.getDescription()); + + // 2. 保存SPU + boolean spuSaved = this.save(product); + if (!spuSaved) { + throw new RuntimeException("保存商品SPU失败"); + } + + // 3. 处理SKU + if (productCreateDTO.getPriceType() == 1 && CollUtil.isNotEmpty(productCreateDTO.getSkuList())) { + // 多SKU模式,保存SKU列表 + List skuList = productCreateDTO.getSkuList().stream() + .map(skuDTO -> { + MallProductSkuEntity sku = new MallProductSkuEntity(); + sku.setProductId(product.getId()); + sku.setSkuCode(skuDTO.getSkuCode()); + sku.setSkuName(skuDTO.getSkuName()); + sku.setPrice(skuDTO.getPrice()); + sku.setCostPrice(skuDTO.getCostPrice()); + sku.setStock(skuDTO.getStock()); + sku.setBarcode(skuDTO.getBarcode()); + sku.setWeightG(skuDTO.getWeightG()); + sku.setVolumeCm3(skuDTO.getVolumeCm3()); + sku.setStatus(skuDTO.getStatus()); + return sku; + }) + .collect(Collectors.toList()); + + boolean skuSaved = mallProductSkuService.saveBatch(skuList); + if (!skuSaved) { + throw new RuntimeException("保存商品SKU失败"); + } + + // 计算总库存 + int totalStock = skuList.stream() + .mapToInt(sku -> sku.getStock() != null ? sku.getStock() : 0) + .sum(); + product.setTotalStock(totalStock); + this.updateById(product); + } else if (productCreateDTO.getPriceType() == 2) { + // 单价SPU模式,设置总库存为0(或根据业务需求设置) + product.setTotalStock(0); + this.updateById(product); + } + + return true; + } + + @Override + @Transactional(rollbackFor = Exception.class) + public boolean updateProductWithSkus(MallProductCreateDTO productCreateDTO) { + // 业务验证 + validateProductData(productCreateDTO); + + // 1. 更新SPU + MallProductEntity product = this.getById(productCreateDTO.getId()); + if (product == null) { + throw new RuntimeException("商品不存在"); + } + + // 更新SPU字段 + product.setMerchantId(productCreateDTO.getMerchantId()); + product.setCategoryId(productCreateDTO.getCategoryId()); + product.setTitle(productCreateDTO.getTitle()); + product.setSubTitle(productCreateDTO.getSubTitle()); + product.setBrand(productCreateDTO.getBrand()); + product.setCoverImage(productCreateDTO.getCoverImage()); + product.setCarouselImages(productCreateDTO.getCarouselImages()); + product.setPriceType(productCreateDTO.getPriceType()); + product.setSpuPrice(productCreateDTO.getSpuPrice()); + product.setOriginalPrice(productCreateDTO.getOriginalPrice()); + product.setUnit(productCreateDTO.getUnit()); + product.setStatus(productCreateDTO.getStatus()); + product.setSaleStartTime(productCreateDTO.getSaleStartTime()); + product.setSaleEndTime(productCreateDTO.getSaleEndTime()); + product.setDescription(productCreateDTO.getDescription()); + + // 2. 保存SPU + boolean spuUpdated = this.updateById(product); + if (!spuUpdated) { + throw new RuntimeException("更新商品SPU失败"); + } + + // 3. 处理SKU + if (productCreateDTO.getPriceType() == 1 && CollUtil.isNotEmpty(productCreateDTO.getSkuList())) { + // 多SKU模式,先删除原有SKU,再保存新的SKU列表 + LambdaQueryWrapper deleteWrapper = Wrappers.lambdaQuery(); + deleteWrapper.eq(MallProductSkuEntity::getProductId, product.getId()); + mallProductSkuService.remove(deleteWrapper); + + List skuList = productCreateDTO.getSkuList().stream() + .map(skuDTO -> { + MallProductSkuEntity sku = new MallProductSkuEntity(); + sku.setProductId(product.getId()); + sku.setSkuCode(skuDTO.getSkuCode()); + sku.setSkuName(skuDTO.getSkuName()); + sku.setPrice(skuDTO.getPrice()); + sku.setCostPrice(skuDTO.getCostPrice()); + sku.setStock(skuDTO.getStock()); + sku.setBarcode(skuDTO.getBarcode()); + sku.setWeightG(skuDTO.getWeightG()); + sku.setVolumeCm3(skuDTO.getVolumeCm3()); + sku.setStatus(skuDTO.getStatus()); + return sku; + }) + .collect(Collectors.toList()); + + boolean skuSaved = mallProductSkuService.saveBatch(skuList); + if (!skuSaved) { + throw new RuntimeException("保存商品SKU失败"); + } + + // 计算总库存 + int totalStock = skuList.stream() + .mapToInt(sku -> sku.getStock() != null ? sku.getStock() : 0) + .sum(); + product.setTotalStock(totalStock); + this.updateById(product); + } else if (productCreateDTO.getPriceType() == 2) { + // 单价SPU模式,删除所有SKU + LambdaQueryWrapper deleteWrapper = Wrappers.lambdaQuery(); + deleteWrapper.eq(MallProductSkuEntity::getProductId, product.getId()); + mallProductSkuService.remove(deleteWrapper); + + product.setTotalStock(0); + this.updateById(product); + } + + return true; + } + + @Override + public MallProductCreateDTO getProductWithSkus(Long id) { + // 1. 获取SPU信息 + MallProductEntity product = this.getById(id); + if (product == null) { + throw new RuntimeException("商品不存在"); + } + + // 2. 转换为DTO + MallProductCreateDTO dto = new MallProductCreateDTO(); + dto.setId(product.getId()); + dto.setMerchantId(product.getMerchantId()); + dto.setCategoryId(product.getCategoryId()); + dto.setTitle(product.getTitle()); + dto.setSubTitle(product.getSubTitle()); + dto.setBrand(product.getBrand()); + dto.setCoverImage(product.getCoverImage()); + dto.setCarouselImages(product.getCarouselImages()); + dto.setPriceType(product.getPriceType()); + dto.setSpuPrice(product.getSpuPrice()); + dto.setOriginalPrice(product.getOriginalPrice()); + dto.setUnit(product.getUnit()); + dto.setStatus(product.getStatus()); + dto.setSaleStartTime(product.getSaleStartTime()); + dto.setSaleEndTime(product.getSaleEndTime()); + dto.setDescription(product.getDescription()); + + // 3. 获取SKU信息 + if (product.getPriceType() == 1) { + LambdaQueryWrapper wrapper = Wrappers.lambdaQuery(); + wrapper.eq(MallProductSkuEntity::getProductId, id); + List skuList = mallProductSkuService.list(wrapper); + + List skuDTOList = skuList.stream() + .map(sku -> { + MallProductSkuCreateDTO skuDTO = new MallProductSkuCreateDTO(); + skuDTO.setSkuCode(sku.getSkuCode()); + skuDTO.setSkuName(sku.getSkuName()); + skuDTO.setPrice(sku.getPrice()); + skuDTO.setCostPrice(sku.getCostPrice()); + skuDTO.setStock(sku.getStock()); + skuDTO.setBarcode(sku.getBarcode()); + skuDTO.setWeightG(sku.getWeightG()); + skuDTO.setVolumeCm3(sku.getVolumeCm3()); + skuDTO.setStatus(sku.getStatus()); + return skuDTO; + }) + .collect(Collectors.toList()); + + dto.setSkuList(skuDTOList); + } + + return dto; + } + + /** + * 商品业务数据验证 + * @param productCreateDTO 商品创建DTO + */ + private void validateProductData(MallProductCreateDTO productCreateDTO) { + // 多SKU模式验证 + if (productCreateDTO.getPriceType() == 1) { + if (CollUtil.isEmpty(productCreateDTO.getSkuList())) { + throw new RuntimeException("多SKU模式下必须提供SKU列表"); + } + // 验证SKU编码唯一性 + long distinctSkuCodes = productCreateDTO.getSkuList().stream() + .map(MallProductSkuCreateDTO::getSkuCode) + .distinct() + .count(); + if (distinctSkuCodes != productCreateDTO.getSkuList().size()) { + throw new RuntimeException("SKU编码不能重复"); + } + } + + // 单价SPU模式验证 + if (productCreateDTO.getPriceType() == 2) { + if (productCreateDTO.getSpuPrice() == null || productCreateDTO.getSpuPrice().compareTo(BigDecimal.ZERO) <= 0) { + throw new RuntimeException("单价SPU模式下必须设置有效的SPU价格"); + } + } + } } \ No newline at end of file diff --git a/pjcloud-supermarket/pjcloud-supermarket-biz/src/test/java/com/bsd/pjcloud/supermarket/service/MallProductServiceTest.java b/pjcloud-supermarket/pjcloud-supermarket-biz/src/test/java/com/bsd/pjcloud/supermarket/service/MallProductServiceTest.java new file mode 100644 index 000000000..5a954e1cf --- /dev/null +++ b/pjcloud-supermarket/pjcloud-supermarket-biz/src/test/java/com/bsd/pjcloud/supermarket/service/MallProductServiceTest.java @@ -0,0 +1,104 @@ +package com.bsd.pjcloud.supermarket.service; + +import com.bsd.pjcloud.supermarket.dto.MallProductCreateDTO; +import com.bsd.pjcloud.supermarket.dto.MallProductSkuCreateDTO; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; + +import javax.annotation.Resource; +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.Arrays; + +/** + * 商品服务测试类 + * + * @author zjf + * @date 2025-01-27 + */ +@SpringBootTest +@ActiveProfiles("test") +public class MallProductServiceTest { + + @Resource + private MallProductService mallProductService; + + @Test + public void testCreateProductWithSkus() { + // 创建测试数据 + MallProductCreateDTO productCreateDTO = new MallProductCreateDTO(); + productCreateDTO.setMerchantId(1L); + productCreateDTO.setCategoryId(100L); + productCreateDTO.setTitle("测试商品"); + productCreateDTO.setSubTitle("测试副标题"); + productCreateDTO.setBrand("测试品牌"); + productCreateDTO.setCoverImage("https://example.com/cover.jpg"); + productCreateDTO.setCarouselImages("https://example.com/img1.jpg,https://example.com/img2.jpg"); + productCreateDTO.setPriceType(1); // 多SKU模式 + productCreateDTO.setOriginalPrice(new BigDecimal("999.00")); + productCreateDTO.setUnit("台"); + productCreateDTO.setStatus(1); + productCreateDTO.setSaleStartTime(LocalDateTime.now()); + productCreateDTO.setSaleEndTime(LocalDateTime.now().plusYears(1)); + productCreateDTO.setDescription("测试商品描述"); + productCreateDTO.setDeptId(1L); + + // 创建SKU列表 + MallProductSkuCreateDTO sku1 = new MallProductSkuCreateDTO(); + sku1.setSkuCode("TEST-SKU-001"); + sku1.setSkuName("测试SKU 1"); + sku1.setPrice(new BigDecimal("599.00")); + sku1.setCostPrice(new BigDecimal("400.00")); + sku1.setStock(100); + sku1.setBarcode("1234567890123"); + sku1.setWeightG(200); + sku1.setVolumeCm3(300); + sku1.setStatus(1); + sku1.setDeptId(1L); + + MallProductSkuCreateDTO sku2 = new MallProductSkuCreateDTO(); + sku2.setSkuCode("TEST-SKU-002"); + sku2.setSkuName("测试SKU 2"); + sku2.setPrice(new BigDecimal("699.00")); + sku2.setCostPrice(new BigDecimal("500.00")); + sku2.setStock(50); + sku2.setBarcode("1234567890124"); + sku2.setWeightG(250); + sku2.setVolumeCm3(350); + sku2.setStatus(1); + sku2.setDeptId(1L); + + productCreateDTO.setSkuList(Arrays.asList(sku1, sku2)); + + // 执行测试 + boolean result = mallProductService.createProductWithSkus(productCreateDTO); + assert result : "商品创建应该成功"; + } + + @Test + public void testCreateProductWithSinglePrice() { + // 创建测试数据 - 单价SPU模式 + MallProductCreateDTO productCreateDTO = new MallProductCreateDTO(); + productCreateDTO.setMerchantId(1L); + productCreateDTO.setCategoryId(100L); + productCreateDTO.setTitle("测试商品-单价模式"); + productCreateDTO.setSubTitle("测试副标题"); + productCreateDTO.setBrand("测试品牌"); + productCreateDTO.setCoverImage("https://example.com/cover.jpg"); + productCreateDTO.setCarouselImages("https://example.com/img1.jpg"); + productCreateDTO.setPriceType(2); // 单价SPU模式 + productCreateDTO.setSpuPrice(new BigDecimal("599.00")); + productCreateDTO.setOriginalPrice(new BigDecimal("999.00")); + productCreateDTO.setUnit("台"); + productCreateDTO.setStatus(1); + productCreateDTO.setSaleStartTime(LocalDateTime.now()); + productCreateDTO.setSaleEndTime(LocalDateTime.now().plusYears(1)); + productCreateDTO.setDescription("测试商品描述"); + productCreateDTO.setDeptId(1L); + + // 执行测试 + boolean result = mallProductService.createProductWithSkus(productCreateDTO); + assert result : "商品创建应该成功"; + } +}